@ -52,9 +52,12 @@
import sys , os , re , time , argparse
from datetime import datetime
from array import array
try :
if sys . version_info [ 0 ] < 3 : raise Exception ( " prefer usb.core with python-2.x because of https://github.com/jnweiger/led-badge-ls32/issues/9 " )
if sys . version_info [ 0 ] < 3 : 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
except :
@ -80,7 +83,6 @@ or
print ( """ Please with Linux or MacOS or help us implement support for """ + sys . platform )
sys . exit ( 1 )
__version = " 0.12 "
font_11x44 = (
@ -201,21 +203,56 @@ font_11x44 = (
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 " àäòöùüèéêëôöûîïÿç " + \
u " ÀÅÄÉÈÊËÖÔÜÛÙŸ "
char_offset = { }
for i in range ( len ( charmap ) ) :
char_offset [ charmap [ i ] ] = 11 * i
# print(i, charmap[i], char_offset[charmap[i]])
bitmap_preloaded = [ ( [ ] , 0 ) ]
bitmap_preloaded = [ ( [ ] , 0 ) ]
bitmaps_preloaded_unused = False
bitmap_named = {
@ -258,7 +295,7 @@ bitmap_named = {
' 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 ,
' 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 ,
@ -284,7 +321,7 @@ def bitmap_char(ch):
return bitmap_preloaded [ ord ( ch ) ]
o = char_offset [ ch ]
return ( font_11x44 [ o : o + 11 ] , 1 )
return ( font_11x44 [ o : o + 11 ] , 1 )
def bitmap_text ( text ) :
@ -296,6 +333,7 @@ def bitmap_text(text):
" :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 colonrepl ( m ) :
name = m . group ( 1 )
if name == ' ' :
@ -304,7 +342,7 @@ def bitmap_text(text):
return chr ( int ( name ) )
if ' . ' in name :
bitmap_preloaded . append ( bitmap_img ( name ) )
return chr ( len ( bitmap_preloaded ) - 1 )
return chr ( len ( bitmap_preloaded ) - 1 )
b = bitmap_named [ name ]
return b [ 2 ]
@ -312,7 +350,7 @@ def bitmap_text(text):
buf = array ( ' B ' )
cols = 0
for c in text :
( b , n ) = bitmap_char ( c )
( b , n ) = bitmap_char ( c )
buf . extend ( b )
cols + = n
return ( buf , cols )
@ -328,15 +366,15 @@ def bitmap_img(file):
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 )
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
x = 8 * col + bit
if x < im . width and row < im . height :
pixel_color = im . getpixel ( ( x , row ) )
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 ) :
@ -344,7 +382,7 @@ def bitmap_img(file):
else :
sys . exit ( " %s : Unknown pixel format detected ( %s )! " % ( file , pixel_color ) )
if monochrome_color > 127 :
bit_val = 1 << ( 7 - bit )
bit_val = 1 << ( 7 - bit )
byte_val + = bit_val
buf . append ( byte_val )
im . close ( )
@ -374,16 +412,16 @@ def header(lengths, speeds, modes, blink, ants, brightness=100):
Speeds come in as 1. .8 , but are needed 0. .7 here .
"""
a = [ int ( x ) for x in re . split ( r ' [ \ s,]+ ' , ants ) ]
a = a + [ a [ - 1 ] ] * ( 8 - len ( a ) ) # repeat last element
a = a + [ a [ - 1 ] ] * ( 8 - len ( a ) ) # repeat last element
b = [ int ( x ) for x in re . split ( r ' [ \ s,]+ ' , blink ) ]
b = b + [ b [ - 1 ] ] * ( 8 - len ( b ) ) # repeat last element
b = b + [ b [ - 1 ] ] * ( 8 - len ( b ) ) # repeat last element
s = [ int ( x ) - 1 for x in re . split ( r ' [ \ s,]+ ' , speeds ) ]
s = s + [ s [ - 1 ] ] * ( 8 - len ( s ) ) # repeat last element
s = [ int ( x ) - 1 for x in re . split ( r ' [ \ s,]+ ' , speeds ) ]
s = s + [ s [ - 1 ] ] * ( 8 - len ( s ) ) # repeat last element
m = [ int ( x ) for x in re . split ( r ' [ \ s,]+ ' , modes ) ]
m = m + [ m [ - 1 ] ] * ( 8 - len ( m ) ) # repeat last element
m = m + [ m [ - 1 ] ] * ( 8 - len ( m ) ) # repeat last element
h = list ( proto_header )
@ -395,38 +433,45 @@ def header(lengths, speeds, modes, blink, ants, brightness=100):
h [ 5 ] = 0x10
for i in range ( 8 ) :
h [ 6 ] + = b [ i ] << i
h [ 7 ] + = a [ i ] << i
h [ 6 ] + = b [ i ] << i
h [ 7 ] + = a [ i ] << i
for i in range ( 8 ) :
h [ 8 + i ] = 16 * s [ i ] + m [ i ]
h [ 8 + i ] = 16 * s [ i ] + m [ i ]
for i in range ( len ( lengths ) ) :
h [ 17 + ( 2 * i ) - 1 ] = lengths [ i ] / / 256
h [ 17 + ( 2 * i ) ] = lengths [ i ] % 256
h [ 17 + ( 2 * i ) - 1 ] = lengths [ i ] / / 256
h [ 17 + ( 2 * i ) ] = lengths [ i ] % 256
cdate = datetime . now ( )
h [ 38 + 0 ] = cdate . year % 100
h [ 38 + 1 ] = cdate . month
h [ 38 + 2 ] = cdate . day
h [ 38 + 3 ] = cdate . hour
h [ 38 + 4 ] = cdate . minute
h [ 38 + 5 ] = cdate . second
h [ 38 + 0 ] = cdate . year % 100
h [ 38 + 1 ] = cdate . month
h [ 38 + 2 ] = cdate . day
h [ 38 + 3 ] = cdate . hour
h [ 38 + 4 ] = cdate . minute
h [ 38 + 5 ] = cdate . second
return h
parser = argparse . ArgumentParser ( formatter_class = argparse . RawDescriptionHelpFormatter , description = ' Upload messages or graphics to a 11x44 led badge via USB HID. \n Version %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 = argparse . ArgumentParser ( formatter_class = argparse . RawDescriptionHelpFormatter ,
description = ' Upload messages or graphics to a 11x44 led badge via USB HID. \n Version %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 ( ' -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 ' , ' --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 ( bitmap_named . 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 ( ' -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 ( bitmap_named . 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 "
@ -451,14 +496,15 @@ parser.add_argument('--mode-help', action='version', help=argparse.SUPPRESS, ver
args = parser . parse_args ( )
if have_pyhidapi :
devinfo = pyhidapi . hid_enumerate ( 0x0416 , 0x5020 )
#dev = pyhidapi.hid_open(0x0416, 0x5020)
# dev = pyhidapi.hid_open(0x0416, 0x5020)
else :
dev = usb . core . find ( idVendor = 0x0416 , idProduct = 0x5020 )
if have_pyhidapi :
if devinfo :
dev = pyhidapi . hid_open_path ( devinfo [ 0 ] . path )
print ( " using [ %s %s ] int= %d page= %s via pyHIDAPI " % ( devinfo [ 0 ] . manufacturer_string , devinfo [ 0 ] . product_string , devinfo [ 0 ] . interface_number , devinfo [ 0 ] . usage_page ) )
print ( " using [ %s %s ] int= %d page= %s via pyHIDAPI " % (
devinfo [ 0 ] . manufacturer_string , devinfo [ 0 ] . product_string , devinfo [ 0 ] . interface_number , devinfo [ 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. " )
@ -487,15 +533,16 @@ for arg in args.message:
msgs . append ( bitmap ( arg ) )
if bitmaps_preloaded_unused == True :
print ( " \n WARNING: \n Your preloaded images are not used. \n Try without ' -p ' or embed the control character ' ^A ' in your message. \n " )
print (
" \n WARNING: \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 in msgs :
# trivial hack to support 12x48 badges:
# patch extra empty lines into the message stream.
for o in reversed ( range ( 1 , int ( len ( msg [ 0 ] ) / 11 ) + 1 ) ) :
msg [ 0 ] [ o * 11 : o * 11 ] = array ( ' B ' , [ 0 ] )
for o in reversed ( range ( 1 , int ( len ( msg [ 0 ] ) / 11 ) + 1 ) ) :
msg [ 0 ] [ o * 11 : o * 11 ] = array ( ' B ' , [ 0 ] )
else :
print ( " Type: 11x44 " )
@ -505,22 +552,22 @@ buf.extend(header(list(map(lambda x: x[1], msgs)), args.speed, args.mode, args.b
for msg in msgs :
buf . extend ( msg [ 0 ] )
needpadding = len ( buf ) % 64
needpadding = len ( buf ) % 64
if needpadding :
buf . extend ( ( 0 , ) * ( 64 - needpadding ) )
buf . extend ( ( 0 , ) * ( 64 - needpadding ) )
# print(buf) # array('B', [119, 97, 110, 103, 0, 0, 0, 0, 48, 48, 48, 48, 48, 48, 48, 48, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 60, 126, 255, 255, 255, 255, 126, 60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
if len ( buf ) > 8192 :
print ( " Writing more than 8192 bytes damages the display! " )
print ( " Writing more than 8192 bytes damages the display! " )
sys . exit ( 1 )
if have_pyhidapi :
pyhidapi . hid_write ( dev , buf )
else :
for i in range ( int ( len ( buf ) / 64 ) ) :
for i in range ( int ( len ( buf ) / 64 ) ) :
time . sleep ( 0.1 )
dev . write ( 1 , buf [ i * 64 : i * 64 + 64 ] )
dev . write ( 1 , buf [ i * 64 : i * 64 + 64 ] )
if have_pyhidapi :
pyhidapi . hid_close ( dev )