tom-churchill / temper-windows

MIT License
0 stars 3 forks source link

hello. i made an update to provide some generic discovery of TEMPerX probes if you would like to update #1

Closed ci-vamp closed 1 year ago

ci-vamp commented 1 year ago

hello your code really helped me. i could not get this working on windows through the GUI because it only allowed a single device to be polled at a time. after research i found your library. thank you

i have made some small adjustments to it that allows it to be more dynamic for discovery of TEMPerX probe devices when loading temperatures.

you can see the following changes. these all have defaults so will be backward compatible with previous version:

if you would like please include this in your codebase for release. if not i understand and will use my local version.

thank you again for your help.

"""
Windows USB direct access to TEMPerX probes

adapted from: https://github.com/tom-churchill/temper-windows

Original Author: Tom Churchill
Original License: MIT
"""

import time
import pywinusb.hid as hid

# all TEMPerX devices begin with this prefix
# magic string used as a fallback to load all devices if no HID filter criteria are provided
TEMPer_DEVICE_PREFIX = "TEMPer"

class TemperWindows:
    def __init__(self, product_name = None, vendor_id = None, product_id = None):
        self.read_data = None
        self.read_data_received = False

        self.product_name = product_name
        self.vendor_id = vendor_id
        self.product_id = product_id

    def raw_data_handler(self, data):
        self.read_data = data
        self.read_data_received = True

    @staticmethod
    def convert_data_to_temperature(data):
        return float(data[3] * 256 + data[4]) / 100

    def get_temper_devices(self, product_name = None, vendor_id = None, product_id = None):
        filter_dict = dict()

        if product_name or self.product_name:
            filter_dict = dict(product_name=(
                product_name or self.product_name))
        if vendor_id or self.vendor_id:
            filter_dict = dict(
                **filter_dict, vendor_id=(vendor_id or self.vendor_id))
        if product_id or self.product_id:
            filter_dict = dict(
                **filter_dict, product_id=(product_id or self.product_id))

        if filter_dict:
            return hid.HidDeviceFilter(**filter_dict).get_devices()

        return [dev for dev in hid.find_all_hid_devices() if TEMPer_DEVICE_PREFIX in dev.product_name]

    def get_temperature(self, thermometer_index: int = 0, product_name = None, vendor_id = None, product_id = None):
        self.read_data = None
        self.read_data_received = False

        devices = self.get_temper_devices(
            product_name=product_name, vendor_id=vendor_id, product_id=product_id)

        if thermometer_index > len(devices) - 1:
            return None

        device = devices[thermometer_index]

        try:
            device.open()
            device.set_raw_data_handler(self.raw_data_handler)

            write_data = [0x00, 0x01, 0x80, 0x33, 0x01, 0x00, 0x00, 0x00, 0x00]

            # reset read_data_received before sending our data
            self.read_data_received = False

            device.send_output_report(write_data)

            # wait for read data to be received
            # 0.01 to start with to avoid an unnecessary long wait for the majority of time
            sleep_amount = 0.01
            while self.read_data_received is False:
                time.sleep(sleep_amount)
                sleep_amount = 0.05  # 0.05 after to avoid causing high cpu

            # noinspection PyTypeChecker
            return self.convert_data_to_temperature(self.read_data)
        finally:
            device.close()
ci-vamp commented 1 year ago

@tom-churchill actually i think this will not work because the write_data is to be specific to each device.

may i ask how you discovered what write_data to send to the device you wrote the program for? i am trying to use for TEMPer2 probes with vendor 0x1a86 and product 0xe025

write_data = [0x00, 0x01, 0x80, 0x33, 0x01, 0x00, 0x00, 0x00, 0x00]
ci-vamp commented 1 year ago

update: i have sorted out. this was indeed the correct write data to send to the any TEMPer USB device. however, the issue had to do with my probe having 2 temperature sensors inside.

i have refactored the code to handle both single and multiple sensor readings. it is backwards compatible by making it an option to load both with default of False

this was a tough problem to solve. i had to find a "USB analyzer" tool to view the data packets and work out what was happening with the GUI program compared to the TemperWindows program.

