Python script to read data from VA18b multimeter.

Some years ago I wanted a cheap multimeter with pc interface. The one I was able to obtain quickly was the VA18b. There was no official software for GNU/Linux. There is a Perl script and some C implementations. I wanted to interface multiple serial devices (temperature sensor + the DMM) so I wrote a Python script to do so. I will describe the part for the multimeter. The nice thing is that the communication protocol is known. The adapter is just a generic USBSerial (Prolific pl2303 or Sunplus SPCP8x5) device. I creates a ttyUSBx device.

VA18b

The way those cheap multimeter usually work is by sending the state of every segment of the LCD. My VA18b has an LCD (image bellow) that consists of 56 segments. So it need to send 56 bit to describe the sate of every one. The multimeter actually sends 14 bytes (14×8=112 bits) but uses only the low nibble of the byte to transfer the segment state. The serial port setting is 2400 baud, 8 bits, Stop 1, Parity none.

LCD

console

#!/usr/bin/python2.7

#
#	Just a quick n' dirty script to read the Data from a VA18B multimeter.
#	This DMM uses the low nibble of 14 bytes to send the lcd segments.
#
#       License: GPL v3. http://www.gnu.org/licenses/gpl-3.0-standalone.html
#

import signal
import sys
import time
import serial
import io
import getopt

interval = '1.0'
device = '/dev/ttyUSB0' 


def showhelp():
	print '        va18b.py -i <time in secs> -d <device>'
	print '   Eg:  va18b.py -i 1.5 -d /dev/ttyUSB1'
	
	
try:
	opts, args = getopt.getopt(sys.argv[1:],"hd:i:",['help'])
except getopt.GetoptError:
	print 'Error: -i, -d option require a value.'
	showhelp()
	sys.exit(1)
        
for opt, arg in opts:
	if opt in ('-h','--help'):
	  showhelp()
	  sys.exit(1)
	if opt == '-i':
	  interval = arg
	if opt == '-d':
	  device = arg
	  
try:
	port=serial.Serial(port=device,
			   baudrate=2400,
			   bytesize=serial.EIGHTBITS,
			   stopbits=serial.STOPBITS_ONE,
			   parity=serial.PARITY_NONE,
			   timeout=None)
	
	if not port.isOpen():
		port.open()

except IOError,err:
	print '\nError:' + str(err) + '\n'
	sys.exit(1)


def signal_handler(signal, frame):
        sys.stderr.write('\n\nYou pressed Ctrl+C!\n\n')
        port.flushInput()
	port.close()
        sys.exit(0)
        
signal.signal(signal.SIGINT, signal_handler)

# Every packet is 14 bytes long.
def get_bytes():
	i=0
	substr=''
	while i<=13:
		byte=port.read(1)
		# converting every byte to binary format keeping the low nibble.
		substr += '{0:08b}'.format(ord(byte))[4:]
		i += 1
	return substr
      

