Merge remote-tracking branch 'origin/master' into HEAD

master
Francois Cartegnie 1 week ago
commit 100a91eda6
  1. 291
      README.md
  2. 774
      lednamebadge.py
  3. 28
      photos/bitmap_data_descr.puml
  4. 85
      tests/abstract_write_method_test.py
  5. 57
      tests/test_lednamebadge_api.py
  6. 163
      tests/test_lednamebadge_select_method.py

@ -1,4 +1,5 @@
# Led-Badge-44x11
Upload tool for a LED name tag with USB-HID interface
![LED Mini Board](photos/blueBadge.jpg)
@ -48,92 +49,139 @@ access to the badge via USB.
sudo apt install python3-usb python3-pil
#### manually using a python virtual environment
Using a venv will allow to use pip to install dependencies without the danger
that the installed modules will interfere with the system installed ones.
On some systems (esp. those with Python 2 *and* 3 installed), you have to
address Python 3 explicitly by using the commands `python3` / `pip3` instead
of `python` / `pip`.
sudo apt install python3-venv
python3 -m venv ledtag
python -m venv ledtag
source ledtag/bin/activate
pip install pyhidapi pyusb pillow
# this should now work:
# python3 led-badge-11x44.py -m 6 -s 8 "Hello" "World!"
# python led-badge-11x44.py -m 6 -s 8 "Hello" "World!"
if the udev rules are installed, you should be able to access the badge without sudo / root privileges.
To reuse the venv again at a later point:
source ledtag/bin/activate
python3 led-badge-11x44.py …
python led-badge-11x44.py …
### Required dependencies on Fedora Systems
sudo dnf install hidapi python3-hidapi python3-pillow python3-pyusb
### Manual link the HID API lib for some Linuxes
For some distributions there is a discrepancy in where pyhidapi searches for the
lib and where the package hidapi places it. A simple solution is to link the
library to the needed place, e.g.
ln -s /usr/lib/libhidapi-hidraw.so.0 /usr/local/lib/
### Required dependencies on Mac Systems
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`.
sudo easy_install pip
pip install pyhidapi
pip install pillow
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" < /dev/null 2> /dev/null
brew install hidapi
or
Try with `pip3 install pyhidapi`
### Required dependencies on Windows 10
### Required dependencies on Windows 7/10
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:
Download win/inf-wizard.exe to your desktop. Right click 'Run as Administrator'
-> Click 0x0416 0x5020 LS32 Custm HID
-> Next -> Next -> Documents LS32_Sustm_HID.inf -> Save (we don't need that file)
-> Install Now... -> Driver Install Complete -> OK
- Please use version 1.2.6.0 of 'libusb-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`
Download python from python.org
[x] install Launcher for all Users
[x] Add Python 3.7 to PATH
-> Click the 'Install Now ...' text message.
There are other - meanwhile recommended, but untested here - ways to install and setup
newer versions of `libusb-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)
Run cmd.exe as Administrator, enter:
pip install pyusb
pip install pillow
Of course, Python is needed:
### Required dependencies on Windows 7
- 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.
Download win/win32-bin-1.2.6.0.zip to your desktop. Extract.
Find the inf-wizard.exe in the bin folder. Right click 'Run as Administrator'
Then continue as with windows 10 above.
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
#### Examples:
Sudo may or may not be needed for accessing the USB device, depending on your system.
To run these examples on linux, you might have to prepend `sudo` for accessing
the USB device or install a udev rule as stated above. On Windows, maybe you
have to run the `cmd.exe`, where you enter the commands, with `Run as administrator`,
which is similar to the `sudo`on linux.
On some systems (esp. those with Python 2 *and* 3 installed), you have to
address Python 3 explicitly by using `python3` instead of `python`. Run
`python -V` to see which version of python is configured as default.
sudo python3 ./led-badge-11x44.py "Hello World!"
python ./led-badge-11x44.py "Hello World!"
loads the text 'Hello World!' as the first message, and scrolls it from right to left (default scroll mode=0) and speed 4 (default). After an upload the device shows the first message once and returns to the charging screen if still connected to USB. Either pull the plug or press the small button next to the USB connector.
loads the text 'Hello World!' as the first message, and scrolls it from right to left (default scroll mode=0) and speed
4 (default). After an upload the device shows the first message once and returns to the charging screen if still
connected to USB. Either pull the plug or press the small button next to the USB connector.
sudo python3 ./led-badge-11x44.py -m 6 -s 8 "Hello" "World!"
python ./led-badge-11x44.py -m 6 -s 8 "Hello" "World!"
loads the text 'Hello' as message one and 'World!' as message two. Compare the difference in quoting to the previous example. Up to 8 messages can be uploaded. This example uses mode 6, which drops the words with a nice little animation vertically into the display area. Speed is set to maximum here, for smoothness.
loads the text 'Hello' as message one and 'World!' as message two. Compare the difference in quoting to the previous
example. Up to 8 messages can be uploaded. This example uses mode 6, which drops the words with a nice little animation
vertically into the display area. Speed is set to maximum here, for smoothness.
Per default you will only see 'Hello'. To see all messages, press the small button next to the USB connector multiple times, until you briefly see 'M1-8'. Now the display loops through all uploaded messages.
Per default, you will only see 'Hello'. To see all messages, press the small button next to the USB connector multiple
times, until you briefly see 'M1-8'. Now the display loops through all uploaded messages.
sudo python3 ./led-badge-11x44.py -m 5 :gfx/fablabnbg_logo_44x11.png:
python ./led-badge-11x44.py -m 5 :gfx/fablabnbg_logo_44x11.png:
loads a fullscreen still image. Avoid whitespace between colons and name. If you receive a message `ImportError: cannot import name '_imaging'`, then try to update the corresponding package: `sudo pip install -U Pillow`
loads a fullscreen still image. Avoid whitespace between colons and name. If you receive a
message `ImportError: cannot import name '_imaging'`, then try to update the corresponding
package: `sudo pip install -U pillow`
sudo python3 ./led-badge-11x44.py "I:HEART2:my:gfx/fablab_logo_16x11.png:fablab:1:"
python ./led-badge-11x44.py "I:HEART2:my:gfx/fablab_logo_16x11.png:fablab:1:"
uses one builtin and one loaded image. The heart is builtin, and the fablab-logo is loaded from file. The fablab logo is used twice, once before the word 'fablab' and again behind through the reference ':1:' (which references the first loaded image).
uses one builtin and one loaded image. The heart is builtin, and the fablab-logo is loaded from file. The fablab logo is
used twice, once before the word 'fablab' and again behind through the reference ':1:' (which references the first
loaded image).
![LED Mini Board](photos/love_my_fablab.jpg)
sudo python3 ./led-badge-11x44.py -s7 -m0,1 :bicycle: :bicycle_r:
python ./led-badge-11x44.py -s7 -m0,1 :bicycle: :bicycle_r:
shows a bicycle crossing the display in left-to-right and right-to-left (as a second message). If you select the 'M1-8' mode, the bike permanently runs back and forth the display. You may add a short message to one or both, to make it appear the bike is pulling the text around.
shows a bicycle crossing the display in left-to-right and right-to-left (as a second message). If you select the 'M1-8'
mode, the bike permanently runs back and forth the display. You may add a short message to one or both, to make it
appear the bike is pulling the text around.
![LED Mini Board](photos/bicycle.gif)
sudo python3 ./led-badge-11-x44.py -b0,1 -s1 -m5 " :heart2: :HEART2:" " :HEART2:"
python ./led-badge-11-x44.py -b0,1 -s1 -m5 " :heart2: :HEART2:" " :HEART2:"
shows a simple animation of a slowly beating heart on the first message, and a blinking heart on the second message.
@ -159,56 +207,110 @@ plays a doom game video.
![LED Mini Board](photos/doom.gif)
python3 ./led-badge-11x44.py --list-names
python ./led-badge-11x44.py --list-names
prints the list of builtin icon names, including :happy: :happy2: :heart: :HEART: :heart2: :HEART2: :fablab: :bicycle: :bicycle_r: :owncloud: ::
prints the list of builtin icon names, including :happy: :happy2: :heart: :HEART: :heart2: :HEART2: :fablab: :bicycle: :
bicycle_r: :owncloud: ::
python3 ./led-badge-11x44.py --help
python ./led-badge-11x44.py --help
lists all write methods. Does not write anything to the device.
python ./led-badge-11x44.py -M list "dummy message"
lists all devices available with write method 'hidapi'. Does not write anything to the device.
python ./led-badge-11x44.py -M hidapi -D list "dummy message"
programs a specific device with a specific write method.
python ./led-badge-11x44.py -M hidapi -D "3-1:1.0" "Hello World!"
prints some condensed help:
python ./led-badge-11x44.py -h
<pre>
usage: led-badge-11x44.py [-h] [-t TYPE] [-s SPEED] [-B BRIGHTNESS] [-m MODE] [-b BLINK] [-a ANTS] [-l] MESSAGE [MESSAGE ...]
usage: lednamebadge.py [-h] [-t TYPE] [-H HID] [-M METHOD] [-D DEVICE_ID]
[-s SPEED] [-B BRIGHTNESS] [-m MODE] [-b BLINK]
[-a ANTS] [-l]
MESSAGE [MESSAGE ...]
Upload messages or graphics to a 11x44 led badge via USB HID.
Version 0.12 from https://github.com/jnweiger/led-name-badge-ls32
Version 0.14 from https://github.com/jnweiger/led-badge-ls32
-- see there for more examples and for updates.
positional arguments:
MESSAGE Up to 8 message texts with embedded builtin icons or loaded images within colons(:) -- See -l for a list of builtins
MESSAGE Up to 8 message texts with embedded builtin icons or
loaded images within colons(:) -- See -l for a list of
builtins.
options:
-h, --help show this help message and exit
-t TYPE, --type TYPE Type of display: supported values are 12x48 or (default) 11x44. Rename the program to led-badge-12x48, to switch the default.
-t TYPE, --type TYPE Type of display: supported values are 12x48 or
(default) 11x44. Rename the program to led-
badge-12x48, to switch the default.
-H HID, --hid HID Deprecated, only for backwards compatibility, please
use -M! Set to 1 to ensure connect via HID API,
program will then not fallback to usb.core library.
-M METHOD, --method METHOD
Force using the given write method. Use one of 'auto',
'list' or whatever list is printing.
-D DEVICE_ID, --device-id DEVICE_ID
Force using the given device id, if ambiguous. Use
one of 'auto', 'list' or whatever list is printing.
-s SPEED, --speed SPEED
Scroll speed (Range 1..8). Up to 8 comma-separated values
Scroll speed (Range 1..8). Up to 8 comma-separated
values.
-B BRIGHTNESS, --brightness BRIGHTNESS
Brightness for the display in percent: 25, 50, 75, or 100
-m MODE, --mode MODE Up to 8 mode values: Scroll-left(0) -right(1) -up(2) -down(3); still-centered(4); animation(5); drop-down(6); curtain(7);
laser(8); See '--mode-help' for more details.
Brightness for the display in percent: 25, 50, 75, or
100.
-m MODE, --mode MODE Up to 8 mode values: Scroll-left(0) -right(1) -up(2)
-down(3); still-centered(4); animation(5); drop-
down(6); curtain(7); laser(8); See '--mode-help' for
more details.
-b BLINK, --blink BLINK
1: blinking, 0: normal. Up to 8 comma-separated values
-a ANTS, --ants ANTS 1: animated border, 0: normal. Up to 8 comma-separated values
-l, --list-names list named icons to be embedded in messages and exit
1: blinking, 0: normal. Up to 8 comma-separated
values.
-a ANTS, --ants ANTS 1: animated border, 0: normal. Up to 8 comma-separated
values.
-l, --list-names list named icons to be embedded in messages and exit.
Example combining image and text:
sudo ./led-badge-11x44.py "I:HEART2:you"
sudo lednamebadge.py "I:HEART2:you"
</pre>
There are some options defining the default type:
- use lednamebadge.py directly: default type is 11x44
- rename lednamebadge.py to something with '12' and use that: default type is 12x48
- use led-badge-11x44.py: default type is 11x44
- use led-badge-12x48.py: default type is 12x48
For all these options you can override the default type with -t
- use `lednamebadge.py` directly: default type is 11x44
- rename `lednamebadge.py` to something with `12` (e.g. `badge12.py`) and use that: default type is 12x48
- use `led-badge-11x44.py`: default type is 11x44
- use `led-badge-12x48.py`: default type is 12x48
For all these options you can override the default type with command line option `-t`
There are two options to control which device is programmed with which method. At this time there are two write
methods:
one is using the python package pyusb (`libusb`), the other one is using pyhidapi (`hidapi`).
Depending on your execution environment both methods can be used, but sometime one does not work as expected. Then
you can choose the method to be used explicitly with option `-M`. With `-M list` you can print a list of available write
methods. If you have connected multiple devices, you can list up the ids with option `-D list` or give one of the
listed device ids to program that specific device. The default for both options is `auto`, which programs just the first
device found with preferably the write method `hidapi`. The IDs for the same device are different depending on the
write method. Also, they can change between computer startups or reconnects.
### Animations
See the gfx/starfield folder for examples. An animation of N frames is provided as an image N*48 pixels wide, for both 48 and 44 pixel wide devices.
See the gfx/starfield folder for examples. An animation of N frames is provided as an image N*48 pixels wide,
for both 48 and 44 pixel wide devices.
## Usage as module
### Writing to the device
You can use lednamebadge.py as a module in your own content creation code for writing your generated scenes to the device.
You can use lednamebadge.py as a module in your own content creation code for writing your generated scenes to
the device.
- create the header
- append your own content
@ -217,10 +319,10 @@ You can use lednamebadge.py as a module in your own content creation code for wr
The method `header()` takes a number of parameters:
- up to 8 lengths as a tuple of numbers
- each length is the number of byte-columns for the corresponding bitmap data, that is the number of bytes of the
corresponding bitmap data divided by 11 (for the 11x44 devices) respective 12 (for the 12x48-devices), where one
byte is 8 pixels wide.
- arguments comparable to the command line arguments: up to 8 speeds, modes, blink-flags, ants-flags each as tuple of
- each length is the number of byte-columns for the corresponding bitmap data, that is the number of bytes of the
corresponding bitmap data divided by 11 (for the 11x44 devices) respective 12 (for the 12x48-devices), where one
byte is 8 pixels wide.
- arguments comparable to the command line arguments: up to 8 speeds, modes, blink-flags, ants-flags each as tuple of
numbers, and an (optional) brightness as number.
- Optionally, you can give a timestamp as datetime. It is written to the device as part of the header, but not visible
at the devices display.
@ -229,13 +331,13 @@ Your own content has to be a byte array with the bitmap data for all scenes. Of
See the following graphic for better understanding:
![bitmap_data_onebyte.png](photos%2Fbitmap_data_onebyte.png)
![bitmap_data_onebyte.png](photos/bitmap_data_onebyte.png)
![bitmap_data_onescene.png](photos%2Fbitmap_data_onescene.png)
![bitmap_data_onescene.png](photos/bitmap_data_onescene.png)
For a 12x48 device there have to be 12 bytes for each byte-column instead of 11, of course.
![bitmap_data_all.png](photos%2Fbitmap_data_all.png)
![bitmap_data_all.png](photos/bitmap_data_all.png)
Example:
@ -252,12 +354,52 @@ This would be achieved by these calls:
from lednamebadge import LedNameBadge
buf = array('B')
buf.extend(LedNameBadge.header((4, 8), (3, 2), (4,), (0,), (0,1), 50))
buf.extend(LedNameBadge.header((4, 8), (3, 2), (4,), (0,), (0, 1), 50))
buf.extend(scene_one_bytes)
buf.extend(scene_two_bytes)
LedNameBadge.write(buf)
```
#### Specifying a write method or device id
There are two more parameters on the method `write`: the write method and the device id. They work exactly like the
command line option '-M' and '-D'. Both default to `auto`.
```
LedNameBadge.write(buf, 'libusb', '3:10:2')
```
Even with `list` you get the respective list of the available choices printed to stdout, which is less handy,
if used as a module. Therefore, there are 2 methods for retrieving this information as normal data objects:
1. `get_available_methods()` which returns all implemented write methods as a dict with the method names as
the keys and a boolean each as the values. The boolean indicates if the method is basically usable (means the
corresponding import succeeded)
2. `get_available_device_ids(method)` which returns information about all connected / available devices, also as
a dict with the device ids as the keys and a descriptive string each as the values.
```
>>> import lednamebadge
>>> lednamebadge.LedNameBadge.get_available_methods()
{'hidapi': True, 'libusb': True}
>>> lednamebadge.LedNameBadge.get_available_methods('hidapi')
{'3-6:1.0': 'LSicroelectronics - LS32 Custm HID (if=0)', '3-7.3:1.0': 'LSicroelectronics - LS32 Custm HID (if=0)', '3-1:1.0': 'wch.cn - CH583 (if=0)'}
>>> lednamebadge.LedNameBadge.get_available_methods('libusb')
{'3:20:1': 'LSicroelectronics - LS32 Custm HID (bus=3 dev=20 endpoint=1)', '3:21:1': 'LSicroelectronics - LS32 Custm HID (bus=3 dev=21 endpoint=1)', '3:18:2': 'wch.cn - CH583 (bus=3 dev=18 endpoint=2)'}
```
This way you can connect multiple devices to one computer and program them one by another with different calls
to `write`.
If you have mor than one with the same description string, it is difficult distinguish which real device belongs to
which id. Esp. after a reconnect or restart, the ids may change or exchange. If you have different USB buses, connect
only one device to a bus. So you can decide by bus number. Or keep a specific connect order (while the computer is
already running), then you can decide by device number. Maybe the hidapi method is a bit more reliable. You have
to experiment a bit.
### Using the text generation
You can also use the text/icon/graphic generation of this module to get the corresponding byte buffers.
@ -275,10 +417,10 @@ scene_c_bitmap = creator.bitmap("gfx/starfield/starfield_020.png")
```
The resulting bitmaps are tuples with the byte array and the length each. These lengths can be used in header() directly
and the byte arrays can be concatenated to the header. Examle:
and the byte arrays can be concatenated to the header. Example:
```python
from lednamebadge import *
from lednamebadge import *
creator = SimpleTextAndIcons()
scene_x_bitmap = creator.bitmap("Hello :HEART2: World!")
@ -287,7 +429,7 @@ your_own_stuff = create_own_bitmap_data()
lengths = (scene_x_bitmap[1], scene_y_bitmap[1], your_own_stuff.len)
buf = array('B')
buf.extend(LedNameBadge.header(lengths, (3,), (0,), (0,1,0), (0,0,1), 100))
buf.extend(LedNameBadge.header(lengths, (3,), (0,), (0, 1, 0), (0, 0, 1), 100))
buf.extend(scene_x_bitmap[0])
buf.extend(scene_y_bitmap[0])
buf.extend(your_own_stuff.bytes)
@ -307,9 +449,10 @@ Just run `plantuml "*.puml"` from the `photos` directory to regenerate all diagr
Run `python run_tests.py` from the `tests` directory.
## Related References (for USB-Serial devices)
* https://github.com/Caerbannog/led-mini-board
* http://zunkworks.com/projects/programmablelednamebadges/
* https://github.com/DirkReiners/LEDBadgeProgrammer
* https://bitbucket.org/bartj/led/src
* http://www.daveakerman.com/?p=1440
* https://github.com/stoggi/ledbadge
* https://github.com/Caerbannog/led-mini-board
* http://zunkworks.com/projects/programmablelednamebadges/ (Offline since 2019. As of 07-2024, it is still available on https://web.archive.org)
* https://github.com/DirkReiners/LEDBadgeProgrammer
* https://bitbucket.org/bartj/led/src
* http://www.daveakerman.com/?p=1440
* https://github.com/stoggi/ledbadge

