I've rewritten the code now to properly deal with the presence of both a 0c45:7401 and a 0c45:7403 simultaneously. Note that I'm not convince about the accuracy of the temperature readings (accuracy as in whether there's any systematic bias to the output. The reproducibility seems to be excellent though).
Original post:
I just received this usb thermocouple reader (TEMPer1k4): http://www.pcsensor.com/index.php?_a=product&product_id=104
About the product:
lsusb shows 0c45:7403 (Microdia Foot Switch)
dmesg shows
[13448.536120] usb 6-1: new low-speed USB device number 3 using uhci_hcd [13448.709126] usb 6-1: New USB device found, idVendor=0c45, idProduct=7403 [13448.709139] usb 6-1: New USB device strings: Mfr=1, Product=2, SerialNumber=0 [13448.709146] usb 6-1: Product: TH1000isoV1.5 [13448.709152] usb 6-1: Manufacturer: RDing
Getting it to work:
You can use the same program as in this post: http://verahill.blogspot.com.au/2013/12/532-temper-temperature-monitoring-usb.html with some minor modifications. Before running
sudo python setup.py install
make some changes in temperusb/temper.py.
Firstly, you need to allow for the detection of devices with the 0c45:7403 ID
15 VIDPIDs = [(0x0c45L,0x7401L),(0x0c45L,0x7403L)]
Secondly, you need to make sure that the program knows which device is which and treats them differently. Finally, you need to collect the right data-- the TEMPer1k4 returns a data_s with a length of 8 (caveat -- I haven't checked what the output from 0c45:7401 looks like. Maybe it too yields 8 fields), and the [2:4] data block contains the internal temperature of the USB device, while the [4:6] data block holds the thermocouple junction temperature. Of a sort -- you get a number which you need to translate into a temperature. See the bottom of this post for how I worked it all out. Please note that you should calibrate the thermocouple -- the uncorrected output seems to be at least 2-3 degrees too high.temper.py
cli.py15 VIDPIDs = [(0x0c45L,0x7401L),(0x0c45L,0x7403L)] 63 def getid(self): 64 return self._device.idProduct 125 if self._device.idProduct==29697: 126 temp_c = 125.0/32000.0*(struct.unpack('>h', data_s[2:4])[0])+0.0025 127 if format == 'celsius': 128 temp_c = temp_c * self._scale + self._offset 129 return temp_c 130 elif format == 'fahrenheit': 131 return temp_c*1.8+32.0 132 elif format == 'millicelsius': 133 return int(temp_c*1000) 134 else: 135 raise ValueError("Unknown format") 136 elif self._device.idProduct==29699: 137 temp_c = (struct.unpack('>h', data_s[4:6])[0])*0.25-2.00 138 temp_internal=(125.0/32000.0*(struct.unpack('>h', data_s[2:4])[0]))+0.0025 139 if format == 'celsius': 140 temp_c = temp_c * self._scale + self._offset 141 return temp_c 142 elif format == 'fahrenheit': 143 if self._device.idProduct==29697: #0x7401 144 return temp_c*1.8+32.0 145 elif self._device.idProduct==29699: #0x7403 146 return temp_internal 147
Beyond this, follow the instructions in http://verahill.blogspot.com.au/2013/12/532-temper-temperature-monitoring-usb.html and make sure to set up a rules file with the correct USB ID for this device.31 for i, dev in enumerate(devs): 32 readings.append({'device': i, 33 'id':dev.getid(), 34 'temperature_c': dev.get_temperature(), 35 'temperature_f': 36 dev.get_temperature(format="fahrenheit"), 37 'ports': dev.get_ports(), 38 'bus': dev.get_bus() 39 }) 40 41 for reading in readings: 42 if disp_ports: 43 portinfo = " (bus %s - port %s)" % (reading['bus'], 44 reading['ports']) 45 else: 46 portinfo = "" 47 if reading['id']==29697: 48 print('Device #%i%s: %0.1f°C %0.1f°F' 49 % (reading['device'], 50 portinfo, 51 reading['temperature_c'], 52 reading['temperature_f'])) 53 elif reading['id']==29699: 54 print('Device #%i%s: %0.1f°C %0.1f°C' 55 % (reading['device'], 56 portinfo, 57 reading['temperature_c'], 58 reading['temperature_f']))
Done!
If you're curious about the details, have a look below.
Analysing USB traffic:
Since I wasn't quite sure where to start I figured the easiest approach would be to sniff the traffic between the usb device and the offically supported programme from PC Sensors. So I installed it Win XP in virtualbox, read a couple of temperatures and compared them with the captured data.
To capture data I did:
sudo apt-get install tshark sudo modprobe usbmon lsusbBus 008 Device 003: ID 17ef:4815 Lenovo Integrated Webcam [R5U877] Bus 008 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub Bus 006 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub Bus 005 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub Bus 004 Device 043: ID 0c45:7403 Microdia Foot Switch Bus 004 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub Bus 004 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub Bus 007 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub Bus 003 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub Bus 002 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub Bus 001 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hubsudo tshark -D1. eth0 2. wlan0 3. nflog 4. nfqueue 5. usbmon1 6. usbmon2 7. usbmon3 8. usbmon4 9. usbmon5 10. usbmon6 11. usbmon7 12. usbmon8 13. any 14. lo (Loopback)touch 1.pcap chmod ugo+w 1.pcap sudo tshark -i usbmon4 -w $HOME/1.pcap
I opened the 1.pcap file in wireshark and looked for fields called leftover data.
Here are two examples of such 'leftover data' fields:
Host to USB: 01 80 00 00 00 00 00 00 USB to Host: 80 06 17 f0 00 5c 0f ff
Subjecting the system to different temperatures helped me figure out what fields where changing -- I've marked them in bold above (blue for the thermocouple, red for the internal temperature). Interestingly, I got overflow when freezing the thermocouple between two ice cubes, which indicated that there was a fairly high lower limit (1.5 degrees)
The following are some of the data I harvested. The temperature is in the first column, the hex code is in the second one and the decimal value is in the third one:
Internal:In other words, T(thermo)=output(thermo)*0.25+1.50 and T(int.)=(output(int.)-0.0025)/0.0625. Because the internal data is in fields 2:4 it's seen as e.g. 17b0 instead of 17b, so the decimal version needs to be divided by 16 (line 126 above).
23.63 17a 378 23.69 17b 379 23.94 17f 383 23.75 17c 380 21.88 15e 350 22.31 165 357 22.88 16e 366
Thermocouple: 23.75 05f 95 23.50 05e 9 38.25 099 153 24.00 066 102 87.50 15e 350 79.50 13e 318
I also set up an /etc/temper.conf file and did chown $USER /etc/temper.conf in order to be able to read it (must be a better way). I tried to calibrate the thermocouple in my kitchen with iced water and boiling water. The boiling water gave a reading of 102 degrees, while the iced water gave me 7.5 degrees Celsius even after what should've been long enough to achieve equilibrium. I'll calibrate it in the lab later. So for now a simple offset might be enough to give a working temperature.
temper-poll -pso temper.conf becameDevice #0 (bus 2 - port 2)
2-2: scale = 1.00, offset = -4.0
The temper-usb code is changing too quickly!. You'll thus need to read and understand the code rather than just pasting it in. Here are the full, edited cli.py an temper.py files.
cli.py
# encoding: utf-8 from __future__ import print_function from temper import TemperHandler def main(): th = TemperHandler() devs = th.get_devices() readings = [] print("Found %i devices" % len(devs)) for i, dev in enumerate(devs): readings.append({'device': i, 'id':dev.getid(), 'temperature_c': dev.get_temperature(), 'temperature_f': dev.get_temperature(format="fahrenheit") }) for reading in readings: if reading['id']==29697: print('Device #%i: %0.1f°C %0.1f°F' % (reading['device'], reading['temperature_c'], reading['temperature_f'])) elif reading['id']==29699: print('Device #%i: %0.1f°C %0.1f°C' % (reading['device'], reading['temperature_c'], reading['temperature_f']))
temper.py
# encoding: utf-8 # # Handles devices reporting themselves as USB VID/PID 0C45:7401 (mine also says RDing TEMPerV1.2). # # Copyright 2012, 2013 Philipp Adelt# # This code is licensed under the GNU public license (GPL). See LICENSE.md for details. import usb import sys import struct VIDPIDs = [(0x0c45L,0x7401L),(0x0c45L,0x7402L),(0x0c45L,0x7403L)] REQ_INT_LEN = 8 REQ_BULK_LEN = 8 TIMEOUT = 2000 class TemperDevice(): def __init__(self, device): self._device = device self._handle = None def get_temperature(self, format='celsius'): try: if not self._handle: self._handle = self._device.open() try: self._handle.detachKernelDriver(0) except usb.USBError: pass try: self._handle.detachKernelDriver(1) except usb.USBError: pass self._handle.setConfiguration(1) self._handle.claimInterface(0) self._handle.claimInterface(1) self._handle.controlMsg(requestType=0x21, request=0x09, value=0x0201, index=0x00, buffer="\x01\x01", timeout=TIMEOUT) # ini_control_transfer self._control_transfer(self._handle, "\x01\x80\x33\x01\x00\x00\x00\x00") # uTemperatura self._interrupt_read(self._handle) self._control_transfer(self._handle, "\x01\x82\x77\x01\x00\x00\x00\x00") # uIni1 self._interrupt_read(self._handle) self._control_transfer(self._handle, "\x01\x86\xff\x01\x00\x00\x00\x00") # uIni2 self._interrupt_read(self._handle) self._interrupt_read(self._handle) self._control_transfer(self._handle, "\x01\x80\x33\x01\x00\x00\x00\x00") # uTemperatura data = self._interrupt_read(self._handle) data_s = "".join([chr(byte) for byte in data]) if self._device.idProduct==29697: temp_c = 125.0/32000.0*(struct.unpack('>h', data_s[2:4])[0])+0.0025 if format == 'celsius': return temp_c elif format == 'fahrenheit': return temp_c*1.8+32.0 elif format == 'millicelsius': return int(temp_c*1000) else: raise ValueError("Unknown format") elif self._device.idProduct==29699: temp_c = (struct.unpack('>h', data_s[4:6])[0])*0.25-4.00 temp_internal=(125.0/32000.0*(struct.unpack('>h', data_s[2:4])[0]))+0.0025 if format == 'celsius': return temp_c elif format == 'fahrenheit': if self._device.idProduct==29697: #0x7401 return temp_c*1.8+32.0 elif self._device.idProduct==29699: #0x7403 return temp_internal elif format == 'millicelsius': return int(temp_c*1000) else: raise ValueError("Unknown format") except usb.USBError, e: self.close() if "not permitted" in str(e): raise Exception("Permission problem accessing USB. Maybe I need to run as root?") else: raise def getid(self): #print self._device.idProduct return self._device.idProduct def close(self): if self._handle: try: self._handle.releaseInterface() except ValueError: pass self._handle = None def _control_transfer(self, handle, data): handle.controlMsg(requestType=0x21, request=0x09, value=0x0200, index=0x01, buffer=data, timeout=TIMEOUT) def _interrupt_read(self, handle): return handle.interruptRead(0x82, REQ_INT_LEN) class TemperHandler(): def __init__(self): busses = usb.busses() self._devices = [] for bus in busses: self._devices.extend([TemperDevice(x) for x in bus.devices if (x.idVendor,x.idProduct) in VIDPIDs]) def get_devices(self): return self._devices