#!/usr/bin/python
# Read data from a Digitek INO2513 multimeter over RS232.
# Adam Sampson <ats@offog.org>

import serial

class MultimeterException(Exception): pass

#  a
# f b
#  g
# e c
#  d
INO2513_DIGITS = {
	#    efadcgb
	int("1111101", 2): "0",
	int("0000101", 2): "1",
	int("1011011", 2): "2",
	int("0011111", 2): "3",
	int("0100111", 2): "4",
	int("0111110", 2): "5",
	int("1111110", 2): "6",
	int("0010101", 2): "7",
	int("1111111", 2): "8",
	int("0111111", 2): "9",
	int("0000000", 2): " ",
	int("1101000", 2): "L",
	}

def decode_ino2513(msg):
	def bit(n, b):
		return (msg[n] & (1 << b)) != 0
	def digit(n):
		segs = ((msg[n] & 0x7) << 4) | (msg[n + 1] & 0xF)
		if not segs in INO2513_DIGITS:
			raise MultimeterException("unrecognised digit %02x" % segs)
		return INO2513_DIGITS[segs]

	# Other bits that I'm not decoding:
	# ac = bit(0, 3)
	# dc = bit(0, 2)
	# auto = bit(0, 1)
	# diode = bit(9, 0)
	# tone = bit(10, 0)

	digits = digit(1) + digit(3) + digit(5) + digit(7)
	if digits == " 0L ":
		return ("O.L", None)

	try:
		value = float(digits)
	except ValueError:
		raise MultimeterException("unrecognised digits: '%s'" % digits)
	if bit(3, 3): # DP between 1 and 2
		value /= 1000.0
	elif bit(5, 3): # DP between 2 and 3
		value /= 100.0
	elif bit(7, 3): # DP between 3 and 4
		value /= 10.0
	if bit(1, 3): # minus
		value = -value

	unit = None
	if bit(11, 3):
		unit = "F"
	if bit(11, 2):
		unit = "ohm"
	elif bit(12, 3):
		unit = "A"
	elif bit(12, 2):
		unit = "V"
	elif bit(12, 1):
		unit = "Hz"
	elif bit(13, 2):
		unit = "degC"

	if unit is None:
		raise MultimeterException("unable to determine unit")

	if bit(9, 3):
		unit = "u" + unit
	elif bit(9, 2):
		unit = "n" + unit
	elif bit(10, 3):
		unit = "m" + unit
	elif bit(9, 1):
		unit = "k" + unit
	elif bit(10, 1):
		unit = "M" + unit

	return (value, unit)

def read_ino2513(device):
	"""A generator that returns readings from a Digitek INO2513
	multimeter attached to the given serial port.

	In the event that you've lost your INO2513 cable, it has a mini
	barrel jack on one end and a DE9 female on the other. The centre
	of the barrel jack goes to pin 4, the outside goes to pin 2, and
	there's a 10k resistor between pin 2 and pin 3.

	The returned values are pairs (value, unit), where the unit is
	"{n,u,m,,k,M}{F,ohm,A,V,Hz,degC}" and the value is a float.
	If the unit is None, then the value is an error message as a
	string (currently only "O.L")."""

	ser = serial.Serial(device, 2400)

	LEN = 14
	buf = [0] * LEN
	while True:
		c = ord(ser.read(1))
		buf = buf[1:] + [c]

		if (c >> 4) == LEN:
			ok = True
			for i in range(LEN):
				if (buf[i] >> 4) != (i + 1):
					ok = False
					break
			if ok:
				v = decode_ino2513(buf)
				if v is not None:
					yield v

def main():
	meter = read_ino2513("/dev/ttyUSB0")
	for (value, unit) in meter:
		if unit is None:
			print value
		else:
			print "%3.3f %s" % (value, unit)

if __name__ == "__main__":
	main()
