summaryrefslogtreecommitdiffstats
path: root/glucometerutils/support/hiddevice.py
blob: 3cf2e10a68a7a3f8581d365c2fcbac5d4f853e52 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
"""Common routines and base driver class for HID-based meters.
"""

__author__ = 'Diego Elio Pettenò'
__email__ = 'flameeyes@flameeyes.com'
__copyright__ = 'Copyright © 2017, Diego Elio Pettenò'
__license__ = 'MIT'

import logging
import os

try:
    from typing import BinaryIO, Optional, Text
except ImportError:
    pass

from glucometerutils import exceptions


class HidDevice:
    """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.

    Optional parameters available:

      TIMEOUT_MS: (int, default: 0) the read timeout in milliseconds, used
        for hidapi reads only. If < 1, hidapi will be provided no timeout.
    """

    USB_VENDOR_ID = None  # type: int
    USB_PRODUCT_ID = None  # type: int

    TIMEOUT_MS = 0  # type: int

    def __init__(self, device):
        # type: (Optional[Text]) -> None
        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')  # type: Optional[BinaryIO]
        else:
            self.handle_ = None
            logging.info(
                'No --device parameter provided, using hidapi library.')
            try:
                import hid
                self.hidapi_handle_ = hid.device()
                self.hidapi_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):
        # type: (bytes) -> None
        """Writes a report to the HID handle."""

        if self.handle_:
            written = self.handle_.write(report)
        else:
            written = self.hidapi_handle_.write(report)

        if written < 0:
            raise exceptions.CommandError()

    def _read(self, size=64):
        # type: (int) -> bytes
        """Read a report from the HID handle.

        This is important as it handles the one incompatible interface between
        hidraw devices and hidapi handles.
        """
        if self.handle_:
            return bytes(self.handle_.read(size))

        return bytes(self.hidapi_handle_.read(
            size, timeout_ms=self.TIMEOUT_MS))