summaryrefslogtreecommitdiffstats
path: root/glucometerutils/support/hiddevice.py
blob: 41ad17c86e4041c828390ce4ba6fd7d1d567e5cb (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
# -*- coding: utf-8 -*-
#
# SPDX-License-Identifier: MIT
"""Common routines and base driver class for HID-based meters.
"""

import logging
import os
from typing import BinaryIO, Optional, Text, Tuple

from glucometerutils import exceptions


class HidSession:
    """An access class to speak to USB HID based devices.

    This class does not implement a full driver, but rather provide simpler read/write
    methods abstracting the HID library.
    """

    def __init__(self, usb_id, device, timeout_ms=0):
        # type: (Optional[Tuple[int, int]], Optional[Text], int) -> None
        """Construct a new session object.

        Args:
          usb_id: Optional pair of vendor_id and product_id for the session.
            This is required to use the hidapi library.
          device: Optional path to Linux hidraw-style device path. If not provided,
            usb_id needs to be provided instead.
          timeout_ms: Timeout in milliseconds for read operations. Only relevant when
            using hidapi library.
        """

        self._timeout_ms = timeout_ms

        if not usb_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=f"Path {device} does not exist.")

        # 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

                assert usb_id
                vendor_id, product_id = usb_id
                self.hidapi_handle_ = hid.device()
                self.hidapi_handle_.open(vendor_id, product_id)
            except ImportError:
                raise exceptions.ConnectionFailed(
                    message='Missing requied "hidapi" module.'
                )
            except OSError as e:
                raise exceptions.ConnectionFailed(
                    message=f"Unable to connect to meter: {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))