pythonpywinusb

Retrieve data/exception from a handler function


I'm trying to use Python and pywinusb-0.3.2 to read data from a USB weighing scale when a valid request hits the web server (requested_serial_number is coming from the query string).

So I have the following code inside my do_GET:

all_hids = hid.find_all_hid_devices()

if all_hids:
    for index, device in enumerate(all_hids):
        if device.serial_number == requested_serial_number:
            device.open()
            try:
                device.set_raw_data_handler(scales_handler)
                while True:
                    sleep(0.1)
            except GotStableWeightException as e:
                self.do_HEAD(200)
                self.send_body(e.value)

And this is my scales_handler (plus custom exception definition):

def scales_handler(data):
    print("Byte 0: {0}".format(data[0]))
    print("Byte 1: {0}".format(data[1]))
    print("Byte 2: {0}".format(data[2]))
    print("Byte 3: {0}".format(data[3]))
    print("Byte 4: {0}".format(data[4]))
    print("Byte 5: {0}".format(data[5]))
    print("All: {0}".format(data))
    if data[1] == 4:
        raise GotStableWeightException(data[4] + (256 * data[5]))

class GotStableWeightException(Exception):
    def __init__(self, value):
        self.value = value
    def __str__(self):
        return repr(self.value)

So the process is:

  1. Python starts listening for requests on port 8000 using http.server's HTTPServer and BaseHTTPRequestHandler
  2. A request comes in and is routed to my do_GET function.
  3. The do_GET performs some basic validation and then searches for a matching USB HID (my first code sample).
  4. If it finds a match, it opens the device and passes in a data handler.

What I'm trying to achieve:

I want to allow the data handler to keep reading from the scale until data[1] == 4 (which indicates a stable reading from the scale). At that point, I want to retrieve this reading in my do_GET and send it to the waiting HTTP client, close the device and end the function.

However, my GotStableWeightException isn't caught inside my do_GET, I think because it is thrown in a different thread. I'm new to programming with multiple threads, and I'm having trouble working out how I can get the result back to do_GET once it matches the data[1] == 4 condition.

EDIT

What I get:

Here's the output I see in the terminal from scales_handler:

Byte 0: 3
Byte 1: 4
Byte 2: 2
Byte 3: 0
Byte 4: 204
Byte 5: 0
All: [3, 4, 2, 0, 204, 0]
Exception in thread Thread-1:
Traceback (most recent call last):
  File "C:\Python33\lib\threading.py", line 639, in _bootstrap_inner
    self.run()
  File "C:\Python33\lib\site-packages\pywinusb-0.3.2-py3.3.egg\pywinusb\hid\core.py", line 886, in run
    hid_object._process_raw_report(raw_report)
  File "C:\Python33\lib\site-packages\pywinusb-0.3.2-py3.3.egg\pywinusb\hid\helpers.py", line 64, in new_function
    return function_target(*args, **kw)
  File "C:\Python33\lib\site-packages\pywinusb-0.3.2-py3.3.egg\pywinusb\hid\core.py", line 714, in _process_raw_report
    self.__raw_handler(helpers.ReadOnlyList(raw_report))
  File "C:\USB HID Scales\server.py", line 28, in scales_handler
    raise GotStableWeightException(data[4] + (256 * data[5]))
GotStableWeightException: 204

Solution

  • So device is a threaded HidDevice class. As you point out, raising an exception in this threaded object won't get caught by the handler (your do_GET).

    However, I'm wondering if raising an exception is really easiest thing to do (exceptions tend to be reserved for errors and problems). To achieve your aims, is it possible to use a global variable and do something like this;

    global e_value
    e_value = None
    
    def scales_handler(data):
        print("Byte 0: {0}".format(data[0]))
        print("Byte 1: {0}".format(data[1]))
        print("Byte 2: {0}".format(data[2]))
        print("Byte 3: {0}".format(data[3]))
        print("Byte 4: {0}".format(data[4]))
        print("Byte 5: {0}".format(data[5]))
        print("All: {0}".format(data))
        if data[1] == 4:
            e_value = data[4] + (256 * data[5]))
    
    devices = []
    try:
        for index, device in enumerate(all_hids):
            if device.serial_number == requested_serial_number:
                devices.append(device)
                device.open()
                device.set_raw_data_handler(sample_handler)
        while True:  
            time.sleep(0.1)
    finally:
        for device in devices:
            device.close()
    
    if e_value is not None:
        self.do_HEAD(200)
        self.send_body(e_value) 
    

    I can't attest to what your devices are, so I should warn that this isn't thread safe - if more than one device has data[1] == 4 you will only have e_value set by the last device to access it. (Though fixing this would be simple with a global array and counter object).