From b416635cbf5202b2e748914cd0f0b283e9a85704 Mon Sep 17 00:00:00 2001 From: James Stewart Date: Fri, 9 May 2014 08:38:56 +1000 Subject: [PATCH 001/159] Refactor and update to use usb.core instead of usb.legacy * TEMPer devices are now discovered and created using usb.core * Bus and Port are now correctly identified * Added missing docstrings * Tidied up whitespace for PEP8 compliance --- temperusb/temper.py | 213 +++++++++++++++++++++++++++----------------- 1 file changed, 129 insertions(+), 84 deletions(-) diff --git a/temperusb/temper.py b/temperusb/temper.py index 2fc4054..6bda77d 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -1,41 +1,54 @@ # encoding: utf-8 # -# Handles devices reporting themselves as USB VID/PID 0C45:7401 (mine also says RDing TEMPerV1.2). +# Handles devices reporting themselves as USB VID/PID 0C45:7401 (mine also says +# RDing TEMPerV1.2). # # Copyright 2012-2014 Philipp Adelt # -# This code is licensed under the GNU public license (GPL). See LICENSE.md for details. +# This code is licensed under the GNU public license (GPL). See LICENSE.md for +# details. import usb import sys import struct import os import re +import logging -VIDPIDs = [(0x0c45L,0x7401L)] +VIDPIDS = [(0x0c45L, 0x7401L)] REQ_INT_LEN = 8 -REQ_BULK_LEN = 8 -TIMEOUT = 2000 +ENDPOINT = 0x82 +INTERFACE = 1 +TIMEOUT = 5000 USB_PORTS_STR = '^\s*(\d+)-(\d+(?:\.\d+)*)' CALIB_LINE_STR = USB_PORTS_STR +\ '\s*:\s*scale\s*=\s*([+|-]?\d*\.\d+)\s*,\s*offset\s*=\s*([+|-]?\d*\.\d+)' +USB_SYS_PREFIX = '/sys/bus/usb/devices/' -USB_SYS_PREFIX='/sys/bus/usb/devices/' def readattr(path, name): - """Read attribute from sysfs and return as string""" + """ + Read attribute from sysfs and return as string + """ try: - f = open(USB_SYS_PREFIX + path + "/" + name); - return f.readline().rstrip("\n"); + f = open(USB_SYS_PREFIX + path + "/" + name) + return f.readline().rstrip("\n") except IOError: return None -def find_ports(bus, device): - """look into sysfs and find a device that matches given\ - bus/device ID combination, then returns the port chain it is\ - plugged on.""" - bus_id = int(bus.dirname) - dev_id = int(device.filename) + +def find_ports(device): + """ + Find the port chain a device is plugged on. + + This is done by searching sysfs for a device that matches the device + bus/address combination. + + Useful when the underlying usb lib does not return device.port_number for + whatever reason. + """ + bus_id = device.bus + dev_id = device.address for dirent in os.listdir(USB_SYS_PREFIX): matches = re.match(USB_PORTS_STR + '$', dirent) if matches: @@ -50,17 +63,25 @@ def find_ports(bus, device): else: devnum = None if busnum == bus_id and devnum == dev_id: - return matches.groups()[1] + return int(matches.groups()[1]) + -class TemperDevice(): - def __init__(self, device, bus): +class TemperDevice(object): + """ + A TEMPer USB thermometer. + """ + def __init__(self, device): self._device = device - self._bus = bus - self._handle = None - self._ports = find_ports(bus, device) + self._bus = device.bus + self._ports = getattr(device, 'port_number', None) + if self._ports == None: + self._ports = find_ports(device) self.set_calibration_data() def set_calibration_data(self): + """ + Set device calibration data based on settings in /etc/temper.conf. + """ self._scale = 1.0 self._offset = 0.0 try: @@ -80,83 +101,107 @@ def set_calibration_data(self): if ports == self._ports: self._scale = scale self._offset = offset + def get_ports(self): + """ + Get device USB ports. + """ if self._ports: return self._ports - else: - return "" + return '' def get_bus(self): - if self._bus and hasattr(self._bus, 'dirname'): - return str(int(self._bus.dirname)) - else: - return "" + """ + Get device USB bus. + """ + if self._bus: + return self._bus + return '' def get_temperature(self, format='celsius'): + """ + Get device temperature reading. + """ 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]) - temp_c = 125.0/32000.0*(struct.unpack('>h', data_s[2:4])[0]) - temp_c = temp_c * self._scale + self._offset - 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") + # Take control of device if required + if self._device.is_kernel_driver_active: + for interface in [0, 1]: + try: + self._device.detach_kernel_driver(interface) + except usb.USBError: + pass + self._device.set_configuration(1) + self._device.ctrl_transfer( + bmRequestType=0x21, + bRequest=0x09, + wValue=0x0201, + wIndex=0x00, + data_or_wLength='\x01\x01', + timeout=TIMEOUT) + # Get temperature + self._control_transfer("\x01\x80\x33\x01\x00\x00\x00\x00") # Temp + self._interrupt_read() + self._control_transfer("\x01\x82\x77\x01\x00\x00\x00\x00") # Ini1 + self._interrupt_read() + self._control_transfer("\x01\x86\xff\x01\x00\x00\x00\x00") # Ini2 + self._interrupt_read() + self._interrupt_read() + self._control_transfer("\x01\x80\x33\x01\x00\x00\x00\x00") # Temp + data = self._interrupt_read() except usb.USBError, e: - self.close() + # Catch the permissions exception and add our message if "not permitted" in str(e): - raise Exception("Permission problem accessing USB. Maybe I need to run as root?") + raise Exception( + "Permission problem accessing USB. " + "Maybe I need to run as root?") else: raise + # Interpret device response + data_s = "".join([chr(byte) for byte in data]) + temp_c = 125.0/32000.0*(struct.unpack('>h', data_s[2:4])[0]) + temp_c = temp_c * self._scale + self._offset + 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") + + def _control_transfer(self, data): + """ + Send device a control request with standard parameters and as + payload. + """ + self._device.ctrl_transfer( + bmRequestType=0x21, + bRequest=0x09, + wValue=0x0200, + wIndex=0x01, + data_or_wLength=data, + timeout=TIMEOUT) + + def _interrupt_read(self): + """ + Read data from device. + """ + data = self._device.read(ENDPOINT, REQ_INT_LEN, interface=INTERFACE, timeout=TIMEOUT) + return data + + +class TemperHandler(object): + """ + Handler for TEMPer USB thermometers. + """ - 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, bus) for x in bus.devices if (x.idVendor,x.idProduct) in VIDPIDs]) + for vid, pid in VIDPIDS: + self._devices = [TemperDevice(device) for device in \ + usb.core.find(find_all=True, idVendor=vid, idProduct=pid)] def get_devices(self): + """ + Get a list of all devices attached to this handler + """ return self._devices From 48e7a8fefff386ae7de2eaf07c7fb52a5f20bc80 Mon Sep 17 00:00:00 2001 From: amorphic Date: Sat, 10 May 2014 16:02:55 +1000 Subject: [PATCH 002/159] Adding pyusb 1.0.0b1 as a dependency --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 46e92c9..cbc3eb9 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ description='Reads temperature from TEMPerV1 devices (USB 0c45:7401)', long_description=open('README.md').read(), packages=['temperusb'], - install_requires=['pyusb'], + install_requires=['pyusb>=1.0.0b1'], entry_points={ 'console_scripts': [ 'temper-poll = temperusb.cli:main', From edd394d7eace8150241d512be3bcaf431fa57775 Mon Sep 17 00:00:00 2001 From: amorphic Date: Sat, 10 May 2014 16:15:51 +1000 Subject: [PATCH 003/159] Port chains were being returned that could not be cast to int(). Casting to float() seems to work. --- temperusb/temper.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/temperusb/temper.py b/temperusb/temper.py index 6bda77d..41cf235 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -54,16 +54,16 @@ def find_ports(device): if matches: bus_str = readattr(dirent, 'busnum') if bus_str: - busnum = int(bus_str) + busnum = float(bus_str) else: busnum = None dev_str = readattr(dirent, 'devnum') if dev_str: - devnum = int(dev_str) + devnum = float(dev_str) else: devnum = None if busnum == bus_id and devnum == dev_id: - return int(matches.groups()[1]) + return float(matches.groups()[1]) class TemperDevice(object): From d7d0b1a7863bc1e7f37109fca4559c66b7924aed Mon Sep 17 00:00:00 2001 From: amorphic Date: Sat, 10 May 2014 17:23:03 +1000 Subject: [PATCH 004/159] Fixed issue #6 and added logging * Previously a ```detach_kernel_driver()``` call was being made on every poll, rather than only when it was required. This seems to have been breaking the USB connection after a small number of polls. Now a kernel detach is only requested when explicitly required. In preliminary tests this seems to have fixed the various USBErrors documented in issue #7. * Added debug and info logging. --- temperusb/temper.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/temperusb/temper.py b/temperusb/temper.py index 41cf235..f55e698 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -19,11 +19,13 @@ REQ_INT_LEN = 8 ENDPOINT = 0x82 INTERFACE = 1 +CONFIG_NO = 1 TIMEOUT = 5000 USB_PORTS_STR = '^\s*(\d+)-(\d+(?:\.\d+)*)' CALIB_LINE_STR = USB_PORTS_STR +\ '\s*:\s*scale\s*=\s*([+|-]?\d*\.\d+)\s*,\s*offset\s*=\s*([+|-]?\d*\.\d+)' USB_SYS_PREFIX = '/sys/bus/usb/devices/' +LOGGER = logging.getLogger(__name__) def readattr(path, name): @@ -124,13 +126,12 @@ def get_temperature(self, format='celsius'): """ try: # Take control of device if required - if self._device.is_kernel_driver_active: - for interface in [0, 1]: - try: - self._device.detach_kernel_driver(interface) - except usb.USBError: - pass - self._device.set_configuration(1) + if self._device.is_kernel_driver_active(INTERFACE): + try: + self._device.detach_kernel_driver(INTERFACE) + except usb.USBError as err: + LOGGER.debug('Error detaching kernel driver: {0}'.format(err)) + self._device.set_configuration(CONFIG_NO) self._device.ctrl_transfer( bmRequestType=0x21, bRequest=0x09, @@ -174,6 +175,7 @@ def _control_transfer(self, data): Send device a control request with standard parameters and as payload. """ + LOGGER.debug('Sending control transfer: {0}'.format(data)) self._device.ctrl_transfer( bmRequestType=0x21, bRequest=0x09, @@ -187,6 +189,7 @@ def _interrupt_read(self): Read data from device. """ data = self._device.read(ENDPOINT, REQ_INT_LEN, interface=INTERFACE, timeout=TIMEOUT) + LOGGER.debug('Read data: {0}'.format(data)) return data @@ -199,6 +202,7 @@ def __init__(self): for vid, pid in VIDPIDS: self._devices = [TemperDevice(device) for device in \ usb.core.find(find_all=True, idVendor=vid, idProduct=pid)] + LOGGER.info('Found {0} TEMPer devices'.format(len(self._devices))) def get_devices(self): """ From ab43dbdfe333b4628334e979548f4b569cd7c366 Mon Sep 17 00:00:00 2001 From: Philipp Adelt Date: Mon, 12 May 2014 07:49:53 +0000 Subject: [PATCH 005/159] Add note and solution to the linux kernel claiming the TEMPer as a keyboard --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index f87f729..48257c5 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,21 @@ Device #0 (bus 1 - port 1.3): 22.4°C 72.3°F Which tells you there is a a USB hub plugged (internally or externally) on the port 1 of the bus 1 of the host, and your TEMPer device is on the port 3 of that hub. +## Tell kernel to leave TEMPer alone (Errors `usb.core.USBError: [Errno 16] Resource busy` and `Unknown error`) + +By default, the Linux kernel claims (e.g. opens/uses) the TEMPer device as a keyboard (HID device). +When that happens, this script is not able to set the configuration and communicate with it. + +You will see one of those two errors when running `sudo temper-poll`. Your `dmesg` log will show something similar to this: + + usb 1-1.3: usbfs: interface 0 claimed by usbhid while 'temper-poll' sets config #1 + +To prevent this, add this to the kernel command line: + + usbhid.quirks=0x0c45:0x7401:0x4 + +On Raspbian, this will be `/boot/cmdline.txt`. Reboot after saving and retry. Hat tip to and more information from [AndiDog here](http://unix.stackexch + # Serving via SNMP Using [NetSNMP](http://www.net-snmp.org/), you can use `temper/snmp.py` From e5658563d9fcf426122e434d8310481b4c6e51ed Mon Sep 17 00:00:00 2001 From: Philipp Adelt Date: Mon, 12 May 2014 07:52:59 +0000 Subject: [PATCH 006/159] README-prettification --- README.md | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 48257c5..caa568d 100644 --- a/README.md +++ b/README.md @@ -32,18 +32,25 @@ If your system does not provide access as a normal user to the USB device, you n temper-poll accepts -p option now, which adds the USB bus and port information each device is plugged on. without -p option -$ temper-poll -Found 1 devices -Device #0: 22.5°C 72.5°F + + $ temper-poll + Found 1 devices + Device #0: 22.5°C 72.5°F with -p option -$ temper-poll -p -Found 1 devices -Device #0 (bus 1 - port 1.3): 22.4°C 72.3°F + + $ temper-poll -p + Found 1 devices + Device #0 (bus 1 - port 1.3): 22.4°C 72.3°F Which tells you there is a a USB hub plugged (internally or externally) on the port 1 of the bus 1 of the host, and your TEMPer device is on the port 3 of that hub. -## Tell kernel to leave TEMPer alone (Errors `usb.core.USBError: [Errno 16] Resource busy` and `Unknown error`) +## Tell kernel to leave TEMPer alone + +Regarding errors: + +- `usb.core.USBError: [Errno 16] Resource busy` +- `Unknown error` By default, the Linux kernel claims (e.g. opens/uses) the TEMPer device as a keyboard (HID device). When that happens, this script is not able to set the configuration and communicate with it. @@ -56,7 +63,7 @@ To prevent this, add this to the kernel command line: usbhid.quirks=0x0c45:0x7401:0x4 -On Raspbian, this will be `/boot/cmdline.txt`. Reboot after saving and retry. Hat tip to and more information from [AndiDog here](http://unix.stackexch +On Raspbian, this will be `/boot/cmdline.txt`. Reboot after saving and retry. Hat tip to and more information from [AndiDog here](http://unix.stackexchange.com/questions/55495/prevent-usbhid-from-claiming-usb-device). # Serving via SNMP From 6a3041224b01f8f6427b6c467ba884d00d974b1a Mon Sep 17 00:00:00 2001 From: Philipp Adelt Date: Mon, 12 May 2014 07:57:38 +0000 Subject: [PATCH 007/159] Update author entry of James --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index caa568d..78c460a 100644 --- a/README.md +++ b/README.md @@ -267,4 +267,4 @@ as seen on [Google+](https://plus.google.com/105569853186899442987/posts/N9T7xAj * Additional work by Brian Cline * Calibration code by Joji Monma (@GM3D on Github) * Munin plugin by Alexander Schier (@allo- on Github) -* PyPI packge work by James Stewart (@amorphic on Github) +* PyPI package work and rewrite to `libusb1` by James Stewart (@amorphic on Github) From f645357eb85d4cb3f8e2a0925fe3e8ccc3ad7430 Mon Sep 17 00:00:00 2001 From: Philipp Adelt Date: Mon, 12 May 2014 08:01:25 +0000 Subject: [PATCH 008/159] Bump PyPI version 1.1.2 -> 1.2.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index cbc3eb9..661b141 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ author='Philipp Adelt', author_email='autosort-github@philipp.adelt.net ', url='https://github.com/padelt/temper-python', - version='1.1.2', + version='1.2.0', description='Reads temperature from TEMPerV1 devices (USB 0c45:7401)', long_description=open('README.md').read(), packages=['temperusb'], From a9c8b68570f94d4912c765a2b0c57e0399835c1f Mon Sep 17 00:00:00 2001 From: James Stewart Date: Mon, 19 May 2014 18:40:42 +1000 Subject: [PATCH 009/159] Fixed bug causing USBError after repeated polling * Added a device reset() after each poll which seems to eliminate the USBErrors which were being thrown consistently after repeated polling. * Added logging * Moved commands to a global --- temperusb/temper.py | 40 +++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/temperusb/temper.py b/temperusb/temper.py index 6bda77d..c9f3274 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -24,6 +24,12 @@ CALIB_LINE_STR = USB_PORTS_STR +\ '\s*:\s*scale\s*=\s*([+|-]?\d*\.\d+)\s*,\s*offset\s*=\s*([+|-]?\d*\.\d+)' USB_SYS_PREFIX = '/sys/bus/usb/devices/' +COMMANDS = { + 'temp': '\x01\x80\x33\x01\x00\x00\x00\x00', + 'ini1': '\x01\x82\x77\x01\x00\x00\x00\x00', + 'ini2': '\x01\x86\xff\x01\x00\x00\x00\x00', +} +LOGGER = logging.getLogger(__name__) def readattr(path, name): @@ -77,6 +83,8 @@ def __init__(self, device): if self._ports == None: self._ports = find_ports(device) self.set_calibration_data() + LOGGER.debug('Found device | Bus:{0} Ports:{1}'.format( + self._bus, self._ports)) def set_calibration_data(self): """ @@ -125,36 +133,36 @@ def get_temperature(self, format='celsius'): try: # Take control of device if required if self._device.is_kernel_driver_active: + LOGGER.debug('Taking control of device on bus {0} ports ' + '{1}'.format(self._bus, self._ports)) for interface in [0, 1]: try: self._device.detach_kernel_driver(interface) - except usb.USBError: - pass - self._device.set_configuration(1) + except usb.USBError as err: + LOGGER.debug(err) + self._device.set_configuration() self._device.ctrl_transfer( - bmRequestType=0x21, - bRequest=0x09, - wValue=0x0201, - wIndex=0x00, - data_or_wLength='\x01\x01', - timeout=TIMEOUT) + bmRequestType=0x21, bRequest=0x09, wValue=0x0201, + wIndex=0x00, data_or_wLength='\x01\x01', timeout=TIMEOUT) # Get temperature - self._control_transfer("\x01\x80\x33\x01\x00\x00\x00\x00") # Temp + self._control_transfer(COMMANDS['temp']) self._interrupt_read() - self._control_transfer("\x01\x82\x77\x01\x00\x00\x00\x00") # Ini1 + self._control_transfer(COMMANDS['ini1']) self._interrupt_read() - self._control_transfer("\x01\x86\xff\x01\x00\x00\x00\x00") # Ini2 + self._control_transfer(COMMANDS['ini2']) self._interrupt_read() self._interrupt_read() - self._control_transfer("\x01\x80\x33\x01\x00\x00\x00\x00") # Temp + self._control_transfer(COMMANDS['temp']) data = self._interrupt_read() - except usb.USBError, e: + self._device.reset() + except usb.USBError as err: # Catch the permissions exception and add our message - if "not permitted" in str(e): + if "not permitted" in str(err): raise Exception( "Permission problem accessing USB. " "Maybe I need to run as root?") else: + LOGGER.error(err) raise # Interpret device response data_s = "".join([chr(byte) for byte in data]) @@ -174,6 +182,7 @@ def _control_transfer(self, data): Send device a control request with standard parameters and as payload. """ + LOGGER.debug('Ctrl transfer: {0}'.format(data)) self._device.ctrl_transfer( bmRequestType=0x21, bRequest=0x09, @@ -187,6 +196,7 @@ def _interrupt_read(self): Read data from device. """ data = self._device.read(ENDPOINT, REQ_INT_LEN, interface=INTERFACE, timeout=TIMEOUT) + LOGGER.debug('Read data: {0}'.format(data)) return data From 811f3c54da4552c5a9a4caf62b8e145880ca3816 Mon Sep 17 00:00:00 2001 From: James Stewart Date: Tue, 10 Jun 2014 18:38:13 +1000 Subject: [PATCH 010/159] Ports are not always floats so returning as strings instead --- setup.py | 2 +- temperusb/temper.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 661b141..4c7572f 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ author='Philipp Adelt', author_email='autosort-github@philipp.adelt.net ', url='https://github.com/padelt/temper-python', - version='1.2.0', + version='1.2.1', description='Reads temperature from TEMPerV1 devices (USB 0c45:7401)', long_description=open('README.md').read(), packages=['temperusb'], diff --git a/temperusb/temper.py b/temperusb/temper.py index 26cbdb3..dd1aa71 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -70,7 +70,7 @@ def find_ports(device): else: devnum = None if busnum == bus_id and devnum == dev_id: - return float(matches.groups()[1]) + return str(matches.groups()[1]) class TemperDevice(object): From 504f68e1edd3310b56315395a13bf5ffb57c2254 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20S=C3=A9hier?= Date: Wed, 2 Jul 2014 16:14:57 +0200 Subject: [PATCH 011/159] add quiet output option - -q: outputs just a number, without unit. usefull for parsing in external programs - add -c and -f options for choosing unit (degress celcius of farhenheit) --- temperusb/cli.py | 52 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/temperusb/cli.py b/temperusb/cli.py index 7b91811..9689fbc 100644 --- a/temperusb/cli.py +++ b/temperusb/cli.py @@ -4,29 +4,46 @@ import getopt, sys, os.path def usage(): - print("%s [-p] [-h|--help]" % os.path.basename(sys.argv[0])) + print("%s [-p] [-q] [-c|-f] [-h|--help]" % os.path.basename(sys.argv[0])) + print(" -q quiet: only output temperature as a floating number. Usefull for external program parsing") + print(" this option requires the use of -c or -f") + print(" -c with -q, outputs temperature in celcius degrees.") + print(" -f with -q, outputs temperature in fahrenheit degrees.") def main(): try: - opts, args = getopt.getopt(sys.argv[1:], ":hp", ["help"]) + opts, args = getopt.getopt(sys.argv[1:], ":hpcfq", ["help"]) except getopt.GetoptError as err: print(str(err)) usage() sys.exit(2) + degree_unit = False disp_ports = False + quiet_output = False for o, a in opts: if o == "-p": disp_ports = True + elif o == "-c": + degree_unit = 'c' + elif o == "-f": + degree_unit = 'f' + elif o == "-q": + quiet_output = True elif o in ("-h", "--help"): usage() sys.exit() else: assert False, "unhandled option" + if quiet_output and not degree_unit: + print('You need to specify unit (-c of -f) when using -q option') + sys.exit(1) + th = TemperHandler() devs = th.get_devices() readings = [] - print("Found %i devices" % len(devs)) + if not quiet_output: + print("Found %i devices" % len(devs)) for i, dev in enumerate(devs): readings.append({'device': i, @@ -38,13 +55,24 @@ def main(): }) for reading in readings: - if disp_ports: - portinfo = " (bus %s - port %s)" % (reading['bus'], - reading['ports']) + if quiet_output: + if degree_unit == 'c': + print('%0.1f' + % reading['temperature_c']) + elif degree_unit == 'f': + print('%0.1f' + % reading['temperature_f']) + else: + print('how did I end up here?') + sys.exit(1) else: - portinfo = "" - print('Device #%i%s: %0.1f°C %0.1f°F' - % (reading['device'], - portinfo, - reading['temperature_c'], - reading['temperature_f'])) + if disp_ports: + portinfo = " (bus %s - port %s)" % (reading['bus'], + reading['ports']) + else: + portinfo = "" + print('Device #%i%s: %0.1f°C %0.1f°F' + % (reading['device'], + portinfo, + reading['temperature_c'], + reading['temperature_f'])) From c0a968ba6d0149fe80cfdaeba6889dcbf7a3b8f2 Mon Sep 17 00:00:00 2001 From: amorphic Date: Thu, 9 Oct 2014 18:20:15 +1100 Subject: [PATCH 012/159] Add vendor/product pid for new model TEMPer. --- temperusb/temper.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/temperusb/temper.py b/temperusb/temper.py index dd1aa71..612f756 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -15,7 +15,10 @@ import re import logging -VIDPIDS = [(0x0c45L, 0x7401L)] +VIDPIDS = [ + (0x0c45L, 0x7401L), + (0x1130L, 0x660cL), +] REQ_INT_LEN = 8 ENDPOINT = 0x82 INTERFACE = 1 From b90d34989df98c984fdf82136edc7e7b2bbe4c87 Mon Sep 17 00:00:00 2001 From: amorphic Date: Thu, 9 Oct 2014 18:21:32 +1100 Subject: [PATCH 013/159] Append devices discovered for each VIDPID. --- temperusb/temper.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/temperusb/temper.py b/temperusb/temper.py index 612f756..a7567ff 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -205,8 +205,9 @@ class TemperHandler(object): """ def __init__(self): + self._devices = [] for vid, pid in VIDPIDS: - self._devices = [TemperDevice(device) for device in \ + self._devices += [TemperDevice(device) for device in \ usb.core.find(find_all=True, idVendor=vid, idProduct=pid)] LOGGER.info('Found {0} TEMPer devices'.format(len(self._devices))) From e2a60aac27e628b103dfd17414867caab8a4d4ee Mon Sep 17 00:00:00 2001 From: amorphic Date: Tue, 11 Nov 2014 07:13:43 +1100 Subject: [PATCH 014/159] Remove new VIDPID as it is not compatible with current implementation. --- temperusb/temper.py | 1 - 1 file changed, 1 deletion(-) diff --git a/temperusb/temper.py b/temperusb/temper.py index a7567ff..06510a3 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -17,7 +17,6 @@ VIDPIDS = [ (0x0c45L, 0x7401L), - (0x1130L, 0x660cL), ] REQ_INT_LEN = 8 ENDPOINT = 0x82 From 4ec22004ae97190956447dae0207848f3122d3c8 Mon Sep 17 00:00:00 2001 From: amorphic Date: Tue, 11 Nov 2014 07:50:35 +1100 Subject: [PATCH 015/159] Use new pyusb 1.0.0b2 API. Fixes #21. Note: includes a version bump for deployment to PyPI. --- setup.py | 6 ++++-- temperusb/temper.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 4c7572f..032a8cc 100644 --- a/setup.py +++ b/setup.py @@ -5,11 +5,13 @@ author='Philipp Adelt', author_email='autosort-github@philipp.adelt.net ', url='https://github.com/padelt/temper-python', - version='1.2.1', + version='1.2.2', description='Reads temperature from TEMPerV1 devices (USB 0c45:7401)', long_description=open('README.md').read(), packages=['temperusb'], - install_requires=['pyusb>=1.0.0b1'], + install_requires=[ + 'pyusb>=1.0.0b2', + ], entry_points={ 'console_scripts': [ 'temper-poll = temperusb.cli:main', diff --git a/temperusb/temper.py b/temperusb/temper.py index 06510a3..8d3e146 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -193,7 +193,7 @@ def _interrupt_read(self): """ Read data from device. """ - data = self._device.read(ENDPOINT, REQ_INT_LEN, interface=INTERFACE, timeout=TIMEOUT) + data = self._device.read(ENDPOINT, REQ_INT_LEN, timeout=TIMEOUT) LOGGER.debug('Read data: {0}'.format(data)) return data From cd7fe9961dc28ba1439045872fda85c5162bf046 Mon Sep 17 00:00:00 2001 From: Philipp Adelt Date: Wed, 12 Nov 2014 23:35:57 +0100 Subject: [PATCH 016/159] Revert to pyusb==1.0.0.b1 with version now fixed; package version bump to 1.2.3; fixes #24 --- setup.py | 4 ++-- temperusb/temper.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 032a8cc..544c859 100644 --- a/setup.py +++ b/setup.py @@ -5,12 +5,12 @@ author='Philipp Adelt', author_email='autosort-github@philipp.adelt.net ', url='https://github.com/padelt/temper-python', - version='1.2.2', + version='1.2.3', description='Reads temperature from TEMPerV1 devices (USB 0c45:7401)', long_description=open('README.md').read(), packages=['temperusb'], install_requires=[ - 'pyusb>=1.0.0b2', + 'pyusb==1.0.0b1', ], entry_points={ 'console_scripts': [ diff --git a/temperusb/temper.py b/temperusb/temper.py index 8d3e146..06510a3 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -193,7 +193,7 @@ def _interrupt_read(self): """ Read data from device. """ - data = self._device.read(ENDPOINT, REQ_INT_LEN, timeout=TIMEOUT) + data = self._device.read(ENDPOINT, REQ_INT_LEN, interface=INTERFACE, timeout=TIMEOUT) LOGGER.debug('Read data: {0}'.format(data)) return data From 5fe5ef7f60902d0509458a7cbe66cf27e2c9a17a Mon Sep 17 00:00:00 2001 From: Philipp Adelt Date: Sat, 31 Jan 2015 19:08:46 +0100 Subject: [PATCH 017/159] Convenience execution of main() when cli.py is started from the command line --- temperusb/cli.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/temperusb/cli.py b/temperusb/cli.py index 9689fbc..b897b81 100644 --- a/temperusb/cli.py +++ b/temperusb/cli.py @@ -76,3 +76,6 @@ def main(): portinfo, reading['temperature_c'], reading['temperature_f'])) + +if __name__ == '__main__': + main() From 3c1b4de77ef774e6029a22ca44d7aac7a48a8993 Mon Sep 17 00:00:00 2001 From: Doug Hellmann Date: Sat, 21 Mar 2015 09:24:31 -0400 Subject: [PATCH 018/159] fix imports after moving temper module After the temper module was moved into the temperusb package, the imports in the modules were not updated to reflect the new location. --- temperusb/__init__.py | 2 +- temperusb/cli.py | 2 +- temperusb/snmp.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/temperusb/__init__.py b/temperusb/__init__.py index d62bdce..ef19ec6 100644 --- a/temperusb/__init__.py +++ b/temperusb/__init__.py @@ -1 +1 @@ -from temper import TemperDevice, TemperHandler +from .temper import TemperDevice, TemperHandler diff --git a/temperusb/cli.py b/temperusb/cli.py index b897b81..c764ccd 100644 --- a/temperusb/cli.py +++ b/temperusb/cli.py @@ -1,6 +1,6 @@ # encoding: utf-8 from __future__ import print_function -from temper import TemperHandler +from temperusb.temper import TemperHandler import getopt, sys, os.path def usage(): diff --git a/temperusb/snmp.py b/temperusb/snmp.py index ee8618b..262012d 100644 --- a/temperusb/snmp.py +++ b/temperusb/snmp.py @@ -12,7 +12,7 @@ import syslog import threading import snmp_passpersist as snmp -from temper import TemperHandler, TemperDevice +from temperusb.temper import TemperHandler, TemperDevice ERROR_TEMPERATURE = 9999 From 7792cbce56adc9fc42230a2826a167a71c3e6539 Mon Sep 17 00:00:00 2001 From: Philip Jay Date: Mon, 15 Jun 2015 23:03:56 +1000 Subject: [PATCH 019/159] Eliminate kernel messages on get_temperature call Developed against CentOS 6.6; kernel 2.6.32-504.16.2.el6.x86_64. --- temperusb/temper.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/temperusb/temper.py b/temperusb/temper.py index 06510a3..51e8cfa 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -144,6 +144,11 @@ def get_temperature(self, format='celsius'): except usb.USBError as err: LOGGER.debug(err) self._device.set_configuration() + # Prevent kernel message: + # "usbfs: process (python) did not claim interface x before use" + for interface in [0, 1]: + usb.util.claim_interface(self._device, interface) + usb.util.claim_interface(self._device, interface) self._device.ctrl_transfer(bmRequestType=0x21, bRequest=0x09, wValue=0x0201, wIndex=0x00, data_or_wLength='\x01\x01', timeout=TIMEOUT) @@ -157,7 +162,10 @@ def get_temperature(self, format='celsius'): self._interrupt_read() self._control_transfer(COMMANDS['temp']) data = self._interrupt_read() - self._device.reset() + # Seems unneccessary to reset each time + # Also ends up hitting syslog with this kernel message each time: + # "reset low speed USB device number x using uhci_hcd" + # self._device.reset() except usb.USBError as err: # Catch the permissions exception and add our message if "not permitted" in str(err): From b77295215af172519bd237e26d501ba9679ca414 Mon Sep 17 00:00:00 2001 From: Philip Jay Date: Mon, 15 Jun 2015 23:47:02 +1000 Subject: [PATCH 020/159] Add support for two-sensor TEMPer devices I've maintained backward compatibility with existing method call behaviour, and return type. * if you call get_temperature(), you'll still get the first sensor's reading returned as a float or int. * if you call get_temperature(sensor=x), you'll get sensor x's reading returned as a float or int. * if you call get_temperature(sensor="all"), you'll get both sensors readings returned as a list of floats or ints. --- temperusb/temper.py | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/temperusb/temper.py b/temperusb/temper.py index 06510a3..7b4d599 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -129,10 +129,21 @@ def get_bus(self): return self._bus return '' - def get_temperature(self, format='celsius'): + def get_temperature(self, format='celsius', sensor=0): """ Get device temperature reading. """ + # Only supported sensors are 0 and 1 at this stage. + # If you have the 8 sensor model, please contribute to the + # discussion here: https://github.com/padelt/temper-python/issues/19 + if sensor not in [0, 1, "all"]: + raise ValueError('Only sensor 0 or 1, or the keyword "all" supported') + + if sensor == 0 or sensor == 1: + offsets = [(sensor + 1) * 2,] + elif sensor == "all": + offsets = [2, 4,] + try: # Take control of device if required if self._device.is_kernel_driver_active: @@ -167,16 +178,28 @@ def get_temperature(self, format='celsius'): else: LOGGER.error(err) raise + # Interpret device response - data_s = "".join([chr(byte) for byte in data]) - temp_c = 125.0/32000.0*(struct.unpack('>h', data_s[2:4])[0]) - temp_c = temp_c * self._scale + self._offset + temp_c = [] + for offset in offsets: + data_s = "".join([chr(byte) for byte in data]) + value = (struct.unpack('>h', data_s[offset:(offset + 2)])[0]) + temp_c.append((125.0 / 32000.0) * value) + temp_c[-1] = temp_c[-1] * self._scale + self._offset + + # Return the result or results if format == 'celsius': + if len(temp_c) == 1: + return temp_c[0] return temp_c elif format == 'fahrenheit': - return temp_c*1.8+32.0 + if len(temp_c) == 1: + return temp_c[0] * 1.8 + 32.0 + return [x * 1.8 + 32.0 for x in temp_c] elif format == 'millicelsius': - return int(temp_c*1000) + if len(temp_c) == 1: + return int(temp_c[0] * 1000) + return [int(x * 1000) for x in temp_c] else: raise ValueError("Unknown format") From 48bbdeec24bf5d6327be2cdbdd2e9190f847a6dd Mon Sep 17 00:00:00 2001 From: Philip Jay Date: Tue, 16 Jun 2015 00:05:11 +1000 Subject: [PATCH 021/159] Add method to scale without using /etc/temper.conf This avoids knowing all the USB bus details, and provides an easy way to override scale and offset. --- temperusb/temper.py | 46 +++++++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/temperusb/temper.py b/temperusb/temper.py index 06510a3..dc5b61d 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -89,29 +89,35 @@ def __init__(self, device): LOGGER.debug('Found device | Bus:{0} Ports:{1}'.format( self._bus, self._ports)) - def set_calibration_data(self): + def set_calibration_data(self, scale=None, offset=None): """ Set device calibration data based on settings in /etc/temper.conf. """ - self._scale = 1.0 - self._offset = 0.0 - try: - f = open('/etc/temper.conf', 'r') - except IOError: - f = None - if f: - lines = f.read().split('\n') - f.close() - for line in lines: - matches = re.match(CALIB_LINE_STR, line) - if matches: - bus = int(matches.groups()[0]) - ports = matches.groups()[1] - scale = float(matches.groups()[2]) - offset = float(matches.groups()[3]) - if ports == self._ports: - self._scale = scale - self._offset = offset + if scale is not None and offset is not None: + self._scale = scale + self._offset = offset + elif scale is None and offset is None: + self._scale = 1.0 + self._offset = 0.0 + try: + f = open('/etc/temper.conf', 'r') + except IOError: + f = None + if f: + lines = f.read().split('\n') + f.close() + for line in lines: + matches = re.match(CALIB_LINE_STR, line) + if matches: + bus = int(matches.groups()[0]) + ports = matches.groups()[1] + scale = float(matches.groups()[2]) + offset = float(matches.groups()[3]) + if ports == self._ports: + self._scale = scale + self._offset = offset + else: + raise RuntimeError("Must set both scale and offset, or neither") def get_ports(self): """ From 150b1c604b0fcf73638d61d00a5aee804fc91ac7 Mon Sep 17 00:00:00 2001 From: foundbobby Date: Tue, 15 Dec 2015 18:56:56 -0800 Subject: [PATCH 022/159] Update cli.py to grant access to multiple sensors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Granting access to multiple sensors. Tested with TemperNTC, sample output below: python cli.py Found 1 devices Device #0: 11.3°C 52.4°F python cli.py -s 0 Found 1 devices Device #0: 11.3°C 52.4°F python cli.py -s 1 Found 1 devices Device #0: 8.1°C 46.5°F python cli.py -q -c -s 0 11.3 python cli.py -q -c -s 1 8.1 --- temperusb/cli.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/temperusb/cli.py b/temperusb/cli.py index b897b81..7c97a61 100644 --- a/temperusb/cli.py +++ b/temperusb/cli.py @@ -4,20 +4,22 @@ import getopt, sys, os.path def usage(): - print("%s [-p] [-q] [-c|-f] [-h|--help]" % os.path.basename(sys.argv[0])) + print("%s [-p] [-q] [-c|-f] [-s 0|1] [-h|--help]" % os.path.basename(sys.argv[0])) print(" -q quiet: only output temperature as a floating number. Usefull for external program parsing") print(" this option requires the use of -c or -f") print(" -c with -q, outputs temperature in celcius degrees.") print(" -f with -q, outputs temperature in fahrenheit degrees.") + print(" -s with sensor ID following utilizes that sensor on the device (multisensor devices only).") def main(): try: - opts, args = getopt.getopt(sys.argv[1:], ":hpcfq", ["help"]) + opts, args = getopt.getopt(sys.argv[1:], ":hpcfqs:", ["help"]) except getopt.GetoptError as err: print(str(err)) usage() sys.exit(2) degree_unit = False + sensor_id = 0 # Default to first sensor unless specified disp_ports = False quiet_output = False for o, a in opts: @@ -29,6 +31,11 @@ def main(): degree_unit = 'f' elif o == "-q": quiet_output = True + elif o == "-s": + try: + sensor_id = int(float(a)) + except ValueError: + assert False, "Sensor ID could not be parsed, please use valid integer" elif o in ("-h", "--help"): usage() sys.exit() @@ -47,9 +54,9 @@ def main(): for i, dev in enumerate(devs): readings.append({'device': i, - 'temperature_c': dev.get_temperature(), + 'temperature_c': dev.get_temperature(sensor=sensor_id), 'temperature_f': - dev.get_temperature(format="fahrenheit"), + dev.get_temperature(format="fahrenheit",sensor=sensor_id), 'ports': dev.get_ports(), 'bus': dev.get_bus() }) @@ -71,7 +78,7 @@ def main(): reading['ports']) else: portinfo = "" - print('Device #%i%s: %0.1f°C %0.1f°F' + print('Device #%i%s: %0.1f°C %0.1f°F' % (reading['device'], portinfo, reading['temperature_c'], From d7777134c4c3a1d51cd489cc91c48997fc01a8dc Mon Sep 17 00:00:00 2001 From: foundbobby Date: Wed, 16 Dec 2015 19:47:59 -0800 Subject: [PATCH 023/159] Update cli.py --- temperusb/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/temperusb/cli.py b/temperusb/cli.py index 7c97a61..45d038d 100644 --- a/temperusb/cli.py +++ b/temperusb/cli.py @@ -33,7 +33,7 @@ def main(): quiet_output = True elif o == "-s": try: - sensor_id = int(float(a)) + sensor_id = int(a) except ValueError: assert False, "Sensor ID could not be parsed, please use valid integer" elif o in ("-h", "--help"): From 8541eda7182a01d125a237c5bf3ca6ee88bdd280 Mon Sep 17 00:00:00 2001 From: foundbobby Date: Thu, 17 Dec 2015 22:09:30 -0800 Subject: [PATCH 024/159] Add "all" sensor flag support to cli.py --- temperusb/cli.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/temperusb/cli.py b/temperusb/cli.py index 45d038d..6d95ba3 100644 --- a/temperusb/cli.py +++ b/temperusb/cli.py @@ -4,12 +4,12 @@ import getopt, sys, os.path def usage(): - print("%s [-p] [-q] [-c|-f] [-s 0|1] [-h|--help]" % os.path.basename(sys.argv[0])) + print("%s [-p] [-q] [-c|-f] [-s 0|1|all] [-h|--help]" % os.path.basename(sys.argv[0])) print(" -q quiet: only output temperature as a floating number. Usefull for external program parsing") print(" this option requires the use of -c or -f") print(" -c with -q, outputs temperature in celcius degrees.") print(" -f with -q, outputs temperature in fahrenheit degrees.") - print(" -s with sensor ID following utilizes that sensor on the device (multisensor devices only).") + print(" -s with sensor ID (or all) following utilizes that sensor on the device (multisensor devices only).") def main(): try: @@ -32,10 +32,13 @@ def main(): elif o == "-q": quiet_output = True elif o == "-s": - try: - sensor_id = int(a) - except ValueError: - assert False, "Sensor ID could not be parsed, please use valid integer" + if a == "all": + sensor_id = "all" + else: + try: + sensor_id = int(a) + except ValueError: + assert False, "Sensor ID could not be parsed, please use valid integer" elif o in ("-h", "--help"): usage() sys.exit() From b4e4da3a90d1d24cbd228fb83ef5e7f6c8ab755b Mon Sep 17 00:00:00 2001 From: Philipp Adelt Date: Mon, 21 Dec 2015 17:45:35 +0100 Subject: [PATCH 025/159] Leave reset disabled but do dispose_resources. --- temperusb/temper.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/temperusb/temper.py b/temperusb/temper.py index 51e8cfa..912ad7b 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -166,6 +166,10 @@ def get_temperature(self, format='celsius'): # Also ends up hitting syslog with this kernel message each time: # "reset low speed USB device number x using uhci_hcd" # self._device.reset() + + # Be a nice citizen and undo potential interface claiming. + # Also see: https://github.com/walac/pyusb/blob/master/docs/tutorial.rst#dont-be-selfish + usb.util.dispose_resources(self._device) except usb.USBError as err: # Catch the permissions exception and add our message if "not permitted" in str(err): From 892e6d2a3cf6c640e2ec1c298dfcf179a36c1158 Mon Sep 17 00:00:00 2001 From: Philipp Adelt Date: Tue, 22 Dec 2015 11:05:17 +0100 Subject: [PATCH 026/159] Add contribution of @ps-jay to readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 78c460a..2a5f3ae 100644 --- a/README.md +++ b/README.md @@ -268,3 +268,4 @@ as seen on [Google+](https://plus.google.com/105569853186899442987/posts/N9T7xAj * Calibration code by Joji Monma (@GM3D on Github) * Munin plugin by Alexander Schier (@allo- on Github) * PyPI package work and rewrite to `libusb1` by James Stewart (@amorphic on Github) +* Reduced kernel messages and more work by Philip Jay (@ps-jay on Github) From 9cf769efba204b05d4980ae7ea02dfcb64cb246f Mon Sep 17 00:00:00 2001 From: Philipp Adelt Date: Tue, 22 Dec 2015 12:32:06 +0100 Subject: [PATCH 027/159] Repair SNMP passpersist mode. --- temperusb/snmp.py | 7 ++++--- temperusb/temper.py | 4 ++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/temperusb/snmp.py b/temperusb/snmp.py index 262012d..1269089 100644 --- a/temperusb/snmp.py +++ b/temperusb/snmp.py @@ -68,9 +68,10 @@ def update(self): else: try: with self.usb_lock: - self.pp.add_int('318.1.1.1.2.2.2.0', int(max([d.get_temperature() for d in self.devs]))) - for i, dev in enumerate(self.devs[:3]): # use max. first 3 devices - self.pp.add_int('9.9.13.1.3.1.3.%i' % (i+1), int(dev.get_temperature())) + temperatures = [d.get_temperature() for d in self.devs] + self.pp.add_int('318.1.1.1.2.2.2.0', int(max(temperatures))) + for i, temperature in enumerate(temperatures[:3]): # use max. first 3 devices + self.pp.add_int('9.9.13.1.3.1.3.%i' % (i+1), int(temperature)) except Exception, e: self.logger.write_log('Exception while updating data: %s' % str(e)) # Report an exceptionally large temperature to set off all alarms. diff --git a/temperusb/temper.py b/temperusb/temper.py index 912ad7b..a952342 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -209,6 +209,10 @@ def _interrupt_read(self): LOGGER.debug('Read data: {0}'.format(data)) return data + def close(self): + """Does nothing in this device. Other device types may need to do cleanup here.""" + pass + class TemperHandler(object): """ From 91bc60f72071a3ce0eaf23a77c1ee82e1b927120 Mon Sep 17 00:00:00 2001 From: Philipp Adelt Date: Tue, 22 Dec 2015 12:33:23 +0100 Subject: [PATCH 028/159] Bump PyPI version to 1.2.4 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 544c859..ed992b7 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ author='Philipp Adelt', author_email='autosort-github@philipp.adelt.net ', url='https://github.com/padelt/temper-python', - version='1.2.3', + version='1.2.4', description='Reads temperature from TEMPerV1 devices (USB 0c45:7401)', long_description=open('README.md').read(), packages=['temperusb'], From dc38ff66ae9d8e1c12c68819ff791516141af6f4 Mon Sep 17 00:00:00 2001 From: Philip Jay Date: Tue, 22 Dec 2015 23:51:30 +1100 Subject: [PATCH 029/159] Replace assert with sys.exit() Asserts get optimised out of the code if using the optimiser - resulting in unexpected behaviour. --- temperusb/cli.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/temperusb/cli.py b/temperusb/cli.py index 6d95ba3..501eb89 100644 --- a/temperusb/cli.py +++ b/temperusb/cli.py @@ -37,8 +37,10 @@ def main(): else: try: sensor_id = int(a) - except ValueError: - assert False, "Sensor ID could not be parsed, please use valid integer" + except ValueError as err: + print(str(err)) + usage() + sys.exit(3) elif o in ("-h", "--help"): usage() sys.exit() From f8ea4f339caeedfaf113fff0eaa8ddcc4d0aa98b Mon Sep 17 00:00:00 2001 From: Philip Jay Date: Tue, 22 Dec 2015 23:52:49 +1100 Subject: [PATCH 030/159] Update help text --- temperusb/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/temperusb/cli.py b/temperusb/cli.py index 501eb89..c8f2cc2 100644 --- a/temperusb/cli.py +++ b/temperusb/cli.py @@ -9,7 +9,7 @@ def usage(): print(" this option requires the use of -c or -f") print(" -c with -q, outputs temperature in celcius degrees.") print(" -f with -q, outputs temperature in fahrenheit degrees.") - print(" -s with sensor ID (or all) following utilizes that sensor on the device (multisensor devices only).") + print(" -s sensor ID 0, 1, or all, to utilize that sensor(s) on the device (multisensor devices only).") def main(): try: From baa547abfd55dc309329dc270f3154f5a160ccf7 Mon Sep 17 00:00:00 2001 From: Philip Jay Date: Tue, 22 Dec 2015 23:55:30 +1100 Subject: [PATCH 031/159] Add error checking for sensor ID number --- temperusb/cli.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/temperusb/cli.py b/temperusb/cli.py index c8f2cc2..322f23d 100644 --- a/temperusb/cli.py +++ b/temperusb/cli.py @@ -37,6 +37,10 @@ def main(): else: try: sensor_id = int(a) + if not (sensor_id == 0 or sensor_id == 1): + raise ValueError( + "sensor_id should be 0 or 1, %d given" % sensor_id + ) except ValueError as err: print(str(err)) usage() From 93d635b0a1d6dabe05fac6d664849cdc4fd96bd4 Mon Sep 17 00:00:00 2001 From: Philip Jay Date: Wed, 23 Dec 2015 00:20:49 +1100 Subject: [PATCH 032/159] Replace assert with RuntimeError() Asserts get optimised out, so using an exception is safer. --- temperusb/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/temperusb/cli.py b/temperusb/cli.py index c764ccd..18bd24d 100644 --- a/temperusb/cli.py +++ b/temperusb/cli.py @@ -33,7 +33,7 @@ def main(): usage() sys.exit() else: - assert False, "unhandled option" + raise RuntimeError("Unhandled option '%s'" % o) if quiet_output and not degree_unit: print('You need to specify unit (-c of -f) when using -q option') From 82f7e42b698c832cf1999ad993a82625a924b7d8 Mon Sep 17 00:00:00 2001 From: Philip Jay Date: Wed, 23 Dec 2015 00:54:30 +1100 Subject: [PATCH 033/159] Add code to handle "all" sensor case, with -q arg --- temperusb/cli.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/temperusb/cli.py b/temperusb/cli.py index 322f23d..0939e53 100644 --- a/temperusb/cli.py +++ b/temperusb/cli.py @@ -73,11 +73,25 @@ def main(): for reading in readings: if quiet_output: if degree_unit == 'c': - print('%0.1f' - % reading['temperature_c']) + if type(reading['temperature_c']) is float: + print('%0.1f' + % reading['temperature_c']) + else: + output = '' + for sensor in reading['temperature_c']: + output += '%0.1f; ' % sensor + output = output[0:len(output) - 2] + print(output) elif degree_unit == 'f': - print('%0.1f' - % reading['temperature_f']) + if type(reading['temperature_c']) is float: + print('%0.1f' + % reading['temperature_f']) + else: + output = '' + for sensor in reading['temperature_f']: + output += '%0.1f; ' % sensor + output = output[0:len(output) - 2] + print(output) else: print('how did I end up here?') sys.exit(1) From 5e643e60b8f2818406f96184e61105ecf9320fe9 Mon Sep 17 00:00:00 2001 From: Philip Jay Date: Wed, 23 Dec 2015 01:03:32 +1100 Subject: [PATCH 034/159] Add code to handle "all" sensor case, not -q arg --- temperusb/cli.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/temperusb/cli.py b/temperusb/cli.py index 0939e53..5f3c647 100644 --- a/temperusb/cli.py +++ b/temperusb/cli.py @@ -101,11 +101,25 @@ def main(): reading['ports']) else: portinfo = "" - print('Device #%i%s: %0.1f°C %0.1f°F' - % (reading['device'], - portinfo, - reading['temperature_c'], - reading['temperature_f'])) + + if type(reading['temperature_c']) is float: + print('Device #%i%s: %0.1f°C %0.1f°F' + % (reading['device'], + portinfo, + reading['temperature_c'], + reading['temperature_f'])) + else: + output = 'Device #%i%s: ' % ( + reading['device'], + portinfo, + ) + for index in range(0, len(reading['temperature_c'])): + output += '%0.1f°C %0.1f°F; ' % ( + reading['temperature_c'][index], + reading['temperature_f'][index], + ) + output = output[0:len(output) - 2] + print(output) if __name__ == '__main__': main() From b279f4ccdd5489706c9fb6c9f15a1ded0443d4de Mon Sep 17 00:00:00 2001 From: Philip Jay Date: Wed, 23 Dec 2015 01:05:19 +1100 Subject: [PATCH 035/159] Alter print & sys.exit() to a ValueError() --- temperusb/cli.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/temperusb/cli.py b/temperusb/cli.py index 5f3c647..4309374 100644 --- a/temperusb/cli.py +++ b/temperusb/cli.py @@ -93,8 +93,7 @@ def main(): output = output[0:len(output) - 2] print(output) else: - print('how did I end up here?') - sys.exit(1) + raise ValueError('degree_unit expected to be c or f, got %s' % degree_unit) else: if disp_ports: portinfo = " (bus %s - port %s)" % (reading['bus'], From f9ff2fff2ce8bdd16b26c76c9db4dd075f7776a3 Mon Sep 17 00:00:00 2001 From: Philipp Adelt Date: Tue, 22 Dec 2015 15:06:29 +0100 Subject: [PATCH 036/159] Use pyusb 1.0.0rc1; disable unneeded interaction; no more USBErrors for padelt; simplify temperature calculation --- setup.py | 5 +++-- temperusb/temper.py | 32 ++++++++++++++++++++------------ 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/setup.py b/setup.py index ed992b7..ada9ceb 100644 --- a/setup.py +++ b/setup.py @@ -5,13 +5,14 @@ author='Philipp Adelt', author_email='autosort-github@philipp.adelt.net ', url='https://github.com/padelt/temper-python', - version='1.2.4', + version='1.3.0b1', description='Reads temperature from TEMPerV1 devices (USB 0c45:7401)', long_description=open('README.md').read(), packages=['temperusb'], install_requires=[ - 'pyusb==1.0.0b1', + 'pyusb>=1.0.0rc1', ], + dependency_links = ['git+https://github.com/walac/pyusb.git#egg=pyusb-1.0.0rc1'], entry_points={ 'console_scripts': [ 'temper-poll = temperusb.cli:main', diff --git a/temperusb/temper.py b/temperusb/temper.py index 1e3a8ae..9bffa8c 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -155,19 +155,27 @@ def get_temperature(self, format='celsius'): for interface in [0, 1]: usb.util.claim_interface(self._device, interface) usb.util.claim_interface(self._device, interface) - self._device.ctrl_transfer(bmRequestType=0x21, bRequest=0x09, - wValue=0x0201, wIndex=0x00, data_or_wLength='\x01\x01', - timeout=TIMEOUT) + + # Turns out we don't actually need that ctrl_transfer. + # Disabling this reduces number of USBErrors from ~7/30 to 0! + #self._device.ctrl_transfer(bmRequestType=0x21, bRequest=0x09, + # wValue=0x0201, wIndex=0x00, data_or_wLength='\x01\x01', + # timeout=TIMEOUT) + + + # Turns out a whole lot of that magic seems unnecessary. + #self._control_transfer(COMMANDS['temp']) + #self._interrupt_read() + #self._control_transfer(COMMANDS['ini1']) + #self._interrupt_read() + #self._control_transfer(COMMANDS['ini2']) + #self._interrupt_read() + #self._interrupt_read() + # Get temperature self._control_transfer(COMMANDS['temp']) - self._interrupt_read() - self._control_transfer(COMMANDS['ini1']) - self._interrupt_read() - self._control_transfer(COMMANDS['ini2']) - self._interrupt_read() - self._interrupt_read() - self._control_transfer(COMMANDS['temp']) data = self._interrupt_read() + # Seems unneccessary to reset each time # Also ends up hitting syslog with this kernel message each time: # "reset low speed USB device number x using uhci_hcd" @@ -187,7 +195,7 @@ def get_temperature(self, format='celsius'): raise # Interpret device response data_s = "".join([chr(byte) for byte in data]) - temp_c = 125.0/32000.0*(struct.unpack('>h', data_s[2:4])[0]) + temp_c = (struct.unpack('>h', data_s[2:4])[0])/256.0 temp_c = temp_c * self._scale + self._offset if format == 'celsius': return temp_c @@ -211,7 +219,7 @@ def _interrupt_read(self): """ Read data from device. """ - data = self._device.read(ENDPOINT, REQ_INT_LEN, interface=INTERFACE, timeout=TIMEOUT) + data = self._device.read(ENDPOINT, REQ_INT_LEN, timeout=TIMEOUT) LOGGER.debug('Read data: {0}'.format(data)) return data From a5bb47a4ae74081abe9994b02d874903c8e606f4 Mon Sep 17 00:00:00 2001 From: Philipp Adelt Date: Tue, 22 Dec 2015 17:38:27 +0100 Subject: [PATCH 037/159] Compare USB ports from calibration file and internal device as strings (suggestion by @DiegoBAELDE) --- temperusb/temper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/temperusb/temper.py b/temperusb/temper.py index 1e3a8ae..dd8844e 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -113,7 +113,7 @@ def set_calibration_data(self, scale=None, offset=None): ports = matches.groups()[1] scale = float(matches.groups()[2]) offset = float(matches.groups()[3]) - if ports == self._ports: + if str(ports) == str(self._ports): self._scale = scale self._offset = offset else: From e6448337f5277dd9e7db924feff677f2fb6f0056 Mon Sep 17 00:00:00 2001 From: Philipp Adelt Date: Tue, 22 Dec 2015 17:39:12 +0100 Subject: [PATCH 038/159] Bump package version to 1.2.5 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index ed992b7..3db7f12 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ author='Philipp Adelt', author_email='autosort-github@philipp.adelt.net ', url='https://github.com/padelt/temper-python', - version='1.2.4', + version='1.2.5', description='Reads temperature from TEMPerV1 devices (USB 0c45:7401)', long_description=open('README.md').read(), packages=['temperusb'], From f06419b711aae8ce35efa702a463618898aa7e64 Mon Sep 17 00:00:00 2001 From: Philip Jay Date: Tue, 5 Jan 2016 23:24:35 +1100 Subject: [PATCH 039/159] Split the getting data code into its' own method Prep work for the new get_temperatures() method. --- temperusb/temper.py | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/temperusb/temper.py b/temperusb/temper.py index 1c264d5..51fc60b 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -135,21 +135,10 @@ def get_bus(self): return self._bus return '' - def get_temperature(self, format='celsius', sensor=0): + def get_data(self): """ - Get device temperature reading. + Get data from the USB device. """ - # Only supported sensors are 0 and 1 at this stage. - # If you have the 8 sensor model, please contribute to the - # discussion here: https://github.com/padelt/temper-python/issues/19 - if sensor not in [0, 1, "all"]: - raise ValueError('Only sensor 0 or 1, or the keyword "all" supported') - - if sensor == 0 or sensor == 1: - offsets = [(sensor + 1) * 2,] - elif sensor == "all": - offsets = [2, 4,] - try: # Take control of device if required if self._device.is_kernel_driver_active: @@ -195,6 +184,7 @@ def get_temperature(self, format='celsius', sensor=0): # Be a nice citizen and undo potential interface claiming. # Also see: https://github.com/walac/pyusb/blob/master/docs/tutorial.rst#dont-be-selfish usb.util.dispose_resources(self._device) + return data except usb.USBError as err: # Catch the permissions exception and add our message if "not permitted" in str(err): @@ -204,7 +194,24 @@ def get_temperature(self, format='celsius', sensor=0): else: LOGGER.error(err) raise - + + def get_temperature(self, format='celsius', sensor=0): + """ + Get device temperature reading. + """ + # Only supported sensors are 0 and 1 at this stage. + # If you have the 8 sensor model, please contribute to the + # discussion here: https://github.com/padelt/temper-python/issues/19 + if sensor not in [0, 1, "all"]: + raise ValueError('Only sensor 0 or 1, or the keyword "all" supported') + + if sensor == 0 or sensor == 1: + offsets = [(sensor + 1) * 2,] + elif sensor == "all": + offsets = [2, 4,] + + data = self.get_data() + # Interpret device response temp_c = [] for offset in offsets: From 7e1e1a63a92c8660c87f9b331404f07d59b8d785 Mon Sep 17 00:00:00 2001 From: Philip Jay Date: Tue, 5 Jan 2016 23:28:36 +1100 Subject: [PATCH 040/159] Add sensor_count parameter to TemperDevice class Not used yet, will be in the new get_temperatures() method. --- temperusb/temper.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/temperusb/temper.py b/temperusb/temper.py index 51fc60b..b7ec42f 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -79,7 +79,14 @@ class TemperDevice(object): """ A TEMPer USB thermometer. """ - def __init__(self, device): + def __init__(self, device, sensor_count=1): + # Currently this only supports 1 and 2 sensor models. + # If you have the 8 sensor model, please contribute to the + # discussion here: https://github.com/padelt/temper-python/issues/19 + if sensor_count not in [1, 2,]: + raise ValueError('Only sensor_count of 1 or 2 supported') + + self._sensor_count = int(sensor_count) self._device = device self._bus = device.bus self._ports = getattr(device, 'port_number', None) From 712aaf9ded7c46424dc8bfd5ce750674f86f4f68 Mon Sep 17 00:00:00 2001 From: Philip Jay Date: Tue, 5 Jan 2016 23:58:16 +1100 Subject: [PATCH 041/159] Add new get_temperatures() method Gives the multi-sensor functionality wanted, without messing about too much with the existing methods. See: https://github.com/padelt/temper-python/pull/34#issuecomment-166891004 --- temperusb/temper.py | 47 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/temperusb/temper.py b/temperusb/temper.py index b7ec42f..8f780d1 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -243,6 +243,53 @@ def get_temperature(self, format='celsius', sensor=0): else: raise ValueError("Unknown format") + def get_temperatures(self, sensors=None): + """ + Get device temperature reading. + + Params: + - sensors: optional list of sensors to get a reading for, examples: + [0,] - get reading for sensor 0 + [0, 1,] - get reading for sensors 0 and 1 + None - get readings for all sensors + """ + _sensors = sensors + if _sensors is None: + _sensors = range(0, self._sensor_count) + + if not set(_sensors).issubset(range(0, self._sensor_count)): + raise ValueError( + 'Some or all of the sensors in the list %s are out of range ' + 'given a sensor_count of %d. Valid range: %s' % ( + _sensors, + self._sensor_count, + range(0, self._sensor_count), + ) + ) + + data = self.get_data() + + results = {} + + # Interpret device response + for sensor in _sensors: + offset = (sensor + 1) * 2 + data_s = "".join([chr(byte) for byte in data]) + value = (struct.unpack('>h', data_s[offset:(offset + 2)])[0]) + celsius = (125.0 / 32000.0) * value + celsius = celsius * self._scale + self._offset + results[sensor] = { + 'ports': self.get_ports(), + 'bus': self.get_bus(), + 'sensor': sensor, + 'temperature_f': celsius * 1.8 + 32.0, + 'temperature_c': celsius, + 'temperature_mc': celsius * 1000, + 'temperature_k': celsius + 273.15, + } + + return results + def _control_transfer(self, data): """ Send device a control request with standard parameters and as From c0edc18699b34f0e9cb0b5be8535e85c3811ad23 Mon Sep 17 00:00:00 2001 From: Philip Jay Date: Wed, 6 Jan 2016 00:05:13 +1100 Subject: [PATCH 042/159] Refactor get_temperature() to make use of new code This simplified things a bit! --- temperusb/temper.py | 36 +++++------------------------------- 1 file changed, 5 insertions(+), 31 deletions(-) diff --git a/temperusb/temper.py b/temperusb/temper.py index 8f780d1..a32931a 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -206,40 +206,14 @@ def get_temperature(self, format='celsius', sensor=0): """ Get device temperature reading. """ - # Only supported sensors are 0 and 1 at this stage. - # If you have the 8 sensor model, please contribute to the - # discussion here: https://github.com/padelt/temper-python/issues/19 - if sensor not in [0, 1, "all"]: - raise ValueError('Only sensor 0 or 1, or the keyword "all" supported') - - if sensor == 0 or sensor == 1: - offsets = [(sensor + 1) * 2,] - elif sensor == "all": - offsets = [2, 4,] - - data = self.get_data() - - # Interpret device response - temp_c = [] - for offset in offsets: - data_s = "".join([chr(byte) for byte in data]) - value = (struct.unpack('>h', data_s[offset:(offset + 2)])[0]) - temp_c.append(value / 256.0) - temp_c[-1] = temp_c[-1] * self._scale + self._offset + results = self.get_temperatures(sensors=[sensor,]) - # Return the result or results if format == 'celsius': - if len(temp_c) == 1: - return temp_c[0] - return temp_c + return results[sensor]['temperature_c'] elif format == 'fahrenheit': - if len(temp_c) == 1: - return temp_c[0] * 1.8 + 32.0 - return [x * 1.8 + 32.0 for x in temp_c] + return results[sensor]['temperature_f'] elif format == 'millicelsius': - if len(temp_c) == 1: - return int(temp_c[0] * 1000) - return [int(x * 1000) for x in temp_c] + return results[sensor]['temperature_mc'] else: raise ValueError("Unknown format") @@ -276,7 +250,7 @@ def get_temperatures(self, sensors=None): offset = (sensor + 1) * 2 data_s = "".join([chr(byte) for byte in data]) value = (struct.unpack('>h', data_s[offset:(offset + 2)])[0]) - celsius = (125.0 / 32000.0) * value + celsius = value / 256.0 celsius = celsius * self._scale + self._offset results[sensor] = { 'ports': self.get_ports(), From f02fa745b6287a613fe15b5ce4b7f09bec623213 Mon Sep 17 00:00:00 2001 From: Philip Jay Date: Wed, 6 Jan 2016 00:26:48 +1100 Subject: [PATCH 043/159] Add a setter for sensor_count --- temperusb/temper.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/temperusb/temper.py b/temperusb/temper.py index a32931a..39a5e53 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -80,13 +80,8 @@ class TemperDevice(object): A TEMPer USB thermometer. """ def __init__(self, device, sensor_count=1): - # Currently this only supports 1 and 2 sensor models. - # If you have the 8 sensor model, please contribute to the - # discussion here: https://github.com/padelt/temper-python/issues/19 - if sensor_count not in [1, 2,]: - raise ValueError('Only sensor_count of 1 or 2 supported') + self.set_sensor_count(sensor_count) - self._sensor_count = int(sensor_count) self._device = device self._bus = device.bus self._ports = getattr(device, 'port_number', None) @@ -126,6 +121,20 @@ def set_calibration_data(self, scale=None, offset=None): else: raise RuntimeError("Must set both scale and offset, or neither") + def set_sensor_count(self, count): + """ + Set number of sensors on the device. + + To do: revamp /etc/temper.conf file to include this data. + """ + # Currently this only supports 1 and 2 sensor models. + # If you have the 8 sensor model, please contribute to the + # discussion here: https://github.com/padelt/temper-python/issues/19 + if count not in [1, 2,]: + raise ValueError('Only sensor_count of 1 or 2 supported') + + self._sensor_count = int(count) + def get_ports(self): """ Get device USB ports. From 33f58c402cfaebe375e66a2804d48efeedc7b6cc Mon Sep 17 00:00:00 2001 From: Philip Jay Date: Wed, 6 Jan 2016 01:26:59 +1100 Subject: [PATCH 044/159] Refactor cli.py to use get_temperatures() method (and related sensor_count functionality) --- temperusb/cli.py | 109 ++++++++++++++++++++++++----------------------- 1 file changed, 55 insertions(+), 54 deletions(-) diff --git a/temperusb/cli.py b/temperusb/cli.py index 64ed3fb..aacfdb9 100644 --- a/temperusb/cli.py +++ b/temperusb/cli.py @@ -4,22 +4,24 @@ import getopt, sys, os.path def usage(): - print("%s [-p] [-q] [-c|-f] [-s 0|1|all] [-h|--help]" % os.path.basename(sys.argv[0])) + print("%s [-p] [-q] [-c|-f] [-s 0|1|all] [-S 1|2] [-h|--help]" % os.path.basename(sys.argv[0])) print(" -q quiet: only output temperature as a floating number. Usefull for external program parsing") print(" this option requires the use of -c or -f") print(" -c with -q, outputs temperature in celcius degrees.") print(" -f with -q, outputs temperature in fahrenheit degrees.") - print(" -s sensor ID 0, 1, or all, to utilize that sensor(s) on the device (multisensor devices only).") + print(" -s sensor ID 0, 1, or all, to utilize that sensor(s) on the device (multisensor devices only). Default: 0") + print(" -S specify the number of sensors on the device. Default: 1") def main(): try: - opts, args = getopt.getopt(sys.argv[1:], ":hpcfqs:", ["help"]) + opts, args = getopt.getopt(sys.argv[1:], ":hpcfqs:S:", ["help"]) except getopt.GetoptError as err: print(str(err)) usage() sys.exit(2) degree_unit = False - sensor_id = 0 # Default to first sensor unless specified + sensor_count = 1 # Default unless specified otherwise + sensor_id = [0,] # Default to first sensor unless specified disp_ports = False quiet_output = False for o, a in opts: @@ -41,10 +43,23 @@ def main(): raise ValueError( "sensor_id should be 0 or 1, %d given" % sensor_id ) + # convert to list + sensor_id = [sensor_id,] except ValueError as err: print(str(err)) usage() sys.exit(3) + elif o == "-S": + try: + sensor_count = int(a) + if not (sensor_count == 1 or sensor_count == 2): + raise ValueError( + "sensor_count should be 1 or 2, %d given" % sensor_count + ) + except ValueError as err: + print(str(err)) + usage() + sys.exit(4) elif o in ("-h", "--help"): usage() sys.exit() @@ -55,70 +70,56 @@ def main(): print('You need to specify unit (-c of -f) when using -q option') sys.exit(1) + # handle the sensor_id "all" option - convert to number of sensors + if sensor_id == "all": + sensor_id = range(0, sensor_count) + + if not set(sensor_id).issubset(range(0, sensor_count)): + print('You specified a sensor_id (-s), without specifying -S with an appropriate number of sensors') + sys.exit(5) + th = TemperHandler() devs = th.get_devices() readings = [] if not quiet_output: print("Found %i devices" % len(devs)) - for i, dev in enumerate(devs): - readings.append({'device': i, - 'temperature_c': dev.get_temperature(sensor=sensor_id), - 'temperature_f': - dev.get_temperature(format="fahrenheit",sensor=sensor_id), - 'ports': dev.get_ports(), - 'bus': dev.get_bus() - }) + for dev in devs: + dev.set_sensor_count(sensor_count) + readings.append(dev.get_temperatures(sensors=sensor_id)) - for reading in readings: + for i, reading in enumerate(readings): + output = '' if quiet_output: if degree_unit == 'c': - if type(reading['temperature_c']) is float: - print('%0.1f' - % reading['temperature_c']) - else: - output = '' - for sensor in reading['temperature_c']: - output += '%0.1f; ' % sensor - output = output[0:len(output) - 2] - print(output) + dict_key = 'temperature_c' elif degree_unit == 'f': - if type(reading['temperature_c']) is float: - print('%0.1f' - % reading['temperature_f']) - else: - output = '' - for sensor in reading['temperature_f']: - output += '%0.1f; ' % sensor - output = output[0:len(output) - 2] - print(output) + dict_key = 'temperature_f' else: raise ValueError('degree_unit expected to be c or f, got %s' % degree_unit) - else: - if disp_ports: - portinfo = " (bus %s - port %s)" % (reading['bus'], - reading['ports']) - else: - portinfo = "" - if type(reading['temperature_c']) is float: - print('Device #%i%s: %0.1f°C %0.1f°F' - % (reading['device'], - portinfo, - reading['temperature_c'], - reading['temperature_f'])) - else: - output = 'Device #%i%s: ' % ( - reading['device'], - portinfo, + for sensor in sorted(reading): + output += '%0.1f; ' % reading[sensor][dict_key] + output = output[0:len(output) - 2] + else: + portinfo = '' + tempinfo = '' + for sensor in sorted(reading): + if disp_ports and portinfo == '': + portinfo = " (bus %s - port %s)" % (reading['bus'], + reading['ports']) + tempinfo += '%0.1f°C %0.1f°F; ' % ( + reading[sensor]['temperature_c'], + reading[sensor]['temperature_f'], ) - for index in range(0, len(reading['temperature_c'])): - output += '%0.1f°C %0.1f°F; ' % ( - reading['temperature_c'][index], - reading['temperature_f'][index], - ) - output = output[0:len(output) - 2] - print(output) + tempinfo = tempinfo[0:len(output) - 2] + + output = 'Device #%i%s: %s' % ( + i, + portinfo, + tempinfo, + ) + print(output) if __name__ == '__main__': main() From a23925e02a824d8a39b7ae6c952add4284ad14a2 Mon Sep 17 00:00:00 2001 From: Philip Jay Date: Wed, 6 Jan 2016 01:42:13 +1100 Subject: [PATCH 045/159] Update contributor message --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2a5f3ae..85bbb53 100644 --- a/README.md +++ b/README.md @@ -268,4 +268,4 @@ as seen on [Google+](https://plus.google.com/105569853186899442987/posts/N9T7xAj * Calibration code by Joji Monma (@GM3D on Github) * Munin plugin by Alexander Schier (@allo- on Github) * PyPI package work and rewrite to `libusb1` by James Stewart (@amorphic on Github) -* Reduced kernel messages and more work by Philip Jay (@ps-jay on Github) +* Reduced kernel messages and support multiple sensors by Philip Jay (@ps-jay on Github) From dd81cb1fedb242c2ead542a649d64cc37ae7bb59 Mon Sep 17 00:00:00 2001 From: Philipp Adelt Date: Tue, 5 Jan 2016 20:48:46 +0100 Subject: [PATCH 046/159] Bump package version to 1.3.1 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 0a699e2..c1dae98 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ author='Philipp Adelt', author_email='autosort-github@philipp.adelt.net ', url='https://github.com/padelt/temper-python', - version='1.3.0', + version='1.3.1', description='Reads temperature from TEMPerV1 devices (USB 0c45:7401)', long_description=open('README.md').read(), packages=['temperusb'], From af7867639c332e24b54276c0c5fd01640281539b Mon Sep 17 00:00:00 2001 From: Andrew Williams Date: Wed, 6 Jan 2016 22:11:07 +0000 Subject: [PATCH 047/159] Updated Munin plugin --- etc/munin-temperature | 70 ++++++++++++++++++++++++++++++++----------- 1 file changed, 53 insertions(+), 17 deletions(-) diff --git a/etc/munin-temperature b/etc/munin-temperature index 1aa3699..4e82d0f 100755 --- a/etc/munin-temperature +++ b/etc/munin-temperature @@ -4,29 +4,65 @@ # munin-plugin for temper # # Copyright 2013 Alexander Schier -# +# # This code is licensed under the GNU public license (GPL). See LICENSE.md for details. -from temper.temper import TemperHandler +#%# capabilities=autoconf + import sys -handler = TemperHandler() -is_config = len(sys.argv) == 2 and sys.argv[1] == "config" -if is_config: +def get_handler(): + from temperusb.temper import TemperHandler + return TemperHandler() + + +def autoconf(): + try: + handler = get_handler() + except ImportError: + print "no (temper-python package is not installed)" + else: + if len(handler.get_devices()): + print "yes" + else: + print "no (No devices found)" + + +def config(): + handler = get_handler() print "graph_title Temperature" - print "graph_vlabel °C" + print "graph_vlabel Degrees Celsius" print "graph_category sensors" + for device in handler.get_devices(): + port = device.get_ports() + port_name = port.replace('.', '_') + print "temp_" + port_name + ".label Port {0:s} Temperature".format(port) -devices = handler.get_devices() -for device in devices: - port = device.get_ports() - port_name = port.replace('.', '_') - if is_config: - if len(devices) > 1: - print "temp_" + port_name + ".label Temperature in °C (port {0:s})".format(port) - else: - print "temp_" + port_name + ".label Temperature in °C" - else: - temp = device.get_temperature() + +def fetch(): + handler = get_handler() + for device in handler.get_devices(): + port = device.get_ports() + port_name = port.replace('.', '_') + try: + temp = device.get_temperature() + except Exception: + temp = 'U' print "temp_" + port_name + ".value {0:f}".format(temp) + + +def main(): + if len(sys.argv) == 2: + arg = sys.argv[1] + if arg == 'autoconf': + autoconf() + elif arg == 'config': + config() + else: + fetch() + sys.exit(0) + + +if __name__ == '__main__': + main() \ No newline at end of file From 44ba01adad3b6cc12c0c51c71485608e2907af04 Mon Sep 17 00:00:00 2001 From: Andrew Williams Date: Wed, 6 Jan 2016 22:17:30 +0000 Subject: [PATCH 048/159] Fix issue with single device path port names erroring with the Munin plugin --- etc/munin-temperature | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/etc/munin-temperature b/etc/munin-temperature index 4e82d0f..dd28cec 100755 --- a/etc/munin-temperature +++ b/etc/munin-temperature @@ -36,7 +36,7 @@ def config(): print "graph_category sensors" for device in handler.get_devices(): port = device.get_ports() - port_name = port.replace('.', '_') + port_name = str(port).replace('.', '_') print "temp_" + port_name + ".label Port {0:s} Temperature".format(port) @@ -44,7 +44,7 @@ def fetch(): handler = get_handler() for device in handler.get_devices(): port = device.get_ports() - port_name = port.replace('.', '_') + port_name = str(port).replace('.', '_') try: temp = device.get_temperature() except Exception: From 59912734bea136a766d1d30e3c080ae220598b16 Mon Sep 17 00:00:00 2001 From: Will Furnass Date: Sun, 17 Jan 2016 19:19:54 +0000 Subject: [PATCH 049/159] Rewrite temper-poll script so uses argparse --- temperusb/cli.py | 127 +++++++++++++++-------------------------------- 1 file changed, 39 insertions(+), 88 deletions(-) diff --git a/temperusb/cli.py b/temperusb/cli.py index aacfdb9..e51d902 100644 --- a/temperusb/cli.py +++ b/temperusb/cli.py @@ -1,102 +1,56 @@ # encoding: utf-8 -from __future__ import print_function -from temperusb.temper import TemperHandler -import getopt, sys, os.path +from __future__ import print_function, absolute_import +import argparse -def usage(): - print("%s [-p] [-q] [-c|-f] [-s 0|1|all] [-S 1|2] [-h|--help]" % os.path.basename(sys.argv[0])) - print(" -q quiet: only output temperature as a floating number. Usefull for external program parsing") - print(" this option requires the use of -c or -f") - print(" -c with -q, outputs temperature in celcius degrees.") - print(" -f with -q, outputs temperature in fahrenheit degrees.") - print(" -s sensor ID 0, 1, or all, to utilize that sensor(s) on the device (multisensor devices only). Default: 0") - print(" -S specify the number of sensors on the device. Default: 1") +from .temper import TemperHandler -def main(): - try: - opts, args = getopt.getopt(sys.argv[1:], ":hpcfqs:S:", ["help"]) - except getopt.GetoptError as err: - print(str(err)) - usage() - sys.exit(2) - degree_unit = False - sensor_count = 1 # Default unless specified otherwise - sensor_id = [0,] # Default to first sensor unless specified - disp_ports = False - quiet_output = False - for o, a in opts: - if o == "-p": - disp_ports = True - elif o == "-c": - degree_unit = 'c' - elif o == "-f": - degree_unit = 'f' - elif o == "-q": - quiet_output = True - elif o == "-s": - if a == "all": - sensor_id = "all" - else: - try: - sensor_id = int(a) - if not (sensor_id == 0 or sensor_id == 1): - raise ValueError( - "sensor_id should be 0 or 1, %d given" % sensor_id - ) - # convert to list - sensor_id = [sensor_id,] - except ValueError as err: - print(str(err)) - usage() - sys.exit(3) - elif o == "-S": - try: - sensor_count = int(a) - if not (sensor_count == 1 or sensor_count == 2): - raise ValueError( - "sensor_count should be 1 or 2, %d given" % sensor_count - ) - except ValueError as err: - print(str(err)) - usage() - sys.exit(4) - elif o in ("-h", "--help"): - usage() - sys.exit() - else: - raise RuntimeError("Unhandled option '%s'" % o) - if quiet_output and not degree_unit: - print('You need to specify unit (-c of -f) when using -q option') - sys.exit(1) +def parse_args(): + descr = "Temperature data from a TEMPer v1.2 sensor." + + parser = argparse.ArgumentParser(description=descr) + parser.add_argument("-p", "--disp_ports", action='store_true', + help="Display ports") + units = parser.add_mutually_exclusive_group(required=False) + units.add_argument("-c", "--celsius", action='store_true', + help="Quiet: just degrees celcius as decimal") + units.add_argument("-f", "--fahrenheit", action='store_true', + help="Quiet: just degrees fahrenheit as decimal") + parser.add_argument("-s", "--sensor_ids", choices=['0', '1', 'all'], + help="IDs of sensors to use on the device " + + "(multisensor devices only)", default='0') + parser.add_argument("-S", "--sensor_count", choices=[1, 2], type=int, + help="Specify the number of sensors on the device", + default='1') + args = parser.parse_args() - # handle the sensor_id "all" option - convert to number of sensors - if sensor_id == "all": - sensor_id = range(0, sensor_count) + args.sensor_ids = list(range(args.sensor_count)) if args.sensor_ids == 'all' \ + else [int(args.sensor_ids)] + return args - if not set(sensor_id).issubset(range(0, sensor_count)): - print('You specified a sensor_id (-s), without specifying -S with an appropriate number of sensors') - sys.exit(5) + +def main(): + args = parse_args() + quiet = args.celsius or args.fahrenheit th = TemperHandler() devs = th.get_devices() - readings = [] - if not quiet_output: + if not quiet: print("Found %i devices" % len(devs)) + readings = [] + for dev in devs: - dev.set_sensor_count(sensor_count) - readings.append(dev.get_temperatures(sensors=sensor_id)) + dev.set_sensor_count(args.sensor_count) + readings.append(dev.get_temperatures(sensors=args.sensor_ids)) for i, reading in enumerate(readings): output = '' - if quiet_output: - if degree_unit == 'c': + if quiet: + if args.celsius: dict_key = 'temperature_c' - elif degree_unit == 'f': + elif args.fahrenheit: dict_key = 'temperature_f' - else: - raise ValueError('degree_unit expected to be c or f, got %s' % degree_unit) for sensor in sorted(reading): output += '%0.1f; ' % reading[sensor][dict_key] @@ -105,7 +59,7 @@ def main(): portinfo = '' tempinfo = '' for sensor in sorted(reading): - if disp_ports and portinfo == '': + if args.disp_ports and portinfo == '': portinfo = " (bus %s - port %s)" % (reading['bus'], reading['ports']) tempinfo += '%0.1f°C %0.1f°F; ' % ( @@ -114,12 +68,9 @@ def main(): ) tempinfo = tempinfo[0:len(output) - 2] - output = 'Device #%i%s: %s' % ( - i, - portinfo, - tempinfo, - ) + output = 'Device #%i%s: %s' % (i, portinfo, tempinfo) print(output) + if __name__ == '__main__': main() From 37ba05a4e0e059fcc2c9e65cd64708af6fb324a5 Mon Sep 17 00:00:00 2001 From: Will Furnass Date: Mon, 18 Jan 2016 20:50:01 +0000 Subject: [PATCH 050/159] Make Python 3.x compatible (snmp-passpersist dependency 2.x only at present though) --- temperusb/snmp.py | 6 +++--- temperusb/temper.py | 24 ++++++++++++++---------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/temperusb/snmp.py b/temperusb/snmp.py index 1269089..2501dfb 100644 --- a/temperusb/snmp.py +++ b/temperusb/snmp.py @@ -43,7 +43,7 @@ def _initialize(self): self.logger.write_log('Found %i thermometer devices.' % len(self.devs)) for i, d in enumerate(self.devs): self.logger.write_log('Initial temperature of device #%i: %0.1f degree celsius' % (i, d.get_temperature())) - except Exception, e: + except Exception as e: self.logger.write_log('Exception while initializing: %s' % str(e)) def _reinitialize(self): @@ -53,7 +53,7 @@ def _reinitialize(self): for i,d in enumerate(self.devs): try: d.close() - except Exception, e: + except Exception as e: self.logger.write_log('Exception closing device #%i: %s' % (i, str(e))) self._initialize() @@ -72,7 +72,7 @@ def update(self): self.pp.add_int('318.1.1.1.2.2.2.0', int(max(temperatures))) for i, temperature in enumerate(temperatures[:3]): # use max. first 3 devices self.pp.add_int('9.9.13.1.3.1.3.%i' % (i+1), int(temperature)) - except Exception, e: + except Exception as e: self.logger.write_log('Exception while updating data: %s' % str(e)) # Report an exceptionally large temperature to set off all alarms. # snmp_passpersist does not expose an API to remove an OID. diff --git a/temperusb/temper.py b/temperusb/temper.py index 39a5e53..e0be921 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -16,7 +16,7 @@ import logging VIDPIDS = [ - (0x0c45L, 0x7401L), + (0x0c45, 0x7401), ] REQ_INT_LEN = 8 ENDPOINT = 0x82 @@ -28,11 +28,12 @@ '\s*:\s*scale\s*=\s*([+|-]?\d*\.\d+)\s*,\s*offset\s*=\s*([+|-]?\d*\.\d+)' USB_SYS_PREFIX = '/sys/bus/usb/devices/' COMMANDS = { - 'temp': '\x01\x80\x33\x01\x00\x00\x00\x00', - 'ini1': '\x01\x82\x77\x01\x00\x00\x00\x00', - 'ini2': '\x01\x86\xff\x01\x00\x00\x00\x00', + 'temp': b'\x01\x80\x33\x01\x00\x00\x00\x00', + 'ini1': b'\x01\x82\x77\x01\x00\x00\x00\x00', + 'ini2': b'\x01\x86\xff\x01\x00\x00\x00\x00', } LOGGER = logging.getLogger(__name__) +IS_PY2 = sys.version[0] == '2' def readattr(path, name): @@ -141,7 +142,7 @@ def get_ports(self): """ if self._ports: return self._ports - return '' + return '' def get_bus(self): """ @@ -238,15 +239,15 @@ def get_temperatures(self, sensors=None): """ _sensors = sensors if _sensors is None: - _sensors = range(0, self._sensor_count) + _sensors = list(range(0, self._sensor_count)) - if not set(_sensors).issubset(range(0, self._sensor_count)): + if not set(_sensors).issubset(list(range(0, self._sensor_count))): raise ValueError( 'Some or all of the sensors in the list %s are out of range ' 'given a sensor_count of %d. Valid range: %s' % ( _sensors, self._sensor_count, - range(0, self._sensor_count), + list(range(0, self._sensor_count)), ) ) @@ -257,7 +258,10 @@ def get_temperatures(self, sensors=None): # Interpret device response for sensor in _sensors: offset = (sensor + 1) * 2 - data_s = "".join([chr(byte) for byte in data]) + if IS_PY2: + data_s = b"".join([chr(byte) for byte in data]) + else: + data_s = data.tobytes() value = (struct.unpack('>h', data_s[offset:(offset + 2)])[0]) celsius = value / 256.0 celsius = celsius * self._scale + self._offset @@ -305,7 +309,7 @@ def __init__(self): for vid, pid in VIDPIDS: self._devices += [TemperDevice(device) for device in \ usb.core.find(find_all=True, idVendor=vid, idProduct=pid)] - LOGGER.info('Found {0} TEMPer devices'.format(len(self._devices))) + LOGGER.info('Found {0} TEMPer devices'.format(len(self._devices))) def get_devices(self): """ From f8cb49130e100e91807962b5db53872c33019097 Mon Sep 17 00:00:00 2001 From: Will Furnass Date: Mon, 18 Jan 2016 20:54:02 +0000 Subject: [PATCH 051/159] Break-out inline conditional to multiple lines (more obvious) --- temperusb/cli.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/temperusb/cli.py b/temperusb/cli.py index e51d902..6c3f655 100644 --- a/temperusb/cli.py +++ b/temperusb/cli.py @@ -24,8 +24,11 @@ def parse_args(): default='1') args = parser.parse_args() - args.sensor_ids = list(range(args.sensor_count)) if args.sensor_ids == 'all' \ - else [int(args.sensor_ids)] + if args.sensor_ids == 'all': + args.sensor_ids = range(args.sensor_count) + else: + args.sensor_ids = [int(args.sensor_ids)] + return args From 578dd8a10969b0b12ad5ebaaa3e1b2a436f8ad93 Mon Sep 17 00:00:00 2001 From: Philipp Adelt Date: Wed, 20 Jan 2016 10:20:09 +0100 Subject: [PATCH 052/159] Bump PyPI version; add PyPI classifiers, add README statement about Python version compatibility --- README.md | 5 +++++ setup.py | 11 +++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 85bbb53..b4903a6 100644 --- a/README.md +++ b/README.md @@ -261,6 +261,11 @@ To find out bus and port numbers, you can also try running temper-poll with -p o The USB interaction pattern is extracted from [here](http://www.isp-sl.com/pcsensor-1.0.0.tgz) as seen on [Google+](https://plus.google.com/105569853186899442987/posts/N9T7xAjEtyF). +# Compatibility with Python versions + +This should work on both Python 2 and 3. It was tested with Python 2.7.3 and 3.2.3. +The `snmp_passpersist` mode is Python 2 only because the upstream package is not ready yet. + # Authors * Original rewrite by Philipp Adelt diff --git a/setup.py b/setup.py index c1dae98..4b5a08f 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ author='Philipp Adelt', author_email='autosort-github@philipp.adelt.net ', url='https://github.com/padelt/temper-python', - version='1.3.1', + version='1.4.0', description='Reads temperature from TEMPerV1 devices (USB 0c45:7401)', long_description=open('README.md').read(), packages=['temperusb'], @@ -18,5 +18,12 @@ 'temper-poll = temperusb.cli:main', 'temper-snmp = temperusb.snmp:main' ] - } + }, + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Environment :: Console', + 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 3', + ], ) From 09ccda7f9dfa6428f22f73a5d47183734366e94c Mon Sep 17 00:00:00 2001 From: Will Furnass Date: Wed, 20 Jan 2016 10:25:46 +0000 Subject: [PATCH 053/159] Add Will Furnass to contrib list for PRs #45 and #46 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b4903a6..b9dc7be 100644 --- a/README.md +++ b/README.md @@ -274,3 +274,4 @@ The `snmp_passpersist` mode is Python 2 only because the upstream package is not * Munin plugin by Alexander Schier (@allo- on Github) * PyPI package work and rewrite to `libusb1` by James Stewart (@amorphic on Github) * Reduced kernel messages and support multiple sensors by Philip Jay (@ps-jay on Github) +* Python 3 compatibility and rewrite of cli.py to use argparse by Will Furnass (@willfurnass on Github) From 657228efcb16a07b3345e31257db3ef15c6854cb Mon Sep 17 00:00:00 2001 From: Christian von Roques Date: Fri, 8 Jan 2016 10:06:39 -0400 Subject: [PATCH 054/159] cli: explicitly call logging.basicConfig() Do not rely on logging.warning() implicitly configuring logging. We actually hit this and had logging complain about not being able to find a handler, when PyUSB encounters a timeout before us having logged anything. --- temperusb/cli.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/temperusb/cli.py b/temperusb/cli.py index 6c3f655..7275d57 100644 --- a/temperusb/cli.py +++ b/temperusb/cli.py @@ -1,6 +1,7 @@ # encoding: utf-8 from __future__ import print_function, absolute_import import argparse +import logging from .temper import TemperHandler @@ -36,6 +37,8 @@ def main(): args = parse_args() quiet = args.celsius or args.fahrenheit + logging.basicConfig(level = logging.ERROR if quiet else logging.WARNING) + th = TemperHandler() devs = th.get_devices() if not quiet: From 7af174a4063e570f2f0c0484080a289b1b808446 Mon Sep 17 00:00:00 2001 From: Christian von Roques Date: Fri, 8 Jan 2016 10:50:49 -0400 Subject: [PATCH 055/159] fix logging of binary data by logging its repr() Logging '\x01\x80\x33\x01\x00\x00\x00\x00' as a string results in "3", the only printable character it contains. --- temperusb/temper.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/temperusb/temper.py b/temperusb/temper.py index e0be921..0f71503 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -282,7 +282,7 @@ def _control_transfer(self, data): Send device a control request with standard parameters and as payload. """ - LOGGER.debug('Ctrl transfer: {0}'.format(data)) + LOGGER.debug('Ctrl transfer: %r', data) self._device.ctrl_transfer(bmRequestType=0x21, bRequest=0x09, wValue=0x0200, wIndex=0x01, data_or_wLength=data, timeout=TIMEOUT) @@ -291,7 +291,7 @@ def _interrupt_read(self): Read data from device. """ data = self._device.read(ENDPOINT, REQ_INT_LEN, timeout=TIMEOUT) - LOGGER.debug('Read data: {0}'.format(data)) + LOGGER.debug('Read data: %r', data) return data def close(self): From 1f09ed5c5b44cc3d587b92c4ffa23c8f3bc7dc7b Mon Sep 17 00:00:00 2001 From: Christian von Roques Date: Fri, 8 Jan 2016 12:05:27 -0400 Subject: [PATCH 056/159] reset device and try again if stuck This is necessary for our TEMPer V1.4 and adds robustness against the device being in a weird state caused by other users of the device. --- temperusb/temper.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/temperusb/temper.py b/temperusb/temper.py index 0f71503..c1a8770 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -152,11 +152,14 @@ def get_bus(self): return self._bus return '' - def get_data(self): + def get_data(self, reset_device=False): """ Get data from the USB device. """ try: + if reset_device: + self._device.reset() + # Take control of device if required if self._device.is_kernel_driver_active: LOGGER.debug('Taking control of device on bus {0} ports ' @@ -193,16 +196,15 @@ def get_data(self): self._control_transfer(COMMANDS['temp']) data = self._interrupt_read() - # Seems unneccessary to reset each time - # Also ends up hitting syslog with this kernel message each time: - # "reset low speed USB device number x using uhci_hcd" - # self._device.reset() - # Be a nice citizen and undo potential interface claiming. # Also see: https://github.com/walac/pyusb/blob/master/docs/tutorial.rst#dont-be-selfish usb.util.dispose_resources(self._device) return data except usb.USBError as err: + if not reset_device: + LOGGER.warning("Encountered %s, resetting %r and trying again.", err, self._device) + return self.get_data(True) + # Catch the permissions exception and add our message if "not permitted" in str(err): raise Exception( From e3053dc50dd7e1b6fe90aad7564a37b9175550e9 Mon Sep 17 00:00:00 2001 From: Christian von Roques Date: Fri, 8 Jan 2016 12:15:13 -0400 Subject: [PATCH 057/159] restructure device initialisation * Only claim the used interface, not all. * Detach kernel driver from both interfaces if actually attached. This releases claims of the interfaces by the kernel driver, which in necessary for us to set_configuration(). is_kernel_driver_active is a function, call it, instead of testing for its presence. --- temperusb/temper.py | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/temperusb/temper.py b/temperusb/temper.py index c1a8770..c1923ff 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -160,21 +160,22 @@ def get_data(self, reset_device=False): if reset_device: self._device.reset() - # Take control of device if required - if self._device.is_kernel_driver_active: - LOGGER.debug('Taking control of device on bus {0} ports ' - '{1}'.format(self._bus, self._ports)) - for interface in [0, 1]: - try: - self._device.detach_kernel_driver(interface) - except usb.USBError as err: - LOGGER.debug(err) - self._device.set_configuration() - # Prevent kernel message: - # "usbfs: process (python) did not claim interface x before use" - for interface in [0, 1]: - usb.util.claim_interface(self._device, interface) - usb.util.claim_interface(self._device, interface) + # detach kernel driver from both interfaces if attached, so we can set_configuration() + for interface in [0,1]: + if self._device.is_kernel_driver_active(interface): + LOGGER.debug('Detaching kernel driver for interface %d ' + 'of %r on ports %r', interface, self._device, self._ports) + self._device.detach_kernel_driver(interface) + + self._device.set_configuration() + + # Prevent kernel message: + # "usbfs: process (python) did not claim interface x before use" + # This will become unnecessary once pull-request #124 for + # PyUSB has been accepted and we depend on a fixed release + # of PyUSB. Until then, and even with the fix applied, it + # does not hurt to explicitly claim the interface. + usb.util.claim_interface(self._device, INTERFACE) # Turns out we don't actually need that ctrl_transfer. # Disabling this reduces number of USBErrors from ~7/30 to 0! From 84fa940afb70171c5321f5a7a30a3b38172945b7 Mon Sep 17 00:00:00 2001 From: Christian von Roques Date: Fri, 8 Jan 2016 12:39:40 -0400 Subject: [PATCH 058/159] reenable the magic necessary for TEMPerV1.4 --- temperusb/temper.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/temperusb/temper.py b/temperusb/temper.py index c1923ff..846334f 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -184,9 +184,13 @@ def get_data(self, reset_device=False): # timeout=TIMEOUT) + # Magic: Our TEMPerV1.4 likes to be asked twice. When + # only asked once, it get's stuck on the next access and + # requires a reset. + self._control_transfer(COMMANDS['temp']) + self._interrupt_read() + # Turns out a whole lot of that magic seems unnecessary. - #self._control_transfer(COMMANDS['temp']) - #self._interrupt_read() #self._control_transfer(COMMANDS['ini1']) #self._interrupt_read() #self._control_transfer(COMMANDS['ini2']) From 167eea7eaea17c76effc725349660357f7b5317a Mon Sep 17 00:00:00 2001 From: Christian von Roques Date: Fri, 8 Jan 2016 12:54:57 -0400 Subject: [PATCH 059/159] simplify extraction of temperature --- temperusb/temper.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/temperusb/temper.py b/temperusb/temper.py index 846334f..338b6dd 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -9,8 +9,6 @@ # details. import usb -import sys -import struct import os import re import logging @@ -33,7 +31,6 @@ 'ini2': b'\x01\x86\xff\x01\x00\x00\x00\x00', } LOGGER = logging.getLogger(__name__) -IS_PY2 = sys.version[0] == '2' def readattr(path, name): @@ -265,12 +262,7 @@ def get_temperatures(self, sensors=None): # Interpret device response for sensor in _sensors: offset = (sensor + 1) * 2 - if IS_PY2: - data_s = b"".join([chr(byte) for byte in data]) - else: - data_s = data.tobytes() - value = (struct.unpack('>h', data_s[offset:(offset + 2)])[0]) - celsius = value / 256.0 + celsius = data[offset] + data[offset+1] / 256.0 celsius = celsius * self._scale + self._offset results[sensor] = { 'ports': self.get_ports(), From c49c2c01d16ef4de067d3fcf0107b67485cf98ad Mon Sep 17 00:00:00 2001 From: Philipp Adelt Date: Mon, 15 Feb 2016 17:19:23 +0100 Subject: [PATCH 060/159] Fix bug in 'temper-poll -p' to show bus and ports data. --- setup.py | 2 +- temperusb/cli.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 4b5a08f..a5cf3db 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ author='Philipp Adelt', author_email='autosort-github@philipp.adelt.net ', url='https://github.com/padelt/temper-python', - version='1.4.0', + version='1.4.1', description='Reads temperature from TEMPerV1 devices (USB 0c45:7401)', long_description=open('README.md').read(), packages=['temperusb'], diff --git a/temperusb/cli.py b/temperusb/cli.py index 6c3f655..eb31c39 100644 --- a/temperusb/cli.py +++ b/temperusb/cli.py @@ -63,8 +63,7 @@ def main(): tempinfo = '' for sensor in sorted(reading): if args.disp_ports and portinfo == '': - portinfo = " (bus %s - port %s)" % (reading['bus'], - reading['ports']) + portinfo = " (bus %(bus)s - port %(ports)s)" % reading[sensor] tempinfo += '%0.1f°C %0.1f°F; ' % ( reading[sensor]['temperature_c'], reading[sensor]['temperature_f'], From 7f722cc7f82bf2e1621222ab12970213214e0ac5 Mon Sep 17 00:00:00 2001 From: Philip Jay Date: Fri, 15 Apr 2016 22:32:54 +1000 Subject: [PATCH 061/159] Add note about TEMPer1F_V1.3 devices --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index b9dc7be..eca14b0 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ a USB ID like this: `0c45:7401 Microdia` My device came from [M-Ware ID7747](http://www.m-ware.de/m-ware-usb-thermometer-40--120-c-emailbenachrichtigung-id7747/a-7747/) and also reports itself as 'RDing TEMPerV1.2'. +The code also works for devices reporting as 'RDing TEMPer1F_V1.3'. These devices seem to only support temperature readings from the remote probe, where as the TEMPerV1.2 can read from the internal device, and the external probe. + Also provides a passpersist-module for NetSNMP (as found in the `snmpd` packages of Debian and Ubuntu) to present the temperature of 1-3 USB devices via SNMP. From 45786737e970cf439fc63291d375f2b674093102 Mon Sep 17 00:00:00 2001 From: Philip Jay Date: Fri, 15 Apr 2016 23:46:28 +1000 Subject: [PATCH 062/159] Add a table of known working devices I hadn't realised until now that I was using a TEMPer2_M12_V1.3, rather than a TEMPerV1.2. --- README.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index eca14b0..fbd111d 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,24 @@ a USB ID like this: `0c45:7401 Microdia` My device came from [M-Ware ID7747](http://www.m-ware.de/m-ware-usb-thermometer-40--120-c-emailbenachrichtigung-id7747/a-7747/) and also reports itself as 'RDing TEMPerV1.2'. -The code also works for devices reporting as 'RDing TEMPer1F_V1.3'. These devices seem to only support temperature readings from the remote probe, where as the TEMPerV1.2 can read from the internal device, and the external probe. - Also provides a passpersist-module for NetSNMP (as found in the `snmpd` packages of Debian and Ubuntu) to present the temperature of 1-3 USB devices via SNMP. +### Reported working devices + +| USB ID | Name Reported | Notes | +| ---------------------------------------------- | ------------------------ | ----------------------- | +| `0c45:7401 Microdia` | `RDing TEMPerV1.2` | First supported device | +| `0c45:7401 Microdia TEMPer Temperature Sensor` | `RDing TEMPer2_M12_V1.3` | Two sensor device | +| `0c45:7401 Microdia` | `RDing TEMPer1F_V1.3` | Single external sensor, but better precision is possible by using "sensor 2" | + +### Future support likely + +| USB ID | Name Reported | Notes | +| ---------------------------------------------- | ------------------------ | ----------------------- | +| `0c45:7401 Microdia` | `RDing TEMPerV1.4` | See PR #43 | + # Requirements Basically, `libusb` bindings for python (PyUSB) and `snmp-passpersist` from PyPI. From 0ff97a8dc6c8aeaaa2c435c17920c76c8ac45978 Mon Sep 17 00:00:00 2001 From: Philip Jay Date: Fri, 15 Apr 2016 23:49:15 +1000 Subject: [PATCH 063/159] Fix for merged PR43 Was merged as I was writing this! --- README.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/README.md b/README.md index fbd111d..4a8da9f 100644 --- a/README.md +++ b/README.md @@ -14,12 +14,7 @@ via SNMP. | `0c45:7401 Microdia` | `RDing TEMPerV1.2` | First supported device | | `0c45:7401 Microdia TEMPer Temperature Sensor` | `RDing TEMPer2_M12_V1.3` | Two sensor device | | `0c45:7401 Microdia` | `RDing TEMPer1F_V1.3` | Single external sensor, but better precision is possible by using "sensor 2" | - -### Future support likely - -| USB ID | Name Reported | Notes | -| ---------------------------------------------- | ------------------------ | ----------------------- | -| `0c45:7401 Microdia` | `RDing TEMPerV1.4` | See PR #43 | +| `0c45:7401 Microdia` | `RDing TEMPerV1.4` | | # Requirements From 877242689e769c947a0cae63bb33d3e4731b6f1a Mon Sep 17 00:00:00 2001 From: Philipp Adelt Date: Fri, 15 Apr 2016 15:52:43 +0200 Subject: [PATCH 064/159] Bump package version to 1.5.0 - first release with support for Temper v1.4 devices --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a5cf3db..014d12b 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ author='Philipp Adelt', author_email='autosort-github@philipp.adelt.net ', url='https://github.com/padelt/temper-python', - version='1.4.1', + version='1.5.0', description='Reads temperature from TEMPerV1 devices (USB 0c45:7401)', long_description=open('README.md').read(), packages=['temperusb'], From 76fce1bf4cb1547990b89a8851a233478386f6d0 Mon Sep 17 00:00:00 2001 From: Philip Jay Date: Fri, 15 Apr 2016 23:54:24 +1000 Subject: [PATCH 065/159] Add contribution line for @roques --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4a8da9f..f806d85 100644 --- a/README.md +++ b/README.md @@ -284,3 +284,4 @@ The `snmp_passpersist` mode is Python 2 only because the upstream package is not * PyPI package work and rewrite to `libusb1` by James Stewart (@amorphic on Github) * Reduced kernel messages and support multiple sensors by Philip Jay (@ps-jay on Github) * Python 3 compatibility and rewrite of cli.py to use argparse by Will Furnass (@willfurnass on Github) +* TEMPerV1.4 support by Christian von Roques (@roques on Github) From ef4646660c029d35af033f35719d1ce49d189fed Mon Sep 17 00:00:00 2001 From: Philip Jay Date: Sat, 16 Apr 2016 00:50:41 +1000 Subject: [PATCH 066/159] Add lookups for sensor_count and offset by product This better supports TEMPer1F_V1.3, which only has one sensor, and has an offset at 4, instead of 2. (Actually, offset 2 still works for this model, but has less precision than offset 4) --- temperusb/temper.py | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/temperusb/temper.py b/temperusb/temper.py index 338b6dd..62282bb 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -86,6 +86,7 @@ def __init__(self, device, sensor_count=1): if self._ports == None: self._ports = find_ports(device) self.set_calibration_data() + self.set_sensor_count(self.lookup_sensor_count()) LOGGER.debug('Found device | Bus:{0} Ports:{1}'.format( self._bus, self._ports)) @@ -119,6 +120,35 @@ def set_calibration_data(self, scale=None, offset=None): else: raise RuntimeError("Must set both scale and offset, or neither") + def lookup_offset(self, sensor): + """ + Lookup the number of sensors on the device by product name. + """ + if self._device.product == 'TEMPer1F_V1.3': + # Has only 1 sensor, and it's at offset = 4 + return 4 + + # All others follow this pattern - if not, contribute here: https://github.com/padelt/temper-python/issues + # Sensor 0 = Offset 2 + # Sensor 1 = Offset 4 + return (sensor + 1) * 2 + + def lookup_sensor_count(self): + """ + Lookup the number of sensors on the device by product name. + """ + if self._device.product == 'TEMPer1F_V1.3': + return 1 + + # All others are two - if not the case, contribute here: https://github.com/padelt/temper-python/issues + return 2 + + def get_sensor_count(self): + """ + Get number of sensors on the device. + """ + return self._sensor_count + def set_sensor_count(self, count): """ Set number of sensors on the device. @@ -127,7 +157,7 @@ def set_sensor_count(self, count): """ # Currently this only supports 1 and 2 sensor models. # If you have the 8 sensor model, please contribute to the - # discussion here: https://github.com/padelt/temper-python/issues/19 + # discussion here: https://github.com/padelt/temper-python/issues if count not in [1, 2,]: raise ValueError('Only sensor_count of 1 or 2 supported') @@ -261,7 +291,7 @@ def get_temperatures(self, sensors=None): # Interpret device response for sensor in _sensors: - offset = (sensor + 1) * 2 + offset = self.lookup_offset(sensor) celsius = data[offset] + data[offset+1] / 256.0 celsius = celsius * self._scale + self._offset results[sensor] = { From e9e88052716074631c785dc98bd8fb9acab85bcd Mon Sep 17 00:00:00 2001 From: Philip Jay Date: Sat, 16 Apr 2016 00:52:18 +1000 Subject: [PATCH 067/159] Rework cli.py to use auto-lookup of sensor_count --- temperusb/cli.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/temperusb/cli.py b/temperusb/cli.py index 79ad552..c5ba9a1 100644 --- a/temperusb/cli.py +++ b/temperusb/cli.py @@ -20,16 +20,10 @@ def parse_args(): parser.add_argument("-s", "--sensor_ids", choices=['0', '1', 'all'], help="IDs of sensors to use on the device " + "(multisensor devices only)", default='0') - parser.add_argument("-S", "--sensor_count", choices=[1, 2], type=int, - help="Specify the number of sensors on the device", - default='1') + parser.add_argument("-S", "--sensor_count", type=int, + help="Override auto-detected number of sensors on the device") args = parser.parse_args() - if args.sensor_ids == 'all': - args.sensor_ids = range(args.sensor_count) - else: - args.sensor_ids = [int(args.sensor_ids)] - return args @@ -47,7 +41,15 @@ def main(): readings = [] for dev in devs: - dev.set_sensor_count(args.sensor_count) + if args.sensor_count is not None: + # Override auto-detection from args + dev.set_sensor_count(int(args.sensor_count)) + + if args.sensor_ids == 'all': + args.sensor_ids = range(dev.get_sensor_count()) + else: + args.sensor_ids = [int(args.sensor_ids)] + readings.append(dev.get_temperatures(sensors=args.sensor_ids)) for i, reading in enumerate(readings): From 2d5bc2806fc8a1d6fd66a77e13f18c10b48bc51c Mon Sep 17 00:00:00 2001 From: Philip Jay Date: Sat, 16 Apr 2016 00:59:37 +1000 Subject: [PATCH 068/159] Update contribution in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f806d85..b519e7d 100644 --- a/README.md +++ b/README.md @@ -282,6 +282,6 @@ The `snmp_passpersist` mode is Python 2 only because the upstream package is not * Calibration code by Joji Monma (@GM3D on Github) * Munin plugin by Alexander Schier (@allo- on Github) * PyPI package work and rewrite to `libusb1` by James Stewart (@amorphic on Github) -* Reduced kernel messages and support multiple sensors by Philip Jay (@ps-jay on Github) +* Reduced kernel messages, support multiple sensors, and support TEMPer1F_V1.3 by Philip Jay (@ps-jay on Github) * Python 3 compatibility and rewrite of cli.py to use argparse by Will Furnass (@willfurnass on Github) * TEMPerV1.4 support by Christian von Roques (@roques on Github) From 24a59ac38b997809afd12b2fbbb86ee39e1d59fd Mon Sep 17 00:00:00 2001 From: Philip Jay Date: Mon, 18 Apr 2016 22:59:54 +1000 Subject: [PATCH 069/159] Fix for not comparing bus when reading calibration --- temperusb/temper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/temperusb/temper.py b/temperusb/temper.py index 338b6dd..d4c013b 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -113,7 +113,7 @@ def set_calibration_data(self, scale=None, offset=None): ports = matches.groups()[1] scale = float(matches.groups()[2]) offset = float(matches.groups()[3]) - if str(ports) == str(self._ports): + if (str(ports) == str(self._ports)) and (str(bus) == str(self._bus)): self._scale = scale self._offset = offset else: From dc9aea1ec929d3e220819459cd19ac6c65508a03 Mon Sep 17 00:00:00 2001 From: James Stewart Date: Wed, 20 Apr 2016 15:53:23 +1000 Subject: [PATCH 070/159] Add CHANGELOG file --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..40f13a9 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,10 @@ +All notable changes to this project will be documented in this file. + +* This project adheres to [Semantic Versioning](http://semver.org/). +* This project follows the guidelines outlined on [keepachangelog.com](http://keepachangelog.com/). + +## [Unreleased] + +## [1.5.0] - 2016-04-20 +## Added +- Changelog file. From f6ad1d218582b0448a96302a7e755f735b18bf3f Mon Sep 17 00:00:00 2001 From: Philip Jay Date: Wed, 20 Apr 2016 20:40:31 +1000 Subject: [PATCH 071/159] Add Commit ID for v1.5.0 release --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40f13a9..8fba7f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,6 @@ All notable changes to this project will be documented in this file. ## [Unreleased] -## [1.5.0] - 2016-04-20 +## [1.5.0] - 2016-04-20 - Commit ID: 8752b14 ## Added - Changelog file. From 00e0f2987b9c7a67c8a5959837538852a8ee60e4 Mon Sep 17 00:00:00 2001 From: Philip Jay Date: Wed, 20 Apr 2016 20:41:21 +1000 Subject: [PATCH 072/159] Formatting --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fba7f7..3c6bf1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ +# Change log All notable changes to this project will be documented in this file. * This project adheres to [Semantic Versioning](http://semver.org/). @@ -6,5 +7,5 @@ All notable changes to this project will be documented in this file. ## [Unreleased] ## [1.5.0] - 2016-04-20 - Commit ID: 8752b14 -## Added +### Added - Changelog file. From 6c205044c23c9b1a3fc55bf8521590ee9cd22676 Mon Sep 17 00:00:00 2001 From: Philip Jay Date: Thu, 21 Apr 2016 22:49:58 +1000 Subject: [PATCH 073/159] Fix for traceback when multiple devices attached --- temperusb/cli.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/temperusb/cli.py b/temperusb/cli.py index c5ba9a1..8224b33 100644 --- a/temperusb/cli.py +++ b/temperusb/cli.py @@ -46,11 +46,11 @@ def main(): dev.set_sensor_count(int(args.sensor_count)) if args.sensor_ids == 'all': - args.sensor_ids = range(dev.get_sensor_count()) + sensors = range(dev.get_sensor_count()) else: - args.sensor_ids = [int(args.sensor_ids)] + sensors = [int(args.sensor_ids)] - readings.append(dev.get_temperatures(sensors=args.sensor_ids)) + readings.append(dev.get_temperatures(sensors=sensors)) for i, reading in enumerate(readings): output = '' From a88818b622aa88315dd511e30dbf3735d111f55e Mon Sep 17 00:00:00 2001 From: Philipp Adelt Date: Sun, 12 Jun 2016 15:13:53 +0200 Subject: [PATCH 074/159] Mention TEMPer1F_V1.3 support in CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c6bf1f..2564fb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ All notable changes to this project will be documented in this file. * This project follows the guidelines outlined on [keepachangelog.com](http://keepachangelog.com/). ## [Unreleased] +### Added +- Support for `TEMPer1F_V1.3`'s behaviour: Only one sensor, data is at offset 4. ## [1.5.0] - 2016-04-20 - Commit ID: 8752b14 ### Added From 5841c7041d5e48cad783ec57f6110afe9de1c234 Mon Sep 17 00:00:00 2001 From: Philipp Adelt Date: Sun, 12 Jun 2016 15:26:01 +0200 Subject: [PATCH 075/159] Mention fix in CHANGELOG.md --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2564fb3..bb6fccf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,10 @@ All notable changes to this project will be documented in this file. ## [Unreleased] ### Added -- Support for `TEMPer1F_V1.3`'s behaviour: Only one sensor, data is at offset 4. +- Support for `TEMPer1F_V1.3`'s behaviour: Only one sensor, data is at offset 4 from ps-jay. + +### Fixed +- Comparing only port without bus may lead to calibration being applied to multiple devices instead of one from ps-jay. ## [1.5.0] - 2016-04-20 - Commit ID: 8752b14 ### Added From ceb0617babb7fef0c878e5d59d52ca921328d887 Mon Sep 17 00:00:00 2001 From: Philipp Adelt Date: Sun, 12 Jun 2016 16:53:31 +0200 Subject: [PATCH 076/159] Bump to package version 1.5.1 --- CHANGELOG.md | 2 ++ setup.py | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb6fccf..fb397d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ All notable changes to this project will be documented in this file. * This project follows the guidelines outlined on [keepachangelog.com](http://keepachangelog.com/). ## [Unreleased] + +## [1.5.1] - 2016-06-12 ### Added - Support for `TEMPer1F_V1.3`'s behaviour: Only one sensor, data is at offset 4 from ps-jay. diff --git a/setup.py b/setup.py index 014d12b..12293d2 100644 --- a/setup.py +++ b/setup.py @@ -5,14 +5,13 @@ author='Philipp Adelt', author_email='autosort-github@philipp.adelt.net ', url='https://github.com/padelt/temper-python', - version='1.5.0', + version='1.5.1', description='Reads temperature from TEMPerV1 devices (USB 0c45:7401)', long_description=open('README.md').read(), packages=['temperusb'], install_requires=[ 'pyusb>=1.0.0rc1', ], - dependency_links = ['git+https://github.com/walac/pyusb.git#egg=pyusb-1.0.0rc1'], entry_points={ 'console_scripts': [ 'temper-poll = temperusb.cli:main', From 0379b37acec7fe119102fc03d29adf0469550b36 Mon Sep 17 00:00:00 2001 From: Philipp Adelt Date: Sun, 12 Jun 2016 16:55:52 +0200 Subject: [PATCH 077/159] Note 1.5.1 commit ID in changelog. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb397d9..dbb03ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ All notable changes to this project will be documented in this file. ## [Unreleased] -## [1.5.1] - 2016-06-12 +## [1.5.1] - 2016-06-12 - Commit ID: ceb0617 ### Added - Support for `TEMPer1F_V1.3`'s behaviour: Only one sensor, data is at offset 4 from ps-jay. From 97c5f1beea2d56630b6a47bd7ab49e3b9ef0c9ff Mon Sep 17 00:00:00 2001 From: "Eric S. Raymond" Date: Tue, 6 Sep 2016 13:45:07 -0400 Subject: [PATCH 078/159] Clarify installation instructions. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b519e7d..aeffc53 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Under Debian/Ubuntu, treat yourself to some package goodness: # Installation and usage -After you run +Clone the repository, cd into its top-level directory, and run sudo python setup.py install From d158f929172fd7dcb85343a5289a641b31813d07 Mon Sep 17 00:00:00 2001 From: Philipp Adelt Date: Wed, 7 Sep 2016 09:53:33 +0200 Subject: [PATCH 079/159] Add clarification to changelog; Fix copyright --- CHANGELOG.md | 2 ++ temperusb/temper.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dbb03ab..296aab5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ All notable changes to this project will be documented in this file. * This project follows the guidelines outlined on [keepachangelog.com](http://keepachangelog.com/). ## [Unreleased] +### Fixed +- Clarification of install documentation from eric-s-raymond. ## [1.5.1] - 2016-06-12 - Commit ID: ceb0617 ### Added diff --git a/temperusb/temper.py b/temperusb/temper.py index 28f5cd8..7066f9d 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -3,7 +3,7 @@ # Handles devices reporting themselves as USB VID/PID 0C45:7401 (mine also says # RDing TEMPerV1.2). # -# Copyright 2012-2014 Philipp Adelt +# Copyright 2012-2016 Philipp Adelt and contributors. # # This code is licensed under the GNU public license (GPL). See LICENSE.md for # details. From 69034a44d479d55e42e9b12aab35ff6577f1db22 Mon Sep 17 00:00:00 2001 From: Philipp Adelt Date: Wed, 7 Sep 2016 09:56:26 +0200 Subject: [PATCH 080/159] Workaround for misleading error message when at least one TEMPer USB device node has insufficient permissions. (#63) --- CHANGELOG.md | 1 + temperusb/temper.py | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 296aab5..33fc27b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ All notable changes to this project will be documented in this file. ## [Unreleased] ### Fixed - Clarification of install documentation from eric-s-raymond. +- Workaround for misleading error message when at least one TEMPer USB device node has insufficient permissions. (#63) ## [1.5.1] - 2016-06-12 - Commit ID: ceb0617 ### Added diff --git a/temperusb/temper.py b/temperusb/temper.py index 7066f9d..1471ea6 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -86,6 +86,18 @@ def __init__(self, device, sensor_count=1): if self._ports == None: self._ports = find_ports(device) self.set_calibration_data() + try: + # Try to trigger a USB permission issue early so the + # user is not presented with seemingly unrelated error message. + # https://github.com/padelt/temper-python/issues/63 + self.lookup_sensor_count() + except ValueError, e: + if 'langid' in e.message: + raise usb.core.USBError("Error reading langids from device. "+ + "This might be a permission issue. Please check that the device "+ + "node for your TEMPer devices can be read and written by the "+ + "user running this code. The temperusb README.md contains hints "+ + "about how to fix this. Search for 'USB device permissions'.") self.set_sensor_count(self.lookup_sensor_count()) LOGGER.debug('Found device | Bus:{0} Ports:{1}'.format( self._bus, self._ports)) From e904dbe7945ea1e3a703021ea3ffb10915834578 Mon Sep 17 00:00:00 2001 From: Philipp Adelt Date: Wed, 7 Sep 2016 09:57:23 +0200 Subject: [PATCH 081/159] Prepare release of 1.5.2 --- CHANGELOG.md | 2 ++ setup.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33fc27b..5220826 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ All notable changes to this project will be documented in this file. * This project follows the guidelines outlined on [keepachangelog.com](http://keepachangelog.com/). ## [Unreleased] + +## [1.5.2] - 2016-09-07 ### Fixed - Clarification of install documentation from eric-s-raymond. - Workaround for misleading error message when at least one TEMPer USB device node has insufficient permissions. (#63) diff --git a/setup.py b/setup.py index 12293d2..532cc50 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ author='Philipp Adelt', author_email='autosort-github@philipp.adelt.net ', url='https://github.com/padelt/temper-python', - version='1.5.1', + version='1.5.2', description='Reads temperature from TEMPerV1 devices (USB 0c45:7401)', long_description=open('README.md').read(), packages=['temperusb'], From 45047b4b259d7895f00131936899cae268a86332 Mon Sep 17 00:00:00 2001 From: bit Date: Mon, 17 Oct 2016 14:39:02 +0200 Subject: [PATCH 082/159] use python 3 compatible exception syntax --- temperusb/temper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/temperusb/temper.py b/temperusb/temper.py index 1471ea6..9821068 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -91,7 +91,7 @@ def __init__(self, device, sensor_count=1): # user is not presented with seemingly unrelated error message. # https://github.com/padelt/temper-python/issues/63 self.lookup_sensor_count() - except ValueError, e: + except ValueError as e: if 'langid' in e.message: raise usb.core.USBError("Error reading langids from device. "+ "This might be a permission issue. Please check that the device "+ From 06148f27dcded3ee9c92dcc7838a41d986ec0e7e Mon Sep 17 00:00:00 2001 From: Philipp Adelt Date: Mon, 5 Dec 2016 11:53:49 +0100 Subject: [PATCH 083/159] Added hints for local development in a virtualenv --- CHANGELOG.md | 2 + DEVELOPMENT.md | 139 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 DEVELOPMENT.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 5220826..75aecb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ All notable changes to this project will be documented in this file. * This project follows the guidelines outlined on [keepachangelog.com](http://keepachangelog.com/). ## [Unreleased] +### Added +- Hints for local development ## [1.5.2] - 2016-09-07 ### Fixed diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 0000000..9c14228 --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,139 @@ +For development purposes, you will sometimes need to change some code and try it. +This should happen without changing the main installation of `temper-python`. +Here is how to do it. + +You will need these tools installed: + +- `git` +- `python` (if you don't know which, grab Python 3) +- `virtualenv` for the Python version (see below) + +# Clone the repository + +This will clone into a directory named `temper-dev`: + +``` +pa@plug2:~/temper$ git clone https://github.com/padelt/temper-python.git temper-dev +Cloning into 'temper-dev'... +remote: Counting objects: 544, done. +Receiving objects: 100% (544/544), 118.51 KiB, done. +remote: Total 544 (delta 0), reused 0 (delta 0), pack-reused 544 +Resolving deltas: 100% (329/329), done. +pa@plug2:~/temper$ cd temper-dev/ +pa@plug2:~/temper/temper-dev$ +``` + +# How to find `virtualenv` + +A virtualenv basically isolates all the package installation we are going to do +in a subdirectory instead of the global python repository. + +Unfortunately, availability of virtualenv differs greatly between Python versions. + +In Python 2 and until 3.3, this is a seperate tool, usually installed from your distribution +packages and available as a binary named `virtualenv` (check availability using +`which virtualenv`). + +In Python 3.4+, we finally reached a sane solution: Virtualenv is a module in +the standard Python distribution and is called using `python -m venv` followed +by your desire virtualenv directory. + +# Setting up a `virtualenv` and activating it + +Check which python binary is available and what you want by entering `python` +and hitting the Tab key twice to have your shell suggest some: + +``` +pa@plug2:~/temper/temper-dev$ python +python python2.7 python3 python3.2mu python-config +python2 python2.7-config python3.2 python3mu +``` + +I will choose `python3.2`. + + +To have it set up in the subdirectory `venv` (the name could be any valid +directory name), try this: + +``` +pa@plug2:~/temper/temper-dev$ virtualenv -p python3.2 venv +Running virtualenv with interpreter /usr/bin/python3.2 +New python executable in venv/bin/python3.2 +Also creating executable in venv/bin/python +Installing setuptools, pip, wheel...done. +pa@plug2:~/temper/temper-dev$ ll venv/bin/ +insgesamt 2792 +-rw-r--r-- 1 pa pa 2242 Dez 5 11:34 activate +-rw-r--r-- 1 pa pa 1268 Dez 5 11:34 activate.csh +-rw-r--r-- 1 pa pa 2481 Dez 5 11:34 activate.fish +-rw-r--r-- 1 pa pa 1137 Dez 5 11:34 activate_this.py +-rwxr-xr-x 1 pa pa 262 Dez 5 11:34 easy_install +-rwxr-xr-x 1 pa pa 262 Dez 5 11:34 easy_install-3.2 +-rwxr-xr-x 1 pa pa 234 Dez 5 11:34 pip +-rwxr-xr-x 1 pa pa 234 Dez 5 11:34 pip3 +-rwxr-xr-x 1 pa pa 234 Dez 5 11:34 pip3.2 +lrwxrwxrwx 1 pa pa 9 Dez 5 11:34 python -> python3.2 +lrwxrwxrwx 1 pa pa 9 Dez 5 11:34 python3 -> python3.2 +-rwxr-xr-x 1 pa pa 2814320 Dez 5 11:34 python3.2 +-rwxr-xr-x 1 pa pa 241 Dez 5 11:34 wheel +pa@plug2:~/temper/temper-dev$ +``` + +Now activate it: + +``` +pa@plug2:~/temper/temper-dev$ . venv/bin/activate +(venv)pa@plug2:~/temper/temper-dev$ +``` + +What this does is prepend your PATH environment variable to prefer the python +executable in the virtualenv. All the installations using `pip` will now go +there and not into your global python repo. + +To later deactivate it, run `deactivate` (which is a function set into your +running `bash` by `activate`). + +Check that the right python binary will be called: + +``` +(venv)pa@plug2:~/temper/temper-dev$ which python +/home/pa/temper/temper-dev/venv/bin/python +``` + +Great! + +# Install `temper-python` into the virtualenv + +``` +(venv)pa@plug2:~/temper/temper-dev$ python setup.py install +running install +... +Installing temper-poll script to /home/pa/temper/temper-dev/venv/bin +... +Finished processing dependencies for temperusb==1.5.2 +(venv)pa@plug2:~/temper/temper-dev$ +``` + +Now we can run `temper-poll` for testing. Since the virtualenv is active, +our fresh install is found first: +``` +(venv)pa@plug2:~/temper/temper-dev$ which temper-poll +/home/pa/temper/temper-dev/venv/bin/temper-poll +(venv)pa@plug2:~/temper/temper-dev$ temper-poll +Found 2 devices +Device #0: 30.9°C 87.7°F +Device #1: 17.1°C 62.8°F +(venv)pa@plug2:~/temper/temper-dev$ +``` + +# Development/testing workflow + +To test a change, you need to follow this workflow: + +- Make your changes to e.g. `temperusb/temper.py` +- Run `python setup.py install --force` (the `--force` will have it + reinstalled despite the package version in `setup.py` not changing). +- Run `temper-poll` + +This is a simple and surefire way to deal with module names and +dependencies. From ab9ee54942d8b22b35c2b243224062c8870a45c6 Mon Sep 17 00:00:00 2001 From: smeek Date: Mon, 9 Jan 2017 14:37:03 +0000 Subject: [PATCH 084/159] Add basic support for the TEMPerHUM device Added the TEMPerHUM USB device id to the list of known working devices in the README. Extended VIDPIDS list with the new values for the TEMPerHUM in temper.py. The device reports itself as TEMPer1F_H1_V1.4. It is a single sensor device, but it reports both temperature (at offset 2-3) and relative humidity (at offset 4-5) from that sensor. --- README.md | 13 +++++++------ temperusb/temper.py | 4 +++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index aeffc53..20b6e14 100644 --- a/README.md +++ b/README.md @@ -9,12 +9,13 @@ via SNMP. ### Reported working devices -| USB ID | Name Reported | Notes | -| ---------------------------------------------- | ------------------------ | ----------------------- | -| `0c45:7401 Microdia` | `RDing TEMPerV1.2` | First supported device | -| `0c45:7401 Microdia TEMPer Temperature Sensor` | `RDing TEMPer2_M12_V1.3` | Two sensor device | -| `0c45:7401 Microdia` | `RDing TEMPer1F_V1.3` | Single external sensor, but better precision is possible by using "sensor 2" | -| `0c45:7401 Microdia` | `RDing TEMPerV1.4` | | +| USB ID | Name Reported | Notes | +| ------------------------------------------------------------ | ------------------------ | ----------------------- | +| `0c45:7401 Microdia` | `RDing TEMPerV1.2` | First supported device | +| `0c45:7401 Microdia TEMPer Temperature Sensor` | `RDing TEMPer2_M12_V1.3` | Two sensor device | +| `0c45:7401 Microdia` | `RDing TEMPer1F_V1.3` | Single external sensor, but better precision is possible by using "sensor 2" | +| `0c45:7401 Microdia` | `RDing TEMPerV1.4` | | +| `0c45:7402 Microdia TEMPerHUM Temperature & Humidity Sensor` | `RDing TEMPer1F_H1_V1.4` | Single sensor which reports both temperature and relative-humidity | # Requirements diff --git a/temperusb/temper.py b/temperusb/temper.py index 9821068..46b879a 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -15,6 +15,7 @@ VIDPIDS = [ (0x0c45, 0x7401), + (0x0c45, 0x7402), ] REQ_INT_LEN = 8 ENDPOINT = 0x82 @@ -149,7 +150,8 @@ def lookup_sensor_count(self): """ Lookup the number of sensors on the device by product name. """ - if self._device.product == 'TEMPer1F_V1.3': + if (self._device.product == 'TEMPer1F_V1.3') or \ + (self._device.product == 'TEMPer1F_H1_V1.4'): return 1 # All others are two - if not the case, contribute here: https://github.com/padelt/temper-python/issues From 4ba4baa5fc53925e99c1ea6aaf778e10a5068af8 Mon Sep 17 00:00:00 2001 From: smeek Date: Fri, 20 Jan 2017 16:27:38 +0000 Subject: [PATCH 085/159] Add support for reading humidity Reads the relative humidity from those devices that support it. --- etc/99-tempsensor.rules | 1 + temperusb/cli.py | 37 ++++++++++++++++++----- temperusb/temper.py | 67 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 97 insertions(+), 8 deletions(-) diff --git a/etc/99-tempsensor.rules b/etc/99-tempsensor.rules index 30a2e1e..037a343 100644 --- a/etc/99-tempsensor.rules +++ b/etc/99-tempsensor.rules @@ -1 +1,2 @@ SUBSYSTEMS=="usb", ACTION=="add", ATTRS{idVendor}=="0c45", ATTRS{idProduct}=="7401", MODE="666" +SUBSYSTEMS=="usb", ACTION=="add", ATTRS{idVendor}=="0c45", ATTRS{idProduct}=="7402", MODE="666" diff --git a/temperusb/cli.py b/temperusb/cli.py index 8224b33..17b41bc 100644 --- a/temperusb/cli.py +++ b/temperusb/cli.py @@ -17,6 +17,8 @@ def parse_args(): help="Quiet: just degrees celcius as decimal") units.add_argument("-f", "--fahrenheit", action='store_true', help="Quiet: just degrees fahrenheit as decimal") + units.add_argument("-H", "--humidity", action='store_true', + help="Quiet: just percentage relative humidity as decimal") parser.add_argument("-s", "--sensor_ids", choices=['0', '1', 'all'], help="IDs of sensors to use on the device " + "(multisensor devices only)", default='0') @@ -29,7 +31,7 @@ def parse_args(): def main(): args = parse_args() - quiet = args.celsius or args.fahrenheit + quiet = args.celsius or args.fahrenheit or args.humidity logging.basicConfig(level = logging.ERROR if quiet else logging.WARNING) @@ -50,7 +52,17 @@ def main(): else: sensors = [int(args.sensor_ids)] - readings.append(dev.get_temperatures(sensors=sensors)) + temperatures = dev.get_temperatures(sensors=sensors) + humidities = dev.get_humidity(sensors=sensors) + combinations = {} + for k, v in temperatures.items(): + c = v.copy() + try: + c.update(humidities[k]) + except: + pass + combinations[k] = c + readings.append(combinations) for i, reading in enumerate(readings): output = '' @@ -59,6 +71,8 @@ def main(): dict_key = 'temperature_c' elif args.fahrenheit: dict_key = 'temperature_f' + elif args.humidity: + dict_key = 'humidity_pc' for sensor in sorted(reading): output += '%0.1f; ' % reading[sensor][dict_key] @@ -66,16 +80,25 @@ def main(): else: portinfo = '' tempinfo = '' + huminfo = '' for sensor in sorted(reading): if args.disp_ports and portinfo == '': portinfo = " (bus %(bus)s - port %(ports)s)" % reading[sensor] - tempinfo += '%0.1f°C %0.1f°F; ' % ( - reading[sensor]['temperature_c'], - reading[sensor]['temperature_f'], - ) + try: + tempinfo += '%0.1f°C %0.1f°F; ' % ( + reading[sensor]['temperature_c'], + reading[sensor]['temperature_f'], + ) + except: + pass + try: + huminfo += '%0.1f%%RH; ' % (reading[sensor]['humidity_pc']) + except: + pass tempinfo = tempinfo[0:len(output) - 2] + huminfo = huminfo[0:len(output) - 2] - output = 'Device #%i%s: %s' % (i, portinfo, tempinfo) + output = 'Device #%i%s: %s %s' % (i, portinfo, tempinfo, huminfo) print(output) diff --git a/temperusb/temper.py b/temperusb/temper.py index 46b879a..321266f 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -12,6 +12,7 @@ import os import re import logging +import struct VIDPIDS = [ (0x0c45, 0x7401), @@ -146,6 +147,16 @@ def lookup_offset(self, sensor): # Sensor 1 = Offset 4 return (sensor + 1) * 2 + def lookup_humidity_offset(self, sensor): + """ + Lookup the offset of the humidity data by product name. + """ + if self._device.product == 'TEMPer1F_H1_V1.4': + # Has only 1 sensor, and the humidity data is at offset = 4 + return 4 + + return None + def lookup_sensor_count(self): """ Lookup the number of sensors on the device by product name. @@ -240,7 +251,16 @@ def get_data(self, reset_device=False): # Get temperature self._control_transfer(COMMANDS['temp']) - data = self._interrupt_read() + temp_data = self._interrupt_read() + + # Get humidity + if self._device.product == 'TEMPer1F_H1_V1.4': + humidity_data = temp_data + else: + humidity_data = None + + # Combine temperature and humidity data + data = {'temp_data': temp_data, 'humidity_data': humidity_data} # Be a nice citizen and undo potential interface claiming. # Also see: https://github.com/walac/pyusb/blob/master/docs/tutorial.rst#dont-be-selfish @@ -300,6 +320,7 @@ def get_temperatures(self, sensors=None): ) data = self.get_data() + data = data['temp_data'] results = {} @@ -320,6 +341,50 @@ def get_temperatures(self, sensors=None): return results + def get_humidity(self, sensors=None): + """ + Get device humidity reading. + + Params: + - sensors: optional list of sensors to get a reading for, examples: + [0,] - get reading for sensor 0 + [0, 1,] - get reading for sensors 0 and 1 + None - get readings for all sensors + """ + _sensors = sensors + if _sensors is None: + _sensors = list(range(0, self._sensor_count)) + + if not set(_sensors).issubset(list(range(0, self._sensor_count))): + raise ValueError( + 'Some or all of the sensors in the list %s are out of range ' + 'given a sensor_count of %d. Valid range: %s' % ( + _sensors, + self._sensor_count, + list(range(0, self._sensor_count)), + ) + ) + + data = self.get_data() + data = data['humidity_data'] + + results = {} + + # Interpret device response + for sensor in _sensors: + offset = self.lookup_humidity_offset(sensor) + if offset is None: + continue + humidity = (struct.unpack_from('>H', data, offset)[0] * 32) / 1000.0 + results[sensor] = { + 'ports': self.get_ports(), + 'bus': self.get_bus(), + 'sensor': sensor, + 'humidity_pc': humidity, + } + + return results + def _control_transfer(self, data): """ Send device a control request with standard parameters and as From 711d2a0be875de3bc23c5721f004a77294fe9494 Mon Sep 17 00:00:00 2001 From: smeek Date: Mon, 9 Jan 2017 15:15:43 +0000 Subject: [PATCH 086/159] Update temper.py to fix temperature conversion The temperature is returned as a signed 8.8 fixed point representation. Use `struct.unpack_from()` to get the signed value out correctly. Otherwise the readings go crazy when it's very cold. --- temperusb/temper.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/temperusb/temper.py b/temperusb/temper.py index 9821068..3281cb4 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -12,6 +12,7 @@ import os import re import logging +import struct VIDPIDS = [ (0x0c45, 0x7401), @@ -304,7 +305,8 @@ def get_temperatures(self, sensors=None): # Interpret device response for sensor in _sensors: offset = self.lookup_offset(sensor) - celsius = data[offset] + data[offset+1] / 256.0 + celsius = struct.unpack_from('>h', data, offset)[0] / 256.0 + # Apply scaling and offset (if any) celsius = celsius * self._scale + self._offset results[sensor] = { 'ports': self.get_ports(), From ff01ca21f8c799f7d80d8b9e75093900f49c6a9d Mon Sep 17 00:00:00 2001 From: allo- Date: Thu, 23 Feb 2017 20:44:23 +0100 Subject: [PATCH 087/159] fixed format string error in the munin plugin --- etc/munin-temperature | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/etc/munin-temperature b/etc/munin-temperature index dd28cec..d914247 100755 --- a/etc/munin-temperature +++ b/etc/munin-temperature @@ -37,7 +37,7 @@ def config(): for device in handler.get_devices(): port = device.get_ports() port_name = str(port).replace('.', '_') - print "temp_" + port_name + ".label Port {0:s} Temperature".format(port) + print "temp_" + port_name + ".label Port {0:s} Temperature".format(str(port)) def fetch(): @@ -65,4 +65,4 @@ def main(): if __name__ == '__main__': - main() \ No newline at end of file + main() From 1603448d55f2bc5c4fcd39c01233a3190113b5de Mon Sep 17 00:00:00 2001 From: Philipp Adelt Date: Fri, 31 Mar 2017 17:48:14 +0200 Subject: [PATCH 088/159] Add CHANGELOG entry for PR #68 --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75aecb0..5de798d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,10 @@ All notable changes to this project will be documented in this file. ## [Unreleased] ### Added - Hints for local development +### Fixed +- Negative temperature readings incorrectly wrapped around to very high temperatures -## [1.5.2] - 2016-09-07 +## [1.5.2] - 2016-09-07 - Commit ID: e904dbe ### Fixed - Clarification of install documentation from eric-s-raymond. - Workaround for misleading error message when at least one TEMPer USB device node has insufficient permissions. (#63) From 5ead3414da819e443b7b92d8909886da2f7f32ef Mon Sep 17 00:00:00 2001 From: Philipp Adelt Date: Fri, 31 Mar 2017 18:11:00 +0200 Subject: [PATCH 089/159] Add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5de798d..5710222 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file. ## [Unreleased] ### Added +- Support for 0c45:7402 (RDing TEMPer1F_H1_V1.4) including humidity - Hints for local development ### Fixed - Negative temperature readings incorrectly wrapped around to very high temperatures From fbd32b83fd04f318ff8b6112629bea2845954e3e Mon Sep 17 00:00:00 2001 From: Philipp Adelt Date: Fri, 31 Mar 2017 18:23:06 +0200 Subject: [PATCH 090/159] Add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5710222..5e1b54b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ All notable changes to this project will be documented in this file. - Hints for local development ### Fixed - Negative temperature readings incorrectly wrapped around to very high temperatures +- Fixed format string error in the munin plugin (PR#71) ## [1.5.2] - 2016-09-07 - Commit ID: e904dbe ### Fixed From cac887fda480ced6fb3af1ee053ae0256459191e Mon Sep 17 00:00:00 2001 From: Philipp Adelt Date: Fri, 31 Mar 2017 18:29:04 +0200 Subject: [PATCH 091/159] Bump package version to 1.5.3pre to help debugging --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 532cc50..6156086 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ author='Philipp Adelt', author_email='autosort-github@philipp.adelt.net ', url='https://github.com/padelt/temper-python', - version='1.5.2', + version='1.5.3pre', description='Reads temperature from TEMPerV1 devices (USB 0c45:7401)', long_description=open('README.md').read(), packages=['temperusb'], From 4da8be1c223504259a191d2ec790d7aff21444d7 Mon Sep 17 00:00:00 2001 From: Philipp Adelt Date: Mon, 3 Apr 2017 17:05:34 +0200 Subject: [PATCH 092/159] Release docs and preparation for 1.5.3 --- CHANGELOG.md | 5 +++++ DEVELOPMENT.md | 30 ++++++++++++++++++++++++++++++ setup.py | 2 +- 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e1b54b..d3be836 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,14 @@ All notable changes to this project will be documented in this file. * This project follows the guidelines outlined on [keepachangelog.com](http://keepachangelog.com/). ## [Unreleased] + +No changes yet. + +## [1.5.3] - 2017-04-03 ### Added - Support for 0c45:7402 (RDing TEMPer1F_H1_V1.4) including humidity - Hints for local development +- Add release documentation to `DEVELOPMENT.md`. ### Fixed - Negative temperature readings incorrectly wrapped around to very high temperatures - Fixed format string error in the munin plugin (PR#71) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 9c14228..fb4ea74 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -137,3 +137,33 @@ To test a change, you need to follow this workflow: This is a simple and surefire way to deal with module names and dependencies. + +# Release workflow + +1. Edit `setup.py` to reflect the new version. +1. Edit `CHANGELOG.md` to document the new version (without commit ID). +1. Setup your `~.pypirc`: + ``` + [distutils] + index-servers = + pypi + pypitest + + [pypi] + repository=https://pypi.python.org/pypi + username=myusername + password=mypass + + [pypitest] + repository=https://testpypi.python.org/pypi + username=myusername + password=mypass + ```` +1. Test-Upload: `python setup.py sdist upload -r pypitest` +1. Check if https://testpypi.python.org/pypi/temperusb looks good. +1. Commit changes and note commit ID. +1. Tag the revision and push the tag to Github: + `git tag v1.5.3 && git push origin v1.5.3` +1. Edit `CHANGELOG.md` noting the commit ID you just tagged. +1. Commit and push that change. + diff --git a/setup.py b/setup.py index 6156086..378678d 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ author='Philipp Adelt', author_email='autosort-github@philipp.adelt.net ', url='https://github.com/padelt/temper-python', - version='1.5.3pre', + version='1.5.3', description='Reads temperature from TEMPerV1 devices (USB 0c45:7401)', long_description=open('README.md').read(), packages=['temperusb'], From 3e9c9d382fbbb5b5497b7cfa83b56770818e0f1a Mon Sep 17 00:00:00 2001 From: Philipp Adelt Date: Mon, 3 Apr 2017 17:06:22 +0200 Subject: [PATCH 093/159] Note commit id --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3be836..c30f2f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ All notable changes to this project will be documented in this file. No changes yet. -## [1.5.3] - 2017-04-03 +## [1.5.3] - 2017-04-03 - Commit ID: 4da8be1 ### Added - Support for 0c45:7402 (RDing TEMPer1F_H1_V1.4) including humidity - Hints for local development From c75d5fdc0bc3b831c87ed946b4cd05d2437860c4 Mon Sep 17 00:00:00 2001 From: Philipp Adelt Date: Mon, 3 Apr 2017 17:07:41 +0200 Subject: [PATCH 094/159] Add missing step --- DEVELOPMENT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index fb4ea74..f205cc2 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -166,4 +166,4 @@ dependencies. `git tag v1.5.3 && git push origin v1.5.3` 1. Edit `CHANGELOG.md` noting the commit ID you just tagged. 1. Commit and push that change. - +1. Live PyPI upload: `python setup.py sdist upload -r pypi` From f4fc430e814accb45784fc9dbb12529f14235ada Mon Sep 17 00:00:00 2001 From: Nicholas Sielicki Date: Wed, 2 Aug 2017 21:22:47 -0500 Subject: [PATCH 095/159] parse readme as utf-8 instead of ascii The readme contains some UTF-8 symbols (ie: a degree symbol). When generating long_description in setup.py, this was producing a UnicodeDecodeError and erroring out. Signed-off-by: Nicholas Sielicki --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 378678d..83fdda2 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ url='https://github.com/padelt/temper-python', version='1.5.3', description='Reads temperature from TEMPerV1 devices (USB 0c45:7401)', - long_description=open('README.md').read(), + long_description=open('README.md', encoding='utf-8').read(), packages=['temperusb'], install_requires=[ 'pyusb>=1.0.0rc1', From 8a289112f6a71be24b7ba451da0e8d5883094716 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Bourgeois?= Date: Tue, 26 Dec 2017 14:32:16 +0100 Subject: [PATCH 096/159] Fixing the munin plugin --- etc/munin-temperature | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/etc/munin-temperature b/etc/munin-temperature index d914247..de54f42 100755 --- a/etc/munin-temperature +++ b/etc/munin-temperature @@ -9,6 +9,7 @@ #%# capabilities=autoconf +from __future__ import print_function import sys @@ -21,23 +22,23 @@ def autoconf(): try: handler = get_handler() except ImportError: - print "no (temper-python package is not installed)" + print ("no (temper-python package is not installed)") else: if len(handler.get_devices()): - print "yes" + print ("yes") else: - print "no (No devices found)" + print ("no (No devices found)") def config(): handler = get_handler() - print "graph_title Temperature" - print "graph_vlabel Degrees Celsius" - print "graph_category sensors" + print ("graph_title Temperature") + print ("graph_vlabel Degrees Celsius") + print ("graph_category sensors") for device in handler.get_devices(): port = device.get_ports() port_name = str(port).replace('.', '_') - print "temp_" + port_name + ".label Port {0:s} Temperature".format(str(port)) + print ("temp_" + port_name + ".label Port {0:s} Temperature".format(str(port))) def fetch(): @@ -49,7 +50,7 @@ def fetch(): temp = device.get_temperature() except Exception: temp = 'U' - print "temp_" + port_name + ".value {0:f}".format(temp) + print ("temp_" + port_name + ".value {0:f}".format(temp)) def main(): From dd0759f486c74306a2b7d24e037d76a8f042d522 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Bourgeois?= Date: Tue, 26 Dec 2017 14:35:02 +0100 Subject: [PATCH 097/159] Fixing the setup.py script for python 2 --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 83fdda2..38fc466 100644 --- a/setup.py +++ b/setup.py @@ -1,3 +1,4 @@ +from io import open from setuptools import setup setup( From f128c36d7cf07043efe867addf6790a1e0adcbb8 Mon Sep 17 00:00:00 2001 From: Jonathan McDowell Date: Tue, 8 May 2018 22:05:01 +0100 Subject: [PATCH 098/159] Report TEMPerV1.2 devices as having a single sensor --- temperusb/temper.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/temperusb/temper.py b/temperusb/temper.py index 41bb430..7dfc9ac 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -161,7 +161,8 @@ def lookup_sensor_count(self): """ Lookup the number of sensors on the device by product name. """ - if (self._device.product == 'TEMPer1F_V1.3') or \ + if (self._device.product == 'TEMPerV1.2') or \ + (self._device.product == 'TEMPer1F_V1.3') or \ (self._device.product == 'TEMPer1F_H1_V1.4'): return 1 From c4042edba2e5738656be875f42234141e9f45dae Mon Sep 17 00:00:00 2001 From: Rob Taft Date: Thu, 21 Mar 2019 05:28:15 -0400 Subject: [PATCH 099/159] Added support for 3 sensor tempers and TEMPerNTC1.O --- temperusb/temper.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/temperusb/temper.py b/temperusb/temper.py index 41bb430..6bf3556 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -164,7 +164,7 @@ def lookup_sensor_count(self): if (self._device.product == 'TEMPer1F_V1.3') or \ (self._device.product == 'TEMPer1F_H1_V1.4'): return 1 - + if (self._device.product == 'TEMPerNTC1.O'): return 3 # All others are two - if not the case, contribute here: https://github.com/padelt/temper-python/issues return 2 @@ -183,8 +183,8 @@ def set_sensor_count(self, count): # Currently this only supports 1 and 2 sensor models. # If you have the 8 sensor model, please contribute to the # discussion here: https://github.com/padelt/temper-python/issues - if count not in [1, 2,]: - raise ValueError('Only sensor_count of 1 or 2 supported') + if count not in [1, 2, 3]: + raise ValueError('Only sensor_count of 1-3 supported') self._sensor_count = int(count) From f8c5edfc7809b9ca60b504d6920920b2c863fe96 Mon Sep 17 00:00:00 2001 From: Markus Neumann Date: Sat, 27 Apr 2019 21:59:08 +0200 Subject: [PATCH 100/159] typo fix just a typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 20b6e14..c4e1162 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ you should end up with two scripts conveniently installed: /usr/local/bin/temper-poll /usr/local/bin/temper-snmp -If your system does not provide access as a normal user to the USB device, you need to rum them as root. See "USB device permissions" section for more on this. +If your system does not provide access as a normal user to the USB device, you need to run them as root. See "USB device permissions" section for more on this. temper-poll accepts -p option now, which adds the USB bus and port information each device is plugged on. From 4d3d040f43fe4bc5807b4c4dee6284ac035ff907 Mon Sep 17 00:00:00 2001 From: Dave T Date: Sat, 11 May 2019 16:20:55 +0100 Subject: [PATCH 101/159] Made error message about usb permissions display correction in python 3.6 --- temperusb/temper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/temperusb/temper.py b/temperusb/temper.py index 41bb430..08e0bd7 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -94,7 +94,7 @@ def __init__(self, device, sensor_count=1): # https://github.com/padelt/temper-python/issues/63 self.lookup_sensor_count() except ValueError as e: - if 'langid' in e.message: + if 'langid' in str(e): raise usb.core.USBError("Error reading langids from device. "+ "This might be a permission issue. Please check that the device "+ "node for your TEMPer devices can be read and written by the "+ From 5f2e6946f943577197af9813119c11eb81f6baf2 Mon Sep 17 00:00:00 2001 From: Dave T Date: Sat, 11 May 2019 23:34:11 +0100 Subject: [PATCH 102/159] Added support for TemperHUM with si7021 type sensor. --- temperusb/cli.py | 2 +- temperusb/temper.py | 26 +++++++++++++++++--------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/temperusb/cli.py b/temperusb/cli.py index 17b41bc..a8de695 100644 --- a/temperusb/cli.py +++ b/temperusb/cli.py @@ -7,7 +7,7 @@ def parse_args(): - descr = "Temperature data from a TEMPer v1.2 sensor." + descr = "Temperature data from a TEMPer v1.2/v1.3 sensor." parser = argparse.ArgumentParser(description=descr) parser.add_argument("-p", "--disp_ports", action='store_true', diff --git a/temperusb/temper.py b/temperusb/temper.py index 08e0bd7..a1705ef 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -101,8 +101,8 @@ def __init__(self, device, sensor_count=1): "user running this code. The temperusb README.md contains hints "+ "about how to fix this. Search for 'USB device permissions'.") self.set_sensor_count(self.lookup_sensor_count()) - LOGGER.debug('Found device | Bus:{0} Ports:{1}'.format( - self._bus, self._ports)) + LOGGER.debug('Found device | Bus:{0} Ports:{1} SensorCount:{2}'.format( + self._bus, self._ports, self._sensor_count)) def set_calibration_data(self, scale=None, offset=None): """ @@ -154,7 +154,8 @@ def lookup_humidity_offset(self, sensor): if self._device.product == 'TEMPer1F_H1_V1.4': # Has only 1 sensor, and the humidity data is at offset = 4 return 4 - + if self._device.product == 'TEMPERHUM1V1.3': + return 4 return None def lookup_sensor_count(self): @@ -162,7 +163,8 @@ def lookup_sensor_count(self): Lookup the number of sensors on the device by product name. """ if (self._device.product == 'TEMPer1F_V1.3') or \ - (self._device.product == 'TEMPer1F_H1_V1.4'): + (self._device.product == 'TEMPer1F_H1_V1.4') or \ + (self._device.product == 'TEMPERHUM1V1.3'): return 1 # All others are two - if not the case, contribute here: https://github.com/padelt/temper-python/issues @@ -254,7 +256,9 @@ def get_data(self, reset_device=False): temp_data = self._interrupt_read() # Get humidity - if self._device.product == 'TEMPer1F_H1_V1.4': + LOGGER.debug("ID='%s'" % self._device.product) + if (self._device.product == 'TEMPer1F_H1_V1.4') or \ + (self._device.product == 'TEMPERHUM1V1.3'): humidity_data = temp_data else: humidity_data = None @@ -327,7 +331,10 @@ def get_temperatures(self, sensors=None): # Interpret device response for sensor in _sensors: offset = self.lookup_offset(sensor) - celsius = struct.unpack_from('>h', data, offset)[0] / 256.0 + if self._device.product == 'TEMPERHUM1V1.3': #si7021 type device. + celsius = struct.unpack_from('>h', data, offset)[0] * 175.72 / 65536 - 46.85 + else: # fm75 (?) type device + celsius = struct.unpack_from('>h', data, offset)[0] / 256.0 # Apply scaling and offset (if any) celsius = celsius * self._scale + self._offset results[sensor] = { @@ -365,10 +372,8 @@ def get_humidity(self, sensors=None): list(range(0, self._sensor_count)), ) ) - data = self.get_data() data = data['humidity_data'] - results = {} # Interpret device response @@ -376,7 +381,10 @@ def get_humidity(self, sensors=None): offset = self.lookup_humidity_offset(sensor) if offset is None: continue - humidity = (struct.unpack_from('>H', data, offset)[0] * 32) / 1000.0 + if self._device.product == 'TEMPERHUM1V1.3': #si7021 type device. + humidity = (struct.unpack_from('>H', data, offset)[0] * 125) / 65536 -6 + else: #fm75 (?) type device + humidity = (struct.unpack_from('>H', data, offset)[0] * 32) / 1000.0 results[sensor] = { 'ports': self.get_ports(), 'bus': self.get_bus(), From d6fd1c0412dcf4891f13eb0756927d184c1367df Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Sat, 7 Nov 2020 22:49:21 +0000 Subject: [PATCH 103/159] add first pytests --- tests/test_temper.py | 117 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 tests/test_temper.py diff --git a/tests/test_temper.py b/tests/test_temper.py new file mode 100644 index 0000000..7d9eb79 --- /dev/null +++ b/tests/test_temper.py @@ -0,0 +1,117 @@ +""" +pytests for temperusb + +run from the project root with: +pytest --cov=temperusb +""" + +import os + +import pytest +import usb +from unittest.mock import MagicMock, patch, Mock + +import temperusb + +from temperusb.temper import TIMEOUT + + +@pytest.mark.parametrize( + [ + "productname", + "vid", + "pid", + "ctrl_data_in_expected", + "data_out_raw", + "temperature_out_expected", + ], + [ + [ + "TEMPerV1.2", + 0x0C45, + 0x7401, + b"\x01\x80\x33\x01\x00\x00\x00\x00", + b"\x00\x00\x20\x1A", # 0x201A converts to 32.1C (fm75) + 32.1, + ], + # [ + # "TEMPer1F_V1.3", # Has 2 sensors + # 0x0C45, + # 0x7401, + # b"\x01\x80\x33\x01\x00\x00\x00\x00", + # b"\x00\x00\x20\x1A\x2B\x33", # 0x201A,0x2B33 converts to 32.1C, 43.2C (fm75) + # 32.1, + # ], + [ + "TEMPERHUM1V1.3", + 0x0C45, + 0x7401, + b"\x01\x80\x33\x01\x00\x00\x00\x00", + b"\x00\x00\x56\x2C", # 0x562C converts to 12.3C (si7021) + 12.3, + ], + [ + "TEMPer1F_H1_V1.4", + 0x0C45, + 0x7401, + b"\x01\x80\x33\x01\x00\x00\x00\x00", + b"\x00\x00\x20\x1A", # 0x201A converts to 32.1C (fm75) + 32.1, + ], + [ + "TEMPerNTC1.O", + 0x0C45, + 0x7401, + b"\x01\x80\x33\x01\x00\x00\x00\x00", + b"\x00\x00\x20\x1A\x2B\x33\x36\x4D", # 0x201A,0x2B33,0x364D converts to 32.1,43.2,54.3C (fm75) + 32.1, + ], + ], +) +def test_TemperDevice( + productname, vid, pid, ctrl_data_in_expected, data_out_raw, temperature_out_expected +): + """ + Patches the underlying usb port call to allow us to verify the data + we would be sending, and fake the return data so that we can test the + conversion coming back. + """ + usbdev = Mock(bus="fakebus", product=productname) + usbdev.is_kernel_driver_active = MagicMock(return_value=False) + + def ctrl_transfer_dummy( + bmRequestType, bRequest, wValue, wIndex, data_or_wLength, timeout + ): + assert data_or_wLength == ctrl_data_in_expected + assert timeout == TIMEOUT + + usbdev.ctrl_transfer = MagicMock( + bmRequestType=0x21, + bRequest=0x09, + wValue=0x0200, + wIndex=0x01, + data_or_wLength=None, + timeout=None, + side_effect=ctrl_transfer_dummy, + ) + # print("usbdev.bus=%s" % usbdev.bus) + usbdev.read = Mock(return_value=data_out_raw) + + def match_pids(find_all, idVendor, idProduct): + if idVendor == vid and idProduct == pid: + return [usbdev] + else: + return [] + + with patch("usb.core.find", side_effect=match_pids, return_value=[usbdev]): + th = temperusb.TemperHandler() + devs = th.get_devices() + # Check that we actually got any devices + assert devs != None + # Check that we only found one sensor + assert len(devs) == 1, "Should be only one sensor type matching" + + # read a temperature + results = devs[0].get_temperatures(None) + # check the temperature is what we were expecting. + assert results[0]["temperature_c"] == pytest.approx(temperature_out_expected, 0.01) From 18ddd768f677b640a9217f308f341882ddc29270 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Sun, 8 Nov 2020 23:03:31 +0000 Subject: [PATCH 104/159] Add ability to pytest humidty sensors --- tests/test_temper.py | 62 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 50 insertions(+), 12 deletions(-) diff --git a/tests/test_temper.py b/tests/test_temper.py index 7d9eb79..afc72cc 100644 --- a/tests/test_temper.py +++ b/tests/test_temper.py @@ -21,55 +21,83 @@ "productname", "vid", "pid", + "count", "ctrl_data_in_expected", "data_out_raw", "temperature_out_expected", + "humidity_out_expected", ], [ [ "TEMPerV1.2", 0x0C45, 0x7401, + 1, b"\x01\x80\x33\x01\x00\x00\x00\x00", b"\x00\x00\x20\x1A", # 0x201A converts to 32.1C (fm75) 32.1, + None, + ], + [ + "TEMPer1F_V1.3", # Has 1 sensor at offset 4 + 0x0C45, + 0x7401, + 1, + b"\x01\x80\x33\x01\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x20\x1A", # 0x201A converts to 32.1C (fm75) + 32.1, + None, ], - # [ - # "TEMPer1F_V1.3", # Has 2 sensors - # 0x0C45, - # 0x7401, - # b"\x01\x80\x33\x01\x00\x00\x00\x00", - # b"\x00\x00\x20\x1A\x2B\x33", # 0x201A,0x2B33 converts to 32.1C, 43.2C (fm75) - # 32.1, - # ], [ "TEMPERHUM1V1.3", 0x0C45, 0x7401, + 1, b"\x01\x80\x33\x01\x00\x00\x00\x00", - b"\x00\x00\x56\x2C", # 0x562C converts to 12.3C (si7021) + b"\x00\x00\x56\x2C\xBF\xB1", # 0x562C,0xBFB1 converts to 12.3C,87.6% (si7021) 12.3, + 87.6, ], [ "TEMPer1F_H1_V1.4", 0x0C45, 0x7401, + 1, b"\x01\x80\x33\x01\x00\x00\x00\x00", - b"\x00\x00\x20\x1A", # 0x201A converts to 32.1C (fm75) + b"\x00\x00\x20\x1A\x0C\x0C", # 0x201A,0x0C0C converts to 32.1C,98.7% (fm75) 32.1, + 98.7, ], [ "TEMPerNTC1.O", 0x0C45, 0x7401, + 3, b"\x01\x80\x33\x01\x00\x00\x00\x00", b"\x00\x00\x20\x1A\x2B\x33\x36\x4D", # 0x201A,0x2B33,0x364D converts to 32.1,43.2,54.3C (fm75) 32.1, + None, ], + # [ + # "????", # Has 2 sensors + # 0x0C45, + # 0x7401, + # 1, + # b"\x01\x80\x33\x01\x00\x00\x00\x00", + # b"\x00\x00\x20\x1A\x2B\x33", # 0x201A,0x2B33 converts to 32.1C, 43.2C (fm75) + # 32.1, + # ], ], ) def test_TemperDevice( - productname, vid, pid, ctrl_data_in_expected, data_out_raw, temperature_out_expected + productname, + vid, + pid, + count, + ctrl_data_in_expected, + data_out_raw, + temperature_out_expected, + humidity_out_expected, ): """ Patches the underlying usb port call to allow us to verify the data @@ -111,7 +139,17 @@ def match_pids(find_all, idVendor, idProduct): # Check that we only found one sensor assert len(devs) == 1, "Should be only one sensor type matching" + dev = devs[0] + + # check that the sensor count reported is what we expect + assert dev.get_sensor_count() == count # read a temperature - results = devs[0].get_temperatures(None) + results = dev.get_temperatures(None) + # check the temperature is what we were expecting. assert results[0]["temperature_c"] == pytest.approx(temperature_out_expected, 0.01) + + # if the device is expected to also report humidty + if humidity_out_expected: + results_h = dev.get_humidity(None) + results_h[0]["humidity_pc"] == pytest.approx(humidity_out_expected) From dfc6078952e09f0ec7e1d0d760eab316528ca858 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Sun, 8 Nov 2020 23:10:16 +0000 Subject: [PATCH 105/159] Allow pytesting of multiple sensors on one device. --- tests/test_temper.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/tests/test_temper.py b/tests/test_temper.py index afc72cc..b85bc34 100644 --- a/tests/test_temper.py +++ b/tests/test_temper.py @@ -35,7 +35,7 @@ 1, b"\x01\x80\x33\x01\x00\x00\x00\x00", b"\x00\x00\x20\x1A", # 0x201A converts to 32.1C (fm75) - 32.1, + [32.1], None, ], [ @@ -45,7 +45,7 @@ 1, b"\x01\x80\x33\x01\x00\x00\x00\x00", b"\x00\x00\x00\x00\x20\x1A", # 0x201A converts to 32.1C (fm75) - 32.1, + [32.1], None, ], [ @@ -55,8 +55,8 @@ 1, b"\x01\x80\x33\x01\x00\x00\x00\x00", b"\x00\x00\x56\x2C\xBF\xB1", # 0x562C,0xBFB1 converts to 12.3C,87.6% (si7021) - 12.3, - 87.6, + [12.3], + [87.6], ], [ "TEMPer1F_H1_V1.4", @@ -65,8 +65,8 @@ 1, b"\x01\x80\x33\x01\x00\x00\x00\x00", b"\x00\x00\x20\x1A\x0C\x0C", # 0x201A,0x0C0C converts to 32.1C,98.7% (fm75) - 32.1, - 98.7, + [32.1], + [98.7], ], [ "TEMPerNTC1.O", @@ -75,7 +75,7 @@ 3, b"\x01\x80\x33\x01\x00\x00\x00\x00", b"\x00\x00\x20\x1A\x2B\x33\x36\x4D", # 0x201A,0x2B33,0x364D converts to 32.1,43.2,54.3C (fm75) - 32.1, + [32.1, 43.2, 54.3], None, ], # [ @@ -85,7 +85,7 @@ # 1, # b"\x01\x80\x33\x01\x00\x00\x00\x00", # b"\x00\x00\x20\x1A\x2B\x33", # 0x201A,0x2B33 converts to 32.1C, 43.2C (fm75) - # 32.1, + # [32.1,43.2], # ], ], ) @@ -146,10 +146,12 @@ def match_pids(find_all, idVendor, idProduct): # read a temperature results = dev.get_temperatures(None) - # check the temperature is what we were expecting. - assert results[0]["temperature_c"] == pytest.approx(temperature_out_expected, 0.01) + for i, temperature in enumerate(temperature_out_expected): + # check the temperature is what we were expecting. + assert results[i]["temperature_c"] == pytest.approx(temperature, 0.01) # if the device is expected to also report humidty if humidity_out_expected: - results_h = dev.get_humidity(None) - results_h[0]["humidity_pc"] == pytest.approx(humidity_out_expected) + for i, humidity in enumerate(humidity_out_expected): + results_h = dev.get_humidity(None) + results_h[i]["humidity_pc"] == pytest.approx(humidity) From 053eb5a2155094afcae11e6d53b08e8776302055 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Sun, 8 Nov 2020 23:20:11 +0000 Subject: [PATCH 106/159] clean up and documentation --- tests/test_temper.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/tests/test_temper.py b/tests/test_temper.py index b85bc34..26993b6 100644 --- a/tests/test_temper.py +++ b/tests/test_temper.py @@ -2,30 +2,28 @@ pytests for temperusb run from the project root with: -pytest --cov=temperusb +pytest --cov=temperusb --cov-report term-missing """ import os - import pytest import usb from unittest.mock import MagicMock, patch, Mock import temperusb - from temperusb.temper import TIMEOUT @pytest.mark.parametrize( [ - "productname", - "vid", - "pid", - "count", - "ctrl_data_in_expected", - "data_out_raw", - "temperature_out_expected", - "humidity_out_expected", + "productname", # the faked usb device product name + "vid", # faked vendor ID + "pid", # faked vendor ID + "count", # number of sensors we are expect to be reported + "ctrl_data_in_expected", # the ctrl data we expect to be sent to the (faked) usb device + "data_out_raw", # the bytes that the usb device will return (our encoded temp/RHs needs to be in here) + "temperature_out_expected", # array of temperatures that we are expecting to see decoded. + "humidity_out_expected", # array of humidities that we are expecting to see decoded ], [ [ @@ -122,7 +120,6 @@ def ctrl_transfer_dummy( timeout=None, side_effect=ctrl_transfer_dummy, ) - # print("usbdev.bus=%s" % usbdev.bus) usbdev.read = Mock(return_value=data_out_raw) def match_pids(find_all, idVendor, idProduct): From 3bc3e566e936d21c78fc1b3185935e346df2f792 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Sun, 8 Nov 2020 23:28:37 +0000 Subject: [PATCH 107/159] Add case for generic unmatched sensor (default to 2x temperature) --- tests/test_temper.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/tests/test_temper.py b/tests/test_temper.py index 26993b6..9a71996 100644 --- a/tests/test_temper.py +++ b/tests/test_temper.py @@ -26,6 +26,16 @@ "humidity_out_expected", # array of humidities that we are expecting to see decoded ], [ + [ + "generic_unmatched", # Default is to assume 2 fm75 style temperature sensors + 0x0C45, + 0x7401, + 2, + b"\x01\x80\x33\x01\x00\x00\x00\x00", + b"\x00\x00\x20\x1A\x2B\x33", # 0x201A,0x2B33 converts to 32.1C, 43.2C (fm75) + [32.1, 43.2], + None, + ], [ "TEMPerV1.2", 0x0C45, @@ -76,15 +86,6 @@ [32.1, 43.2, 54.3], None, ], - # [ - # "????", # Has 2 sensors - # 0x0C45, - # 0x7401, - # 1, - # b"\x01\x80\x33\x01\x00\x00\x00\x00", - # b"\x00\x00\x20\x1A\x2B\x33", # 0x201A,0x2B33 converts to 32.1C, 43.2C (fm75) - # [32.1,43.2], - # ], ], ) def test_TemperDevice( From b891058e1649073bd7e77fd09c2e373399c8c731 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Tue, 10 Nov 2020 11:10:39 +0000 Subject: [PATCH 108/159] Add reminder to restart after adding usb rule. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index c4e1162..da287f3 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,8 @@ specific USB devices (with matching VID/PID) by anyone. Install like this: sudo cp etc/99-tempsensor.rules /etc/udev/rules.d/ +Then restart. + To check for success, find the bus and device IDs of the devices like this: pi@raspi-temper1 ~ $ lsusb | grep "0c45:7401" From e1270d40c840b5254d5c575c995ffd787185cb81 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Tue, 10 Nov 2020 11:30:47 +0000 Subject: [PATCH 109/159] Add library based configuration for temper-python --- temperusb/device_library.py | 62 +++++++++++++++++++++++++++++++++++++ temperusb/temper.py | 57 +++++++++++++++++----------------- 2 files changed, 91 insertions(+), 28 deletions(-) create mode 100644 temperusb/device_library.py diff --git a/temperusb/device_library.py b/temperusb/device_library.py new file mode 100644 index 0000000..e08f5ec --- /dev/null +++ b/temperusb/device_library.py @@ -0,0 +1,62 @@ +# encoding: utf-8 +# +# TEMPer USB temperature/humidty sensor device driver settings. +# Handles devices reporting themselves as USB VID/PID 0C45:7401 (mine also says +# RDing TEMPerV1.2). +# +# Copyright 2012-2016 Philipp Adelt and contributors. +# +# This code is licensed under the GNU public license (GPL). See LICENSE.md for +# details. + +from enum import Enum + +class TemperType(Enum): + FM75 = 0 + SI7021 = 1 + +class TemperConfig: + def __init__( + self, + temp_sens_offsets: list, + hum_sens_offsets: list = None, + type: TemperType = TemperType.FM75, + ): + self.temp_sens_offsets = temp_sens_offsets + self.hum_sens_offsets = hum_sens_offsets + self.type = type + + +DEVICE_LIBRARY = { + "TEMPerV1.2": TemperConfig( + temp_sens_offsets=[2], + hum_sens_offsets=None, + type=TemperType.FM75, + ), + "TEMPer1F_V1.3": TemperConfig( + # Has only 1 sensor at offset 4 + temp_sens_offsets=[4], + hum_sens_offsets=None, + type=TemperType.FM75, + ), + "TEMPERHUM1V1.3": TemperConfig( + temp_sens_offsets=[2], + hum_sens_offsets=[4], + type=TemperType.SI7021, + ), + "TEMPer1F_H1_V1.4": TemperConfig( + temp_sens_offsets=[2], + hum_sens_offsets=[4], + type=TemperType.FM75, + ), + "TEMPerNTC1.O": TemperConfig( + temp_sens_offsets=[2, 4, 6], + hum_sens_offsets=None, + type=TemperType.FM75, + ), + "generic_fm75": TemperConfig( + temp_sens_offsets=[2, 4], + hum_sens_offsets=None, + type=TemperType.FM75, + ), +} diff --git a/temperusb/temper.py b/temperusb/temper.py index f4911c0..eb5e872 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -14,6 +14,8 @@ import logging import struct +from .device_library import DEVICE_LIBRARY, TemperType, TemperConfig + VIDPIDS = [ (0x0c45, 0x7401), (0x0c45, 0x7402), @@ -33,6 +35,7 @@ 'ini2': b'\x01\x86\xff\x01\x00\x00\x00\x00', } LOGGER = logging.getLogger(__name__) +CONTRIBUTE_URL = "https://github.com/padelt/temper-python/issues" def readattr(path, name): @@ -92,7 +95,7 @@ def __init__(self, device, sensor_count=1): # Try to trigger a USB permission issue early so the # user is not presented with seemingly unrelated error message. # https://github.com/padelt/temper-python/issues/63 - self.lookup_sensor_count() + productname = self._device.product except ValueError as e: if 'langid' in str(e): raise usb.core.USBError("Error reading langids from device. "+ @@ -100,6 +103,21 @@ def __init__(self, device, sensor_count=1): "node for your TEMPer devices can be read and written by the "+ "user running this code. The temperusb README.md contains hints "+ "about how to fix this. Search for 'USB device permissions'.") + + config = DEVICE_LIBRARY.get(productname) + if config is None: + LOGGER.warning( + "Unrecognised sensor type '%s'. " + "Trying to guess communication format. " + "Please add the configuration to 'device_library.py' " + "and submit to %s to benefit other users." + % (self._device.product, CONTRIBUTE_URL) + ) + config = DEVICE_LIBRARY["generic_fm75"] + self.temp_sens_offsets = config.temp_sens_offsets + self.hum_sens_offsets = config.hum_sens_offsets + self.type = config.type + self.set_sensor_count(self.lookup_sensor_count()) LOGGER.debug('Found device | Bus:{0} Ports:{1} SensorCount:{2}'.format( self._bus, self._ports, self._sensor_count)) @@ -138,38 +156,22 @@ def lookup_offset(self, sensor): """ Lookup the number of sensors on the device by product name. """ - if self._device.product == 'TEMPer1F_V1.3': - # Has only 1 sensor, and it's at offset = 4 - return 4 - - # All others follow this pattern - if not, contribute here: https://github.com/padelt/temper-python/issues - # Sensor 0 = Offset 2 - # Sensor 1 = Offset 4 - return (sensor + 1) * 2 + return self.temp_sens_offsets[sensor] def lookup_humidity_offset(self, sensor): """ - Lookup the offset of the humidity data by product name. + Get the the offset of the humidity data. """ - if self._device.product == 'TEMPer1F_H1_V1.4': - # Has only 1 sensor, and the humidity data is at offset = 4 - return 4 - if self._device.product == 'TEMPERHUM1V1.3': - return 4 - return None + if self.hum_sens_offsets: + return self.hum_sens_offsets[sensor] + else: + return None def lookup_sensor_count(self): """ Lookup the number of sensors on the device by product name. """ - if (self._device.product == 'TEMPerV1.2') or \ - (self._device.product == 'TEMPer1F_V1.3') or \ - (self._device.product == 'TEMPERHUM1V1.3') or \ - (self._device.product == 'TEMPer1F_H1_V1.4'): - return 1 - if (self._device.product == 'TEMPerNTC1.O'): return 3 - # All others are two - if not the case, contribute here: https://github.com/padelt/temper-python/issues - return 2 + return len(self.temp_sens_offsets) def get_sensor_count(self): """ @@ -258,8 +260,7 @@ def get_data(self, reset_device=False): # Get humidity LOGGER.debug("ID='%s'" % self._device.product) - if (self._device.product == 'TEMPer1F_H1_V1.4') or \ - (self._device.product == 'TEMPERHUM1V1.3'): + if self.hum_sens_offsets: humidity_data = temp_data else: humidity_data = None @@ -332,7 +333,7 @@ def get_temperatures(self, sensors=None): # Interpret device response for sensor in _sensors: offset = self.lookup_offset(sensor) - if self._device.product == 'TEMPERHUM1V1.3': #si7021 type device. + if self.type == TemperType.SI7021: celsius = struct.unpack_from('>h', data, offset)[0] * 175.72 / 65536 - 46.85 else: # fm75 (?) type device celsius = struct.unpack_from('>h', data, offset)[0] / 256.0 @@ -382,7 +383,7 @@ def get_humidity(self, sensors=None): offset = self.lookup_humidity_offset(sensor) if offset is None: continue - if self._device.product == 'TEMPERHUM1V1.3': #si7021 type device. + if self.type == TemperType.SI7021: humidity = (struct.unpack_from('>H', data, offset)[0] * 125) / 65536 -6 else: #fm75 (?) type device humidity = (struct.unpack_from('>H', data, offset)[0] * 32) / 1000.0 From 0b36a707581a68b4cd9e004721f5a6a1c68f088c Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Wed, 11 Nov 2020 21:47:16 +0000 Subject: [PATCH 110/159] Add support for TEMPer1V1.4 --- temperusb/device_library.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/temperusb/device_library.py b/temperusb/device_library.py index e08f5ec..6cc5a90 100644 --- a/temperusb/device_library.py +++ b/temperusb/device_library.py @@ -54,6 +54,15 @@ def __init__( hum_sens_offsets=None, type=TemperType.FM75, ), + "TEMPer1V1.4": TemperConfig( + temp_sens_offsets=[2, 4], + hum_sens_offsets=None, + type=TemperType.FM75, + ), + # The config used if the sensor type is not recognised. + # If your sensor is working but showing as unrecognised, please + # add a new entry above based on "generic_fm75" below, and submit + # a PR to https://github.com/padelt/temper-python/pulls "generic_fm75": TemperConfig( temp_sens_offsets=[2, 4], hum_sens_offsets=None, From d405d460ee19a3cdfb9b5e68e68e10f8e87bb80f Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Wed, 11 Nov 2020 21:48:20 +0000 Subject: [PATCH 111/159] Add support for TEMPer1V1.4 --- temperusb/device_library.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/temperusb/device_library.py b/temperusb/device_library.py index 6cc5a90..92029db 100644 --- a/temperusb/device_library.py +++ b/temperusb/device_library.py @@ -55,7 +55,7 @@ def __init__( type=TemperType.FM75, ), "TEMPer1V1.4": TemperConfig( - temp_sens_offsets=[2, 4], + temp_sens_offsets=[2], hum_sens_offsets=None, type=TemperType.FM75, ), From c5c56734611f21e19a3e6aa5248917e7f22fe957 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Mon, 16 Nov 2020 21:55:26 +0000 Subject: [PATCH 112/159] Add missing assert for humidity test --- tests/test_temper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_temper.py b/tests/test_temper.py index 9a71996..808fd0c 100644 --- a/tests/test_temper.py +++ b/tests/test_temper.py @@ -152,4 +152,4 @@ def match_pids(find_all, idVendor, idProduct): if humidity_out_expected: for i, humidity in enumerate(humidity_out_expected): results_h = dev.get_humidity(None) - results_h[i]["humidity_pc"] == pytest.approx(humidity) + assert results_h[i]["humidity_pc"] == pytest.approx(humidity, 0.1) From 3305f97998cae3cc273ddaf0408367b6f337e564 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Mon, 16 Nov 2020 22:01:08 +0000 Subject: [PATCH 113/159] Add missing assert for humidity test --- tests/test_temper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_temper.py b/tests/test_temper.py index 9a71996..808fd0c 100644 --- a/tests/test_temper.py +++ b/tests/test_temper.py @@ -152,4 +152,4 @@ def match_pids(find_all, idVendor, idProduct): if humidity_out_expected: for i, humidity in enumerate(humidity_out_expected): results_h = dev.get_humidity(None) - results_h[i]["humidity_pc"] == pytest.approx(humidity) + assert results_h[i]["humidity_pc"] == pytest.approx(humidity, 0.1) From 06b8a97b49df39b55d732559efc36c2781ab3bd6 Mon Sep 17 00:00:00 2001 From: Philipp Adelt Date: Tue, 1 Dec 2020 09:35:39 +0100 Subject: [PATCH 114/159] Update changelog --- .gitignore | 1 + CHANGELOG.md | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/.gitignore b/.gitignore index ff49171..3db74a3 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,4 @@ nosetests.xml # virtualenv _/ +.idea/ diff --git a/CHANGELOG.md b/CHANGELOG.md index c30f2f5..e44a6d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,22 @@ All notable changes to this project will be documented in this file. No changes yet. +## [next-1.6.0] + +Major changes: +- A new architecture for supporting different device types. +- Tests using pytest + +### Added +- Add support for 3 sensor tempers and TEMPerNTC1.O +- Add support for TemperHUM with si7021 type sensor +- Add support for TEMPer1V1.4 + +### Fixed +- Fixes for the munin plugin +- Report TEMPerV1.2 devices as having a single sensor +- Fix error message about USB permissions to display correctly on Python 3.6 + ## [1.5.3] - 2017-04-03 - Commit ID: 4da8be1 ### Added - Support for 0c45:7402 (RDing TEMPer1F_H1_V1.4) including humidity From fa32ebefce28239d1f577d5757afdc935e82b861 Mon Sep 17 00:00:00 2001 From: Philipp Adelt Date: Tue, 1 Dec 2020 09:36:02 +0100 Subject: [PATCH 115/159] Bump copyright years --- temperusb/device_library.py | 2 +- temperusb/snmp.py | 2 +- temperusb/temper.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/temperusb/device_library.py b/temperusb/device_library.py index 92029db..f5b08f9 100644 --- a/temperusb/device_library.py +++ b/temperusb/device_library.py @@ -4,7 +4,7 @@ # Handles devices reporting themselves as USB VID/PID 0C45:7401 (mine also says # RDing TEMPerV1.2). # -# Copyright 2012-2016 Philipp Adelt and contributors. +# Copyright 2012-2020 Philipp Adelt and contributors. # # This code is licensed under the GNU public license (GPL). See LICENSE.md for # details. diff --git a/temperusb/snmp.py b/temperusb/snmp.py index 2501dfb..a59ab03 100644 --- a/temperusb/snmp.py +++ b/temperusb/snmp.py @@ -3,7 +3,7 @@ # Run snmp_temper.py as a pass-persist module for NetSNMP. # See README.md for instructions. # -# Copyright 2012-2014 Philipp Adelt +# Copyright 2012-2020 Philipp Adelt # # This code is licensed under the GNU public license (GPL). See LICENSE.md for details. diff --git a/temperusb/temper.py b/temperusb/temper.py index eb5e872..28423e6 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -3,7 +3,7 @@ # Handles devices reporting themselves as USB VID/PID 0C45:7401 (mine also says # RDing TEMPerV1.2). # -# Copyright 2012-2016 Philipp Adelt and contributors. +# Copyright 2012-2020 Philipp Adelt and contributors. # # This code is licensed under the GNU public license (GPL). See LICENSE.md for # details. From 9b3ada176b46549aa9c9ddb36db9a9015fed54cd Mon Sep 17 00:00:00 2001 From: Philipp Adelt Date: Tue, 1 Dec 2020 09:36:33 +0100 Subject: [PATCH 116/159] Fix escapes for regex; fix typo --- temperusb/temper.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/temperusb/temper.py b/temperusb/temper.py index 28423e6..5085cd5 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -25,9 +25,9 @@ INTERFACE = 1 CONFIG_NO = 1 TIMEOUT = 5000 -USB_PORTS_STR = '^\s*(\d+)-(\d+(?:\.\d+)*)' +USB_PORTS_STR = r'^\s*(\d+)-(\d+(?:\.\d+)*)' CALIB_LINE_STR = USB_PORTS_STR +\ - '\s*:\s*scale\s*=\s*([+|-]?\d*\.\d+)\s*,\s*offset\s*=\s*([+|-]?\d*\.\d+)' + r'\s*:\s*scale\s*=\s*([+|-]?\d*\.\d+)\s*,\s*offset\s*=\s*([+|-]?\d*\.\d+)' USB_SYS_PREFIX = '/sys/bus/usb/devices/' COMMANDS = { 'temp': b'\x01\x80\x33\x01\x00\x00\x00\x00', @@ -160,7 +160,7 @@ def lookup_offset(self, sensor): def lookup_humidity_offset(self, sensor): """ - Get the the offset of the humidity data. + Get the offset of the humidity data. """ if self.hum_sens_offsets: return self.hum_sens_offsets[sensor] @@ -234,11 +234,11 @@ def get_data(self, reset_device=False): # does not hurt to explicitly claim the interface. usb.util.claim_interface(self._device, INTERFACE) - # Turns out we don't actually need that ctrl_transfer. - # Disabling this reduces number of USBErrors from ~7/30 to 0! - #self._device.ctrl_transfer(bmRequestType=0x21, bRequest=0x09, - # wValue=0x0201, wIndex=0x00, data_or_wLength='\x01\x01', - # timeout=TIMEOUT) + # Turns out we don't actually need that ctrl_transfer. + # Disabling this reduces number of USBErrors from ~7/30 to 0! + #self._device.ctrl_transfer(bmRequestType=0x21, bRequest=0x09, + # wValue=0x0201, wIndex=0x00, data_or_wLength='\x01\x01', + # timeout=TIMEOUT) # Magic: Our TEMPerV1.4 likes to be asked twice. When From ee093400c412ddebec0ba0433f549a570007b3a2 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Tue, 1 Dec 2020 12:48:13 +0000 Subject: [PATCH 117/159] Add davet2001 author. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index da287f3..6d35f8c 100644 --- a/README.md +++ b/README.md @@ -288,3 +288,4 @@ The `snmp_passpersist` mode is Python 2 only because the upstream package is not * Reduced kernel messages, support multiple sensors, and support TEMPer1F_V1.3 by Philip Jay (@ps-jay on Github) * Python 3 compatibility and rewrite of cli.py to use argparse by Will Furnass (@willfurnass on Github) * TEMPerV1.4 support by Christian von Roques (@roques on Github) +* Pytest and archicture improvement by Dave Thompson (@davet2001 on Github). From 38e90a730652edf8ec37a8d3d1f95ad9bae4665a Mon Sep 17 00:00:00 2001 From: Philipp Adelt Date: Tue, 1 Dec 2020 13:51:03 +0100 Subject: [PATCH 118/159] Typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6d35f8c..10ff107 100644 --- a/README.md +++ b/README.md @@ -288,4 +288,4 @@ The `snmp_passpersist` mode is Python 2 only because the upstream package is not * Reduced kernel messages, support multiple sensors, and support TEMPer1F_V1.3 by Philip Jay (@ps-jay on Github) * Python 3 compatibility and rewrite of cli.py to use argparse by Will Furnass (@willfurnass on Github) * TEMPerV1.4 support by Christian von Roques (@roques on Github) -* Pytest and archicture improvement by Dave Thompson (@davet2001 on Github). +* Pytest and architecture improvement by Dave Thompson (@davet2001 on Github). From 728c97740bed34d00e0829cb2d5f8f6f3edf699a Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Tue, 1 Dec 2020 22:43:54 +0000 Subject: [PATCH 119/159] Add experimental support for TEMPERHUM1V1.2 --- temperusb/device_library.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/temperusb/device_library.py b/temperusb/device_library.py index f5b08f9..c6ca090 100644 --- a/temperusb/device_library.py +++ b/temperusb/device_library.py @@ -39,6 +39,11 @@ def __init__( hum_sens_offsets=None, type=TemperType.FM75, ), + "TEMPERHUM1V1.2": TemperConfig( + temp_sens_offsets=[2], + hum_sens_offsets=[4], + type=TemperType.SI7021, + ), "TEMPERHUM1V1.3": TemperConfig( temp_sens_offsets=[2], hum_sens_offsets=[4], From 49eef7c9fef968dca6879922ae78cb248c2150e4 Mon Sep 17 00:00:00 2001 From: CreativeThings Date: Sun, 7 Feb 2021 23:28:53 +0100 Subject: [PATCH 120/159] Added description for using temper-python with MQTT --- README.md | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/README.md b/README.md index 10ff107..80d5bf2 100644 --- a/README.md +++ b/README.md @@ -193,6 +193,74 @@ Try running it manually and mimik a passpersist-request (`->` means you should e If you have a problem with the USB side and want to test SNMP, run the script with `--testmode`. +# Using MQTT +While temper-python does not directly support MQTT, it is fairly straightforeward to push the temperature values collected to a MQTT broker periodically, so they may be integrated in for example Home-Assistant. + +In the below example we will show how to push data to a Mosquitto MQTT broker using a small bash script and a CRON job. The setup was tested with temper-python installed on a RaspberryPi running Rasbian Buster and a Mosquitto MQTT broker installed as part of Home-Assistant. + +In this example we will publish one specific temperature value for one specific device, for example the temperatue in Celcius for device 0 +To test this, type on your console: + + $ /usr/local/bin/temper-poll -c -s 0 + 1.9 + +As you can see because of the "-c" option, temper-poll will present a single temperature value in degrees Celcius. To get degrees Farenheit, use option "-f" +The "-s 0" option makes sure temper-poll only looks at Device #0 + +We now need to install the Mosquitto client on the device where you installed temper-python. This will provide the mosquitto_pub client which we will use to push towards the MQTT broker + + sudo apt-get install mosquitto-clients + +To start pushing a value to your MQTT broker, you also need to know the MQTT server IP adress and optionally a username and password. +A mosquitto_pub command looks something like this: + + /usr/bin/mosquitto_pub -h MQTT_IP -m "Some message" -t MQTT_TOPIC -u MQTT_USERNAME -P MQTT_PASSWORD + +If you need more paramaters, have a look at the output of + + mosquitto_pub --help + +If needed, use the "-d" option for mosquitto_pub, which will print debug output about the connection. A successful connection debug print should look like: + + pi@raspberrypi:~ $ /usr/bin/mosquitto_pub -h 10.0.0.* -m "foobar" -t home-assistant/temper_schuur/temperature -u ****** -P ****** -d + Client mosqpub|2107-raspberryp sending CONNECT + Client mosqpub|2107-raspberryp received CONNACK (0) + Client mosqpub|2107-raspberryp sending PUBLISH (d0, q0, r0, m1, 'home-assistant/temper_schuur/temperature', ... (0 bytes)) + Client mosqpub|2107-raspberryp sending DISCONNECT + +We will now combine the two using a small bash script called "temper-push-mqtt". First create the script, then make it executable. + + sudo touch /usr/local/bin/temper-push-mqtt + sudo chmod a+x /usr/local/bin/temper-push-mqtt + sudo nano /usr/local/bin/temper-push-mqtt + +The script should contain: + + #! /bin/bash + T=$(/usr/local/bin/temper-poll -c -s 0) + /usr/bin/mosquitto_pub -h MQTT_IP -m "${T}" -t MQTT_TOPIC -u MQTT_USER -P MQTT_PASSWORD + +If you need other parameters for temper-poll, replace them here. Also replace all MQTT_* values with proper values for you local setup. +If you are using Home-Assistant you should add a sensor to you setup by defining it in configuration.yaml: + + sensor: + - platform: mqtt + name: "Temperatuur Schuur" + state_topic: "home-assistant/temper_schuur/temperature" + unit_of_measurement: "°C" + +Make sure the state_topic value matches the MQTT_TOPIC value in the temper-push-mqtt script + +Finally, to make sure we get periodic data, we create a cron job to run the script every 5 minutes + + sudo crontab -e + +To start a new crontab, which should contain + + */5 * * * * /usr/local/bin/temper-push-mqtt > /var/log/cron_temper-push-mqtt.log 2>&1 + +The above cronjob will run the temper-push-mqtt script every 5 minutes and will log any issues to a logfile /var/log/cron_temper-push-mqtt.log + # Note on multiple device usage The devices I have seen do not have any way to identify them. The serial number is 0. From 7b958dff63d1689d714555bd60f640fc1b80405f Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Thu, 4 Mar 2021 11:45:29 +0000 Subject: [PATCH 121/159] Add debug support from commandline. --- temperusb/cli.py | 9 +++++++-- temperusb/temper.py | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/temperusb/cli.py b/temperusb/cli.py index a8de695..3b3048c 100644 --- a/temperusb/cli.py +++ b/temperusb/cli.py @@ -24,6 +24,8 @@ def parse_args(): "(multisensor devices only)", default='0') parser.add_argument("-S", "--sensor_count", type=int, help="Override auto-detected number of sensors on the device") + parser.add_argument("-v", "--verbose", action='store_true', + help="Verbose: display all debug information") args = parser.parse_args() return args @@ -32,8 +34,11 @@ def parse_args(): def main(): args = parse_args() quiet = args.celsius or args.fahrenheit or args.humidity - - logging.basicConfig(level = logging.ERROR if quiet else logging.WARNING) + debug = args.verbose + lvl = logging.ERROR if quiet else logging.WARNING + if args.verbose: + lvl = logging.DEBUG + logging.basicConfig(level = lvl) th = TemperHandler() devs = th.get_devices() diff --git a/temperusb/temper.py b/temperusb/temper.py index 5085cd5..1d1adbf 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -410,7 +410,7 @@ def _interrupt_read(self): Read data from device. """ data = self._device.read(ENDPOINT, REQ_INT_LEN, timeout=TIMEOUT) - LOGGER.debug('Read data: %r', data) + LOGGER.debug('Read data: %r', ' '.join('{:02x}'.format(x) for x in data)) return data def close(self): From 1318a23c9e7072423f80c13def76a088b347a6a5 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Thu, 4 Mar 2021 11:47:20 +0000 Subject: [PATCH 122/159] Remove rendundant line --- temperusb/cli.py | 1 - 1 file changed, 1 deletion(-) diff --git a/temperusb/cli.py b/temperusb/cli.py index 3b3048c..daaa866 100644 --- a/temperusb/cli.py +++ b/temperusb/cli.py @@ -34,7 +34,6 @@ def parse_args(): def main(): args = parse_args() quiet = args.celsius or args.fahrenheit or args.humidity - debug = args.verbose lvl = logging.ERROR if quiet else logging.WARNING if args.verbose: lvl = logging.DEBUG From a2c35d50a56fbc2e568a983f75c4ad953972cbd4 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Thu, 4 Mar 2021 12:03:24 +0000 Subject: [PATCH 123/159] Add debug output of unrounded C, RH --- temperusb/temper.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/temperusb/temper.py b/temperusb/temper.py index 1d1adbf..f94cec4 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -339,6 +339,7 @@ def get_temperatures(self, sensors=None): celsius = struct.unpack_from('>h', data, offset)[0] / 256.0 # Apply scaling and offset (if any) celsius = celsius * self._scale + self._offset + LOGGER.debug("T=%.5fC" % celsius) results[sensor] = { 'ports': self.get_ports(), 'bus': self.get_bus(), @@ -387,6 +388,7 @@ def get_humidity(self, sensors=None): humidity = (struct.unpack_from('>H', data, offset)[0] * 125) / 65536 -6 else: #fm75 (?) type device humidity = (struct.unpack_from('>H', data, offset)[0] * 32) / 1000.0 + LOGGER.debug("RH=%.5f%%" % humidity) results[sensor] = { 'ports': self.get_ports(), 'bus': self.get_bus(), From 8f1c494ed6bc20f791583a3ca41a31b34f0269b8 Mon Sep 17 00:00:00 2001 From: endolith Date: Fri, 12 Mar 2021 20:00:19 -0500 Subject: [PATCH 124/159] README: Fix some typos --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 10ff107..ed458df 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ with -p option Found 1 devices Device #0 (bus 1 - port 1.3): 22.4°C 72.3°F -Which tells you there is a a USB hub plugged (internally or externally) on the port 1 of the bus 1 of the host, and your TEMPer device is on the port 3 of that hub. +Which tells you there is a USB hub plugged (internally or externally) on the port 1 of the bus 1 of the host, and your TEMPer device is on the port 3 of that hub. ## Tell kernel to leave TEMPer alone @@ -127,13 +127,13 @@ along with `snmpd`. ## What to add to snmpd.conf To emulate an APC Battery/Internal temperature value, add something like this to snmpd.conf. -The highest of all measured temperatures in degrees celcius as an integer is reported. +The highest of all measured temperatures in degrees Celsius as an integer is reported. pass_persist .1.3.6.1.4.1.318.1.1.1.2.2.2 /usr/local/bin/temper-snmp Alternatively, emulate a Cisco device's temperature information with the following. The first three detected devices will be reported as ..13.1.3.1.3.1, ..3.2 and ..3.3 . -The value is the temperature in degree celcius as an integer. +The value is the temperature in degree Celsius as an integer. pass_persist .1.3.6.1.4.1.9.9.13.1.3 /usr/local/bin/temper-snmp @@ -213,7 +213,7 @@ any plugging on the device. Even then, you are not safe. Sorry. ## Note by GM3D -Since calibration parameters must be set per each device, we need some way to identify them physically. As mentioned above, the serial number for all TEMPer devices is zero, so there is no true way to tell which is which programatically. The USB device number does not work either since it changes every time you reboot the machine or plug/unplug the device. The way that possibly can work is identifying them by the combination of the bus number and the USB port (possibly a chain of ports, if you have hubs in between), which is what I am doing for now. +Since calibration parameters must be set per each device, we need some way to identify them physically. As mentioned above, the serial number for all TEMPer devices is zero, so there is no true way to tell which is which programmatically. The USB device number does not work either since it changes every time you reboot the machine or plug/unplug the device. The way that possibly can work is identifying them by the combination of the bus number and the USB port (possibly a chain of ports, if you have hubs in between), which is what I am doing for now. This information is basically the same with what you can get with `lsusb -t` and is based on the information in the sysfs directory `/sys/bus/usb/devices` (see below). So far I am assuming this scheme is persistent enough for regular use cases, but even the bus number may change in some cases like - for example - if your machine is a tablet like machine and you hotplug it to a keyboard dock with a USB root hub in it. In such case you will need to re-run `lsusb` and adjust the bus-port numbers in the configuration file accordingly. At the moment I have no clue about SNMP OID persistence. From 3d3805905fb780b5c4eadef46878fa3234aaeb2d Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Thu, 13 May 2021 21:34:35 +0100 Subject: [PATCH 125/159] Support TEMPer2V1.4 based on #108 --- temperusb/device_library.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/temperusb/device_library.py b/temperusb/device_library.py index f5b08f9..edab467 100644 --- a/temperusb/device_library.py +++ b/temperusb/device_library.py @@ -59,6 +59,11 @@ def __init__( hum_sens_offsets=None, type=TemperType.FM75, ), + "TEMPer2V1.4": TemperConfig( + temp_sens_offsets=[2], + hum_sens_offsets=None, + type=TemperType.FM75, + ), # The config used if the sensor type is not recognised. # If your sensor is working but showing as unrecognised, please # add a new entry above based on "generic_fm75" below, and submit From 395464ac824918c83f75830cb26e8a11787e3852 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Sun, 4 Jul 2021 21:11:29 +0000 Subject: [PATCH 126/159] Add support for TEMPerV1.4 including tests --- temperusb/device_library.py | 5 +++++ tests/test_temper.py | 14 ++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/temperusb/device_library.py b/temperusb/device_library.py index f5b08f9..964cd22 100644 --- a/temperusb/device_library.py +++ b/temperusb/device_library.py @@ -33,6 +33,11 @@ def __init__( hum_sens_offsets=None, type=TemperType.FM75, ), + "TEMPerV1.4": TemperConfig( + temp_sens_offsets=[2], + hum_sens_offsets=None, + type=TemperType.FM75, + ), "TEMPer1F_V1.3": TemperConfig( # Has only 1 sensor at offset 4 temp_sens_offsets=[4], diff --git a/tests/test_temper.py b/tests/test_temper.py index 808fd0c..a33b8c2 100644 --- a/tests/test_temper.py +++ b/tests/test_temper.py @@ -19,9 +19,9 @@ "productname", # the faked usb device product name "vid", # faked vendor ID "pid", # faked vendor ID - "count", # number of sensors we are expect to be reported + "count", # number of sensors we expect to be reported "ctrl_data_in_expected", # the ctrl data we expect to be sent to the (faked) usb device - "data_out_raw", # the bytes that the usb device will return (our encoded temp/RHs needs to be in here) + "data_out_raw", # the bytes that the usb device will return (our encoded temps/RHs need to be in here) "temperature_out_expected", # array of temperatures that we are expecting to see decoded. "humidity_out_expected", # array of humidities that we are expecting to see decoded ], @@ -46,6 +46,16 @@ [32.1], None, ], + [ + "TEMPerV1.4", + 0x0C45, + 0x7401, + 1, + b"\x01\x80\x33\x01\x00\x00\x00\x00", + b"\x00\x00\x20\x1A", # 0x201A converts to 32.1C (fm75) + [32.1], + None, + ], [ "TEMPer1F_V1.3", # Has 1 sensor at offset 4 0x0C45, From f955e69c185a61f89bbb457f2dbc0a1da8b9216c Mon Sep 17 00:00:00 2001 From: James Stewart Date: Wed, 3 Nov 2021 21:51:52 +1100 Subject: [PATCH 127/159] Release 1.6.0 --- CHANGELOG.md | 5 ++--- setup.py | 3 ++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e44a6d1..0ee86c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,9 +8,8 @@ All notable changes to this project will be documented in this file. No changes yet. -## [next-1.6.0] - -Major changes: +## [1.6.0] +### Added - A new architecture for supporting different device types. - Tests using pytest diff --git a/setup.py b/setup.py index 38fc466..4360b85 100644 --- a/setup.py +++ b/setup.py @@ -6,9 +6,10 @@ author='Philipp Adelt', author_email='autosort-github@philipp.adelt.net ', url='https://github.com/padelt/temper-python', - version='1.5.3', + version='1.6.0', description='Reads temperature from TEMPerV1 devices (USB 0c45:7401)', long_description=open('README.md', encoding='utf-8').read(), + long_description_content_type='text/markdown', packages=['temperusb'], install_requires=[ 'pyusb>=1.0.0rc1', From 7d101acc6eace3cafb4f15b9f81b4c28bb7f3d42 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Thu, 2 Dec 2021 20:01:55 +0000 Subject: [PATCH 128/159] Add support for TEMPer2V1.3 --- temperusb/device_library.py | 5 +++++ tests/test_temper.py | 10 ++++++++++ 2 files changed, 15 insertions(+) diff --git a/temperusb/device_library.py b/temperusb/device_library.py index f5b08f9..1bcaf74 100644 --- a/temperusb/device_library.py +++ b/temperusb/device_library.py @@ -28,6 +28,11 @@ def __init__( DEVICE_LIBRARY = { + "TEMPer2V1.3": TemperConfig( + temp_sens_offsets=[2, 4], + hum_sens_offsets=None, + type=TemperType.FM75, + ), "TEMPerV1.2": TemperConfig( temp_sens_offsets=[2], hum_sens_offsets=None, diff --git a/tests/test_temper.py b/tests/test_temper.py index 808fd0c..a1a7c13 100644 --- a/tests/test_temper.py +++ b/tests/test_temper.py @@ -36,6 +36,16 @@ [32.1, 43.2], None, ], + [ + 'TEMPer2V1.3', + 0x0c45, + 0x7401, + 2, + b"\x01\x80\x33\x01\x00\x00\x00\x00", + b"\x80\x04\x0a\xe0\x15\x00\x1d\x15", # 0x0AE0, 0x1500 converts to 10.9C, 21.0C (fm75) + [10.9, 21.0], + None, + ], [ "TEMPerV1.2", 0x0C45, From 47cfa55abca180e96448829feffd9e9fcea873f7 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Thu, 2 Dec 2021 20:07:30 +0000 Subject: [PATCH 129/159] Print the output for each sensor. --- temperusb/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/temperusb/cli.py b/temperusb/cli.py index a8de695..e2a8942 100644 --- a/temperusb/cli.py +++ b/temperusb/cli.py @@ -99,7 +99,7 @@ def main(): huminfo = huminfo[0:len(output) - 2] output = 'Device #%i%s: %s %s' % (i, portinfo, tempinfo, huminfo) - print(output) + print(output) if __name__ == '__main__': From 3fc73253b355ce8eade0d19be28c725f01494fa9 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Thu, 2 Dec 2021 20:10:59 +0000 Subject: [PATCH 130/159] Revert print change. --- temperusb/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/temperusb/cli.py b/temperusb/cli.py index e2a8942..a8de695 100644 --- a/temperusb/cli.py +++ b/temperusb/cli.py @@ -99,7 +99,7 @@ def main(): huminfo = huminfo[0:len(output) - 2] output = 'Device #%i%s: %s %s' % (i, portinfo, tempinfo, huminfo) - print(output) + print(output) if __name__ == '__main__': From 6f44d58ee010d179d352e79daf58b28de3c76090 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Sat, 4 Dec 2021 08:52:07 +0000 Subject: [PATCH 131/159] Add continuous integration --- .github/workflows/ci.yml | 23 +++++++++++++++++++++++ requirements_test.txt | 3 +++ 2 files changed, 26 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 requirements_test.txt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..598d303 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,23 @@ +name: CI + +on: [push] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.8", "3.9", "3.10"] + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements_test.txt + - name: Run unit tests + run: python -m pytest --import-mode=append tests/ + diff --git a/requirements_test.txt b/requirements_test.txt new file mode 100644 index 0000000..3d2b9eb --- /dev/null +++ b/requirements_test.txt @@ -0,0 +1,3 @@ +# Dependencies for running tests. +pytest +pyusb From f83aa0d20f77c26e37a396daddbfdd3875dd8851 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Sat, 4 Dec 2021 09:26:04 +0000 Subject: [PATCH 132/159] Test more python versions --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 598d303..6132299 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.8", "3.9", "3.10"] + python-version: ["2.7", "3.5", "3.6", "3.7", "3.8", "3.9", "3.10"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} From c334c5d50d70ab6122481a0bb8b077e1c4128ae5 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Sat, 4 Dec 2021 09:29:28 +0000 Subject: [PATCH 133/159] Test back to 3.5 but not 2.7 which fails --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6132299..a39d35b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["2.7", "3.5", "3.6", "3.7", "3.8", "3.9", "3.10"] + python-version: ["3.5", "3.6", "3.7", "3.8", "3.9", "3.10"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} From e49a707fa4fdcfc554aaf402192372ecb2f9b6c1 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Sat, 4 Dec 2021 09:36:11 +0000 Subject: [PATCH 134/159] Update compatible python versions --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 387e7ca..1679628 100644 --- a/README.md +++ b/README.md @@ -343,8 +343,7 @@ as seen on [Google+](https://plus.google.com/105569853186899442987/posts/N9T7xAj # Compatibility with Python versions -This should work on both Python 2 and 3. It was tested with Python 2.7.3 and 3.2.3. -The `snmp_passpersist` mode is Python 2 only because the upstream package is not ready yet. +This should work on Python 3.5 and above. It was tested with Python 3.5, 3.6, 3.7, 3.8, 3.9, 3.10. # Authors From 48cd4086dd18c84300043dd04e3e69598919118c Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Sat, 4 Dec 2021 09:46:00 +0000 Subject: [PATCH 135/159] Add pip as an installation method --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1679628..43f547f 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,11 @@ Under Debian/Ubuntu, treat yourself to some package goodness: # Installation and usage -Clone the repository, cd into its top-level directory, and run +To install using pip, run + + pip install temperusb + +To install from source, clone the repository, cd into its top-level directory, and run sudo python setup.py install From aa8d87c0c2809ed6b678e26d4e034398875193d4 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Sat, 4 Dec 2021 09:52:59 +0000 Subject: [PATCH 136/159] Run CI on PR, not just on push --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a39d35b..bf86d52 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,6 @@ name: CI -on: [push] +on: [push, pull_request] jobs: build: From 308c97b8634fb024fd75f299255b18166a0193cd Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Mon, 18 Apr 2022 18:10:28 +0100 Subject: [PATCH 137/159] Support TEMPerHumiV1.1 Add support for TEMPerHumiV1.1 Thank you @bjornenki --- temperusb/device_library.py | 5 +++++ tests/test_temper.py | 10 ++++++++++ 2 files changed, 15 insertions(+) diff --git a/temperusb/device_library.py b/temperusb/device_library.py index 598065f..ddeb5e3 100644 --- a/temperusb/device_library.py +++ b/temperusb/device_library.py @@ -59,6 +59,11 @@ def __init__( hum_sens_offsets=[4], type=TemperType.SI7021, ), + "TEMPerHumiV1.1": TemperConfig( + temp_sens_offsets=[2], + hum_sens_offsets=[4], + type=TemperType.FM75, + ), "TEMPer1F_H1_V1.4": TemperConfig( temp_sens_offsets=[2], hum_sens_offsets=[4], diff --git a/tests/test_temper.py b/tests/test_temper.py index 8efce6e..9b239aa 100644 --- a/tests/test_temper.py +++ b/tests/test_temper.py @@ -96,6 +96,16 @@ [32.1], [98.7], ], + [ + "TEMPer1F_H1_V1.4", + 0x0C45, + 0x7401, + 1, + b"\x01\x80\x33\x01\x00\x00\x00\x00", + b"\x00\x00\x20\x1A\x0C\x0C", # 0x201A,0x0C0C converts to 32.1C,98.7% (fm75) + [32.1], + [98.7], + ], [ "TEMPerNTC1.O", 0x0C45, From 948e8cf86e478d6be0a7ae58d9ab6de79a44ccdc Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Mon, 18 Apr 2022 18:15:57 +0100 Subject: [PATCH 138/159] Fix test for TEMPerHumiV1.1 --- tests/test_temper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_temper.py b/tests/test_temper.py index 9b239aa..58e5222 100644 --- a/tests/test_temper.py +++ b/tests/test_temper.py @@ -87,7 +87,7 @@ [87.6], ], [ - "TEMPer1F_H1_V1.4", + "TEMPerHumiV1.1", 0x0C45, 0x7401, 1, From 9ce631b4d1f4cfc2a64c97127fb79edcedb741f3 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Thu, 21 Apr 2022 20:09:19 +0000 Subject: [PATCH 139/159] Support TEMPerHumiV1.0 --- temperusb/device_library.py | 5 +++++ tests/test_temper.py | 10 ++++++++++ 2 files changed, 15 insertions(+) diff --git a/temperusb/device_library.py b/temperusb/device_library.py index ddeb5e3..f6969f8 100644 --- a/temperusb/device_library.py +++ b/temperusb/device_library.py @@ -59,6 +59,11 @@ def __init__( hum_sens_offsets=[4], type=TemperType.SI7021, ), + "TEMPerHumiV1.0": TemperConfig( + temp_sens_offsets=[2], + hum_sens_offsets=[4], + type=TemperType.FM75, + ), "TEMPerHumiV1.1": TemperConfig( temp_sens_offsets=[2], hum_sens_offsets=[4], diff --git a/tests/test_temper.py b/tests/test_temper.py index 58e5222..408269a 100644 --- a/tests/test_temper.py +++ b/tests/test_temper.py @@ -86,6 +86,16 @@ [12.3], [87.6], ], + [ + "TEMPerHumiV1.0", + 0x0C45, + 0x7401, + 1, + b"\x01\x80\x33\x01\x00\x00\x00\x00", + b"\x00\x00\x20\x1A\x0C\x0C", # 0x201A,0x0C0C converts to 32.1C,98.7% (fm75) + [32.1], + [98.7], + ], [ "TEMPerHumiV1.1", 0x0C45, From 99835b68c8488a10b5295b822aa345dcd4d16f31 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Tue, 2 May 2023 22:27:11 +0100 Subject: [PATCH 140/159] Drop old python <3.7, add 3.11 to CI tests --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bf86d52..484473f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.5", "3.6", "3.7", "3.8", "3.9", "3.10"] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} From df1fead5b309912765b7e752fd51a06204f59df6 Mon Sep 17 00:00:00 2001 From: Dave T Date: Tue, 2 May 2023 21:12:46 +0000 Subject: [PATCH 141/159] Add experimental support for TEMPer2_V3.7 --- temperusb/device_library.py | 5 +++++ temperusb/temper.py | 1 + 2 files changed, 6 insertions(+) diff --git a/temperusb/device_library.py b/temperusb/device_library.py index f6969f8..2d2bc4c 100644 --- a/temperusb/device_library.py +++ b/temperusb/device_library.py @@ -84,6 +84,11 @@ def __init__( hum_sens_offsets=None, type=TemperType.FM75, ), + "TEMPer2_V3.7": TemperConfig( + temp_sens_offsets=[2, 10], + hum_sens_offsets=None, + type=TemperType.FM75, + ), "TEMPer2V1.4": TemperConfig( temp_sens_offsets=[2], hum_sens_offsets=None, diff --git a/temperusb/temper.py b/temperusb/temper.py index f94cec4..dcfc965 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -19,6 +19,7 @@ VIDPIDS = [ (0x0c45, 0x7401), (0x0c45, 0x7402), + (0x1a86, 0xe025), ] REQ_INT_LEN = 8 ENDPOINT = 0x82 From d4a75051a2adeedd24d19f444ebd1d2c6ad01136 Mon Sep 17 00:00:00 2001 From: Dave T Date: Mon, 8 May 2023 09:57:29 +0000 Subject: [PATCH 142/159] Update permissions rule for new sensor id --- etc/99-tempsensor.rules | 1 + 1 file changed, 1 insertion(+) diff --git a/etc/99-tempsensor.rules b/etc/99-tempsensor.rules index 037a343..6a8986d 100644 --- a/etc/99-tempsensor.rules +++ b/etc/99-tempsensor.rules @@ -1,2 +1,3 @@ SUBSYSTEMS=="usb", ACTION=="add", ATTRS{idVendor}=="0c45", ATTRS{idProduct}=="7401", MODE="666" SUBSYSTEMS=="usb", ACTION=="add", ATTRS{idVendor}=="0c45", ATTRS{idProduct}=="7402", MODE="666" +SUBSYSTEMS=="usb", ACTION=="add", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="e025", MODE="666" From 19dd9f88ebde56da626df0cabe0536e6ec8d87e7 Mon Sep 17 00:00:00 2001 From: Philipp Born Date: Sun, 18 Jun 2023 15:44:05 +0200 Subject: [PATCH 143/159] feat(TemperDevice): add get_product function Signed-off-by: Philipp Born --- temperusb/temper.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/temperusb/temper.py b/temperusb/temper.py index dcfc965..d8c77a6 100644 --- a/temperusb/temper.py +++ b/temperusb/temper.py @@ -194,6 +194,12 @@ def set_sensor_count(self, count): self._sensor_count = int(count) + def get_product(self): + """ + Get device product name. + """ + return self._device.product + def get_ports(self): """ Get device USB ports. From 7043ba9ce0713c7cba34d4df0568d2c464e6ed03 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Tue, 19 Dec 2023 15:16:02 +0000 Subject: [PATCH 144/159] Add python 3.12 to tests --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bf86d52..f32f342 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.5", "3.6", "3.7", "3.8", "3.9", "3.10"] + python-version: ["3.5", "3.6", "3.7", "3.8", "3.9", "3.10", "3.12"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} From 403bb0e82f674523aea7e563f5782790a49d3b8b Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Tue, 19 Dec 2023 15:17:44 +0000 Subject: [PATCH 145/159] Add python 3.11 to tests --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f32f342..26d35e3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.5", "3.6", "3.7", "3.8", "3.9", "3.10", "3.12"] + python-version: ["3.5", "3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} From 63e48f9ef628215b1172a3bc2fac39820c2f0753 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Tue, 19 Dec 2023 15:37:47 +0000 Subject: [PATCH 146/159] Update compatible python versions --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 43f547f..b9901ca 100644 --- a/README.md +++ b/README.md @@ -347,7 +347,7 @@ as seen on [Google+](https://plus.google.com/105569853186899442987/posts/N9T7xAj # Compatibility with Python versions -This should work on Python 3.5 and above. It was tested with Python 3.5, 3.6, 3.7, 3.8, 3.9, 3.10. +This should work on Python 3.7 and above. It was tested with Python 3.7, 3.8, 3.9, 3.10, 3.11, 3.12. # Authors From 8a86428fe46ad3bbae993f38584aa6bcef9121f1 Mon Sep 17 00:00:00 2001 From: Dave T Date: Tue, 2 May 2023 21:44:57 +0000 Subject: [PATCH 147/159] Add support for devcontainer development --- .devcontainer/Dockerfile | 22 +++++++++++++++++ .devcontainer/devcontainer.json | 43 +++++++++++++++++++++++++++++++++ .vscode/settings.json | 10 ++++++++ etc/99-tempsensor.rules | 1 - 4 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 .devcontainer/Dockerfile create mode 100644 .devcontainer/devcontainer.json create mode 100644 .vscode/settings.json diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..5e3b9e7 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,22 @@ +# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.145.1/containers/python-3/.devcontainer/base.Dockerfile + +# [Choice] Python version: 3, 3.9, 3.8, 3.7, 3.6 +ARG VARIANT="3" +FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT} + +# [Option] Install Node.js +ARG INSTALL_NODE="true" +ARG NODE_VERSION="lts/*" +RUN if [ "${INSTALL_NODE}" = "true" ]; then su vscode -c "source /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi + +# [Optional] If your pip requirements rarely change, uncomment this section to add them to the image. +# COPY requirements.txt /tmp/pip-tmp/ +# RUN pip3 --disable-pip-version-check --no-cache-dir install -r /tmp/pip-tmp/requirements.txt \ +# && rm -rf /tmp/pip-tmp + +# [Optional] Uncomment this section to install additional OS packages. +# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ +# && apt-get -y install --no-install-recommends + +# [Optional] Uncomment this line to install global node packages. +# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..581e30a --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,43 @@ +{ + "name": "Python 3", + "build": { + "dockerfile": "Dockerfile", + "context": "..", + "args": { + // Update 'VARIANT' to pick a Python version: 3, 3.6, 3.7, 3.8, 3.9 + "VARIANT": "3", + // Options + "INSTALL_NODE": "false", + "NODE_VERSION": "lts/*" + } + }, + "containerEnv": { + "PYTHONPATH": "." + }, + // Set *default* container specific settings.json values on container create. + "settings": { + "terminal.integrated.shell.linux": "/bin/bash", + "python.pythonPath": "/usr/local/bin/python", + "python.linting.enabled": true, + "python.linting.pylintEnabled": true, + "python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8", + "python.formatting.blackPath": "/usr/local/py-utils/bin/black", + "python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf", + "python.linting.banditPath": "/usr/local/py-utils/bin/bandit", + "python.linting.flake8Path": "/usr/local/py-utils/bin/flake8", + "python.linting.mypyPath": "/usr/local/py-utils/bin/mypy", + "python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle", + "python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle", + "python.linting.pylintPath": "/usr/local/py-utils/bin/pylint" + }, + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "ms-python.python" + ] + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "pip3 install --user -r requirements.txt", + // Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root. + // "remoteUser": "vscode" +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..77694e9 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,10 @@ +{ + "python.envFile": "${workspaceFolder}/.env", + "python.testing.pytestArgs": [ + "tests" + ], + "python.testing.unittestEnabled": false, + "python.testing.nosetestsEnabled": false, + "python.testing.pytestEnabled": true, + "python.pythonPath": "/usr/local/bin/python", +} \ No newline at end of file diff --git a/etc/99-tempsensor.rules b/etc/99-tempsensor.rules index 6a8986d..037a343 100644 --- a/etc/99-tempsensor.rules +++ b/etc/99-tempsensor.rules @@ -1,3 +1,2 @@ SUBSYSTEMS=="usb", ACTION=="add", ATTRS{idVendor}=="0c45", ATTRS{idProduct}=="7401", MODE="666" SUBSYSTEMS=="usb", ACTION=="add", ATTRS{idVendor}=="0c45", ATTRS{idProduct}=="7402", MODE="666" -SUBSYSTEMS=="usb", ACTION=="add", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="e025", MODE="666" From e2a2c4e61a19bab5ddeb2eb54f7ac5b9e08b294e Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Tue, 19 Dec 2023 16:48:29 +0000 Subject: [PATCH 148/159] Update devcontainer spec using newer template --- .devcontainer/devcontainer.json | 55 ++++++++++----------------------- requirements.txt | 1 + requirements_test.txt | 4 +-- tests/__init__.py | 0 4 files changed, 20 insertions(+), 40 deletions(-) create mode 100644 requirements.txt create mode 100644 tests/__init__.py diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 581e30a..2babca0 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,43 +1,22 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/python { "name": "Python 3", - "build": { - "dockerfile": "Dockerfile", - "context": "..", - "args": { - // Update 'VARIANT' to pick a Python version: 3, 3.6, 3.7, 3.8, 3.9 - "VARIANT": "3", - // Options - "INSTALL_NODE": "false", - "NODE_VERSION": "lts/*" - } - }, - "containerEnv": { - "PYTHONPATH": "." - }, - // Set *default* container specific settings.json values on container create. - "settings": { - "terminal.integrated.shell.linux": "/bin/bash", - "python.pythonPath": "/usr/local/bin/python", - "python.linting.enabled": true, - "python.linting.pylintEnabled": true, - "python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8", - "python.formatting.blackPath": "/usr/local/py-utils/bin/black", - "python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf", - "python.linting.banditPath": "/usr/local/py-utils/bin/bandit", - "python.linting.flake8Path": "/usr/local/py-utils/bin/flake8", - "python.linting.mypyPath": "/usr/local/py-utils/bin/mypy", - "python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle", - "python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle", - "python.linting.pylintPath": "/usr/local/py-utils/bin/pylint" - }, - // Add the IDs of extensions you want installed when the container is created. - "extensions": [ - "ms-python.python" - ] + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "image": "mcr.microsoft.com/devcontainers/python:1-3.12-bullseye", + + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + // Use 'forwardPorts' to make a list of ports inside the container available locally. // "forwardPorts": [], + // Use 'postCreateCommand' to run commands after the container is created. - // "postCreateCommand": "pip3 install --user -r requirements.txt", - // Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root. - // "remoteUser": "vscode" -} \ No newline at end of file + "postCreateCommand": "pip3 install --user -r requirements.txt -r requirements_test.txt", + + // Configure tool-specific properties. + "customizations": {} + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..4f21f91 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +pyusb==1.2.1 \ No newline at end of file diff --git a/requirements_test.txt b/requirements_test.txt index 3d2b9eb..448d54c 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,3 +1,3 @@ # Dependencies for running tests. -pytest -pyusb +pytest>=7.4.3 + diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 From eb048f87eae9ede97167ffe055ffd6f773d30a2b Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Tue, 19 Dec 2023 16:49:48 +0000 Subject: [PATCH 149/159] Remove unnecessary dockerfile --- .devcontainer/Dockerfile | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 .devcontainer/Dockerfile diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile deleted file mode 100644 index 5e3b9e7..0000000 --- a/.devcontainer/Dockerfile +++ /dev/null @@ -1,22 +0,0 @@ -# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.145.1/containers/python-3/.devcontainer/base.Dockerfile - -# [Choice] Python version: 3, 3.9, 3.8, 3.7, 3.6 -ARG VARIANT="3" -FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT} - -# [Option] Install Node.js -ARG INSTALL_NODE="true" -ARG NODE_VERSION="lts/*" -RUN if [ "${INSTALL_NODE}" = "true" ]; then su vscode -c "source /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi - -# [Optional] If your pip requirements rarely change, uncomment this section to add them to the image. -# COPY requirements.txt /tmp/pip-tmp/ -# RUN pip3 --disable-pip-version-check --no-cache-dir install -r /tmp/pip-tmp/requirements.txt \ -# && rm -rf /tmp/pip-tmp - -# [Optional] Uncomment this section to install additional OS packages. -# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ -# && apt-get -y install --no-install-recommends - -# [Optional] Uncomment this line to install global node packages. -# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 \ No newline at end of file From 87d89a083c77a45b26c561c1c9112dba47c4ed98 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Tue, 19 Dec 2023 16:51:28 +0000 Subject: [PATCH 150/159] Update 99-tempsensor.rules --- etc/99-tempsensor.rules | 1 + 1 file changed, 1 insertion(+) diff --git a/etc/99-tempsensor.rules b/etc/99-tempsensor.rules index 037a343..6a8986d 100644 --- a/etc/99-tempsensor.rules +++ b/etc/99-tempsensor.rules @@ -1,2 +1,3 @@ SUBSYSTEMS=="usb", ACTION=="add", ATTRS{idVendor}=="0c45", ATTRS{idProduct}=="7401", MODE="666" SUBSYSTEMS=="usb", ACTION=="add", ATTRS{idVendor}=="0c45", ATTRS{idProduct}=="7402", MODE="666" +SUBSYSTEMS=="usb", ACTION=="add", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="e025", MODE="666" From 84ed9dfe1e14c701506d8074c0176e02b4169633 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Tue, 19 Dec 2023 16:54:55 +0000 Subject: [PATCH 151/159] Add pyusb to test requirements. --- requirements.txt | 2 +- requirements_test.txt | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 4f21f91..7e2effa 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -pyusb==1.2.1 \ No newline at end of file +pyusb==1.2.1 diff --git a/requirements_test.txt b/requirements_test.txt index 448d54c..8900955 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,3 +1,4 @@ # Dependencies for running tests. -pytest>=7.4.3 +pyusb==1.2.1 +pytest==7.4.3 From 441ab3104cf0829b74ee2754eb59c9d547f36a56 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Tue, 19 Dec 2023 20:53:35 +0000 Subject: [PATCH 152/159] Modify devcontainer to give access to usb devices --- .devcontainer/Dockerfile | 5 +++++ .devcontainer/devcontainer.json | 16 +++++++++++----- 2 files changed, 16 insertions(+), 5 deletions(-) create mode 100644 .devcontainer/Dockerfile diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..41fcd25 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,5 @@ +FROM mcr.microsoft.com/devcontainers/base:alpine-3.18 + +RUN apk add --no-cache \ + libusb=1.0.26-r2 \ + py3-pip=23.1.2-r0 diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 2babca0..3c46125 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,9 +1,12 @@ // For format details, see https://aka.ms/devcontainer.json. For config options, see the -// README at: https://github.com/devcontainers/templates/tree/main/src/python +// README at: https://github.com/devcontainers/templates/tree/main/src/alpine { - "name": "Python 3", + "name": "Python Alpine", // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile - "image": "mcr.microsoft.com/devcontainers/python:1-3.12-bullseye", + "build": { + "dockerfile": "Dockerfile", + "context": ".." + }, // Features to add to the dev container. More info: https://containers.dev/features. // "features": {}, @@ -14,9 +17,12 @@ // Use 'postCreateCommand' to run commands after the container is created. "postCreateCommand": "pip3 install --user -r requirements.txt -r requirements_test.txt", + // Priviledged mode is necessary to get access to usb + "runArgs": ["--privileged"] + // Configure tool-specific properties. - "customizations": {} + // "customizations": {}, // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. - // "remoteUser": "root" + //"remoteUser": "root" } From e3e10e8148176679f81c562e7bbefb3ebed42843 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Tue, 19 Dec 2023 15:44:41 +0000 Subject: [PATCH 153/159] Update CHANGELOG.md --- CHANGELOG.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ee86c2..07a6bc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,16 @@ All notable changes to this project will be documented in this file. No changes yet. -## [1.6.0] +## [1.6.1] - 2023-12-19 +### Added +- Support for for TEMPer2V1.3 +- Support for TEMPerHumiV1.1 +- Support for TEMPerHumiV1.0 +- Experimental support for TEMPer2_V3.7 +- get_product() function to get product name +- Updates to documentation + +## [1.6.0] - 2021-11-03 ### Added - A new architecture for supporting different device types. - Tests using pytest From 08cafca21b7b209157dce08bdd1c95b0c8a00188 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Tue, 19 Dec 2023 21:05:22 +0000 Subject: [PATCH 154/159] Update version info --- setup.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 4360b85..b1cb6c9 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ author='Philipp Adelt', author_email='autosort-github@philipp.adelt.net ', url='https://github.com/padelt/temper-python', - version='1.6.0', + version='1.6.1', description='Reads temperature from TEMPerV1 devices (USB 0c45:7401)', long_description=open('README.md', encoding='utf-8').read(), long_description_content_type='text/markdown', @@ -24,7 +24,6 @@ 'Development Status :: 5 - Production/Stable', 'Environment :: Console', 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', - 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 3', ], ) From d30bf8b4c072bfc6d66e56a360f11853ccaf6b3d Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Thu, 21 Dec 2023 22:14:35 +0000 Subject: [PATCH 155/159] Add publish script --- .devcontainer/devcontainer.json | 7 ++++++- scripts/publish_to_pypi.sh | 36 +++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100755 scripts/publish_to_pypi.sh diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 3c46125..b8c8fea 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -18,11 +18,16 @@ "postCreateCommand": "pip3 install --user -r requirements.txt -r requirements_test.txt", // Priviledged mode is necessary to get access to usb - "runArgs": ["--privileged"] + "runArgs": ["--privileged"], // Configure tool-specific properties. // "customizations": {}, // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. //"remoteUser": "root" + + // Access local .pypi api keys + "mounts": [ + "source=${localEnv:HOME}${localEnv:USERPROFILE}/.pypirc,target=/home/vscode/.pypirc,type=bind,consistency=cached" + ] } diff --git a/scripts/publish_to_pypi.sh b/scripts/publish_to_pypi.sh new file mode 100755 index 0000000..3d1b860 --- /dev/null +++ b/scripts/publish_to_pypi.sh @@ -0,0 +1,36 @@ +#!/bin/bash +# Script to automate publishing to pypi +# Dave T 2023-12-21 +pypi_config_file=~/.pypirc + +pip install twine + +if [ ! -f dist/*.tar.gz ]; then + echo "No releases found. Please run python3 -m setup.py sdist" + exit +fi +twine check dist/* + +echo "Ready to publish." +echo "Default is publishing to testpypi." +read -r -p "If you are fully ready, please publish to pypi by typing 'thisisnotatest': " response +echo "response=$response" +if [ "$response" = "thisisnotatest" ]; then + repository=pypi +else + repository=testpypi +fi + +if [ -f $pypi_config_file ]; then + echo "Using $pypi_config_file for API keys" +else + echo "$pypi_config_file not found, please paste pypi API token below:" + read twine_api_key + export TWINE_USERNAME=__token__ + export TWINE_PASSWORD=$twine_api_key +fi +echo "Publishing to $repository..." +twine upload --repository $repository dist/* +echo "Publishing complete!" +echo +echo "Don't forget to tag this release!" \ No newline at end of file From 75d356ed4ea348ff1d61365d43dffa6aa6078725 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Thu, 21 Dec 2023 22:20:30 +0000 Subject: [PATCH 156/159] Correct typo --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07a6bc0..155de48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ No changes yet. ## [1.6.1] - 2023-12-19 ### Added -- Support for for TEMPer2V1.3 +- Support for TEMPer2V1.3 - Support for TEMPerHumiV1.1 - Support for TEMPerHumiV1.0 - Experimental support for TEMPer2_V3.7 From 7a26fd21806a434bbe81495173b1d1cfccbe6fd2 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Sun, 18 Aug 2024 21:30:46 +0100 Subject: [PATCH 157/159] Add new sensor TEMPer2_M12_V1.3 --- temperusb/device_library.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/temperusb/device_library.py b/temperusb/device_library.py index 2d2bc4c..d1f0b26 100644 --- a/temperusb/device_library.py +++ b/temperusb/device_library.py @@ -84,6 +84,11 @@ def __init__( hum_sens_offsets=None, type=TemperType.FM75, ), + "TEMPer2_M12_V1.3": TemperConfig( + temp_sens_offsets=[2, 4], + hum_sens_offsets=None, + type=TemperType.FM75, + ), "TEMPer2_V3.7": TemperConfig( temp_sens_offsets=[2, 10], hum_sens_offsets=None, From 7638c7e7bec83f841957bc4875b7567a3002a289 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Thu, 9 Jan 2025 22:58:06 +0000 Subject: [PATCH 158/159] Support python 3.13, drop 3.7 --- .github/workflows/ci.yml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0f32ca9..355f7c5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} diff --git a/README.md b/README.md index b9901ca..ea57c0d 100644 --- a/README.md +++ b/README.md @@ -347,7 +347,7 @@ as seen on [Google+](https://plus.google.com/105569853186899442987/posts/N9T7xAj # Compatibility with Python versions -This should work on Python 3.7 and above. It was tested with Python 3.7, 3.8, 3.9, 3.10, 3.11, 3.12. +This should work on Python 3.8 and above. It was tested with Python 3.8, 3.9, 3.10, 3.11, 3.12, 3.13. # Authors From 6a47e0a09337727c452ef4681e6bd7b295f66695 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Thu, 9 Jan 2025 22:52:13 +0000 Subject: [PATCH 159/159] Add TEMPer2_M12_V1.3 --- temperusb/device_library.py | 5 +++++ tests/test_temper.py | 10 ++++++++++ 2 files changed, 15 insertions(+) diff --git a/temperusb/device_library.py b/temperusb/device_library.py index d1f0b26..90cdebb 100644 --- a/temperusb/device_library.py +++ b/temperusb/device_library.py @@ -99,6 +99,11 @@ def __init__( hum_sens_offsets=None, type=TemperType.FM75, ), + "TEMPer2_M12_V1.3": TemperConfig( + temp_sens_offsets=[2, 4], + hum_sens_offsets=None, + type=TemperType.FM75, + ), # The config used if the sensor type is not recognised. # If your sensor is working but showing as unrecognised, please # add a new entry above based on "generic_fm75" below, and submit diff --git a/tests/test_temper.py b/tests/test_temper.py index 408269a..e0c8a59 100644 --- a/tests/test_temper.py +++ b/tests/test_temper.py @@ -66,6 +66,16 @@ [32.1], None, ], + [ + "TEMPer2_M12_V1.3", + 0x0C45, + 0x7401, + 2, + b"\x01\x80\x33\x01\x00\x00\x00\x00", + b"\x00\x00\x20\x1A\x2B\x33", # 0x201A,0x2B33 converts to 32.1C, 43.2C (fm75) + [32.1, 43.2], + None, + ], [ "TEMPer1F_V1.3", # Has 1 sensor at offset 4 0x0C45,