def stream_decode(substr):
  
	ac       = int(substr[0:1])
	dc       = int(substr[1:2])
	auto     = int(substr[2:3])
	pclink   = substr[3:4]
	minus    = int(substr[4:5])
	
	digit1   = substr[5:12]
	dot1     = int(substr[12:13])
	digit2   = substr[13:20]
	dot2     = int(substr[20:21])
	digit3   = substr[21:28]
	dot3     = int(substr[28:29])
	digit4   = substr[29:36]
	
	micro    = int(substr[36:37])
	nano     = int(substr[37:38])
	kilo     = int(substr[38:39])
	diotst   = int(substr[39:40])
	mili     = int(substr[40:41])
	percent  = int(substr[41:42])
	mega     = int(substr[42:43])
	contst   = int(substr[43:44])
	cap      = int(substr[44:45])
	ohm      = int(substr[45:46])
	rel      = int(substr[46:47])
	hold     = int(substr[47:48])
	amp      = int(substr[48:49])
	volts    = int(substr[49:50])
	hertz    = int(substr[50:51])
	lowbat   = int(substr[51:52])
	minm     = int(substr[52:53])
	fahrenh  = substr[53:54]
	celcius  = int(substr[54:55])
	maxm     = int(substr[55:56])
	
	digit = {"1111101":"0",
	         "0000101":"1",
	         "1011011":"2",
	         "0011111":"3",
	         "0100111":"4",
	         "0111110":"5",
	         "1111110":"6",
	         "0010101":"7",
	         "1111111":"8",
	         "0111111":"9",
	         "0000000":"",
	         "1101000":"L"}
	
	value = ("-" if minus else " ") +\
	        digit.get(digit1,"") + ("." if dot1 else "") +\
	        digit.get(digit2,"") + ("." if dot2 else "") +\
	        digit.get(digit3,"") + ("." if dot3 else "") +\
	        digit.get(digit4,"")

	flags = " ".join(["AC"         if ac     else "",
	                  "DC"         if dc     else "",
	                  "Auto"       if auto   else "",
	                  "Diode test" if diotst else "",
	                  "Conti test" if contst else "",
	                  "Capacity"   if cap    else "",
	                  "Rel"        if rel    else "",
	                  "Hold"       if hold   else "",
	                  "Min"        if minm   else "",
	                  "Max"        if maxm   else "",
	                  "LowBat"     if lowbat else ""])
	                 
	units = ("n"    if nano    else "") +\
	        ("u"    if micro   else "") +\
	        ("k"    if kilo    else "") +\
	        ("m"    if mili    else "") +\
	        ("M"    if mega    else "") +\
	        ("%"    if percent else "") +\
	        ("Ohm"  if ohm     else "") +\
	        ("Amp"  if amp     else "") +\
	        ("Volt" if volts   else "") +\
	        ("Hz"   if hertz   else "") +\
	        ("C"    if celcius else "")
	       
	return value + " " + flags + " " + units
	
	
sys.stderr.write('Press Ctrl+C to exit.\n\n')

while 1:
	substr = get_bytes()
	data = stream_decode(substr)

	print data
	time.sleep(float(interval))
	port.flushInput()
This entry was posted in Electronics and tagged , , , , , , , , , , . Bookmark the permalink.