i used this one freeusbanalyzer from HDD Software. i do not know this software so ran it through virus scan and it came clean for the exe download on 12/01/23. i did enable the free trial to use the "HID report" view which helped.

you can see here the HID report

from TEMPer v26 GUI

image

from TemperWindows

image

thank you again for your guidance. i did not know any of this before. hope this helps you or others.

"""
Windows USB direct access to TEMPerX probes

adapted from: https://github.com/tom-churchill/temper-windows

Original Author: Tom Churchill
Original License: MIT
"""

import time
import pywinusb.hid as hid

# all TEMPerX devices begin with this prefix
# magic string used as a fallback to load all devices if no HID filter criteria are provided
_TEMPer_DEVICE_PREFIX = "TEMPer"

# this is the "USB device output report data" used to trigger a temperature data response from the device
# determined by using FreeUsbAnalyzer (a USB traffic analyzer tool from HDD Software)
# https://freeusbanalyzer.com/
# virus scan as of 12/01/23: https://www.virustotal.com/gui/file/3bc7f717f1c83778786fca09b8f50b886596c1e86813be9fb50aa507a26ace4d
_TEMPer_DEVICE_TEMPERATURE_OUTPUT_REPORT_DATA = [
    0x00, 0x01, 0x80, 0x33, 0x01, 0x00, 0x00, 0x00, 0x00]

class HidDeviceReader:
    def __init__(self, device, timeout_seconds=0.1) -> None:
        self.device = device
        self.read_data = []
        self.timeout_seconds = timeout_seconds

    def __raw_data_handler(self, data):
        self.read_data = [*self.read_data, data]

    def load_data(self, output_report_data=[]):
        device_is_active = (self.device.is_plugged()
                            and self.device.is_active())

        assert device_is_active, f"Inactive device {self.device} cannot be opened"

        try:
            if not self.device.is_opened():
                self.device.open()

            self.device.set_raw_data_handler(self.__raw_data_handler)
            self.device.send_output_report(output_report_data)

            load_time = 0
            sleep_time = 0.01
            while load_time <= self.timeout_seconds:
                time.sleep(sleep_time)
                load_time += sleep_time
                # progressively increase
                sleep_time += (2 * sleep_time)
        finally:
            self.device.set_raw_data_handler(None)
            self.device.close()

        return self.read_data

class TemperWindows:
    def __init__(self, product_name=None, vendor_id=None, product_id=None):
        self.product_name = product_name
        self.vendor_id = vendor_id
        self.product_id = product_id

    @staticmethod
    def convert_data_to_temperature(data):
        if not data:
            return None

        return float(data[3] * 256 + data[4]) / 100

    def get_active_temper_devices(self, product_name=None, vendor_id=None, product_id=None):
        filter_dict = dict()

        if product_name or self.product_name:
            filter_dict = dict(product_name=(
                product_name or self.product_name))
        if vendor_id or self.vendor_id:
            filter_dict = dict(
                **filter_dict, vendor_id=(vendor_id or self.vendor_id))
        if product_id or self.product_id:
            filter_dict = dict(
                **filter_dict, product_id=(product_id or self.product_id))

        if filter_dict:
            return hid.HidDeviceFilter(**filter_dict).get_devices()

        return [dev for dev in hid.find_all_hid_devices() if _TEMPer_DEVICE_PREFIX in dev.product_name]

    def get_temperature(self, temp_probe_device_index: int = 0, with_outer_temp=False, product_name=None, vendor_id=None, product_id=None):
        active_devices = self.get_active_temper_devices(
            product_name=product_name, vendor_id=vendor_id, product_id=product_id)

        if temp_probe_device_index > len(active_devices) - 1:
            return (None, None) if with_outer_temp else None

        device = active_devices[temp_probe_device_index]

        device_reader = HidDeviceReader(device, timeout_seconds=0.1)
        temp_data = device_reader.load_data(
            _TEMPer_DEVICE_TEMPERATURE_OUTPUT_REPORT_DATA)

        inner_temp_c = self.convert_data_to_temperature(temp_data[0])
        outer_temp_c = self.convert_data_to_temperature(temp_data[1])

        return (inner_temp_c, outer_temp_c) if with_outer_temp else inner_temp_c