diff --git a/led-badge-11x44.py b/led-badge-11x44.py index ac66a8f..f61c849 100755 --- a/led-badge-11x44.py +++ b/led-badge-11x44.py @@ -52,39 +52,41 @@ 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") - import pyhidapi - pyhidapi.hid_init() - have_pyhidapi = True + 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: - have_pyhidapi = False - try: - import usb.core - except: - print("ERROR: Need the pyhidapi or usb.core module.") - if sys.platform == "darwin": - print("""Please try + have_pyhidapi = False + try: + import usb.core + 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 + 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 with Linux or MacOS or help us implement support for """ + sys.platform) - sys.exit(1) - + else: # windows? + print("""Please with Linux or MacOS or help us implement support for """ + sys.platform) + sys.exit(1) __version = "0.12" font_11x44 = ( - # 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + # '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, @@ -201,94 +203,129 @@ 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]]) + 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 = { - '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, + '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, + '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, + '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, + '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'), + '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] + bitmap_builtin[bitmap_named[i][2]] = bitmap_named[i] def bitmap_char(ch): - """ Returns a tuple of 11 bytes, + """ Returns a tuple of 11 bytes, 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 bitmap_builtin: - return bitmap_builtin[ch][:2] + if ord(ch) < 32: + if ch in bitmap_builtin: + return bitmap_builtin[ch][:2] - global bitmaps_preloaded_unused - bitmaps_preloaded_unused = False - return bitmap_preloaded[ord(ch)] + global bitmaps_preloaded_unused + bitmaps_preloaded_unused = False + return bitmap_preloaded[ord(ch)] - o = char_offset[ch] - return (font_11x44[o:o+11],1) + o = char_offset[ch] + return (font_11x44[o:o + 11], 1) def bitmap_text(text): - """ returns a tuple of (buffer, length_in_byte_columns_aka_chars) + """ 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. @@ -296,137 +333,145 @@ 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 == '': - return ':' - if re.match('^[0-9]*$', name): # py3 name.isdecimal() - return chr(int(name)) - if '.' in name: - bitmap_preloaded.append(bitmap_img(name)) - return chr(len(bitmap_preloaded)-1) - b = bitmap_named[name] - return b[2] - - text = re.sub(r':([^:]*):', colonrepl, text) - buf = array('B') - cols = 0 - for c in text: - (b,n) = bitmap_char(c) - buf.extend(b) - cols += n - return (buf, cols) + + def colonrepl(m): + name = m.group(1) + if name == '': + return ':' + if re.match('^[0-9]*$', name): # py3 name.isdecimal() + return chr(int(name)) + if '.' in name: + bitmap_preloaded.append(bitmap_img(name)) + return chr(len(bitmap_preloaded) - 1) + b = bitmap_named[name] + return b[2] + + text = re.sub(r':([^:]*):', colonrepl, text) + buf = array('B') + cols = 0 + for c in text: + (b, n) = bitmap_char(c) + buf.extend(b) + cols += n + return (buf, cols) def bitmap_img(file): - """ returns a tuple of (buffer, length_in_byte_columns) + """ returns a tuple of (buffer, length_in_byte_columns) """ - from PIL import Image - - 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) + from PIL import Image + + 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(arg): - """ if arg is a valid and existing path name, we load it as an image. + """ if arg is a valid and existing path name, we load it as an image. Otherwise we take it as a string. """ - if os.path.exists(arg): - return bitmap_img(arg) - return bitmap_text(arg) + if os.path.exists(arg): + return bitmap_img(arg) + return bitmap_text(arg) proto_header = ( - 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 + 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 ) def header(lengths, speeds, modes, blink, ants, brightness=100): - """ lengths[0] is the number of chars of the first text + """ lengths[0] is the number of chars of the first text 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 = [int(x) for x in re.split(r'[\s,]+', ants)] + 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 = [int(x) for x in re.split(r'[\s,]+', blink)] + 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 = [int(x) for x in re.split(r'[\s,]+', modes)] + m = m + [m[-1]] * (8 - len(m)) # repeat last element - h = list(proto_header) + h = list(proto_header) - if brightness <= 25: - h[5] = 0x40 - elif brightness <= 50: - h[5] = 0x20 - elif brightness <= 75: - h[5] = 0x10 + if brightness <= 25: + h[5] = 0x40 + elif brightness <= 50: + h[5] = 0x20 + elif brightness <= 75: + h[5] = 0x10 - for i in range(8): - h[6] += b[i]< 8192: - print ("Writing more than 8192 bytes damages the display!") - sys.exit(1) + print("Writing more than 8192 bytes damages the display!") + sys.exit(1) if have_pyhidapi: - pyhidapi.hid_write(dev, buf) + pyhidapi.hid_write(dev, buf) else: - for i in range(int(len(buf)/64)): - time.sleep(0.1) - dev.write(1, buf[i*64:i*64+64]) + for i in range(int(len(buf) / 64)): + time.sleep(0.1) + dev.write(1, buf[i * 64:i * 64 + 64]) if have_pyhidapi: - pyhidapi.hid_close(dev) + pyhidapi.hid_close(dev)