diff --git a/docs/api.rst b/docs/api.rst index 3972eb8..c925415 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -10,6 +10,6 @@ List devices Device class ------------ -.. autoclass:: hid.device +.. autoclass:: hid.Device :members: :undoc-members: diff --git a/docs/examples.md b/docs/examples.md index c01d434..f911ed1 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -22,34 +22,33 @@ for device_dict in hid.enumerate(): try: print("Opening the device") - h = hid.device() - h.open(0x534C, 0x0001) # TREZOR VendorID/ProductID - - print("Manufacturer: %s" % h.get_manufacturer_string()) - print("Product: %s" % h.get_product_string()) - print("Serial No: %s" % h.get_serial_number_string()) - - # enable non-blocking mode - h.set_nonblocking(1) - - # write some data to the device - print("Write the data") - h.write([0, 63, 35, 35] + [0] * 61) - - # wait - time.sleep(0.05) - - # read back the answer - print("Read the data") - while True: - d = h.read(64) - if d: - print(d) - else: - break - - print("Closing the device") - h.close() + h = hid.Device() + with h.open(0x534C, 0x0001): # TREZOR VendorID/ProductID + + print("Manufacturer: %s" % h.get_manufacturer_string()) + print("Product: %s" % h.get_product_string()) + print("Serial No: %s" % h.get_serial_number_string()) + + # enable non-blocking mode + h.set_nonblocking(1) + + # write some data to the device + print("Write the data") + h.write([0, 63, 35, 35] + [0] * 61) + + # wait + time.sleep(0.05) + + # read back the answer + print("Read the data") + while True: + d = h.read(64) + if d: + print(d) + else: + break + + print("Closing the device") except IOError as ex: print(ex) diff --git a/hid.pyx b/hid.pyx index d683bf4..adeba71 100644 --- a/hid.pyx +++ b/hid.pyx @@ -1,25 +1,31 @@ import sys +import warnings from chid cimport * from libc.stddef cimport wchar_t, size_t from cpython.unicode cimport PyUnicode_FromUnicode + cdef extern from "ctype.h": int wcslen(wchar_t*) + cdef extern from "stdlib.h": void free(void* ptr) void* malloc(size_t size) + cdef extern from *: object PyUnicode_FromWideChar(const wchar_t *w, Py_ssize_t size) Py_ssize_t PyUnicode_AsWideChar(object unicode, wchar_t *w, Py_ssize_t size) + cdef object U(wchar_t *wcs): if wcs == NULL: return '' cdef int n = wcslen(wcs) return PyUnicode_FromWideChar(wcs, n) + def enumerate(int vendor_id=0, int product_id=0): """Return a list of discovered HID devices. @@ -65,10 +71,22 @@ def enumerate(int vendor_id=0, int product_id=0): hid_free_enumeration(info) return res -cdef class device: + +cdef class Device: """Device class. A device instance can be used to read from and write to a HID device. + + Use it like: + + .. code-block:: text + + h = hid.device() + with h.open(0x0000, 0x0000): + h.write(...) + + The context manager will take care of closing the device again, regardless of + exceptions. """ cdef hid_device *_c_hid @@ -82,6 +100,8 @@ cdef class device: :param serial_number: :type serial_number: unicode, optional :raises IOError: + :raises MemoryError: + :raises ValueError: """ cdef wchar_t * cserial_number = NULL cdef int serial_len @@ -101,7 +121,9 @@ cdef class device: if cserial_number != NULL: free(cserial_number) if self._c_hid == NULL: - raise IOError('open failed') + raise IOError("open failed") + + return self def open_path(self, bytes path): """Open connection by path. @@ -113,7 +135,9 @@ cdef class device: cdef char* cbuff = path self._c_hid = hid_open_path(cbuff) if self._c_hid == NULL: - raise IOError('open failed') + raise IOError("open failed") + + return self def close(self): """Close connection. @@ -124,6 +148,28 @@ cdef class device: hid_close(self._c_hid) self._c_hid = NULL + def __enter__(self): + """Opening statement for `with ...` clause. + + Either `open...()` method must be called in the same line! + + The `with ...` context manager will make sure the object is opened and closed + (regardless of error status). + + Specify either `path` or the other values. See :func:`~hid.device.open` and + :func:`~hid.open_path` for usages. + """ + if self._c_hid == NULL: + raise ValueError('note open - make sure to call it like: ' + '`with device.open(): ...`') + + def __exit__(self, exc_type, exc_val, exc_tb): + """Closing statement for `with ...` clause. + + This makes a try-finally clause in the user code unnecessary + """ + self.close() + def write(self, buff): """Accept a list of integers (0-255) and send them to the device. @@ -133,7 +179,7 @@ cdef class device: :rtype: int """ if self._c_hid == NULL: - raise ValueError('not open') + raise ValueError("not open") # convert to bytes if sys.version_info < (3, 0): buff = ''.join(map(chr, buff)) @@ -156,7 +202,7 @@ cdef class device: :rtype: int """ if self._c_hid == NULL: - raise ValueError('not open') + raise ValueError("not open") return hid_set_nonblocking(self._c_hid, v) def read(self, int max_length, int timeout_ms=0): @@ -170,7 +216,7 @@ cdef class device: :rtype: List[int] """ if self._c_hid == NULL: - raise ValueError('not open') + raise ValueError("not open") cdef unsigned char lbuff[16] cdef unsigned char* cbuff cdef size_t c_max_length = max_length @@ -188,7 +234,7 @@ cdef class device: with nogil: n = hid_read(c_hid, cbuff, c_max_length) if n is -1: - raise IOError('read error') + raise IOError("read error") res = [] for i in range(n): res.append(cbuff[i]) @@ -206,11 +252,11 @@ cdef class device: :raises IOError: """ if self._c_hid == NULL: - raise ValueError('not open') + raise ValueError("not open") cdef wchar_t buff[255] cdef int r = hid_get_manufacturer_string(self._c_hid, buff, 255) if r < 0: - raise IOError('get manufacturer string error') + raise IOError("get manufacturer string error") return U(buff) @@ -223,11 +269,11 @@ cdef class device: :raises IOError: """ if self._c_hid == NULL: - raise ValueError('not open') + raise ValueError("not open") cdef wchar_t buff[255] cdef int r = hid_get_product_string(self._c_hid, buff, 255) if r < 0: - raise IOError('get product string error') + raise IOError("get product string error") return U(buff) def get_serial_number_string(self): @@ -239,11 +285,11 @@ cdef class device: :raises IOError: """ if self._c_hid == NULL: - raise ValueError('not open') + raise ValueError("not open") cdef wchar_t buff[255] cdef int r = hid_get_serial_number_string(self._c_hid, buff, 255) if r < 0: - raise IOError('get serial number string error') + raise IOError("get serial number string error") return U(buff) def get_indexed_string(self, index): @@ -255,12 +301,12 @@ cdef class device: :raises IOError: """ if self._c_hid == NULL: - raise ValueError('not open') + raise ValueError("not open") cdef wchar_t buff[255] cdef unsigned char c_index = index cdef int r = hid_get_indexed_string(self._c_hid, c_index, buff, 255) if r < 0: - raise IOError('get indexed string error') + raise IOError("get indexed string error") return U(buff) def send_feature_report(self, buff): @@ -272,7 +318,7 @@ cdef class device: :rtype: int """ if self._c_hid == NULL: - raise ValueError('not open') + raise ValueError("not open") # convert to bytes if sys.version_info < (3, 0): buff = ''.join(map(chr, buff)) @@ -299,7 +345,7 @@ cdef class device: :raises IOError: """ if self._c_hid == NULL: - raise ValueError('not open') + raise ValueError("not open") cdef hid_device * c_hid = self._c_hid cdef unsigned char lbuff[16] cdef unsigned char* cbuff @@ -315,7 +361,7 @@ cdef class device: n = hid_get_feature_report(c_hid, cbuff, c_max_length) res = [] if n < 0: - raise IOError('read error') + raise IOError("read error") for i in range(n): res.append(cbuff[i]) finally: @@ -336,7 +382,7 @@ cdef class device: :raises IOError: """ if self._c_hid == NULL: - raise ValueError('not open') + raise ValueError("not open") cdef hid_device * c_hid = self._c_hid cdef unsigned char lbuff[16] cdef unsigned char* cbuff @@ -352,7 +398,7 @@ cdef class device: n = hid_get_input_report(c_hid, cbuff, c_max_length) res = [] if n < 0: - raise IOError('read error') + raise IOError("read error") for i in range(n): res.append(cbuff[i]) finally: @@ -369,5 +415,9 @@ cdef class device: :raises IOError: """ if self._c_hid == NULL: - raise ValueError('not open') + raise ValueError("not open") return U(hid_error(self._c_hid)) + + +"""Old alias for the `Device` class, use the new name instead!""" +device = Device diff --git a/try.py b/try.py index 8ea672b..0d52eff 100644 --- a/try.py +++ b/try.py @@ -14,42 +14,42 @@ # try opening a device, then perform write and read +h = hid.Device() + try: + print("Opening the device") - h = hid.device() - h.open(0x534C, 0x0001) # TREZOR VendorID/ProductID + with h.open(0x534C, 0x0001): # TREZOR VendorID/ProductID - print("Manufacturer: %s" % h.get_manufacturer_string()) - print("Product: %s" % h.get_product_string()) - print("Serial No: %s" % h.get_serial_number_string()) + print("Manufacturer: %s" % h.get_manufacturer_string()) + print("Product: %s" % h.get_product_string()) + print("Serial No: %s" % h.get_serial_number_string()) - # enable non-blocking mode - h.set_nonblocking(1) + # enable non-blocking mode + h.set_nonblocking(1) - # write some data to the device - print("Write the data") - h.write([0, 63, 35, 35] + [0] * 61) + # write some data to the device + print("Write the data") + h.write([0, 63, 35, 35] + [0] * 61) - # wait - time.sleep(0.05) + # wait + time.sleep(0.05) - # read back the answer - print("Read the data") - while True: - d = h.read(64) - if d: - print(d) - else: - break + # read back the answer + print("Read the data") + while True: + d = h.read(64) + if d: + print(d) + else: + break - print("Closing the device") - h.close() + print("Closing the device") except IOError as ex: + print(ex) print("You probably don't have the hard-coded device.") print("Update the h.open() line in this script with the one") print("from the enumeration list output above and try again.") - -print("Done")