22 Responses to Python script to read data from VA18b multimeter.

  1. hex_poland says:

    hi very hlepfull post 🙂 Thx
    i have a problem with Hp infrared printer hp82240
    so i try makeover your program 🙂

  2. Akansha Singh says:

    the program runs on my system but its not printing the values on the terminal. can you comment on that ?

    • alexkaltsas says:

      Does it print anything at all? What is the port created when you connect the usb adapter?

      • Akansha Singh says:

        when the multimeter is connected and the program is run, it only prints “Press Ctrl+C to exit” on the terminal, it also prints the same when the multimeter is not connected.
        I have checked the port and changed it in the program.

      • alexkaltsas says:

        Are you putting the meter on PC-link mode? Press and hold Hz/Duty key, while turning on the meter?

      • Akansha Singh says:

        yes, when I switch on the multimeter, its on PC-link mode. I checked by pressing Hz/Duty key, while turning on the meter. Seems there is some issue in the get_bytes() function. I am using print statement to see where it gets stuck.

  3. Akansha Singh says:

    yes, when I switch on the multimeter, its on PC-link mode. I checked by pressing Hz/Duty key, while turning on the meter.

    • alexkaltsas says:

      If you open any other terminal application, can you see any data? Can you test various baud rates?

      • Akansha Singh says:

        No! when i open the terminal application, and I run the program, i only see “Press Ctrl+C to exit”. the cursor does not even flash for the blank output. i have specified the port number in the program and not in the terminal.

      • alexkaltsas says:

        I mean with any other serial communication application. Like screen, cu, minicom, gtkterm, hterm etc.

  4. Akansha Singh says:

    i was running the program on mac and then i switched to linux but same issue persists. On connecting the multi meter via the usb port and running the given code, I only see Press Ctrl+C to exit and no other data.
    There is no other serial comm application running apart from the multi meter. Also, the multi meter is on PC link mode. I also changed the baud rate in the program but nothing helped.
    Can you please help and correct where I am going wrong ?
    Thanks

  5. Akansha Singh says:

    I checked the multimeter with other programs and found that the issue is that I am not able to switch to PC link mode. The method to do that is to hold/press the Hz/Duty button while switching on the multimeter and PC-Link should be displayed on the screen for data collection.
    But I am not able to see the same. That’s why I don’t see any output on the terminal once I run the program.

  6. nipon says:

    LCD display the same digit (not change) but get_bytes() could not give the same number.
    ————————————————————————-
    nipon@nipon:~/mypycode$ python dmm.py
    Press Ctrl+C to exit.

    10001110101111101011101011011110111100000000000000000010
    -… AC C
    11110000000000000000001000011000111010111110101110101101
    . AC DC Auto Diode test Capacity Rel Hold Min Max ukmM%AmpHz
    11101011111010111010110111101111000000000000000000100001
    -5… AC DC Auto Max Hz
    00000000000000000010000110001110101111101011101011011110
    . Conti test Capacity Rel Min LowBat nukmMAmpVoltC
    ^C

    You pressed Ctrl+C!
    —————————————————————–
    multimeter work correctly with PClink application.

  7. Mahendra Verma says:

    Thank you for the post. We are looking for the programming of Steren Mul-630 multimeter. The baud rate = 2400 bps, data bit= 8 bit, parity check= no, data system: hedadecimal and Data frame length = 15 bytes.
    Our problem is the identification of display code. Is there any procedure for it? Any comment or advice will be helpful.

  8. Mahendra Verma says:

    I got the following information about the Steren multimeter Mul-630:
    Digital Multimeter VA18B VA40B COM port data protocols: V1.1 (Aug.2013)
    1 Direct: unilateralism to PC
    2 Baud rate: 2400 bps
    3 Data bit: 8 bit
    4 Parity check: no
    5 Data system: Hexadecimal
    6 Data frame length: 15 bytes
    7 Data information: LCD table on-off information

    8 Data format: 1st byte → 1X (X is seg1, 4 bits represent the data on the LCD table)
    2nd byte → 2X (X is seg2, 4 bits represent the data on the LCD table)
    3rd byte → 3X (X is seg3, 4 bits represent the data on the LCD table)
    ……
    14th byte -> EX (X is seg14, 4 bits represent the data on the LCD table)
    15th byte -> FX (X is seg15, 4 bits represent the data on the LCD table)

    *. “X” expression: Bit3~Bit 0@ segX (COM1 to COM4)

    For example: the 1st byte is 18(HEX), then the symbol AC is indicated, and DC AUTO PCLINK is disappeared.

    I am trying your code. I am getting different values at different time.
    There are few things which are not clear to me.

    1. Data system: Hexadecimal
    2. Data frame length: 15 bytes:

    Please help me.

    • alexkaltsas says:

      I will have a look when I find some time.

    • alexkaltsas says:

      When you turn on the meter, does it enable all segments? Can you take a picture of those enabled?

      • Mahendra Verma says:

        Thank you. My email is Mahendra@ineel.mx. If you write me, I can send the picture of the multimeter display. I do not know if there is possibility to attach the picture with this message.

        We are able to read the information byte-by-byte. I think the problem is to identify the first byte. As I wrote earlier the information provided by the vender, it says the following line.
        “For example: the 1st byte is 18(HEX), then the symbol AC is indicated, and DC AUTO PCLINK is disappeared.”

        We are using the C# and wrote almost identically to your program. We are reading different information at different time. I mean that the reading is not synchronized the information send by the multimeter.

        Best regards
        Mahendra

  9. Good post!
    Works on Ubuntu 16.04 but there is a problem with line indentation in the source code (line: port.close()) so after copy – paste operation you will get a python interpreter error:
    “IndentationError: unindent does not match any outer indentation level”
    Adding some spaces solves the problem 😉

Leave a comment