1

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
Alex
  • 3,029
  • 3
  • 23
  • 46
  • Can't see anything obviously wrong, are you sure that you do raise the exception? What do your print statements in `scales_handler` produce? – danodonovan May 02 '13 at 10:08
  • I've added the output to the original question... so yeah, pretty sure :) – Alex May 02 '13 at 10:31
  • I am no expert at all, so instead of supplying you a fully functional solution, I will instead just ask some additional questions. I hope these questions may help you solve your situation. First point is... why are you using a exception to pass data from one point of the program to another? Although this may work, it does not sound very nice to me. Exceptions are meant for error trapping and recovery, not to send information. When working with threads, I have used message queues. I define a queue containing data blobs which are sent/received. This queue ensures data access is performed reliabl –  May 02 '13 at 10:09

1 Answers1

1

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).

danodonovan
  • 19,636
  • 10
  • 70
  • 78
  • Thanks for this - it sent me down the right track. I went with a slightly different approach though, as I do need to handle multiple threads. I solved the problem by creating a `ScalesDeviceHandler` object, featuring a `scales_handler` method, that sets a member property `weight` when it receives data. I instantiate the object when I intend to read from the scales, and pass that `scales_handler` method to `set_raw_data_handler`, and then poll the object's own weight property to see if it's been set yet by the internal `scales_handler` method. – Alex May 02 '13 at 14:54