Victor86B digital multimeter app
authorAndrew Tridgell <tridge@samba.org>
Fri, 7 Jan 2011 01:18:01 +0000 (12:18 +1100)
committerAndrew Tridgell <tridge@samba.org>
Fri, 7 Jan 2011 01:18:01 +0000 (12:18 +1100)
DMM/Makefile [new file with mode: 0644]
DMM/dmm.py [new file with mode: 0755]
DMM/dmmread.py [new file with mode: 0755]
DMM/dmmui.ui [new file with mode: 0644]

diff --git a/DMM/Makefile b/DMM/Makefile
new file mode 100644 (file)
index 0000000..ec24f15
--- /dev/null
@@ -0,0 +1,7 @@
+all: dmmui.py
+
+dmmui.py: dmmui.ui
+       pyuic4 dmmui.ui -o dmmui.py
+
+clean:
+       rm -f dmmui.py *~
diff --git a/DMM/dmm.py b/DMM/dmm.py
new file mode 100755 (executable)
index 0000000..d8adbcc
--- /dev/null
@@ -0,0 +1,58 @@
+#!/usr/bin/env python
+
+###################################
+# python Digital Multimeter App
+# (C) Andrew Tridgell 2011
+# Released under GNU GPLv3 or later
+
+from dmmui import *
+from dmmread import *
+import sys
+
+def tick():
+    '''update display'''
+    global device
+    global tickcount
+    try:
+        v = device.read()
+        ui.LCD.display(v.lcd)
+        if v.units:
+            ui.units.setText(v.units)
+        else:
+            ui.units.setText("-")
+        if v.mode:
+            ui.mode.setText(v.mode)
+        else:
+            ui.mode.setText("")
+        if tickcount & 1:
+            ui.tick.setText('*')
+        else:
+            ui.tick.setText(' ')
+        tickcount += 1
+    except DMMTimeout:
+        pass
+    except DMMDataError, msg:
+        print("DMM Data Error: %s" % msg)
+    except KeyboardInterrupt:
+        sys.exit(1)
+
+
+############################################
+# main program
+if __name__ == "__main__":
+    global device, tickcount
+
+    device = DMM_Victor86B()
+    tickcount = 0
+
+    app = QtGui.QApplication(sys.argv)
+    dmm = QtGui.QMainWindow()
+    ui = Ui_DMMUI()
+    ui.setupUi(dmm)
+
+    ctimer = QtCore.QTimer()
+    QtCore.QObject.connect(ctimer, QtCore.SIGNAL("timeout()"), tick)
+    ctimer.start(1)
+
+    dmm.show()
+    sys.exit(app.exec_())
diff --git a/DMM/dmmread.py b/DMM/dmmread.py
new file mode 100755 (executable)
index 0000000..4da8924
--- /dev/null
@@ -0,0 +1,156 @@
+#!/usr/bin/env python
+
+###################################
+# python Digital Multimeter App
+# (C) Andrew Tridgell 2011
+# Released under GNU GPLv3 or later
+
+import usb
+
+class DMMException(Exception):
+    '''an exception class for DMM errors'''
+    def __init__(self, message):
+        Exception.__init__(self, message)
+
+class DMMDeviceError(DMMException):
+    def __init__(self, message):
+        DMMException.__init__(self, message)
+
+class DMMTimeout(DMMDeviceError):
+    def __init__(self, message):
+        DMMDeviceError.__init__(self, "timeout")
+
+class DMMDataError(DMMException):
+    def __init__(self, message):
+        DMMException.__init__(self, message)
+
+class DMMData(object):
+    '''a piece of data from a DMM'''
+    def __init__(self):
+        self.rawData = None
+        self.model = "Unknown"
+        self.fields = {}
+        self.lcd = None
+        self.units = None
+        self.mode = None
+        pass
+
+class DMM(object):
+    '''generic USB multimeter class'''
+    def __init__(self, idVendor, idProduct, idEndpoint,
+                 idInterface=0, model="Unknown", readSize=32):
+        for bus in usb.busses():
+            for device in bus.devices:
+                if device.idVendor == idVendor and device.idProduct == idProduct:
+                    self.device = device
+                    self.handle = device.open()
+                    try:
+                        self.handle.detachKernelDriver(idInterface)
+                    except usb.USBError:
+                        pass
+                    self.handle.claimInterface(idInterface)
+                    self.model = model
+                    self.readSize = readSize
+                    self.idEndpoint = idEndpoint
+                    self.debug = False
+                    return
+        raise DMMDeviceError("Unable to find USB device 0x%04x:0x%04x" % (
+            idVendor, idProduct, idInterface))
+
+    def read(self):
+        '''read a set of data'''
+        ret = DMMData()
+        try:
+            data = self.handle.interruptRead(self.idEndpoint, self.readSize)
+            ret.rawData = data;
+            if self.debug:
+                for d in ret.rawData:
+                    print("%02X " % d),
+                print
+                for i in range(0,len(ret.rawData)):
+                    print("%2d " % i),
+                print
+        except usb.USBError, e:
+            if str(e).find("No data available"):
+                raise DMMTimeout(e)
+            raise DMMDeviceError(e)
+        return ret
+
+
+class DMM_Victor86B(DMM):
+    '''DMM interface for a Victor 86B'''
+    def __init__(self):
+        DMM.__init__(self, idVendor=0x1244, idProduct=0xd237,
+                     idEndpoint=0x81, idInterface=0,
+                     model="Victor86B")
+    def read(self):
+        tries = 4
+        while tries > 0:
+            tries -= 1
+            ret = DMM.read(self)
+            if len(ret.rawData) != 14:
+                continue
+            if ret.rawData[11:14] == ( 0, 0, 0):
+                continue
+            break
+        if tries == 0:
+            raise DMMDataError("Bad DMM data: %s" % ret.rawData)
+        digmap = { 0x42: '0', 0x61: '1', 0x04: '2', 0xE6: '3', 0xA5: '4', 0x2E: '5',
+                   0x4E: '6', 0xE1: '7', 0x46: '8', 0x26: '9', 0x67: ' ', 0xC8: 'L' }
+        units = { (0x8F, 0x6E, 0xAc) : "Hz",
+                  (0xBF, 0x6E, 0x6C) : "C",
+                  (0x8F, 0x6E, 0x8C) : "V",
+                  (0x8F, 0x6E, 0x6C) : "Ohm",
+                  (0x8F, 0xAE, 0x6C) : "kOhm",
+                  (0x8F, 0x7E, 0x7C) : "uA",
+                  (0x8F, 0x6E, 0x7C) : "A",
+                  }
+        v = [(ret.rawData[10] & 0xF0) | (ret.rawData[3]>>4),
+             (ret.rawData[9]  & 0xF0) | (ret.rawData[6]>>4),
+             (ret.rawData[7]  & 0xF0) | (ret.rawData[5]>>4),
+             (ret.rawData[0]  & 0xF0) | (ret.rawData[2]>>4)]
+        offsets = [ 0, 0, 0xEF, 0x01 ]
+        ret.lcd = ""
+        for i in range(0,4):
+            val = v[i]
+            val += offsets[i]
+            val &= 0xFF
+            if val & 0x10:
+                if i == 0:
+                    ret.lcd += "-"
+                else:
+                    ret.lcd += "."
+            val &= 0xEF
+            if not val in digmap:
+                raise DMMDataError("Digit %d invalid value 0x%02X" % (i+1, val))
+            else:
+                ret.lcd += digmap[val]
+        ret.mode = ""
+        u = (ret.rawData[11], ret.rawData[12], ret.rawData[13])
+        if u in units:
+            ret.units = units[u]
+        if ret.rawData[1] == 0x47 and ret.rawData[4] == 0x71:
+            ret.mode = "AC"
+        if ret.rawData[1] == 0x57 and ret.rawData[4] == 0x71:
+            ret.mode = "DC"
+        return ret
+
+
+############################################
+# main program
+if __name__ == "__main__":
+    import sys
+
+    dmm = DMM_Victor86B()
+    dmm.debug = True
+    while True:
+        try:
+            v = dmm.read()
+            print("%s %s" % (v.lcd, v.units))
+        except DMMTimeout:
+            pass
+        except DMMDataError, msg:
+            print("DMM Data Error: %s" % msg)
+        except KeyboardInterrupt:
+            sys.exit(1)
+
diff --git a/DMM/dmmui.ui b/DMM/dmmui.ui
new file mode 100644 (file)
index 0000000..55126c8
--- /dev/null
@@ -0,0 +1,131 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>DMMUI</class>
+ <widget class="QMainWindow" name="DMMUI">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>489</width>
+    <height>325</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Digital Multimeter</string>
+  </property>
+  <widget class="QWidget" name="centralwidget">
+   <widget class="QWidget" name="gridLayoutWidget">
+    <property name="geometry">
+     <rect>
+      <x>0</x>
+      <y>40</y>
+      <width>411</width>
+      <height>261</height>
+     </rect>
+    </property>
+    <layout class="QGridLayout" name="gridLayout">
+     <item row="1" column="0">
+      <widget class="QLCDNumber" name="LCD">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="smallDecimalPoint">
+        <bool>true</bool>
+       </property>
+       <property name="numDigits">
+        <number>5</number>
+       </property>
+       <property name="digitCount">
+        <number>5</number>
+       </property>
+       <property name="mode">
+        <enum>QLCDNumber::Dec</enum>
+       </property>
+       <property name="segmentStyle">
+        <enum>QLCDNumber::Filled</enum>
+       </property>
+       <property name="value" stdset="0">
+        <double>12.340000000000000</double>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </widget>
+   <widget class="QWidget" name="verticalLayoutWidget">
+    <property name="geometry">
+     <rect>
+      <x>410</x>
+      <y>40</y>
+      <width>71</width>
+      <height>261</height>
+     </rect>
+    </property>
+    <layout class="QVBoxLayout" name="verticalLayout">
+     <item>
+      <widget class="QLabel" name="units">
+       <property name="font">
+        <font>
+         <family>Courier 10 Pitch</family>
+         <pointsize>20</pointsize>
+        </font>
+       </property>
+       <property name="text">
+        <string/>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </widget>
+   <widget class="QWidget" name="horizontalLayoutWidget">
+    <property name="geometry">
+     <rect>
+      <x>-2</x>
+      <y>0</y>
+      <width>411</width>
+      <height>41</height>
+     </rect>
+    </property>
+    <layout class="QHBoxLayout" name="horizontalLayout">
+     <item>
+      <widget class="QLabel" name="mode">
+       <property name="font">
+        <font>
+         <family>Courier 10 Pitch</family>
+         <pointsize>20</pointsize>
+        </font>
+       </property>
+       <property name="text">
+        <string/>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </widget>
+   <widget class="QWidget" name="horizontalLayoutWidget_2">
+    <property name="geometry">
+     <rect>
+      <x>409</x>
+      <y>0</y>
+      <width>127</width>
+      <height>41</height>
+     </rect>
+    </property>
+    <layout class="QHBoxLayout" name="horizontalLayout_2">
+     <item>
+      <widget class="QLabel" name="tick">
+       <property name="text">
+        <string/>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </widget>
+  </widget>
+  <widget class="QStatusBar" name="statusbar"/>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>