You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1102 lines
53 KiB
1102 lines
53 KiB
#! /usr/bin/python3
|
|
# -*- encoding: utf-8 -*-
|
|
#
|
|
# (C) 2019 juergen@fabmail.org
|
|
#
|
|
# This is an upload tool for e.g.
|
|
# https://www.sertronics-shop.de/computer/pc-peripheriegeraete/usb-gadgets/led-name-tag-11x44-pixel-usb
|
|
# The font_11x44[] data was downloaded from such a device.
|
|
#
|
|
# Ubuntu install:
|
|
# ---------------
|
|
# sudo apt-get install python3-usb
|
|
#
|
|
# Optional for image support:
|
|
# sudo apt-get install python3-pil
|
|
#
|
|
# Windows install:
|
|
# ----------------
|
|
# 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.
|
|
# v0.2, 2019-03-07, jw support for loading bitmaps added.
|
|
# v0.3 jw option -p to preload graphics for inline use in text.
|
|
# v0.4, 2019-03-08, jw Warning about unused images added. Examples added to the README.
|
|
# v0.5, jw Deprecated -p and CTRL-characters. We now use embedding within colons(:)
|
|
# Added builtin icons and -l to list them.
|
|
# v0.6, 2019-03-14, jw Added --mode-help with hints and example for making animations.
|
|
# Options -b --blink, -a --ants added. Removed -p from usage.
|
|
# v0.7, 2019-05-20, jw Support pyhidapi, fallback to usb.core. Added python2 compatibility.
|
|
# v0.8, 2019-05-23, jw Support usb.core on windows via libusb-win32
|
|
# v0.9, 2019-07-17, jw Support 48x12 configuration too.
|
|
# v0.10, 2019-09-09, jw Support for loading monochrome images. Typos fixed.
|
|
# v0.11, 2019-09-29, jw New option --brightness added.
|
|
# v0.12, 2019-12-27, jw hint at pip3 -- as discussed in https://github.com/jnweiger/led-name-badge-ls32/issues/19
|
|
# v0.13, 2023-11-14, bs modularization.
|
|
# Some comments about this big change:
|
|
# * I wanted to keep this one-python-file-for-complete-command-line-usage, but also needed to introduce importable
|
|
# classes for writing own content to the device (my upcoming GUI editor). Therefore, the file was renamed to an
|
|
# importable python file, and forwarding python files are introduced with the old file names for full
|
|
# compatibility.
|
|
# * A bit of code rearranging and cleanup was necessary for that goal, but I hope the original parts are still
|
|
# recognizable, as I tried to keep all this refactoring as less, as possible and sense-making, but did not do
|
|
# the full clean-codish refactoring. Keeping the classes in one file is part of that refactoring-omittance.
|
|
# * 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
|
|
import os
|
|
import re
|
|
import sys
|
|
import time
|
|
from array import array
|
|
from datetime import datetime
|
|
|
|
|
|
__version = "0.14"
|
|
|
|
|
|
class SimpleTextAndIcons:
|
|
font_11x44 = (
|
|
# 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
|
0x00, 0x38, 0x6c, 0xc6, 0xc6, 0xfe, 0xc6, 0xc6, 0xc6, 0xc6, 0x00,
|
|
0x00, 0xfc, 0x66, 0x66, 0x66, 0x7c, 0x66, 0x66, 0x66, 0xfc, 0x00,
|
|
0x00, 0x7c, 0xc6, 0xc6, 0xc0, 0xc0, 0xc0, 0xc6, 0xc6, 0x7c, 0x00,
|
|
0x00, 0xfc, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0xfc, 0x00,
|
|
0x00, 0xfe, 0x66, 0x62, 0x68, 0x78, 0x68, 0x62, 0x66, 0xfe, 0x00,
|
|
0x00, 0xfe, 0x66, 0x62, 0x68, 0x78, 0x68, 0x60, 0x60, 0xf0, 0x00,
|
|
0x00, 0x7c, 0xc6, 0xc6, 0xc0, 0xc0, 0xce, 0xc6, 0xc6, 0x7e, 0x00,
|
|
0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xfe, 0xc6, 0xc6, 0xc6, 0xc6, 0x00,
|
|
0x00, 0x3c, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00,
|
|
0x00, 0x1e, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0xcc, 0xcc, 0x78, 0x00,
|
|
0x00, 0xe6, 0x66, 0x6c, 0x6c, 0x78, 0x6c, 0x6c, 0x66, 0xe6, 0x00,
|
|
0x00, 0xf0, 0x60, 0x60, 0x60, 0x60, 0x60, 0x62, 0x66, 0xfe, 0x00,
|
|
0x00, 0x82, 0xc6, 0xee, 0xfe, 0xd6, 0xc6, 0xc6, 0xc6, 0xc6, 0x00,
|
|
0x00, 0x86, 0xc6, 0xe6, 0xf6, 0xde, 0xce, 0xc6, 0xc6, 0xc6, 0x00,
|
|
0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00,
|
|
0x00, 0xfc, 0x66, 0x66, 0x66, 0x7c, 0x60, 0x60, 0x60, 0xf0, 0x00,
|
|
0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xd6, 0xde, 0x7c, 0x06,
|
|
0x00, 0xfc, 0x66, 0x66, 0x66, 0x7c, 0x6c, 0x66, 0x66, 0xe6, 0x00,
|
|
0x00, 0x7c, 0xc6, 0xc6, 0x60, 0x38, 0x0c, 0xc6, 0xc6, 0x7c, 0x00,
|
|
0x00, 0x7e, 0x7e, 0x5a, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00,
|
|
0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00,
|
|
0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x6c, 0x38, 0x10, 0x00,
|
|
0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xd6, 0xfe, 0xee, 0xc6, 0x82, 0x00,
|
|
0x00, 0xc6, 0xc6, 0x6c, 0x7c, 0x38, 0x7c, 0x6c, 0xc6, 0xc6, 0x00,
|
|
0x00, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x18, 0x18, 0x18, 0x3c, 0x00,
|
|
0x00, 0xfe, 0xc6, 0x86, 0x0c, 0x18, 0x30, 0x62, 0xc6, 0xfe, 0x00,
|
|
|
|
# 'abcdefghijklmnopqrstuvwxyz'
|
|
0x00, 0x00, 0x00, 0x00, 0x78, 0x0c, 0x7c, 0xcc, 0xcc, 0x76, 0x00,
|
|
0x00, 0xe0, 0x60, 0x60, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x7c, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x7c, 0xc6, 0xc0, 0xc0, 0xc6, 0x7c, 0x00,
|
|
0x00, 0x1c, 0x0c, 0x0c, 0x7c, 0xcc, 0xcc, 0xcc, 0xcc, 0x76, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x7c, 0xc6, 0xfe, 0xc0, 0xc6, 0x7c, 0x00,
|
|
0x00, 0x1c, 0x36, 0x30, 0x78, 0x30, 0x30, 0x30, 0x30, 0x78, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x76, 0xcc, 0xcc, 0x7c, 0x0c, 0xcc, 0x78,
|
|
0x00, 0xe0, 0x60, 0x60, 0x6c, 0x76, 0x66, 0x66, 0x66, 0xe6, 0x00,
|
|
0x00, 0x18, 0x18, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00,
|
|
0x0c, 0x0c, 0x00, 0x1c, 0x0c, 0x0c, 0x0c, 0x0c, 0xcc, 0xcc, 0x78,
|
|
0x00, 0xe0, 0x60, 0x60, 0x66, 0x6c, 0x78, 0x78, 0x6c, 0xe6, 0x00,
|
|
0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0xec, 0xfe, 0xd6, 0xd6, 0xd6, 0xc6, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0xdc, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0xdc, 0x66, 0x66, 0x7c, 0x60, 0x60, 0xf0,
|
|
0x00, 0x00, 0x00, 0x00, 0x7c, 0xcc, 0xcc, 0x7c, 0x0c, 0x0c, 0x1e,
|
|
0x00, 0x00, 0x00, 0x00, 0xde, 0x76, 0x60, 0x60, 0x60, 0xf0, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x7c, 0xc6, 0x70, 0x1c, 0xc6, 0x7c, 0x00,
|
|
0x00, 0x10, 0x30, 0x30, 0xfc, 0x30, 0x30, 0x30, 0x34, 0x18, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x76, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0xc6, 0xc6, 0xc6, 0x6c, 0x38, 0x10, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0xc6, 0xd6, 0xd6, 0xd6, 0xfe, 0x6c, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0xc6, 0x6c, 0x38, 0x38, 0x6c, 0xc6, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0xc6, 0xc6, 0xc6, 0x7e, 0x06, 0x0c, 0xf8,
|
|
0x00, 0x00, 0x00, 0x00, 0xfe, 0x8c, 0x18, 0x30, 0x62, 0xfe, 0x00,
|
|
|
|
# '0987654321^ !"\0$%&/()=?` °\\}][{'
|
|
0x00, 0x7c, 0xc6, 0xce, 0xde, 0xf6, 0xe6, 0xc6, 0xc6, 0x7c, 0x00,
|
|
0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0x7e, 0x06, 0x06, 0xc6, 0x7c, 0x00,
|
|
0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0x7c, 0xc6, 0xc6, 0xc6, 0x7c, 0x00,
|
|
0x00, 0xfe, 0xc6, 0x06, 0x0c, 0x18, 0x30, 0x30, 0x30, 0x30, 0x00,
|
|
0x00, 0x7c, 0xc6, 0xc0, 0xc0, 0xfc, 0xc6, 0xc6, 0xc6, 0x7c, 0x00,
|
|
0x00, 0xfe, 0xc0, 0xc0, 0xfc, 0x06, 0x06, 0x06, 0xc6, 0x7c, 0x00,
|
|
0x00, 0x0c, 0x1c, 0x3c, 0x6c, 0xcc, 0xfe, 0x0c, 0x0c, 0x1e, 0x00,
|
|
0x00, 0x7c, 0xc6, 0x06, 0x06, 0x3c, 0x06, 0x06, 0xc6, 0x7c, 0x00,
|
|
0x00, 0x7c, 0xc6, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc6, 0xfe, 0x00,
|
|
0x00, 0x18, 0x38, 0x78, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x00,
|
|
0x38, 0x6c, 0xc6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x3c, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x18, 0x3c, 0x3c, 0x3c, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00,
|
|
0x66, 0x66, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x7c, 0x04, 0x14, 0x18, 0x10, 0x10, 0x20,
|
|
0x10, 0x7c, 0xd6, 0xd6, 0x70, 0x1c, 0xd6, 0xd6, 0x7c, 0x10, 0x10,
|
|
0x00, 0x60, 0x92, 0x96, 0x6c, 0x10, 0x6c, 0xd2, 0x92, 0x0c, 0x00,
|
|
0x00, 0x38, 0x6c, 0x6c, 0x38, 0x76, 0xdc, 0xcc, 0xcc, 0x76, 0x00,
|
|
0x00, 0x00, 0x02, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0, 0x80, 0x00,
|
|
0x00, 0x0c, 0x18, 0x30, 0x30, 0x30, 0x30, 0x30, 0x18, 0x0c, 0x00,
|
|
0x00, 0x30, 0x18, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x18, 0x30, 0x00,
|
|
0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x7c, 0xc6, 0xc6, 0x0c, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00,
|
|
0x18, 0x18, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x7c, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x7c,
|
|
0x00, 0x10, 0x28, 0x28, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x80, 0xc0, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x02, 0x00, 0x00,
|
|
0x00, 0x70, 0x18, 0x18, 0x18, 0x0e, 0x18, 0x18, 0x18, 0x70, 0x00,
|
|
0x00, 0x3c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x3c, 0x00,
|
|
0x00, 0x3c, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3c, 0x00,
|
|
0x00, 0x0e, 0x18, 0x18, 0x18, 0x70, 0x18, 0x18, 0x18, 0x0e, 0x00,
|
|
|
|
# "@ ~ |<>,;.:-_#'+* "
|
|
0x00, 0x00, 0x3c, 0x42, 0x9d, 0xa5, 0xad, 0xb6, 0x40, 0x3c, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xc0, 0x00, 0x00, 0x00,
|
|
0x00, 0x76, 0xdc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x08, 0x08, 0x7c, 0x08, 0x08, 0x18, 0x18, 0x28, 0x28, 0x48, 0x18,
|
|
0x00, 0x18, 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x18, 0x00,
|
|
0x00, 0x06, 0x0c, 0x18, 0x30, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x00,
|
|
0x00, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x0c, 0x18, 0x30, 0x60, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x10, 0x20,
|
|
0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x08, 0x10,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x00,
|
|
0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
|
|
0x00, 0x6c, 0x6c, 0xfe, 0x6c, 0x6c, 0xfe, 0x6c, 0x6c, 0x00, 0x00,
|
|
0x18, 0x18, 0x08, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x18, 0x18, 0x7e, 0x18, 0x18, 0x00, 0x00, 0x00,
|
|
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,
|
|
0xc6, 0xc6, 0x38, 0x6c, 0xc6, 0xfe, 0xc6, 0xc6, 0xc6, 0xc6, 0x00,
|
|
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,
|
|
|
|
# "àäòöùüèéêëôöûîïÿç"
|
|
0x00, 0x60, 0x18, 0x00, 0x78, 0x0c, 0x7c, 0xcc, 0xcc, 0x76, 0x00,
|
|
0x00, 0x6c, 0x6c, 0x00, 0x78, 0x0c, 0x7c, 0xcc, 0xcc, 0x76, 0x00,
|
|
0x00, 0x60, 0x18, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00,
|
|
0x00, 0x6c, 0x6c, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00,
|
|
0x00, 0x60, 0x18, 0x00, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x76, 0x00,
|
|
0x00, 0x6c, 0x6c, 0x00, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x76, 0x00,
|
|
0x00, 0x60, 0x18, 0x00, 0x7c, 0xc6, 0xfe, 0xc0, 0xc6, 0x7c, 0x00,
|
|
0x00, 0x18, 0x60, 0x00, 0x7c, 0xc6, 0xfe, 0xc0, 0xc6, 0x7c, 0x00,
|
|
0x00, 0x10, 0x6c, 0x00, 0x7c, 0xc6, 0xfe, 0xc0, 0xc6, 0x7c, 0x00,
|
|
0x00, 0x6c, 0x6c, 0x00, 0x7c, 0xc6, 0xfe, 0xc0, 0xc6, 0x7c, 0x00,
|
|
0x00, 0x10, 0x6c, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00,
|
|
0x00, 0x6c, 0x6c, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00,
|
|
0x00, 0x10, 0x6c, 0x00, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x76, 0x00,
|
|
0x00, 0x10, 0x6c, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00,
|
|
0x00, 0x6c, 0x6c, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00,
|
|
0x00, 0x6c, 0x6c, 0x00, 0xc6, 0xc6, 0xc6, 0x7e, 0x06, 0x0c, 0xf8,
|
|
0x00, 0x00, 0x00, 0x7c, 0xc6, 0xc0, 0xc0, 0xc6, 0x7c, 0x10, 0x30,
|
|
|
|
# "ÀÅÄÉÈÊËÖÔÜÛÙŸ"
|
|
0x60, 0x18, 0x38, 0x6c, 0xc6, 0xfe, 0xc6, 0xc6, 0xc6, 0xc6, 0x00,
|
|
0x10, 0x6c, 0x38, 0x6c, 0xc6, 0xfe, 0xc6, 0xc6, 0xc6, 0xc6, 0x00,
|
|
0x6c, 0x6c, 0x38, 0x6c, 0xc6, 0xfe, 0xc6, 0xc6, 0xc6, 0xc6, 0x00,
|
|
0x18, 0x60, 0xfe, 0x62, 0x68, 0x78, 0x68, 0x62, 0x66, 0xfe, 0x00,
|
|
0x60, 0x18, 0xfe, 0x62, 0x68, 0x78, 0x68, 0x62, 0x66, 0xfe, 0x00,
|
|
0x10, 0x6c, 0xfe, 0x62, 0x68, 0x78, 0x68, 0x62, 0x66, 0xfe, 0x00,
|
|
0x6c, 0x6c, 0xfe, 0x62, 0x68, 0x78, 0x68, 0x62, 0x66, 0xfe, 0x00,
|
|
0x6c, 0x6c, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, # Ö
|
|
0x10, 0x6c, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, # Ô
|
|
0x6c, 0x6c, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, # Ü
|
|
0x10, 0x6c, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, # Û
|
|
0x60, 0x18, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, # Ù
|
|
0x66, 0x66, 0x00, 0x66, 0x66, 0x66, 0x3c, 0x18, 0x18, 0x3c, 0x00, # Ÿ
|
|
)
|
|
|
|
charmap = u'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + \
|
|
u'abcdefghijklmnopqrstuvwxyz' + \
|
|
u'0987654321^ !"\0$%&/()=?` °\\}][{' + \
|
|
u"@ ~ |<>,;.:-_#'+* " + \
|
|
u"äöüÄÖÜß" + \
|
|
u"àäòöùüèéêëôöûîïÿç" + \
|
|
u"ÀÅÄÉÈÊËÖÔÜÛÙŸ"
|
|
|
|
char_offsets = {}
|
|
for i in range(len(charmap)):
|
|
char_offsets[charmap[i]] = 11 * i
|
|
# print(i, charmap[i], char_offsets[charmap[i]])
|
|
|
|
bitmap_named = {
|
|
'ball': (array('B', (
|
|
0b00000000,
|
|
0b00000000,
|
|
0b00111100,
|
|
0b01111110,
|
|
0b11111111,
|
|
0b11111111,
|
|
0b11111111,
|
|
0b11111111,
|
|
0b01111110,
|
|
0b00111100,
|
|
0b00000000
|
|
)), 1, '\x1e'),
|
|
'happy': (array('B', (
|
|
0b00000000, # 0x00
|
|
0b00000000, # 0x00
|
|
0b00111100, # 0x3c
|
|
0b01000010, # 0x42
|
|
0b10100101, # 0xa5
|
|
0b10000001, # 0x81
|
|
0b10100101, # 0xa5
|
|
0b10011001, # 0x99
|
|
0b01000010, # 0x42
|
|
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'),
|
|
'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'),
|
|
}
|
|
|
|
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
|
|
|
|
def add_preload_img(self, filename):
|
|
"""Still used by main, but deprecated. PLease use ":"-notation for bitmap() / bitmap_text()"""
|
|
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 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).
|
|
The bits in each byte are horizontal, highest bit is left.
|
|
"""
|
|
if ord(ch) < 32:
|
|
if ch in SimpleTextAndIcons.bitmap_builtin:
|
|
return SimpleTextAndIcons.bitmap_builtin[ch][:2]
|
|
|
|
self.bitmaps_preloaded_unused = False
|
|
return self.bitmap_preloaded[ord(ch)]
|
|
|
|
o = SimpleTextAndIcons.char_offsets[ch]
|
|
return (SimpleTextAndIcons.font_11x44[o:o + 11], 1)
|
|
|
|
def bitmap_text(self, text):
|
|
"""Returns a tuple of (buffer, length_in_byte_columns_aka_chars)
|
|
We preprocess the text string for substitution patterns
|
|
"::" is replaced with a single ":"
|
|
":1: is replaced with CTRL-A referencing the first preloaded or loaded image.
|
|
":happy:" is replaced with a reference to a builtin smiley glyph
|
|
":heart:" is replaced with a reference to a builtin heart glyph
|
|
":gfx/logo.png:" preloads the file gfx/logo.png and is replaced the corresponding control char.
|
|
"""
|
|
|
|
def replace_symbolic(m):
|
|
name = m.group(1)
|
|
if name == '':
|
|
return ':'
|
|
if re.match('^[0-9]*$', name): # py3 name.isdecimal()
|
|
return chr(int(name))
|
|
if '.' in name:
|
|
self.bitmap_preloaded.append(SimpleTextAndIcons.bitmap_img(name))
|
|
return chr(len(self.bitmap_preloaded) - 1)
|
|
return SimpleTextAndIcons.bitmap_named[name][2]
|
|
|
|
text = re.sub(r':([^:]*):', replace_symbolic, text)
|
|
buf = array('B')
|
|
cols = 0
|
|
for c in text:
|
|
(b, n) = self.bitmap_char(c)
|
|
buf.extend(b)
|
|
cols += n
|
|
return (buf, cols)
|
|
|
|
@staticmethod
|
|
def bitmap_img(file):
|
|
"""Returns a tuple of (buffer, length_in_byte_columns) representing the given image file.
|
|
It has to be an 8-bit grayscale image or a color image with 8 bit per channel. Color pixels are converted to
|
|
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.
|
|
"""
|
|
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))
|
|
if im.height != 11:
|
|
sys.exit("%s: image height must be 11px. Seen %d" % (file, im.height))
|
|
buf = array('B')
|
|
cols = int((im.width + 7) / 8)
|
|
for col in range(cols):
|
|
for row in range(11): # [0..10]
|
|
byte_val = 0
|
|
for bit in range(8): # [0..7]
|
|
bit_val = 0
|
|
x = 8 * col + bit
|
|
if x < im.width and row < im.height:
|
|
pixel_color = im.getpixel((x, row))
|
|
if isinstance(pixel_color, tuple):
|
|
monochrome_color = sum(pixel_color[:3]) / len(pixel_color[:3])
|
|
elif isinstance(pixel_color, int):
|
|
monochrome_color = pixel_color
|
|
else:
|
|
sys.exit("%s: Unknown pixel format detected (%s)!" % (file, pixel_color))
|
|
if monochrome_color > 127:
|
|
bit_val = 1 << (7 - bit)
|
|
byte_val += bit_val
|
|
buf.append(byte_val)
|
|
im.close()
|
|
return (buf, cols)
|
|
|
|
def bitmap(self, arg):
|
|
"""If arg is a valid and existing path name, we load it as an image.
|
|
Otherwise, we take it as a string (with ":"-notation, see bitmap_text()).
|
|
"""
|
|
if os.path.exists(arg):
|
|
return SimpleTextAndIcons.bitmap_img(arg)
|
|
return self.bitmap_text(arg)
|
|
|
|
|
|
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 {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, 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:
|
|
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):
|
|
for k, d in self.devices.items():
|
|
d[1].reset()
|
|
WriteLibUsb.usb.util.dispose_resources(d[1])
|
|
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 write access to device!")
|
|
print("Maybe, you have to run this program with administrator rights.")
|
|
if sys.platform.startswith('linux'):
|
|
print("* Try with sudo or add a udev rule like described in README.md.")
|
|
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:
|
|
id = "%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[id] = (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:
|
|
# win32: NotImplementedError: is_kernel_driver_active
|
|
if self.dev.is_kernel_driver_active(0):
|
|
self.dev.detach_kernel_driver(0)
|
|
except:
|
|
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!")
|
|
print("Maybe, you have to run this program with administrator rights.")
|
|
if sys.platform.startswith('linux'):
|
|
print("* Try with sudo or add a udev rule like described in README.md.")
|
|
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])
|
|
|
|
|
|
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:
|
|
id = "%s" % (str(d.path.decode('ascii')),)
|
|
descr = "%s - %s (if=%d)" % (d.manufacturer_string, d.product_string, d.interface_number)
|
|
devices[id] = (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()):
|
|
"""Create a protocol header
|
|
* length, speeds, modes, blinks, ants are iterables with at least one element
|
|
* lengths[0] is the number of chars/byte-columns of the first text/bitmap, lengths[1] of the second,
|
|
and so on...
|
|
* 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,
|
|
* 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.
|
|
"""
|
|
try:
|
|
lengths_sum = sum(lengths)
|
|
except:
|
|
raise TypeError("Please give a list or tuple with at least one number: " + str(lengths))
|
|
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)
|
|
modes = LedNameBadge._prepare_iterable(modes, 0, 8)
|
|
|
|
speeds = [x - 1 for x in speeds]
|
|
|
|
h = list(LedNameBadge._protocol_header_template)
|
|
|
|
if brightness <= 25:
|
|
h[5] = 0x40
|
|
elif brightness <= 50:
|
|
h[5] = 0x20
|
|
elif brightness <= 75:
|
|
h[5] = 0x10
|
|
# else default 100% == 0x00
|
|
|
|
for i in range(8):
|
|
h[6] += blinks[i] << i
|
|
h[7] += ants[i] << i
|
|
|
|
for i in range(8):
|
|
h[8 + i] = 16 * speeds[i] + modes[i]
|
|
|
|
for i in range(len(lengths)):
|
|
h[17 + (2 * i) - 1] = lengths[i] // 256
|
|
h[17 + (2 * i)] = lengths[i] % 256
|
|
|
|
try:
|
|
h[38 + 0] = date.year % 100
|
|
h[38 + 1] = date.month
|
|
h[38 + 2] = date.day
|
|
h[38 + 3] = date.hour
|
|
h[38 + 4] = date.minute
|
|
h[38 + 5] = date.second
|
|
except:
|
|
raise TypeError("Please give a datetime object: " + str(date))
|
|
|
|
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, method = 'auto', device_id = 'auto'):
|
|
"""Write the given buffer to the 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.
|
|
"""
|
|
write_method = LedNameBadge._find_write_method(method, device_id)
|
|
if write_method:
|
|
write_method.write(buf)
|
|
write_method.close()
|
|
|
|
@staticmethod
|
|
def get_available_methods():
|
|
auto_order_methods = LedNameBadge.get_auto_order_method_list()
|
|
return {m.get_name(): m.is_ready() for m in auto_order_methods}
|
|
|
|
@staticmethod
|
|
def get_available_device_ids(method):
|
|
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 []
|
|
|
|
@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 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)
|
|
elif not libusb.is_ready():
|
|
LedNameBadge._print_libusb_install_hints(libusb.get_name())
|
|
sys.exit(1)
|
|
|
|
if method == hidapi.get_name():
|
|
if sys.platform.startswith('win'):
|
|
print("For Windows, please use method '%s' or 'auto'." % (libusb.get_name(),))
|
|
print("Or help us implementing support for Windows.")
|
|
sys.exit(1)
|
|
elif 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)
|
|
|
|
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, maybe you have to run")
|
|
print(" this program as root.")
|
|
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 id, descr in sorted(method_obj.get_available_devices().items()):
|
|
LedNameBadge.print_one_device(id, descr)
|
|
else:
|
|
print("No devices with method '%s' found." % (method_obj.get_name(),))
|
|
|
|
@staticmethod
|
|
def print_one_device(id, descr):
|
|
print(" '%s': %s" % (id, 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))
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
description='Upload messages or graphics to a 11x44 led badge via USB HID.\nVersion %s from https://github.com/jnweiger/led-badge-ls32\n -- see there for more examples and for updates.' % __version,
|
|
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="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. Usue 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.")
|
|
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('-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.",
|
|
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.")
|
|
parser.add_argument('--mode-help', action='version', help=argparse.SUPPRESS, version="""
|
|
|
|
-m 5 "Animation"
|
|
|
|
Animation frames are 6 character (or 48px) wide. Upload an animation of
|
|
N frames as one image N*48 pixels wide, 11 pixels high.
|
|
Frames run from left to right and repeat endless.
|
|
Speeds [1..8] result in ca. [1.2 1.3 2.0 2.4 2.8 4.5 7.5 15] fps.
|
|
|
|
Example of a slowly beating heart:
|
|
sudo %s -s1 -m5 " :heart2: :HEART2:"
|
|
|
|
-m 9 "Smooth"
|
|
-m 10 "Rotate"
|
|
|
|
These modes are mentioned in the BMP Badge software.
|
|
Text is shown static, or sometimes (longer texts?) not shown at all.
|
|
One significant difference is: The text of the first message stays visible after
|
|
upload, even if the USB cable remains connected.
|
|
(No "rotation" or "smoothing"(?) effect can be expected, though)
|
|
""" % sys.argv[0])
|
|
args = parser.parse_args()
|
|
|
|
creator = SimpleTextAndIcons()
|
|
|
|
if args.preload:
|
|
for filename in args.preload:
|
|
creator.add_preload_img(filename)
|
|
|
|
msg_bitmaps = []
|
|
for msg_arg in args.message:
|
|
msg_bitmaps.append(creator.bitmap(msg_arg))
|
|
|
|
if creator.are_preloaded_unused():
|
|
print(
|
|
"\nWARNING:\n Your preloaded images are not used.\n Try without '-p' or embed the control character '^A' in your message.\n")
|
|
|
|
if '12' in args.type or '12' in sys.argv[0]:
|
|
print("Type: 12x48")
|
|
for msg_bitmap in msg_bitmaps:
|
|
# trivial hack to support 12x48 badges:
|
|
# patch extra empty lines into the message stream.
|
|
for i in reversed(range(1, int(len(msg_bitmap[0]) / 11) + 1)):
|
|
msg_bitmap[0][i * 11:i * 11] = array('B', [0])
|
|
else:
|
|
print("Type: 11x44")
|
|
|
|
lengths = [b[1] for b in msg_bitmaps]
|
|
speeds = split_to_ints(args.speed)
|
|
modes = split_to_ints(args.mode)
|
|
blinks = split_to_ints(args.blink)
|
|
ants = split_to_ints(args.ants)
|
|
brightness = int(args.brightness)
|
|
|
|
buf = array('B')
|
|
buf.extend(LedNameBadge.header(lengths, speeds, modes, blinks, ants, brightness))
|
|
|
|
for msg_bitmap in msg_bitmaps:
|
|
buf.extend(msg_bitmap[0])
|
|
|
|
# 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, 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__':
|
|
main()
|
|
|