diff --git a/README.md b/README.md index bad6558..9b1df10 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,8 @@ in a quite low level way and in a quite old version: - Please use version 1.2.6.0 of 'usblib-win32`. It's still available on the [old project repo on SourceForge](https://sourceforge.net/projects/libusb-win32/files/libusb-win32-releases/1.2.6.0/) - Then - - Extract `bin/inf-wizard.exe` from the downloaded zip file. Right click and `Run as Administrator` + - Extract the downloaded zip file and go to the directory `libusb-win32-bin-1.2.6.0\bin` + - Right click on `inf-wizard.exe` and `Run as Administrator` - `Next` -> Select `0x0416 0x5020 LS32 Custm HID` (or similar with the same IDs) - `Next` -> `Next` -> Save as dialog `LS32_Sustm_HID.inf` -> `Save` (just to proceed, we don't need that file) - `Install Now...` -> Driver Install Complete -> `OK` @@ -117,6 +118,7 @@ or specific versions from [here](https://www.python.org/downloads/windows/) - `[x]` install Launcher for all Users - `[x]` Add Python X.Y to PATH - Click the `Install Now ...` text message. + - Optionally click on the 'Disable path length limit' text message. This is always a good thing to do. Install needed the Python packages. On some systems (esp. those with Python 2 *and* 3 installed), you have to address Python 3 explicitly by using the diff --git a/lednamebadge.py b/lednamebadge.py index b5cf567..a5fc36d 100755 --- a/lednamebadge.py +++ b/lednamebadge.py @@ -16,22 +16,43 @@ # # Windows install: # ---------------- -## https://sourceforge.net/projects/libusb-win32/ -> -## -> https://kent.dl.sourceforge.net/project/libusb-win32/libusb-win32-releases/1.2.6.0/libusb-win32-bin-1.2.6.0.zip -## cd libusb-win32-bin-1.2.6.0\bin -## download inf-wizard.exe to your desktop. Right click 'Run as Administrator' -# -> Click 0x0416 0x5020 LS32 Custm HID -# -> Next -> Next -> Dokumente LS32_Sustm_HID.inf -> Save -# -> Install Now... -> Driver Install Complete -> OK -# download python from python.org -# [x] install Launcher for all Users -# [x] Add Python 3.7 to PATH -# -> Click the 'Install Now ...' text message. -# -> Optionally click on the 'Disable path length limit' text message. This is always a good thing to do. -# run cmd.exe as Administrator, enter: -# pip install pyusb -# pip install pillow +# For Windows, we need to set up the libusb API for the LED badge device. +# The way described here, uses [libusb-win32](https://github.com/mcuee/libusb-win32/wiki) +# in a quite low level way and in a quite old version: # +# - Please use version 1.2.6.0 of 'usblib-win32`. It's still available on the +# [old project repo on SourceForge](https://sourceforge.net/projects/libusb-win32/files/libusb-win32-releases/1.2.6.0/) +# - Then +# - Extract the downloaded zip file and go to the directory `libusb-win32-bin-1.2.6.0\bin` +# - Right click on `inf-wizard.exe` and `Run as Administrator` +# - `Next` -> Select `0x0416 0x5020 LS32 Custm HID` (or similar with the same IDs) +# - `Next` -> `Next` -> Save as dialog `LS32_Sustm_HID.inf` -> `Save` (just to proceed, we don't need that file) +# - `Install Now...` -> Driver Install Complete -> `OK` +# +# There are other - meanwhile recommended, but untested here - ways to install and setup +# newer versions of `libus-win32`: use +# [Zadig](https://zadig.akeo.ie/) (it is also available from the old libusb-win32 repo on +# [GitHub repo](https://github.com/mcuee/libusb-win32/releases) of newer releases) +# or [libusbK](https://libusbk.sourceforge.net/UsbK3/index.html) +# +# Of course, Python is needed: +# +# - Download latest python from [python.org](https://www.python.org/downloads/), +# or specific versions from [here](https://www.python.org/downloads/windows/) +# - Checkmark the following options +# - `[x]` install Launcher for all Users +# - `[x]` Add Python X.Y to PATH +# - Click the `Install Now ...` text message. +# - Optionally click on the 'Disable path length limit' text message. This is always a good thing to do. +# +# Install needed the Python packages. On some systems (esp. those with Python 2 +# *and* 3 installed), you have to address Python 3 explicitly by using the +# command `pip3` instead of `pip`. +# +# - Run cmd.exe as Administrator, enter: +# +# pip install pyusb +# pip install pillow # # v0.1, 2019-03-05, jw initial draught. HID code is much simpler than expected. @@ -61,8 +82,9 @@ # somehow, but it is acceptable, as we do not need to save every processor cycle, here :) # * Have fun! # v0.14, 2024-06-02, bs extending write methods. -# * Preparation for further write methods, like bluetooth. -# * Automatic or manual device and endpoint selection, See -M and -D (substituting -H) +# * Preparation for further or updated write methods, like bluetooth. +# * Automatic or manual write method and device selection, See -M and -D (substituting -H) resp. +# get_available_methods() and get_available_device_ids(). import argparse @@ -411,19 +433,37 @@ class SimpleTextAndIcons: class WriteMethod: + """Base class for a write method. That is a way to communicate with a device. Think of using different access + libraries or interfaces for communication. Basically it implements the common parts of the functionalities + 'device detection' and 'write data' and defines te interfaces to the user and the concrete write method class. + """ def __init__(self): + """Call it from your concrete class in your __init__ method with! + """ self.devices = {} def __del__(self): self.close() def get_name(self): + """Returns the name of the write method. + This method is to be implemented in your concrete class. It should just return a short and unique name. + """ raise NotImplementedError() def get_description(self): + """Returns a description of the write method. + This method is to be implemented in your concrete class. It should just return a short description + of how the write method communicates with the device (think of libraries and interfaces). + """ raise NotImplementedError() def open(self, device_id): + """Opens the communication channel to the device, similar to open a file. The device id is one of the ids + returned by get_available_devices() or 'auto', which selects just the first device in that dict. + It is the common part of the opening process. The concrete open is done in _open() and is to be implemented + individually. + """ if self.is_ready() and self.is_device_present(): actual_device_id = None if device_id == 'auto': @@ -437,50 +477,94 @@ class WriteMethod: return False def close(self): + """Close the communication channel to the device, similar to closing a file. + This method is to be implemented in your concrete class. It should close and free all handles and resources. + """ raise NotImplementedError() def get_available_devices(self): + """Get all devices available via the concrete write method. It returns a dict with the device ids as keys + and the device descriptions as values. These device ids are used with 'open()' to specify the wanted device. + It the common part of this process. The concrete part is to be implemented in _get_available_devices() + individually. + """ if self.is_ready() and not self.devices: self.devices = self._get_available_devices() return {id: data[0] for id, data in self.devices.items()} def is_device_present(self): + """Returns True if there is one or more devices available via the concrete write method, False otherwise. + """ self.get_available_devices() return self.devices and len(self.devices) > 0 def _open(self, device_id): + """The concrete open action. This method is to be implemented in your concrete class. It shall open + the communication channel to the device with the specified id, which is one of the ids returned by + _get_available_devices(). It shall return True, if successful, otherwise False. The special id 'auto' + is handled in open(). So, this method is called only with device ids from the dict returned by + _get_available_devices(). + """ raise NotImplementedError() def _get_available_devices(self): + """The concrete get-the-list action. This method is to be implemented in your concrete class. It shall + Return a dict with one entry per available device. The key of an entry is the device id, like it will be + used in open() / _open(). The value af an entry is a tuple with any data according to the needs of your + write method. The only defined element is the first one, which shall be a description of the individual + device (e.g. manufacturer or bus number / address). E.g. { '1:5': ('Nametag 5 on bus 1', any, data)} + """ raise NotImplementedError() def is_ready(self): + """Returns True, if the concrete write method is basically ready for operation, otherwise False. + This method is to be implemented in your concrete class. Basically, if the import instruction for the + needed Python modules and potentially a library / module initialization was successful, it shall return True. + This method does not make any statement about concrete devices or device availability. + """ raise NotImplementedError() def has_device(self): + """Returns True, if there is at least one device available with the concrete write method, otherwise False. + This method is to be implemented in your concrete class. + """ raise NotImplementedError() def write(self, buf): + """Call this to write data to the opened device. + The concrete write action is to be implemented in _write().""" self.add_padding(buf, 64) self.check_length(buf, 8192) self._write(buf) @staticmethod - def add_padding(buf, blocksize): - need_padding = len(buf) % blocksize + def add_padding(buf, block_size): + """The given data array will be extended with zeros according to the given block size. SO, afterwards the + length of the array is a multiple of block_size. + """ + need_padding = len(buf) % block_size if need_padding: - buf.extend((0,) * (blocksize - need_padding)) + buf.extend((0,) * (block_size - need_padding)) @staticmethod - def check_length(buf, maxsize): - if len(buf) > maxsize: - print("Writing more than %d bytes damages the display! Nothing written." % (maxsize,)) + def check_length(buf, max_size): + """Just checks the length of the given data array and abort the program execution if it exceeds max_size. + """ + if len(buf) > max_size: + print("Writing more than %d bytes damages the display! Nothing written." % (max_size,)) sys.exit(1) def _write(self, buf): + """Write the given data array to the opened device. + This method is to be implemented in your concrete class. It shall write the given data array to the opened + device. + """ raise NotImplementedError() class WriteLibUsb(WriteMethod): + """Write to a device using pyusb and libusb. The device ids consist of the bus number, the device number on that bus + and the endpoint number. + """ _module_loaded = False try: import usb.core @@ -580,6 +664,9 @@ class WriteLibUsb(WriteMethod): class WriteUsbHidApi(WriteMethod): + """Write to a device connected to USB using pyhidapi and libhidapi. The device ids are simply the device paths as + used by libhidapi. + """ _module_loaded = False try: import pyhidapi @@ -752,6 +839,10 @@ class LedNameBadge: @staticmethod def _find_write_method(method, device_id): + """Here we try to concentrate all special cases, decisions and messages around the manual or automatic + selection of write methods and device. This way it is a bit easier to extend or modify the different + working run time environments (think of operating system, python version, installed libraries and python + modules, ands so on.)""" auto_order_methods = LedNameBadge.get_auto_order_method_list() hidapi = [m for m in auto_order_methods if m.get_name() == 'hidapi'][0] libusb = [m for m in auto_order_methods if m.get_name() == 'libusb'][0]