#!/usr/bin/python """This utility reads the serial port data from the RadioShack Digital Multimeter 22-812 and translates it into human-readable output. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. Copyright 2006,2008 Eli Carter, retracile@gmail.com """ import sys import time import termios # The elements of each LCD digit are labeled as A-G and the PERIOD: # A # -- # F | | B # -- G # E | | C # . -- # P D # Those map to the bits in a byte: A = 1 << 0 B = 1 << 4 C = 1 << 6 D = 1 << 7 E = 1 << 2 F = 1 << 1 G = 1 << 5 # Characters marked as "speculation" are combinations I have not seen used by # the multimeter, but I have added to avoid "?" output if that combination does # get used in some case. byte_digit_mapping = { 0: " ", B+C: "1", A+B+G+E+D: "2", A+B+G+C+D: "3", F+G+B+C: "4", A+F+G+C+D: "5", # Doubles as "S" A+F+G+E+D+C: "6", A+B+C: "7", A+B+C+D+E+F+G: "8", A+B+C+D+F+G: "9", A+B+C+D+E+F: "0", # Doubles as "O" G: "-", A+F+E+G+B+C: "A", # speculation F+G+C+D+E: "b", # speculation A+F+E+C: "C", E+G+B+C+D: "d", # speculation A+F+G+E+D: "E", A+F+G+E: "F", A+F+E+D+C: "G", # speculation F+E+G+C: "h", F+B+G+E+C: "H", # speculation C: "i", # speculation B+C+D: "J", # speculation F+E+D: "L", E+G+C: "n", E+G+C+D: "o", F+E+A+B+G: "P", E+G: "r", F+G+E+D: "t", E+D+C: "u", # speculation F+E+D+C+B: "U", # speculation } def byte_to_digit(b): """Interpret the LCD elements into something meaningful such as digits and letters. """ PERIOD = 1 << 3 # The period comes before the digit if b & PERIOD: # decimal? digit = "." else: digit = "" try: # Mask out the decimal point indicator digit += byte_digit_mapping[b & ~PERIOD] except KeyError: # TODO: it might be helpful to say which elements were lit... digit += "?" return digit class BitMapper: """Given a byte of output, provide a string of lit LCD elements.""" def __init__(self, mapping): self.mapping = mapping def __call__(self, value): # Each bit of the given value represents the item in the mapping list; # return a string output = [] for i in range(8): if value & (1<<i): output.append(self.mapping[i]) return " ".join(output) class RadioShackDigitalMultimeter22812: """Interface for reading meaningful data from the serial output of the RadioShack Digital Multimeter 22-812. """ indicator_map_1 = BitMapper(["m", "V", "A", "F", "M", "K", "Ohm", "Hz"]) indicator_map_2 = BitMapper(["MIN", "REL", "hFE", "%", "S", "dBm", "n", "u"]) indicator_map_3 = BitMapper(["AUTO", "RS232", "~", "-", "HOLD", "BAT", "DIODE", "BEEP"]) # The mode byte is a number indicating which mode we're in mode_byte_mapping = [ "DC V", "AC V", "DC uA", "DC mA", "DC A", "AC uA", "AC mA", "AC A", "OHM", "CAP", "HZ", "NET HZ", "AMP HZ", "DUTY", "NET DUTY", "AMP DUTY", "WIDTH", "NET WIDTH", "AMP WIDTH", "DIODE", "CONT", "HFE", "LOGIC", "DBM", "EF", "TEMP", ] def __init__(self, serialport, debug=False): self.serialport = serialport self.debug = debug self.setup_serial_port() def setup_serial_port(self): # configure serial port # print termios.tcgetattr(serialport.fileno()) # DEBUG iflag, oflag, cflag, lflag, ispeed, ospeed, cc = termios.tcgetattr( self.serialport.fileno()) # These values were obtained by configuring the serial port with minicom, # then reading the settings. FIXME: It would be nice to set them based on # their meaning rather than raw values. iflag = 1 oflag = 0 cflag = -2147481412 lflag = 0 # 4800 baud ispeed = termios.B4800 ospeed = termios.B4800 result = termios.tcsetattr(self.serialport.fileno(), termios.TCSANOW, [iflag, oflag, cflag, lflag, ispeed, ospeed, cc]) if self.debug: print "result=%s" % result print termios.tcgetattr(serialport.fileno()) def byte_to_mode(self, b): return self.mode_byte_mapping[b] def readline(self): data = [ord(x) for x in serialport.read(9)] timestamp = time.time() # Then interpret the data #print repr(data) # DEBUG # Byte 1 mode = self.byte_to_mode(data[0]) # Byte 2 indicator1 = self.indicator_map_1(data[1]) # Byte 3 indicator2 = self.indicator_map_2(data[2]) # Bytes 4-7 rawlcd = data[3:7] lcd = "".join([byte_to_digit(x) for x in reversed(rawlcd)]) # Check for some special cases such as short/open indicators if lcd.startswith("."): # The period in byte 7 lights "MAX", not a period. lcd = "MAX " + lcd[1:] elif lcd == '5hrt': lcd = 'Short' elif lcd == '0PEn': lcd = 'Open' # Byte 8 indicator3 = self.indicator_map_3(data[7]) # Byte 9 # TODO: This is a checksum + 0x57 # And finally print out the interpretation. return "%.6f %s %s %s %s %s" % (timestamp, mode, indicator3, lcd, indicator2, indicator1) if __name__ == "__main__": if len(sys.argv) < 2: sys.stderr.write("Syntax error: serial device name required.\n") sys.exit(1) serialport = open(sys.argv[1],"r") multimeter = RadioShackDigitalMultimeter22812(serialport) while True: # Read the data and grab a timestamp for the reading print multimeter.readline() # vim:foldmethod=indent foldcolumn=8 # vim:shiftwidth=4 ts=4 softtabstop=4 expandtab