@ -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 'libusb-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 `libusb-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.
@ -60,6 +81,10 @@
# * There is some initialization code executed in the classes not needed, if not imported. This is nagging me
# 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 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
@ -71,7 +96,7 @@ from array import array
from datetime import datetime
__version = "0.13"
__version = "0.14"
class SimpleTextAndIcons:
@ -185,7 +210,7 @@ class SimpleTextAndIcons:
0x00, 0x00, 0x00, 0x66, 0x3c, 0xff, 0x3c, 0x66, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
# "äöüÄÖÜߊ"
# "äöüÄÖÜß"
0x00, 0xcc, 0xcc, 0x00, 0x78, 0x0c, 0x7c, 0xcc, 0xcc, 0x76, 0x00,
0x00, 0xc6, 0xc6, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00,
0x00, 0xcc, 0xcc, 0x00, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x76, 0x00,
@ -193,7 +218,6 @@ class SimpleTextAndIcons:
0xc6, 0xc6, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00,
0xc6, 0xc6, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00,
0x00, 0x3c, 0x66, 0x66, 0x66, 0x7c, 0x66, 0x66, 0x66, 0x6c, 0x60,
0x28, 0x10, 0x7c, 0xc6, 0xe0, 0x38, 0x0c, 0xc6, 0xc6, 0x7c, 0x00,
# "àäòöùüèéêëôöûîïÿç"
0x00, 0x60, 0x18, 0x00, 0x78, 0x0c, 0x7c, 0xcc, 0xcc, 0x76, 0x00,
@ -234,7 +258,7 @@ class SimpleTextAndIcons:
u'abcdefghijklmnopqrstuvwxyz' + \
u'0987654321^ !"\0$%&/()=?` °\\}][{' + \
u"@ ~ |<>,;.:-_#'+* " + \
u"äöüÄÖÜߊ" + \
u"äöüÄÖÜß" + \
u"àäòöùüèéêëôöûîïÿç" + \
u"ÀÅÄÉÈÊËÖÔÜÛÙŸ"
@ -244,7 +268,7 @@ class SimpleTextAndIcons:
# print(i, charmap[i], char_offsets[charmap[i]])
bitmap_named = {
'ball': (array('B', (
'ball': (array('B', (
0b00000000,
0b00000000,
0b00111100,
@ -257,7 +281,7 @@ class SimpleTextAndIcons:
0b00111100,
0b00000000
)), 1, '\x1e'),
'happy': (array('B', (
'happy': (array('B', (
0b00000000, # 0x00
0b00000000, # 0x00
0b00111100, # 0x3c
@ -270,32 +294,31 @@ class SimpleTextAndIcons:
0b00111100, # 0x3c
0b00000000 # 0x00
)), 1, '\x1d'),
'happy2': (array('B', (0x00, 0x08, 0x14, 0x08, 0x01, 0x00, 0x00, 0x61, 0x30, 0x1c, 0x07,
0x00, 0x20, 0x50, 0x20, 0x00, 0x80, 0x80, 0x86, 0x0c, 0x38, 0xe0)), 2, '\x1c'),
'heart': (array('B', (0x00, 0x00, 0x6c, 0x92, 0x82, 0x82, 0x44, 0x28, 0x10, 0x00, 0x00)), 1, '\x1b'),
'HEART': (array('B', (0x00, 0x00, 0x6c, 0xfe, 0xfe, 0xfe, 0x7c, 0x38, 0x10, 0x00, 0x00)), 1, '\x1a'),
'heart2': (array('B', (0x00, 0x0c, 0x12, 0x21, 0x20, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01,
0x00, 0x60, 0x90, 0x08, 0x08, 0x08, 0x10, 0x20, 0x40, 0x80, 0x00)), 2, '\x19'),
'HEART2': (array('B', (0x00, 0x0c, 0x1e, 0x3f, 0x3f, 0x3f, 0x1f, 0x0f, 0x07, 0x03, 0x01,
0x00, 0x60, 0xf0, 0xf8, 0xf8, 0xf8, 0xf0, 0xe0, 0xc0, 0x80, 0x00)), 2, '\x18'),
'fablab': (array('B', (0x07, 0x0e, 0x1b, 0x03, 0x21, 0x2c, 0x2e, 0x26, 0x14, 0x1c, 0x06,
0x80, 0x60, 0x30, 0x80, 0x88, 0x38, 0xe8, 0xc8, 0x10, 0x30, 0xc0)), 2, '\x17'),
'bicycle': (array('B', (0x01, 0x02, 0x00, 0x01, 0x07, 0x09, 0x12, 0x12, 0x10, 0x08, 0x07,
0x00, 0x87, 0x81, 0x5f, 0x22, 0x94, 0x49, 0x5f, 0x49, 0x80, 0x00,
0x00, 0x80, 0x00, 0x80, 0x70, 0xc8, 0x24, 0xe4, 0x04, 0x88, 0x70)), 3, '\x16'),
'happy2': (array('B', (0x00, 0x08, 0x14, 0x08, 0x01, 0x00, 0x00, 0x61, 0x30, 0x1c, 0x07,
0x00, 0x20, 0x50, 0x20, 0x00, 0x80, 0x80, 0x86, 0x0c, 0x38, 0xe0)), 2, '\x1c'),
'heart': (array('B', (0x00, 0x00, 0x6c, 0x92, 0x82, 0x82, 0x44, 0x28, 0x10, 0x00, 0x00)), 1, '\x1b'),
'HEART': (array('B', (0x00, 0x00, 0x6c, 0xfe, 0xfe, 0xfe, 0x7c, 0x38, 0x10, 0x00, 0x00)), 1, '\x1a'),
'heart2': (array('B', (0x00, 0x0c, 0x12, 0x21, 0x20, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01,
0x00, 0x60, 0x90, 0x08, 0x08, 0x08, 0x10, 0x20, 0x40, 0x80, 0x00)), 2, '\x19'),
'HEART2': (array('B', (0x00, 0x0c, 0x1e, 0x3f, 0x3f, 0x3f, 0x1f, 0x0f, 0x07, 0x03, 0x01,
0x00, 0x60, 0xf0, 0xf8, 0xf8, 0xf8, 0xf0, 0xe0, 0xc0, 0x80, 0x00)), 2, '\x18'),
'fablab': (array('B', (0x07, 0x0e, 0x1b, 0x03, 0x21, 0x2c, 0x2e, 0x26, 0x14, 0x1c, 0x06,
0x80, 0x60, 0x30, 0x80, 0x88, 0x38, 0xe8, 0xc8, 0x10, 0x30, 0xc0)), 2, '\x17'),
'bicycle': (array('B', (0x01, 0x02, 0x00, 0x01, 0x07, 0x09, 0x12, 0x12, 0x10, 0x08, 0x07,
0x00, 0x87, 0x81, 0x5f, 0x22, 0x94, 0x49, 0x5f, 0x49, 0x80, 0x00,
0x00, 0x80, 0x00, 0x80, 0x70, 0xc8, 0x24, 0xe4, 0x04, 0x88, 0x70)), 3, '\x16'),
'bicycle_r': (array('B', (0x00, 0x00, 0x00, 0x00, 0x07, 0x09, 0x12, 0x13, 0x10, 0x08, 0x07,
0x00, 0xf0, 0x40, 0xfd, 0x22, 0x94, 0x49, 0xfd, 0x49, 0x80, 0x00,
0x40, 0xa0, 0x80, 0x40, 0x70, 0xc8, 0x24, 0x24, 0x04, 0x88, 0x70)), 3, '\x15'),
'owncloud': (array('B', (0x00, 0x01, 0x02, 0x03, 0x06, 0x0c, 0x1a, 0x13, 0x11, 0x19, 0x0f,
0x78, 0xcc, 0x87, 0xfc, 0x42, 0x81, 0x81, 0x81, 0x81, 0x43, 0xbd,
0x00, 0x00, 0x00, 0x80, 0x80, 0xe0, 0x30, 0x10, 0x28, 0x28, 0xd0)), 3, '\x14'),
'owncloud': (array('B', (0x00, 0x01, 0x02, 0x03, 0x06, 0x0c, 0x1a, 0x13, 0x11, 0x19, 0x0f,
0x78, 0xcc, 0x87, 0xfc, 0x42, 0x81, 0x81, 0x81, 0x81, 0x43, 0xbd,
0x00, 0x00, 0x00, 0x80, 0x80, 0xe0, 0x30, 0x10, 0x28, 0x28, 0xd0)), 3, '\x14'),
}
bitmap_builtin = {}
for i in bitmap_named:
bitmap_builtin[bitmap_named[i][2]] = bitmap_named[i]
def __init__(self):
self.bitmap_preloaded = [([], 0)]
self.bitmaps_preloaded_unused = False
@ -305,17 +328,14 @@ class SimpleTextAndIcons:
self.bitmap_preloaded.append(SimpleTextAndIcons.bitmap_img(filename))
self.bitmaps_preloaded_unused = True
def are_preloaded_unused(self):
"""Still used by main, but deprecated. PLease use ":"-notation for bitmap() / bitmap_text()"""
return self.bitmaps_preloaded_unused == True
return self.bitmaps_preloaded_unused is True
@staticmethod
def _get_named_bitmaps_keys():
return SimpleTextAndIcons.bitmap_named.keys()
def bitmap_char(self, ch):
"""Returns a tuple of 11 bytes, it is the bitmap data of given character.
Example: ch = '_' returns (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255).
@ -329,8 +349,7 @@ class SimpleTextAndIcons:
return self.bitmap_preloaded[ord(ch)]
o = SimpleTextAndIcons.char_offsets[ch]
return (SimpleTextAndIcons.font_11x44[o:o + 11], 1)
return SimpleTextAndIcons.font_11x44[o:o + 11], 1
def bitmap_text(self, text):
"""Returns a tuple of (buffer, length_in_byte_columns_aka_chars)
@ -360,8 +379,7 @@ class SimpleTextAndIcons:
(b, n) = self.bitmap_char(c)
buf.extend(b)
cols += n
return (buf, cols)
return buf, cols
@staticmethod
def bitmap_img(file):
@ -370,7 +388,13 @@ class SimpleTextAndIcons:
grayscale by arithmetic mean. Threshold for an active led is then > 127.
If the width is not a multiple on 8 it will be padded with empty pixel-columns.
"""
from PIL import Image
try:
from PIL import Image
except:
print("If you like to use images, the module pillow is needed. Try:")
print("$ pip install pillow")
LedNameBadge._print_common_install_hints('pillow', 'python3-pillow')
sys.exit(1)
im = Image.open(file)
print("fetching bitmap from file %s -> (%d x %d)" % (file, im.width, im.height))
@ -397,8 +421,7 @@ class SimpleTextAndIcons:
byte_val += bit_val
buf.append(byte_val)
im.close()
return (buf, cols)
return buf, cols
def bitmap(self, arg):
"""If arg is a valid and existing path name, we load it as an image.
@ -409,58 +432,314 @@ class SimpleTextAndIcons:
return self.bitmap_text(arg)
class LedNameBadge:
_protocol_header_template = (
0x77, 0x61, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
)
_have_pyhidapi = False
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':
actual_device_id = sorted(self.devices.keys())[0]
else:
if device_id in self.devices.keys():
actual_device_id = device_id
if actual_device_id:
return self._open(actual_device_id)
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 {did: data[0] for did, 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, 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,) * (block_size - need_padding))
@staticmethod
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:
if sys.version_info[0] < 3:
print("Preferring Pyusb over Pyhidapi with Python 2.x")
raise Exception("Prefer usb.core with python-2.x because of https://github.com/jnweiger/led-badge-ls32/issues/9")
import pyhidapi
pyhidapi.hid_init()
_have_pyhidapi = True
print("Pyhidapi detected")
import usb.core
import usb.util
_module_loaded = True
print("Module usb.core detected")
except:
pass
def __init__(self):
WriteMethod.__init__(self)
self.description = None
self.dev = None
self.endpoint = None
def get_name(self):
return 'libusb'
def get_description(self):
return 'Program a device connected via USB using the pyusb package and libusb.'
def _open(self, device_id):
self.description = self.devices[device_id][0]
self.dev = self.devices[device_id][1]
self.endpoint = self.devices[device_id][2]
print("Libusb device initialized")
return True
def close(self):
if self.dev:
self.dev.reset()
WriteLibUsb.usb.util.dispose_resources(self.dev)
self.description = None
self.dev = None
self.endpoint = None
def _get_available_devices(self):
devs = WriteLibUsb.usb.core.find(idVendor=0x0416, idProduct=0x5020, find_all=True)
devices = {}
for d in devs:
try:
# win32: NotImplementedError: is_kernel_driver_active
if d.is_kernel_driver_active(0):
d.detach_kernel_driver(0)
except:
pass
try:
d.set_configuration()
except WriteLibUsb.usb.core.USBError:
# TODO: use all the nice output in _find_write_method(), somehow.
print("No read access to device list!")
LedNameBadge._print_sudo_hints()
sys.exit(1)
cfg = d.get_active_configuration()[0, 0]
eps = WriteLibUsb.usb.util.find_descriptor(
cfg,
find_all=True,
custom_match=lambda e: WriteLibUsb.usb.util.endpoint_direction(e.bEndpointAddress) == WriteLibUsb.usb.util.ENDPOINT_OUT)
for ep in eps:
did = "%d:%d:%d" % (d.bus, d.address, ep.bEndpointAddress)
descr = ("%s - %s (bus=%d dev=%d endpoint=%d)" %
(d.manufacturer, d.product, d.bus, d.address, ep.bEndpointAddress))
devices[did] = (descr, d, ep)
return devices
def is_ready(self):
return WriteLibUsb._module_loaded
def has_device(self):
return self.dev is not None
def _write(self, buf):
if not self.dev:
return
try:
import usb.core
print("Pyusb detected")
# win32: NotImplementedError: is_kernel_driver_active
if self.dev.is_kernel_driver_active(0):
self.dev.detach_kernel_driver(0)
except:
print("ERROR: Need the pyhidapi or usb.core module.")
if sys.platform == "darwin":
print("""Please try
pip3 install pyhidapi
pip install pyhidapi
brew install hidapi
""")
elif sys.platform == "linux":
print("""Please try
sudo pip3 install pyhidapi
sudo pip install pyhidapi
sudo apt-get install libhidapi-hidraw0
sudo ln -s /usr/lib/x86_64-linux-gnu/libhidapi-hidraw.so.0 /usr/local/lib/
or
sudo apt-get install python3-usb
""")
else: # windows?
print("""Please try with Linux or MacOS or help us implement support for """ + sys.platform)
pass
try:
self.dev.set_configuration()
except WriteLibUsb.usb.core.USBError:
# TODO: use all the nice output in _find_write_method(), somehow.
print("No write access to device!")
LedNameBadge._print_sudo_hints()
sys.exit(1)
print("Write using %s via libusb" % (self.description,))
for i in range(int(len(buf) / 64)):
time.sleep(0.1)
self.endpoint.write(buf[i * 64:i * 64 + 64])
@staticmethod
def _prepare_iterable(iterable, min_, max_):
try:
iterable = [min(max(x, min_), max_) for x in iterable]
iterable = tuple(iterable) + (iterable[-1],) * (8 - len(iterable)) # repeat last element
return iterable
except:
raise TypeError("Please give a list or tuple with at least one number: " + str(iterable))
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
pyhidapi.hid_init()
_module_loaded = True
print("Module pyhidapi detected")
except:
pass
def __init__(self):
WriteMethod.__init__(self)
self.description = None
self.path = None
self.dev = None
def get_name(self):
return 'hidapi'
def get_description(self):
return 'Program a device connected via USB using the pyhidapi package and libhidapi.'
def _open(self, device_id):
self.description = self.devices[device_id][0]
self.path = self.devices[device_id][1]
self.dev = WriteUsbHidApi.pyhidapi.hid_open_path(self.path)
if self.dev:
print("Hidapi device initialized")
return self.dev is not None
def close(self):
if self.dev is not None:
WriteUsbHidApi.pyhidapi.hid_close(self.dev)
self.description = None
self.path = None
self.dev = None
def _get_available_devices(self):
device_infos = WriteUsbHidApi.pyhidapi.hid_enumerate(0x0416, 0x5020)
devices = {}
for d in device_infos:
did = "%s" % (str(d.path.decode('ascii')),)
descr = "%s - %s (if=%d)" % (d.manufacturer_string, d.product_string, d.interface_number)
devices[did] = (descr, d.path)
return devices
def is_ready(self):
return WriteUsbHidApi._module_loaded
def has_device(self):
return self.dev is not None
def _write(self, buf):
if not self.dev:
return
print("Write using [%s] via hidapi" % (self.description,))
for i in range(int(len(buf) / 64)):
# sendbuf must contain "report ID" as first byte. "0" does the job here.
sendbuf = array('B', [0])
# Then, put the 64 payload bytes into the buffer
sendbuf.extend(buf[i * 64:i * 64 + 64])
WriteUsbHidApi.pyhidapi.hid_write(self.dev, sendbuf)
class LedNameBadge:
_protocol_header_template = (
0x77, 0x61, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
)
@staticmethod
def header(lengths, speeds, modes, blinks, ants, brightness=100, date=datetime.now()):
@ -471,7 +750,7 @@ or
* len(length) should match the designated bitmap data
* speeds come in as 1..8, but will be decremented to 0..7, here.
* modes: 0..8
* blinks and ants: 0..1 or even False..True,
* blinks and ants: 0..1 or even False...True,
* brightness, if given, is any number, but it'll be limited to 25, 50, 75, 100 (percent), here
* date, if given, is a datetime object. It will be written in the header, but is not to be seen on the
devices screen.
@ -483,7 +762,6 @@ or
if lengths_sum > (8192 - len(LedNameBadge._protocol_header_template)) / 11 + 1:
raise ValueError("The given lengths seem to be far too high: " + str(lengths))
ants = LedNameBadge._prepare_iterable(ants, 0, 1)
blinks = LedNameBadge._prepare_iterable(blinks, 0, 1)
speeds = LedNameBadge._prepare_iterable(speeds, 1, 8)
@ -524,69 +802,229 @@ or
return h
@staticmethod
def _prepare_iterable(iterable, min_, max_):
try:
iterable = [min(max(x, min_), max_) for x in iterable]
iterable = tuple(iterable) + (iterable[-1],) * (8 - len(iterable)) # repeat last element
return iterable
except:
raise TypeError("Please give a list or tuple with at least one number: " + str(iterable))
@staticmethod
def write(buf):
"""Write the given buffer to the device.
def write(buf, method='auto', device_id='auto'):
"""Write the given buffer to the given device.
It has to begin with a protocol header as provided by header() and followed by the bitmap data.
In short: the bitmap data is organized in bytes with 8 horizontal pixels per byte and 11 resp. 12
bytes per (8 pixels wide) byte-column. Then just put one byte-column after the other and one bitmap
after the other.
The two optional parameters specify the write method and device, which shall be programmed. See
get_available_methods() and get_available_device_ids(). There are two special values each: 'list'
will print the implemented / available write methods resp. the available devices, 'auto' (default) will
choose an appropriate write method resp. the first device found.
"""
need_padding = len(buf) % 64
if need_padding:
buf.extend((0,) * (64 - need_padding))
write_method = LedNameBadge._find_write_method(method, device_id)
if write_method:
write_method.write(buf)
write_method.close()
@staticmethod
def get_available_methods():
"""
Returns the implemented / available write methods as a dict. Each entry has the method name as the key and a
tuple as the value with the method description and a boolean indicating the readiness of that write method.
Basically it is ready if all necessary libraries and Python modules could be loaded. The method name can be
used as a parameter value for write().
"""
auto_order_methods = LedNameBadge._get_auto_order_method_list()
return {m.get_name(): (m.get_description(), m.is_ready()) for m in auto_order_methods}
@staticmethod
def get_available_device_ids(method):
"""Returns all devices available via the given write method as a dict. Each entry has the device id as the key
and the device description as the value. The device id can be used as a parameter value for write().
"""
auto_order_methods = LedNameBadge._get_auto_order_method_list()
wanted_method = [m for m in auto_order_methods if m.get_name() == method]
if wanted_method:
return wanted_method[0].get_available_devices()
return []
if len(buf) > 8192:
print("Writing more than 8192 bytes damages the display!")
@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]
if method == 'list':
LedNameBadge._print_available_methods(auto_order_methods)
sys.exit(0)
if method not in [m.get_name() for m in auto_order_methods] and method != 'auto':
print("Unknown write method '%s'." % (method,))
LedNameBadge._print_available_methods(auto_order_methods)
sys.exit(1)
if LedNameBadge._have_pyhidapi:
dev_info = LedNameBadge.pyhidapi.hid_enumerate(0x0416, 0x5020)
# dev = pyhidapi.hid_open(0x0416, 0x5020)
if dev_info:
dev = LedNameBadge.pyhidapi.hid_open_path(dev_info[0].path)
print("using [%s %s] int=%d page=%s via pyHIDAPI" % (
dev_info[0].manufacturer_string, dev_info[0].product_string, dev_info[0].interface_number, dev_info[0].usage_page))
else:
print("No led tag with vendorID 0x0416 and productID 0x5020 found.")
print("Connect the led tag and run this tool as root.")
if method == 'auto':
if sys.version_info[0] < 3:
method = libusb.get_name()
print("Preferring method %s over %s with Python 2.x" % (libusb.get_name(), hidapi.get_name()))
print("because of https://github.com/jnweiger/led-badge-ls32/issues/9")
elif sys.platform.startswith('darwin'):
method = hidapi.get_name()
print("Selected method %s with MacOs" % (hidapi.get_name(),))
elif sys.platform.startswith('win'):
method = libusb.get_name()
print("Selected method %s with Windows" % (libusb.get_name(),))
elif not libusb.is_ready() and not hidapi.is_ready():
if sys.version_info[0] < 3 or sys.platform.startswith('win'):
LedNameBadge._print_libusb_install_hints(libusb.get_name())
sys.exit(1)
elif sys.platform.startswith('darwin'):
LedNameBadge._print_hidapi_install_hints(hidapi.get_name())
sys.exit(1)
else:
print("One of the python packages 'pyhidapi' or 'pyusb' is needed to run this program (or both).")
LedNameBadge._print_libusb_install_hints(libusb.get_name())
LedNameBadge._print_hidapi_install_hints(hidapi.get_name())
sys.exit(1)
if method == libusb.get_name():
if sys.platform.startswith('darwin'):
print("For MacOs, please use method '%s' or 'auto'." % (hidapi.get_name(),))
print("Or help us implementing support for MacOs.")
sys.exit(1)
for i in range(int(len(buf)/64)):
# sendbuf must contain "report ID" as first byte. "0" does the job here.
sendbuf=array('B',[0])
# Then, put the 64 payload bytes into the buffer
sendbuf.extend(buf[i*64:i*64+64])
LedNameBadge.pyhidapi.hid_write(dev, sendbuf)
LedNameBadge.pyhidapi.hid_close(dev)
else:
dev = LedNameBadge.usb.core.find(idVendor=0x0416, idProduct=0x5020)
if dev is None:
print("No led tag with vendorID 0x0416 and productID 0x5020 found.")
print("Connect the led tag and run this tool as root.")
elif not libusb.is_ready():
LedNameBadge._print_libusb_install_hints(libusb.get_name())
sys.exit(1)
try:
# win32: NotImplementedError: is_kernel_driver_active
if dev.is_kernel_driver_active(0):
dev.detach_kernel_driver(0)
except:
pass
dev.set_configuration()
print("using [%s %s] bus=%d dev=%d" % (dev.manufacturer, dev.product, dev.bus, dev.address))
endpoint = 1
i = 0
while i < int(len(buf) / 64):
time.sleep(0.1)
try:
dev.write(endpoint, buf[i * 64:i * 64 + 64])
i += 1
except ValueError:
if endpoint == 1:
endpoint = 2
i = 0
def split_to_ints(list_str):
return [int(x) for x in re.split(r'[\s,]+', list_str)]
if method == hidapi.get_name():
if sys.version_info[0] < 3:
print("Please use method '%s' or 'auto' with python-2.x" % (libusb.get_name(),))
print("because of https://github.com/jnweiger/led-badge-ls32/issues/9")
sys.exit(1)
elif not hidapi.is_ready():
LedNameBadge._print_hidapi_install_hints(hidapi.get_name())
sys.exit(1)
if sys.platform.startswith('win') and hidapi.is_ready():
print("Method '%s' is not tested under Windows. If not working, please use '%s' or 'auto'" % (
hidapi.get_name(), libusb.get_name()))
print("Or help us implementing support for Windows.")
# But it is not forbidden
first_method_found = None
for m in auto_order_methods:
if method == 'auto' or method == m.get_name():
if not first_method_found:
first_method_found = m
if device_id == 'list':
LedNameBadge._print_available_devices(m)
sys.exit(0)
elif m.open(device_id):
return m
device_id_str = ''
if device_id != 'auto':
device_id_str = ' with device_id %s' % (device_id,)
print("The device is not available with write method '%s'%s." % (method, device_id_str))
if first_method_found:
LedNameBadge._print_available_devices(first_method_found)
print("* Is a led tag device with vendorID 0x0416 and productID 0x5020 connected?")
if device_id != 'auto':
print("* Have you given the right device_id?")
print(" Find the available device ids with option -D list")
print("* If it is connected and still do not work:")
LedNameBadge._print_sudo_hints()
sys.exit(1)
@staticmethod
def _get_auto_order_method_list():
return [WriteUsbHidApi(), WriteLibUsb()]
@staticmethod
def _print_available_methods(methods):
print("Available write methods:")
print(" 'auto': selects the most appropriate of the available methods (default)")
for m in methods:
LedNameBadge._print_one_method(m)
@staticmethod
def _print_one_method(m):
print(" '%s': %s" % (m.get_name(), m.get_description()))
@staticmethod
def _print_available_devices(method_obj):
if method_obj.is_device_present():
print("Known device ids with method '%s' are:" % (method_obj.get_name(),))
for did, descr in sorted(method_obj.get_available_devices().items()):
LedNameBadge._print_one_device(did, descr)
else:
print("No devices with method '%s' found." % (method_obj.get_name(),))
@staticmethod
def _print_one_device(did, descr):
print(" '%s': %s" % (did, descr))
@staticmethod
def _print_libusb_install_hints(name):
print("The method %s is not possible to be used:" % (name,))
print("The modules 'usb.core' and 'usb.util' could not be loaded.")
print("* Have you installed the corresponding python package 'pyusb'? Try:")
print(" $ pip install pyusb")
LedNameBadge._print_common_install_hints('pyusb', 'python3-usb')
if sys.platform.startswith('win'):
print("* Have you installed the libusb driver or libusb-filter for the device?")
elif sys.platform.startswith('linux'):
print("* Is the library itself installed? Try the following")
print(" (or similar, suitable for your distro; the exact command and package name might be different):")
print(" $ sudo apt-get install libusb-1.0-0")
@staticmethod
def _print_hidapi_install_hints(name):
print("The method %s is not possible to be used:" % (name,))
print("The module 'pyhidapi' could not be loaded.")
print("* Have you installed the corresponding python package 'pyhidapi'? Try:")
print(" $ pip install pyhidapi")
LedNameBadge._print_common_install_hints('pyhidapi', 'python3-hidapi')
if sys.platform.startswith('darwin'):
print("* Have you installed the library itself? Try:")
print(" $ brew install hidapi")
elif sys.platform.startswith('linux'):
print("* Is the library itself installed? Try the following")
print(" (or similar, suitable for your distro; the exact command and package name might be different):")
print(" $ sudo apt-get install libhidapi-hidraw0")
print("* If the library is still not found by the module. Try the following")
print(
" (or similar, suitable for your distro; the exact command, library name and paths might be different):")
print(" $ sudo ln -s /usr/lib/x86_64-linux-gnu/libhidapi-hidraw.so.0 /usr/local/lib/")
@staticmethod
def _print_common_install_hints(pip_package, pm_package):
print(" (You may need to use pip3 or pip2 instead of pip depending on your python version.)")
if sys.platform.startswith('win'):
print(" (You may need to run cmd.exe as Administrator for system wide module installation.)")
if sys.platform.startswith('linux'):
print(" (You may need prepend 'sudo' for system wide module installation.)")
print(" (You may also use your package manager. Try the following, e.g for %s)" % (pip_package,))
print(" (or similar, suitable for your distro; the exact command and package name might be different):")
print(" $ sudo apt install %s" % (pm_package,))
@staticmethod
def _print_sudo_hints():
print("Maybe, you have to run this program with administrator rights.")
if sys.platform.startswith('win'):
print("* Open start menu, type 'cmd', click 'Run as Administrator'")
if sys.platform.startswith('linux'):
print("* If Try with sudo or")
print("* If you run the program from a virtual env, you may need to open a root shell beforehand.")
print("* Best: add a udev rule like described in README.md.")
def main():
parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter,
@ -594,20 +1032,37 @@ def main():
epilog='Example combining image and text:\n sudo %s "I:HEART2:you"' % sys.argv[0])
parser.add_argument('-t', '--type', default='11x44',
help="Type of display: supported values are 12x48 or (default) 11x44. Rename the program to led-badge-12x48, to switch the default.")
parser.add_argument('-H', '--hid', default='0', help="Set to 1 to ensure connect via HID API, program will then not fallback to usb.core library")
parser.add_argument('-s', '--speed', default='4', help="Scroll speed (Range 1..8). Up to 8 comma-separated values")
parser.add_argument('-H',
'--hid',
default='0',
help="Deprecated, only for backwards compatibility, please use -M! Set to 1 to ensure connect via HID API, program will then not fallback to usb.core library.")
parser.add_argument('-M',
'--method',
default='auto',
help="Force using the given write method. Use one of 'auto', 'list' or whatever list is printing.")
parser.add_argument('-D',
'--device-id',
default='auto',
help="Force using the given device id, if ambiguous. Use one of 'auto', 'list' or whatever list is printing.")
parser.add_argument('-s', '--speed', default='4', help="Scroll speed (Range 1..8). Up to 8 comma-separated values.")
parser.add_argument('-B', '--brightness', default='100',
help="Brightness for the display in percent: 25, 50, 75, or 100")
help="Brightness for the display in percent: 25, 50, 75, or 100.")
parser.add_argument('-m', '--mode', default='0',
help="Up to 8 mode values: Scroll-left(0) -right(1) -up(2) -down(3); still-centered(4); animation(5); drop-down(6); curtain(7); laser(8); See '--mode-help' for more details.")
parser.add_argument('-b', '--blink', default='0', help="1: blinking, 0: normal. Up to 8 comma-separated values")
parser.add_argument('-a', '--ants', default='0', help="1: animated border, 0: normal. Up to 8 comma-separated values")
parser.add_argument('-b', '--blink', default='0', help="1: blinking, 0: normal. Up to 8 comma-separated values.")
parser.add_argument('-a',
'--ants',
default='0',
help="1: animated border, 0: normal. Up to 8 comma-separated values.")
parser.add_argument('-p', '--preload', metavar='FILE', action='append',
help=argparse.SUPPRESS) # "Load bitmap images. Use ^A, ^B, ^C, ... in text messages to make them visible. Deprecated, embed within ':' instead")
parser.add_argument('-l', '--list-names', action='version', help="list named icons to be embedded in messages and exit",
parser.add_argument('-l',
'--list-names',
action='version',
help="list named icons to be embedded in messages and exit.",
version=':' + ': :'.join(SimpleTextAndIcons._get_named_bitmaps_keys()) + ': :: or e.g. :path/to/some_icon.png:')
parser.add_argument('message', metavar='MESSAGE', nargs='+',
help="Up to 8 message texts with embedded builtin icons or loaded images within colons(:) -- See -l for a list of builtins")
help="Up to 8 message texts with embedded builtin icons or loaded images within colons(:) -- See -l for a list of builtins.")
parser.add_argument('--mode-help', action='version', help=argparse.SUPPRESS, version="""
-m 5 "Animation"
@ -668,11 +1123,20 @@ def main():
for msg_bitmap in msg_bitmaps:
buf.extend(msg_bitmap[0])
if not LedNameBadge._have_pyhidapi:
if args.hid != "0":
sys.exit("HID API access is needed but not initialized. Fix your setup")
# Translate -H to -M parameter
method = args.method
if args.hid == 1:
print("Option -H is deprecated, please use -M!")
if not method or method == 'auto':
method = 'hidapi'
else:
sys.exit("Parameter values are ambiguous. Please use -M only.")
LedNameBadge.write(buf)
LedNameBadge.write(buf, method, args.device_id)
def split_to_ints(list_str):
return [int(x) for x in re.split(r'[\s,]+', list_str)]
if __name__ == '__main__':

@ -1,9 +1,11 @@
' Using <U+002E>.. for three dots / ellipsis, els it would be a dotted line
@startuml bitmap_data_onebyte
salt
title One byte
{#
{ most significant bit 7
leftmost pixel } | bit 6 | ... | bit 1 | { least significant bit 0
leftmost pixel } | bit 6 | <U+002E>.. | bit 1 | { least significant bit 0
rightmost pixel }
}
@enduml
@ -12,17 +14,17 @@ title One byte
salt
title One scene
{#
byte 0 == 8 pixel | byte 11 == 8 pixel | byte 22 == 8 pixel | ...
byte 1 ... | byte 12 ... | byte 23 ... | ...
byte 2 ... | byte 13 ... | byte 24 ... | ...
byte 3 ... | byte 14 ... | byte 25 ... | ...
byte 4 ... | byte 15 ... | byte 26 ... | ...
byte 5 ... | byte 16 ... | byte 27 ... | ...
byte 6 ... | byte 17 ... | byte 28 ... | ...
byte 7 ... | byte 18 ... | byte 29 ... | ...
byte 8 ... | byte 19 ... | byte 30 ... | ...
byte 9 ... | byte 20 ... | byte 31 ... | ...
byte 10 ... | byte 21 ... | byte 32 ... | ...
byte 0 == 8 pixel | byte 11 == 8 pixel | byte 22 == 8 pixel | <U+002E>..
byte 1 ... | byte 12 ... | byte 23 ... | <U+002E>..
byte 2 ... | byte 13 ... | byte 24 ... | <U+002E>..
byte 3 ... | byte 14 ... | byte 25 ... | <U+002E>..
byte 4 ... | byte 15 ... | byte 26 ... | <U+002E>..
byte 5 ... | byte 16 ... | byte 27 ... | <U+002E>..
byte 6 ... | byte 17 ... | byte 28 ... | <U+002E>..
byte 7 ... | byte 18 ... | byte 29 ... | <U+002E>..
byte 8 ... | byte 19 ... | byte 30 ... | <U+002E>..
byte 9 ... | byte 20 ... | byte 31 ... | <U+002E>..
byte 10 ... | byte 21 ... | byte 32 ... | <U+002E>..
}
@enduml
@ -30,6 +32,6 @@ title One scene
salt
title Complete bitmap data
{#
scene 0 == x bytes | ... | scene n == z bytes
scene 0 == x bytes | <U+002E>.. | scene n == z bytes
}
@enduml

@ -0,0 +1,85 @@
import sys
from unittest import TestCase
from unittest.mock import patch, MagicMock
from io import StringIO
class USBError(BaseException):
pass
class AbstractWriteMethodTest(TestCase):
def setUp(self):
print("Real platform: " + sys.platform)
# -------------------------------------------------------------------------
def print_test_conditions(self, pyusb_available, pyhidapi_available, device_available, method, device_id):
print("Test condition: os=%s pyusb=%s pyhidapi=%s device=%s method=%s device_id=%s" % (
sys.platform,
'yes' if pyusb_available else 'no',
'yes' if pyhidapi_available else 'no',
'yes' if device_available else 'no',
method,
device_id))
def prepare_modules(self, pyusb_available, pyhidapi_available, device_available, func):
result = None
output = None
mocks = None
with self.do_import_patch(pyusb_available, pyhidapi_available, device_available) as module_mocks:
with patch('sys.stdout', new_callable=StringIO) as stdio_mock:
import lednamebadge
try:
result = func(lednamebadge.LedNameBadge)
mocks = {'pyhidapi': module_mocks['pyhidapi'], 'usb': module_mocks['usb']}
except(SystemExit):
pass
output = stdio_mock.getvalue()
print(output)
return result, output, mocks
def do_import_patch(self, pyusb_available, pyhidapi_available, device_available):
patch_obj = patch.dict('sys.modules', {
'pyhidapi': self.create_hid_mock(device_available) if pyhidapi_available else None,
'usb': self.create_usb_mock(device_available) if pyusb_available else None,
'usb.core': MagicMock() if pyusb_available else None,
'usb.core.USBError': USBError if pyusb_available else None,
'usb.util': MagicMock() if pyusb_available else None})
# Assure fresh reimport of lednamebadge with current mocks
if 'lednamebadge' in sys.modules:
del sys.modules['lednamebadge']
return patch_obj
def create_hid_mock(self, device_available):
device = MagicMock()
device.path = b'3-4:5-6'
device.manufacturer_string = 'HidApi Test Manufacturer'
device.product_string = 'HidApi Test Product'
device.interface_number = 0
mock = MagicMock()
mock.hid_enumerate.return_value = [device] if device_available else []
mock.hid_open_path.return_value = 123456 if device_available else []
return mock
def create_usb_mock(self, device_available):
device = MagicMock()
device.manufacturer = 'LibUsb Test Manufacturer'
device.product = 'LibUsb Test Product'
device.bus = 3
device.address = 4
ep = MagicMock()
ep.bEndpointAddress = 2
mock = MagicMock()
mock.core = MagicMock()
mock.core.find.return_value = [device] if device_available else []
mock.core.USBError = USBError
mock.util.find_descriptor.return_value = [ep] if device_available else []
return mock

@ -0,0 +1,57 @@
import sys
from array import array
import abstract_write_method_test
class Test(abstract_write_method_test.AbstractWriteMethodTest):
def test_get_methods(self):
methods, output = self.call_info_methods()
self.assertDictEqual({
'hidapi': ('Program a device connected via USB using the pyhidapi package and libhidapi.', True),
'libusb': ('Program a device connected via USB using the pyusb package and libusb.', True)},
methods)
def test_get_device_ids(self):
device_ids, output = self.call_info_ids('libusb')
self.assertDictEqual({
'3:4:2': 'LibUsb Test Manufacturer - LibUsb Test Product (bus=3 dev=4 endpoint=2)'},
device_ids)
device_ids, output = self.call_info_ids('hidapi')
self.assertDictEqual({
'3-4:5-6': 'HidApi Test Manufacturer - HidApi Test Product (if=0)'},
device_ids)
def test_write(self):
device_ids, output, mocks = self.call_write('auto')
mocks['pyhidapi'].hid_write.assert_called_once()
device_ids, output, mocks = self.call_write('hidapi')
mocks['pyhidapi'].hid_write.assert_called_once()
device_ids, output, mocks = self.call_write('libusb')
mocks['usb'].util.find_descriptor.assert_called_once()
mocks['usb'].util.find_descriptor.return_value[0].write.assert_called_once()
# -------------------------------------------------------------------------
def call_info_methods(self):
self.print_test_conditions(True, True, True, '-', '-')
method_obj, output, _ = self.prepare_modules(True, True, True,
lambda m: m.get_available_methods())
return method_obj, output
def call_info_ids(self, method):
self.print_test_conditions(True, True, True, '-', '-')
method_obj, output, _ = self.prepare_modules(True, True, True,
lambda m: m.get_available_device_ids(method))
return method_obj, output
def call_write(self, method):
self.print_test_conditions(True, True, True, 'auto', 'auto')
return self.prepare_modules(True, True, True,
lambda m: m.write(array('B', [1, 2, 3]), method))

@ -0,0 +1,163 @@
from unittest.mock import patch
import abstract_write_method_test
class Test(abstract_write_method_test.AbstractWriteMethodTest):
@patch('sys.platform', new='linux')
def test_list(self):
method, output = self.call_find(True, True, True, 'list', 'auto')
self.assertIn("Available write methods:", output)
self.assertIn("'auto'", output)
self.assertIn("'hidapi'", output)
self.assertIn("'libusb'", output)
method, output = self.call_find(True, True, True, 'hidapi', 'list')
self.assertIn("Known device ids with method 'hidapi' are:", output)
self.assertIn("'3-4:5-6': HidApi Test", output)
method, output = self.call_find(True, True, True, 'libusb', 'list')
self.assertIn("Known device ids with method 'libusb' are:", output)
self.assertIn("'3:4:2': LibUsb Test", output)
@patch('sys.platform', new='linux')
def test_unknown(self):
method, output = self.call_find(True, True, True, 'hello', 'auto')
self.assertIn("Unknown write method 'hello'", output)
self.assertIn("Available write methods:", output)
@patch('sys.platform', new='linux')
def test_all_in_linux_positive(self):
method, output = self.call_find(True, True, True, 'auto', 'auto')
self.assertIn('device initialized', output)
self.assertIsNotNone(method)
method, output = self.call_find(True, True, True, 'libusb', 'auto')
self.assertIn('device initialized', output)
self.assertIsNotNone(method)
method, output = self.call_find(True, True, True, 'hidapi', 'auto')
self.assertIn('device initialized', output)
self.assertIsNotNone(method)
@patch('sys.platform', new='linux')
def test_only_one_lib_linux_positive(self):
method, output = self.call_find(False, True, True, 'auto', 'auto')
self.assertIn('device initialized', output)
self.assertIsNotNone(method)
method, output = self.call_find(False, True, True, 'hidapi', 'auto')
self.assertIn('device initialized', output)
self.assertIsNotNone(method)
method, output = self.call_find(True, False, True, 'auto', 'auto')
self.assertIn('device initialized', output)
self.assertIsNotNone(method)
method, output = self.call_find(True, False, True, 'libusb', 'auto')
self.assertIn('device initialized', output)
self.assertIsNotNone(method)
@patch('sys.platform', new='windows')
def test_windows_positive(self):
method, output = self.call_find(True, False, True, 'auto', 'auto')
self.assertIn('device initialized', output)
self.assertIsNotNone(method)
method, output = self.call_find(True, False, True, 'libusb', 'auto')
self.assertIn('device initialized', output)
self.assertIsNotNone(method)
@patch('sys.platform', new='darwin')
def test_macos_positive(self):
method, output = self.call_find(False, True, True, 'auto', 'auto')
self.assertIn('device initialized', output)
self.assertIsNotNone(method)
method, output = self.call_find(False, True, True, 'hidapi', 'auto')
self.assertIn('device initialized', output)
self.assertIsNotNone(method)
@patch('sys.version_info', new=[2])
def test_python2_positive(self):
method, output = self.call_find(True, True, True, 'auto', 'auto')
self.assertIn('device initialized', output)
self.assertIn('Preferring method libusb', output)
self.assertIsNotNone(method)
# -------------------------------------------------------------------------
@patch('sys.platform', new='linux')
def test_all_in_linux_negative(self):
method, output = self.call_find(True, True, False, 'auto', 'auto')
self.assertNotIn('device initialized', output)
self.assertIn('device is not available', output)
self.assertIsNone(method)
method, output = self.call_find(True, True, False, 'libusb', 'auto')
self.assertNotIn('device initialized', output)
self.assertIn('device is not available', output)
self.assertIsNone(method)
method, output = self.call_find(True, True, False, 'hidapi', 'auto')
self.assertNotIn('device initialized', output)
self.assertIn('device is not available', output)
self.assertIsNone(method)
@patch('sys.platform', new='linux')
def test_all_out_linux_negative(self):
method, output = self.call_find(False, False, False, 'auto', 'auto')
self.assertNotIn('device initialized', output)
self.assertIn('One of the python packages', output)
self.assertIsNone(method)
method, output = self.call_find(False, False, False, 'libusb', 'auto')
self.assertNotIn('device initialized', output)
self.assertIn('is not possible to be used', output)
self.assertIsNone(method)
method, output = self.call_find(False, False, False, 'hidapi', 'auto')
self.assertNotIn('device initialized', output)
self.assertIn('is not possible to be used', output)
self.assertIsNone(method)
@patch('sys.platform', new='windows')
def test_windows_negative(self):
method, output = self.call_find(True, False, True, 'hidapi', 'auto')
self.assertNotIn('device initialized', output)
self.assertIn('is not possible to be used', output)
self.assertIsNone(method)
method, output = self.call_find(True, True, False, 'hidapi', 'auto')
self.assertNotIn('device initialized', output)
self.assertIn('If not working, please use', output)
self.assertIsNone(method)
@patch('sys.platform', new='darwin')
def test_macos_negative(self):
method, output = self.call_find(False, True, True, 'libusb', 'auto')
self.assertNotIn('device initialized', output)
self.assertIn('please use method', output)
self.assertIsNone(method)
@patch('sys.version_info', new=[2])
def test_python2_negative(self):
method, output = self.call_find(True, True, True, 'hidapi', 'auto')
self.assertNotIn('device initialized', output)
self.assertIn('Please use method', output)
self.assertIsNone(method)
# -------------------------------------------------------------------------
def call_find(self, pyusb_available, pyhidapi_available, device_available, method, device_id):
self.print_test_conditions(pyusb_available, pyhidapi_available, device_available, method, device_id)
method_obj, output, _ = self.prepare_modules(pyusb_available, pyhidapi_available, device_available,
lambda m: m._find_write_method(method, device_id))
self.assertEqual(pyusb_available, 'usb.core detected' in output)
self.assertEqual(pyhidapi_available, 'pyhidapi detected' in output)
return method_obj, output
Loading…
Cancel
Save