From 1d5bade6a90e2a87207d269df8c7bc3f945a9369 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Elio=20Petten=C3=B2?= Date: Sun, 4 Jun 2017 22:57:34 +0100 Subject: Add a ne support class for HID-based devices. I'm currently implementing a separate HID-based driver that does not use the Abbott protocol, so this makes my life easier. --- glucometerutils/exceptions.py | 7 ++++ glucometerutils/support/freestyle.py | 40 ++---------------- glucometerutils/support/hiddevice.py | 79 ++++++++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 36 deletions(-) create mode 100644 glucometerutils/support/hiddevice.py diff --git a/glucometerutils/exceptions.py b/glucometerutils/exceptions.py index 52c4e94..e1d9200 100644 --- a/glucometerutils/exceptions.py +++ b/glucometerutils/exceptions.py @@ -27,6 +27,13 @@ class ConnectionFailed(Error): self.message = message +class CommandError(Error): + """It was not possible to send a command to the device.""" + + def __init__(self, message="Unable to send command to device."): + self.message = message + + class InvalidResponse(Error): """The response received from the meter was not understood""" diff --git a/glucometerutils/support/freestyle.py b/glucometerutils/support/freestyle.py index 737eda1..0e71c03 100644 --- a/glucometerutils/support/freestyle.py +++ b/glucometerutils/support/freestyle.py @@ -13,11 +13,11 @@ __license__ = 'MIT' import csv import datetime -import os.path import re import struct from glucometerutils import exceptions +from glucometerutils.support import hiddevice # Sequence of initialization messages sent to the device to establish HID # protocol. @@ -54,7 +54,7 @@ def _verify_checksum(message, expected_checksum_hex): raise exceptions.InvalidChecksum(expected_checksum, calculated_checksum) -class FreeStyleHidDevice(object): +class FreeStyleHidDevice(hiddevice.HidDevice): """Base class implementing the FreeStyle HID common protocol. This class implements opening, initializing the connection and sending @@ -72,37 +72,6 @@ class FreeStyleHidDevice(object): USB_VENDOR_ID = 0x1a61 # Abbott Diabetes Care USB_PRODUCT_ID = None - def __init__(self, device): - # If we do not know for sure the device ID, rely on the user providing a - # device path. - if self.USB_PRODUCT_ID is None and not device: - raise exceptions.CommandLineError( - '--device parameter is required, should point to /dev/hidraw ' - 'for the meter') - - # If the user passed a device path that does not exist, raise an error. - if device and not os.path.exists(device): - raise exceptions.ConnectionFailed( - message='Path %s does not exist.' % device) - - # If the user passed a device, try opening it. Note that I have had no - # success on actually opening the /dev/hidraw path but that's a - # different problem. - try: - if device: - self.handle_ = open(device, 'w+b') - else: - try: - import hid - except ImportError: - raise exceptions.ConnectionFailed( - message='Missing requied "hidapi" module.') - self.handle_ = hid.device() - self.handle_.open(self.USB_VENDOR_ID, self.USB_PRODUCT_ID) - except OSError: - raise exceptions.ConnectionFailed( - message='Unable to connect to meter.') - def connect(self): """Open connection to the device, starting the knocking sequence.""" for message in _INIT_SEQUENCE: @@ -130,12 +99,11 @@ class FreeStyleHidDevice(object): usb_packet = b'\x00' + _STRUCT_PREAMBLE.pack( message_type, cmdlen) + command + bytes(62 - cmdlen) - if self.handle_.write(usb_packet) < 0: - raise exceptions.InvalidResponse() + self._write(usb_packet) def _read_response(self): """Read the response from the device and extracts it.""" - usb_packet = self.handle_.read(64) + usb_packet = self._read() assert usb_packet message_type = usb_packet[0] diff --git a/glucometerutils/support/hiddevice.py b/glucometerutils/support/hiddevice.py new file mode 100644 index 0000000..a3f2776 --- /dev/null +++ b/glucometerutils/support/hiddevice.py @@ -0,0 +1,79 @@ +"""Common routines and base driver class for HID-based meters. +""" + +__author__ = 'Diego Elio Pettenò' +__email__ = 'flameeyes@flameeyes.eu' +__copyright__ = 'Copyright © 2017, Diego Elio Pettenò' +__license__ = 'MIT' + +import logging +import os + +from glucometerutils import exceptions + + +class HidDevice(object): + """A device speaking USB HID protocol driver base. + + This class does not implement an actual driver by itself, but provides an + easier access to the boilerplate code required for speaking USB HID. + + This helper wraps around an optional dependency on hidapi library: if + present the driver will auto-detect the device, if not the device path needs + to be provided and should point to a device implementing Linux's hidraw + interface. + + The following constants can be set by the actual drivers: + + USB_VENDOR_ID: (int) USB vendor ID for the device. + USB_PRODUCT_ID: (int) USB product ID for the device. + + If the VID/PID pair is not provided, the driver will require a device path + to be used. + """ + + USB_VENDOR_ID = None + USB_PRODUCT_ID = None + + def __init__(self, device): + if None in (self.USB_VENDOR_ID, self.USB_PRODUCT_ID) and not device: + raise exceptions.CommandLineError( + '--device parameter is required, should point to a /dev/hidraw ' + 'device node representing the meter.') + + # If the user passed a device path that does not exist, raise an + # error. This is to avoid writing to a file instead of to a device node. + if device and not os.path.exists(device): + raise exceptions.ConnectionFailed( + message='Path %s does not exist.' % device) + + # If the user passed a device, try opening it. + if device: + self.handle_ = open(device, 'w+b') + else: + logging.info( + 'No --device parameter provided, using hidapi library.') + try: + import hid + self.handle_ = hid.device() + self.handle_.open(self.USB_VENDOR_ID, self.USB_PRODUCT_ID) + except ImportError: + raise exceptions.ConnectionFailed( + message='Missing requied "hidapi" module.') + except OSError as e: + raise exceptions.ConnectionFailed( + message='Unable to connect to meter: %s.' % e) + + def _write(self, report): + """Writes a report to the HID handle.""" + + if self.handle_.write(report) < 0: + raise exceptions.CommandError() + + def _read(self, size=64): + """Read a report from the HID handle. + + This is important as it handles the one incompatible interface between + hidraw devices and hidapi handles. + """ + return bytes(self.handle_.read(size)) -- cgit v1.2.3