From f1849a5f88821a0ba997ba0f88818022ae874b3b Mon Sep 17 00:00:00 2001 From: Ben Date: Sun, 5 Nov 2023 21:55:07 +0100 Subject: [PATCH 01/14] Some small refactorings --- led-badge-11x44.py | 262 ++++++++++++++++++++++----------------------- 1 file changed, 131 insertions(+), 131 deletions(-) diff --git a/led-badge-11x44.py b/led-badge-11x44.py index f61c849..da45886 100755 --- a/led-badge-11x44.py +++ b/led-badge-11x44.py @@ -247,9 +247,9 @@ charmap = u'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + \ u"àäòöùüèéêëôöûîïÿç" + \ u"ÀÅÄÉÈÊËÖÔÜÛÙŸ" -char_offset = {} +char_offsets = {} for i in range(len(charmap)): - char_offset[charmap[i]] = 11 * i + char_offsets[charmap[i]] = 11 * i # print(i, charmap[i], char_offset[charmap[i]]) bitmap_preloaded = [([], 0)] @@ -320,7 +320,7 @@ def bitmap_char(ch): bitmaps_preloaded_unused = False return bitmap_preloaded[ord(ch)] - o = char_offset[ch] + o = char_offsets[ch] return (font_11x44[o:o + 11], 1) @@ -334,7 +334,7 @@ def bitmap_text(text): ":gfx/logo.png:" preloads the file gfx/logo.png and is replaced the corresponding control char. """ - def colonrepl(m): + def replace_symbolic(m): name = m.group(1) if name == '': return ':' @@ -343,10 +343,9 @@ def bitmap_text(text): if '.' in name: bitmap_preloaded.append(bitmap_img(name)) return chr(len(bitmap_preloaded) - 1) - b = bitmap_named[name] - return b[2] + return bitmap_named[name][2] - text = re.sub(r':([^:]*):', colonrepl, text) + text = re.sub(r':([^:]*):', replace_symbolic, text) buf = array('B') cols = 0 for c in text: @@ -391,7 +390,7 @@ def bitmap_img(file): def bitmap(arg): """ if arg is a valid and existing path name, we load it as an image. - Otherwise we take it as a string. + Otherwise, we take it as a string. """ if os.path.exists(arg): return bitmap_img(arg) @@ -406,7 +405,7 @@ proto_header = ( ) -def header(lengths, speeds, modes, blink, ants, brightness=100): +def header(lengths, speeds, modes, blink, ants, brightness=100, date=datetime.now()): """ lengths[0] is the number of chars of the first text Speeds come in as 1..8, but are needed 0..7 here. @@ -443,131 +442,132 @@ def header(lengths, speeds, modes, blink, ants, brightness=100): 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] = 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 return h -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('-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(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" - - 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() -if have_pyhidapi: - devinfo = pyhidapi.hid_enumerate(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)) + +if __name__ == '__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('-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(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" + + 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() + if have_pyhidapi: + devinfo = pyhidapi.hid_enumerate(0x0416, 0x5020) + # dev = pyhidapi.hid_open(0x0416, 0x5020) else: - print("No led tag with vendorID 0x0416 and productID 0x5020 found.") - print("Connect the led tag and run this tool as root.") - sys.exit(1) -else: - 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.") + 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)) + else: + print("No led tag with vendorID 0x0416 and productID 0x5020 found.") + print("Connect the led tag and run this tool as root.") + sys.exit(1) + else: + 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.") + 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)) + + if args.preload: + for file in args.preload: + bitmap_preloaded.append(bitmap_img(file)) + bitmaps_preloaded_unused = True + + msg_bitmaps = [] + for msg_arg in args.message: + msg_bitmaps.append(bitmap(msg_arg)) + + if bitmaps_preloaded_unused == True: + 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 o in reversed(range(1, int(len(msg_bitmap[0]) / 11) + 1)): + msg_bitmap[0][o * 11:o * 11] = array('B', [0]) + else: + print("Type: 11x44") + + buf = array('B') + buf.extend(header(list(map(lambda x: x[1], msg_bitmaps)), args.speed, args.mode, args.blink, args.ants, int(args.brightness))) + + for msg_bitmap in msg_bitmaps: + buf.extend(msg_bitmap[0]) + + need_padding = len(buf) % 64 + if need_padding: + buf.extend((0,) * (64 - need_padding)) + + # 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!") 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)) - -if args.preload: - for file in args.preload: - bitmap_preloaded.append(bitmap_img(file)) - bitmaps_preloaded_unused = True - -msgs = [] -for arg in args.message: - msgs.append(bitmap(arg)) - -if bitmaps_preloaded_unused == True: - 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 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]) -else: - print("Type: 11x44") - -buf = array('B') -buf.extend(header(list(map(lambda x: x[1], msgs)), args.speed, args.mode, args.blink, args.ants, int(args.brightness))) - -for msg in msgs: - buf.extend(msg[0]) - -needpadding = len(buf) % 64 -if 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!") - sys.exit(1) - -if have_pyhidapi: - 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]) - -if have_pyhidapi: - pyhidapi.hid_close(dev) + + if have_pyhidapi: + 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]) + + if have_pyhidapi: + pyhidapi.hid_close(dev) From 0d78c48a4cf28d1e342757fbfb730bb74ff8672a Mon Sep 17 00:00:00 2001 From: Ben Date: Sun, 5 Nov 2023 21:55:26 +0100 Subject: [PATCH 02/14] First unittests --- tests/test_led-badge-11x44.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 tests/test_led-badge-11x44.py diff --git a/tests/test_led-badge-11x44.py b/tests/test_led-badge-11x44.py new file mode 100644 index 0000000..a2d8afa --- /dev/null +++ b/tests/test_led-badge-11x44.py @@ -0,0 +1,35 @@ +from unittest import TestCase +import datetime +import importlib + +testee = importlib.import_module("led-badge-11x44") + + +class Test(TestCase): + def setUp(self): + self.test_date = datetime.datetime(2022, 11, 13, 17, 38, 24) + + def test_header_2msgs(self): + buf = testee.header((6,7), "5,3", "6,2", "0,1", "1,0", 75, self.test_date) + self.assertEqual([119, 97, 110, 103, 0, 16, 254, 1, 70, 34, 34, 34, 34, 34, 34, 34, 0, 6, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 11, 13, 17, 38, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], buf) + + def test_header_8msgs(self): + buf = testee.header((1,2,3,4,5,6,7,8), "1,2,3,4,5,6,7,8", "1,2,3,4,5,6,7,8", "0,1,0,1,0,1,0,1", "1,0,1,0,1,0,1,0", 25, self.test_date) + self.assertEqual([119, 97, 110, 103, 0, 64, 170, 85, 1, 18, 35, 52, 69, 86, 103, 120, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0, 0, 0, 0, 0, 0, 22, 11, 13, 17, 38, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], buf) + + def test_header_brightness(self): + buf = testee.header((6,), "4", "4", "0", "0", 25, self.test_date) + self.assertEqual([119, 97, 110, 103, 0, 64, 0, 0, 52, 52, 52, 52, 52, 52, 52, 52, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 11, 13, 17, 38, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], buf) + buf = testee.header((6,), "4", "4", "0", "0", 26, self.test_date) + self.assertEqual([119, 97, 110, 103, 0, 32, 0, 0, 52, 52, 52, 52, 52, 52, 52, 52, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 11, 13, 17, 38, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], buf) + buf = testee.header((6,), "4", "4", "0", "0", 60, self.test_date) + self.assertEqual([119, 97, 110, 103, 0, 16, 0, 0, 52, 52, 52, 52, 52, 52, 52, 52, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 11, 13, 17, 38, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], buf) + buf = testee.header((6,), "4", "4", "0", "0", 80, self.test_date) + self.assertEqual([119, 97, 110, 103, 0, 0, 0, 0, 52, 52, 52, 52, 52, 52, 52, 52, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 11, 13, 17, 38, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], buf) + + def test_header_date(self): + buf1 = testee.header((6,), "4", "4", "0", "0", 100, self.test_date) + buf2 = testee.header((6,), "4", "4", "0", "0", 100) + self.assertEqual(buf1[0:38], buf2[0:38]) + self.assertEqual(buf1[38+6:], buf2[38+6:]) + self.assertNotEqual(buf1[38:38+6], buf2[38:38+6]) From 745398399fb992fb7cc35323e40199dd131f98b2 Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 9 Nov 2023 07:21:07 +0100 Subject: [PATCH 03/14] More unittests --- tests/resources/bitpatterns.png | Bin 0 -> 133 bytes tests/test_led-badge-11x44.py | 55 +++++++++++++++++++++++++------- 2 files changed, 44 insertions(+), 11 deletions(-) create mode 100644 tests/resources/bitpatterns.png diff --git a/tests/resources/bitpatterns.png b/tests/resources/bitpatterns.png new file mode 100644 index 0000000000000000000000000000000000000000..03b7a9046caf53e60264e9a2266defa54985caf8 GIT binary patch literal 133 zcmeAS@N?(olHy`uVBq!ia0vp^5N+9QhawI1YXI-~Wy;IQ`6r6pbdQybW5{{whi|y>@iT+sN-VgN4`N#M# Date: Thu, 9 Nov 2023 08:35:52 +0100 Subject: [PATCH 04/14] Move processing of command line arguments from header() to main code, some clean code refactoring --- led-badge-11x44.py | 52 ++++++++++++++++++++--------------- tests/test_led-badge-11x44.py | 22 +++++++-------- 2 files changed, 41 insertions(+), 33 deletions(-) diff --git a/led-badge-11x44.py b/led-badge-11x44.py index da45886..43fe6b3 100755 --- a/led-badge-11x44.py +++ b/led-badge-11x44.py @@ -397,32 +397,30 @@ def bitmap(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 -) - +def expand_tuple(l): + l = l + (l[-1],) * (8 - len(l)) # repeat last element + return l -def header(lengths, speeds, modes, blink, ants, brightness=100, date=datetime.now()): +def header(lengths, speeds, modes, blinks, ants, brightness=100, date=datetime.now()): """ 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 - - b = [int(x) for x in re.split(r'[\s,]+', blink)] - b = b + [b[-1]] * (8 - len(b)) # repeat last element + 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 + ) - s = [int(x) - 1 for x in re.split(r'[\s,]+', speeds)] - s = s + [s[-1]] * (8 - len(s)) # repeat last element + ants = expand_tuple(ants) + blinks = expand_tuple(blinks) + speeds = expand_tuple(speeds) + modes = expand_tuple(modes) - m = [int(x) for x in re.split(r'[\s,]+', modes)] - m = m + [m[-1]] * (8 - len(m)) # repeat last element + speeds = [x - 1 for x in speeds] - h = list(proto_header) + h = list(protocol_header_template) if brightness <= 25: h[5] = 0x40 @@ -432,11 +430,11 @@ def header(lengths, speeds, modes, blink, ants, brightness=100, date=datetime.no h[5] = 0x10 for i in range(8): - h[6] += b[i] << i - h[7] += a[i] << i + h[6] += blinks[i] << i + h[7] += ants[i] << i for i in range(8): - h[8 + i] = 16 * s[i] + m[i] + h[8 + i] = 16 * speeds[i] + modes[i] for i in range(len(lengths)): h[17 + (2 * i) - 1] = lengths[i] // 256 @@ -452,6 +450,9 @@ def header(lengths, speeds, modes, blink, ants, brightness=100, date=datetime.no return h +def split_to_ints(list_str): + return [int(x) for x in re.split(r'[\s,]+', list_str)] + if __name__ == '__main__': parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter, @@ -546,8 +547,15 @@ if __name__ == '__main__': else: print("Type: 11x44") + lengths = [b[1] for b in msg_bitmaps] + speeds = split_to_ints(args.speeds) + 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(header(list(map(lambda x: x[1], msg_bitmaps)), args.speed, args.mode, args.blink, args.ants, int(args.brightness))) + buf.extend(header(lengths, speeds, modes, blinks, ants, brightness)) for msg_bitmap in msg_bitmaps: buf.extend(msg_bitmap[0]) diff --git a/tests/test_led-badge-11x44.py b/tests/test_led-badge-11x44.py index 12b7cf5..19b6a21 100644 --- a/tests/test_led-badge-11x44.py +++ b/tests/test_led-badge-11x44.py @@ -11,17 +11,17 @@ class Test(TestCase): self.test_date = datetime.datetime(2022, 11, 13, 17, 38, 24) def test_header_2msgs(self): - buf = testee.header((6, 7), "5,3", "6,2", "0,1", "1,0", 75, self.test_date) + buf = testee.header((6, 7), (5, 3), (6, 2), (0, 1), (1, 0), 75, self.test_date) self.assertEqual([119, 97, 110, 103, 0, 16, 254, 1, 70, 34, 34, 34, 34, 34, 34, 34, 0, 6, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 11, 13, 17, 38, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], buf) def test_header_8msgs(self): buf = testee.header((1, 2, 3, 4, 5, 6, 7, 8), - "1,2,3,4,5,6,7,8", - "1,2,3,4,5,6,7,8", - "0,1,0,1,0,1,0,1", - "1,0,1,0,1,0,1,0", + (1, 2, 3, 4, 5, 6, 7, 8), + (1, 2, 3, 4, 5, 6, 7, 8), + (0, 1, 0, 1, 0, 1, 0, 1), + (1, 0, 1, 0, 1, 0, 1, 0), 25, self.test_date) self.assertEqual([119, 97, 110, 103, 0, 64, 170, 85, 1, 18, 35, 52, 69, 86, 103, 120, 0, 1, 0, 2, 0, 3, 0, 4, 0, @@ -29,26 +29,26 @@ class Test(TestCase): 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], buf) def test_header_brightness(self): - buf = testee.header((6,), "4", "4", "0", "0", 25, self.test_date) + buf = testee.header((6,), (4,), (4,), (0,), (0,), 25, self.test_date) self.assertEqual([119, 97, 110, 103, 0, 64, 0, 0, 52, 52, 52, 52, 52, 52, 52, 52, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 11, 13, 17, 38, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], buf) - buf = testee.header((6,), "4", "4", "0", "0", 26, self.test_date) + buf = testee.header((6,), (4,), (4,), (0,), (0,), 26, self.test_date) self.assertEqual([119, 97, 110, 103, 0, 32, 0, 0, 52, 52, 52, 52, 52, 52, 52, 52, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 11, 13, 17, 38, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], buf) - buf = testee.header((6,), "4", "4", "0", "0", 60, self.test_date) + buf = testee.header((6,), (4,), (4,), (0,), (0,), 60, self.test_date) self.assertEqual([119, 97, 110, 103, 0, 16, 0, 0, 52, 52, 52, 52, 52, 52, 52, 52, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 11, 13, 17, 38, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], buf) - buf = testee.header((6,), "4", "4", "0", "0", 80, self.test_date) + buf = testee.header((6,), (4,), (4,), (0,), (0,), 80, self.test_date) self.assertEqual([119, 97, 110, 103, 0, 0, 0, 0, 52, 52, 52, 52, 52, 52, 52, 52, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 11, 13, 17, 38, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], buf) def test_header_date(self): - buf1 = testee.header((6,), "4", "4", "0", "0", 100, self.test_date) - buf2 = testee.header((6,), "4", "4", "0", "0", 100) + buf1 = testee.header((6,), (4,), (4,), (0,), (0,), 100, self.test_date) + buf2 = testee.header((6,), (4,), (4,), (0,), (0,), 100) self.assertEqual(buf1[0:38], buf2[0:38]) self.assertEqual(buf1[38 + 6:], buf2[38 + 6:]) self.assertNotEqual(buf1[38:38 + 6], buf2[38:38 + 6]) From 6b06508ef9581c2caec9a57cefdb253dd7cb748f Mon Sep 17 00:00:00 2001 From: Ben Date: Fri, 10 Nov 2023 06:54:55 +0100 Subject: [PATCH 05/14] Rearranging code: grouping by functionality parameter-processing, write-buffer-preparing, writing. The goal is a nice splitting into classes and methods, a bit more clean-codish and easy usage as a module. --- led-badge-11x44.py | 55 ++++++++++++++++++++-------------------------- 1 file changed, 24 insertions(+), 31 deletions(-) diff --git a/led-badge-11x44.py b/led-badge-11x44.py index 43fe6b3..65b9059 100755 --- a/led-badge-11x44.py +++ b/led-badge-11x44.py @@ -495,34 +495,6 @@ if __name__ == '__main__': (No "rotation" or "smoothing"(?) effect can be expected, though) """ % sys.argv[0]) args = parser.parse_args() - if have_pyhidapi: - devinfo = pyhidapi.hid_enumerate(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)) - else: - print("No led tag with vendorID 0x0416 and productID 0x5020 found.") - print("Connect the led tag and run this tool as root.") - sys.exit(1) - else: - 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.") - 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)) if args.preload: for file in args.preload: @@ -571,11 +543,32 @@ if __name__ == '__main__': sys.exit(1) if have_pyhidapi: + devinfo = pyhidapi.hid_enumerate(0x0416, 0x5020) + # dev = pyhidapi.hid_open(0x0416, 0x5020) + 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)) + else: + print("No led tag with vendorID 0x0416 and productID 0x5020 found.") + print("Connect the led tag and run this tool as root.") + sys.exit(1) pyhidapi.hid_write(dev, buf) + pyhidapi.hid_close(dev) else: + dev = 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.") + 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)) 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) From d79a15f119a48644b7b363e8f99f65f6465d6650 Mon Sep 17 00:00:00 2001 From: Ben Date: Sun, 12 Nov 2023 17:03:18 +0100 Subject: [PATCH 06/14] Rearranging code: new class LedNameBadge for the methods header() and write() (and the USB decision and initialization code), which are the ones, that other content creation sw might want to use. --- led-badge-11x44.py | 257 +++++++++++++++++++++++++-------------------- 1 file changed, 143 insertions(+), 114 deletions(-) diff --git a/led-badge-11x44.py b/led-badge-11x44.py index 65b9059..71d7daf 100755 --- a/led-badge-11x44.py +++ b/led-badge-11x44.py @@ -49,39 +49,22 @@ # 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 -import sys, os, re, time, argparse -from datetime import datetime +import argparse +import os +import re +import sys +import time from array import array +from datetime import datetime 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 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 - 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 with Linux or MacOS or help us implement support for """ + sys.platform) - sys.exit(1) + pass +try: + import usb.core +except: + pass __version = "0.12" @@ -311,7 +294,7 @@ def bitmap_char(ch): """ 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] @@ -397,62 +380,143 @@ def bitmap(arg): return bitmap_text(arg) -def expand_tuple(l): - l = l + (l[-1],) * (8 - len(l)) # repeat last element - return l - -def header(lengths, speeds, modes, blinks, ants, brightness=100, date=datetime.now()): - """ lengths[0] is the number of chars of the first text - - Speeds come in as 1..8, but are needed 0..7 here. - """ - protocol_header_template = ( +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 - ants = expand_tuple(ants) - blinks = expand_tuple(blinks) - speeds = expand_tuple(speeds) - modes = expand_tuple(modes) - - speeds = [x - 1 for x in speeds] - - h = list(protocol_header_template) - - 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] += 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 - - 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 + @staticmethod + def init_class(): + 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 pyhidapi: + pyhidapi.hid_init() + LedNameBadge._have_pyhidapi = True + print("Pyhidapi detected") + else: + raise Exception() + except Exception as e: + try: + if usb.core: + print("Pyusb detected") + else: + raise Exception() + 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 with Linux or MacOS or help us implement support for """ + sys.platform) + sys.exit(1) + + @staticmethod + def _expand_tuple(l): + l = l + (l[-1],) * (8 - len(l)) # repeat last element + return l + + @staticmethod + def header(lengths, speeds, modes, blinks, ants, brightness=100, date=datetime.now()): + """ lengths[0] is the number of chars of the first text + + Speeds come in as 1..8, but are needed 0..7 here. + """ + ants = LedNameBadge._expand_tuple(ants) + blinks = LedNameBadge._expand_tuple(blinks) + speeds = LedNameBadge._expand_tuple(speeds) + modes = LedNameBadge._expand_tuple(modes) + + 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 + + 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 + + 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 + + return h + + @staticmethod + def write(buf): + if len(buf) > 8192: + print("Writing more than 8192 bytes damages the display!") + sys.exit(1) - return h + if LedNameBadge._have_pyhidapi: + dev_info = pyhidapi.hid_enumerate(0x0416, 0x5020) + # dev = pyhidapi.hid_open(0x0416, 0x5020) + if dev_info: + dev = 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.") + sys.exit(1) + pyhidapi.hid_write(dev, buf) + pyhidapi.hid_close(dev) + else: + dev = 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.") + 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)) + for i in range(int(len(buf) / 64)): + time.sleep(0.1) + dev.write(1, buf[i * 64:i * 64 + 64]) + + +LedNameBadge.init_class() def split_to_ints(list_str): - return [int(x) for x in re.split(r'[\s,]+', list_str)] - + return tuple([int(x) for x in re.split(r'[\s,]+', list_str)]) if __name__ == '__main__': parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter, @@ -519,15 +583,15 @@ if __name__ == '__main__': else: print("Type: 11x44") - lengths = [b[1] for b in msg_bitmaps] - speeds = split_to_ints(args.speeds) + lengths = tuple([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(header(lengths, speeds, modes, blinks, ants, brightness)) + buf.extend(LedNameBadge.header(lengths, speeds, modes, blinks, ants, brightness)) for msg_bitmap in msg_bitmaps: buf.extend(msg_bitmap[0]) @@ -536,39 +600,4 @@ if __name__ == '__main__': if need_padding: buf.extend((0,) * (64 - need_padding)) - # 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!") - sys.exit(1) - - if have_pyhidapi: - devinfo = pyhidapi.hid_enumerate(0x0416, 0x5020) - # dev = pyhidapi.hid_open(0x0416, 0x5020) - 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)) - else: - print("No led tag with vendorID 0x0416 and productID 0x5020 found.") - print("Connect the led tag and run this tool as root.") - sys.exit(1) - pyhidapi.hid_write(dev, buf) - pyhidapi.hid_close(dev) - else: - dev = 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.") - 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)) - for i in range(int(len(buf) / 64)): - time.sleep(0.1) - dev.write(1, buf[i * 64:i * 64 + 64]) + LedNameBadge.write(buf) From 46574a7348140f6eb924ae81acbffd2109874f93 Mon Sep 17 00:00:00 2001 From: Ben Date: Sun, 12 Nov 2023 18:48:42 +0100 Subject: [PATCH 07/14] Rearranging code: correcting unittests --- led-badge-11x44.py | 2 ++ tests/test_led-badge-11x44.py | 16 ++++++++-------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/led-badge-11x44.py b/led-badge-11x44.py index 71d7daf..5fe2004 100755 --- a/led-badge-11x44.py +++ b/led-badge-11x44.py @@ -57,6 +57,8 @@ import time from array import array from datetime import datetime +# There are 2 possibilities for accessing the device: using hidapi oer usb-core. Here we just try to import both +# and decide later which one to use, see below in LedNameBadge._init_class(). try: import pyhidapi except: diff --git a/tests/test_led-badge-11x44.py b/tests/test_led-badge-11x44.py index 19b6a21..27d6999 100644 --- a/tests/test_led-badge-11x44.py +++ b/tests/test_led-badge-11x44.py @@ -11,13 +11,13 @@ class Test(TestCase): self.test_date = datetime.datetime(2022, 11, 13, 17, 38, 24) def test_header_2msgs(self): - buf = testee.header((6, 7), (5, 3), (6, 2), (0, 1), (1, 0), 75, self.test_date) + buf = testee.LedNameBadge.header((6, 7), (5, 3), (6, 2), (0, 1), (1, 0), 75, self.test_date) self.assertEqual([119, 97, 110, 103, 0, 16, 254, 1, 70, 34, 34, 34, 34, 34, 34, 34, 0, 6, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 11, 13, 17, 38, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], buf) def test_header_8msgs(self): - buf = testee.header((1, 2, 3, 4, 5, 6, 7, 8), + buf = testee.LedNameBadge.header((1, 2, 3, 4, 5, 6, 7, 8), (1, 2, 3, 4, 5, 6, 7, 8), (1, 2, 3, 4, 5, 6, 7, 8), (0, 1, 0, 1, 0, 1, 0, 1), @@ -29,26 +29,26 @@ class Test(TestCase): 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], buf) def test_header_brightness(self): - buf = testee.header((6,), (4,), (4,), (0,), (0,), 25, self.test_date) + buf = testee.LedNameBadge.header((6,), (4,), (4,), (0,), (0,), 25, self.test_date) self.assertEqual([119, 97, 110, 103, 0, 64, 0, 0, 52, 52, 52, 52, 52, 52, 52, 52, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 11, 13, 17, 38, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], buf) - buf = testee.header((6,), (4,), (4,), (0,), (0,), 26, self.test_date) + buf = testee.LedNameBadge.header((6,), (4,), (4,), (0,), (0,), 26, self.test_date) self.assertEqual([119, 97, 110, 103, 0, 32, 0, 0, 52, 52, 52, 52, 52, 52, 52, 52, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 11, 13, 17, 38, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], buf) - buf = testee.header((6,), (4,), (4,), (0,), (0,), 60, self.test_date) + buf = testee.LedNameBadge.header((6,), (4,), (4,), (0,), (0,), 60, self.test_date) self.assertEqual([119, 97, 110, 103, 0, 16, 0, 0, 52, 52, 52, 52, 52, 52, 52, 52, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 11, 13, 17, 38, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], buf) - buf = testee.header((6,), (4,), (4,), (0,), (0,), 80, self.test_date) + buf = testee.LedNameBadge.header((6,), (4,), (4,), (0,), (0,), 80, self.test_date) self.assertEqual([119, 97, 110, 103, 0, 0, 0, 0, 52, 52, 52, 52, 52, 52, 52, 52, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 11, 13, 17, 38, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], buf) def test_header_date(self): - buf1 = testee.header((6,), (4,), (4,), (0,), (0,), 100, self.test_date) - buf2 = testee.header((6,), (4,), (4,), (0,), (0,), 100) + buf1 = testee.LedNameBadge.header((6,), (4,), (4,), (0,), (0,), 100, self.test_date) + buf2 = testee.LedNameBadge.header((6,), (4,), (4,), (0,), (0,), 100) self.assertEqual(buf1[0:38], buf2[0:38]) self.assertEqual(buf1[38 + 6:], buf2[38 + 6:]) self.assertNotEqual(buf1[38:38 + 6], buf2[38:38 + 6]) From d13f9ebf9faa58c4fd124a355704314222faf85f Mon Sep 17 00:00:00 2001 From: Ben Date: Sun, 12 Nov 2023 18:54:52 +0100 Subject: [PATCH 08/14] Rearranging code: moving the content creation functions to a new class SimpleTextAndIcons to have them out of the way when importing only LedNameBadge. --- led-badge-11x44.py | 659 ++++++++++++++++++---------------- tests/test_led-badge-11x44.py | 30 +- 2 files changed, 363 insertions(+), 326 deletions(-) diff --git a/led-badge-11x44.py b/led-badge-11x44.py index 5fe2004..1143a9f 100755 --- a/led-badge-11x44.py +++ b/led-badge-11x44.py @@ -70,316 +70,334 @@ except: __version = "0.12" -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_offset[charmap[i]]) - -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, - 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 bitmap_char(ch): - """ 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] - - global bitmaps_preloaded_unused - bitmaps_preloaded_unused = False - return bitmap_preloaded[ord(ch)] - - o = char_offsets[ch] - return (font_11x44[o:o + 11], 1) - - -def bitmap_text(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: - bitmap_preloaded.append(bitmap_img(name)) - return chr(len(bitmap_preloaded) - 1) - return bitmap_named[name][2] - - text = re.sub(r':([^:]*):', replace_symbolic, 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) - """ - 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. - Otherwise, we take it as a string. - """ - if os.path.exists(arg): - return bitmap_img(arg) - return bitmap_text(arg) + +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_offset[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): + self.bitmap_preloaded.append(SimpleTextAndIcons.bitmap_img(filename)) + self.bitmaps_preloaded_unused = True + + + def are_preloaded_unused(self): + return self.bitmaps_preloaded_unused == True + + + @staticmethod + def get_named_bitmaps_keys(): + return SimpleTextAndIcons.bitmap_named.keys() + + + def bitmap_char(self, ch): + """ 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 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) + """ + 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(self, arg): + """ 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 SimpleTextAndIcons.bitmap_img(arg) + return self.bitmap_text(arg) class LedNameBadge: @@ -391,6 +409,7 @@ class LedNameBadge: ) _have_pyhidapi = False + @staticmethod def init_class(): try: @@ -429,11 +448,13 @@ or print("""Please with Linux or MacOS or help us implement support for """ + sys.platform) sys.exit(1) + @staticmethod def _expand_tuple(l): l = l + (l[-1],) * (8 - len(l)) # repeat last element return l + @staticmethod def header(lengths, speeds, modes, blinks, ants, brightness=100, date=datetime.now()): """ lengths[0] is the number of chars of the first text @@ -476,6 +497,7 @@ or return h + @staticmethod def write(buf): if len(buf) > 8192: @@ -536,7 +558,7 @@ if __name__ == '__main__': 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:') + 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=""" @@ -562,16 +584,17 @@ if __name__ == '__main__': """ % sys.argv[0]) args = parser.parse_args() + creator = SimpleTextAndIcons() + if args.preload: - for file in args.preload: - bitmap_preloaded.append(bitmap_img(file)) - bitmaps_preloaded_unused = True + for filename in args.preload: + creator.add_preload_img(filename) msg_bitmaps = [] for msg_arg in args.message: - msg_bitmaps.append(bitmap(msg_arg)) + msg_bitmaps.append(creator.bitmap(msg_arg)) - if bitmaps_preloaded_unused == True: + 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") @@ -580,8 +603,8 @@ if __name__ == '__main__': for msg_bitmap in msg_bitmaps: # trivial hack to support 12x48 badges: # patch extra empty lines into the message stream. - for o in reversed(range(1, int(len(msg_bitmap[0]) / 11) + 1)): - msg_bitmap[0][o * 11:o * 11] = array('B', [0]) + 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") diff --git a/tests/test_led-badge-11x44.py b/tests/test_led-badge-11x44.py index 27d6999..efd8c0f 100644 --- a/tests/test_led-badge-11x44.py +++ b/tests/test_led-badge-11x44.py @@ -18,12 +18,12 @@ class Test(TestCase): def test_header_8msgs(self): buf = testee.LedNameBadge.header((1, 2, 3, 4, 5, 6, 7, 8), - (1, 2, 3, 4, 5, 6, 7, 8), - (1, 2, 3, 4, 5, 6, 7, 8), - (0, 1, 0, 1, 0, 1, 0, 1), - (1, 0, 1, 0, 1, 0, 1, 0), - 25, - self.test_date) + (1, 2, 3, 4, 5, 6, 7, 8), + (1, 2, 3, 4, 5, 6, 7, 8), + (0, 1, 0, 1, 0, 1, 0, 1), + (1, 0, 1, 0, 1, 0, 1, 0), + 25, + self.test_date) self.assertEqual([119, 97, 110, 103, 0, 64, 170, 85, 1, 18, 35, 52, 69, 86, 103, 120, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0, 0, 0, 0, 0, 0, 22, 11, 13, 17, 38, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], buf) @@ -54,15 +54,29 @@ class Test(TestCase): self.assertNotEqual(buf1[38:38 + 6], buf2[38:38 + 6]) def test_bitmap_png(self): - buf = testee.bitmap("resources/bitpatterns.png") + creator = testee.SimpleTextAndIcons() + buf = creator.bitmap("resources/bitpatterns.png") self.assertEqual((array('B', [128, 64, 32, 16, 8, 4, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 64, 32, 0, 1, 2, 3, 4, 5, 15, 31, 63, 127, 255]), 3), buf) def test_bitmap_text(self): - buf = testee.bitmap("/:HEART2:\\") + creator = testee.SimpleTextAndIcons() + buf = creator.bitmap("/:HEART2:\\") self.assertEqual((array('B', [0, 0, 2, 6, 12, 24, 48, 96, 192, 128, 0, 0, 12, 30, 63, 63, 63, 31, 15, 7, 3, 1, 0, 96, 240, 248, 248, 248, 240, 224, 192, 128, 0, 0, 128, 192, 96, 48, 24, 12, 6, 2, 0, 0]), 4), buf) + + def test_preload(self): + creator = testee.SimpleTextAndIcons() + self.assertFalse(creator.are_preloaded_unused()) + creator.add_preload_img("resources/bitpatterns.png") + self.assertTrue(creator.are_preloaded_unused()) + buf = creator.bitmap("\x01") + self.assertFalse(creator.are_preloaded_unused()) + self.assertEqual((array('B', + [128, 64, 32, 16, 8, 4, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 64, 32, 0, 1, 2, 3, + 4, 5, 15, 31, 63, 127, 255]), + 3), buf) From 67738e87da0a573718056a794ac41cfc1c3e119a Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 13 Nov 2023 07:02:14 +0100 Subject: [PATCH 09/14] Rearranging code: code for USB importing libraries directly in class instead of method, that's less structured but easier. --- led-badge-11x44.py | 68 ++++++++++++++++------------------------------ 1 file changed, 24 insertions(+), 44 deletions(-) diff --git a/led-badge-11x44.py b/led-badge-11x44.py index 1143a9f..71a09bf 100755 --- a/led-badge-11x44.py +++ b/led-badge-11x44.py @@ -57,16 +57,6 @@ import time from array import array from datetime import datetime -# There are 2 possibilities for accessing the device: using hidapi oer usb-core. Here we just try to import both -# and decide later which one to use, see below in LedNameBadge._init_class(). -try: - import pyhidapi -except: - pass -try: - import usb.core -except: - pass __version = "0.12" @@ -409,34 +399,27 @@ class LedNameBadge: ) _have_pyhidapi = False - - @staticmethod - def init_class(): + 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 + print("Pyhidapi detected") + except Exception: 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 pyhidapi: - pyhidapi.hid_init() - LedNameBadge._have_pyhidapi = True - print("Pyhidapi detected") - else: - raise Exception() - except Exception as e: - try: - if usb.core: - print("Pyusb detected") - else: - raise Exception() - except: - print("ERROR: Need the pyhidapi or usb.core module.") - if sys.platform == "darwin": - print("""Please try + import usb.core + print("Pyusb detected") + 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 @@ -444,9 +427,9 @@ class LedNameBadge: 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) @staticmethod @@ -505,20 +488,20 @@ or sys.exit(1) if LedNameBadge._have_pyhidapi: - dev_info = pyhidapi.hid_enumerate(0x0416, 0x5020) + dev_info = LedNameBadge.pyhidapi.hid_enumerate(0x0416, 0x5020) # dev = pyhidapi.hid_open(0x0416, 0x5020) if dev_info: - dev = pyhidapi.hid_open_path(dev_info[0].path) + 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.") sys.exit(1) - pyhidapi.hid_write(dev, buf) - pyhidapi.hid_close(dev) + LedNameBadge.pyhidapi.hid_write(dev, buf) + LedNameBadge.pyhidapi.hid_close(dev) else: - dev = usb.core.find(idVendor=0x0416, idProduct=0x5020) + 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.") @@ -536,9 +519,6 @@ or dev.write(1, buf[i * 64:i * 64 + 64]) -LedNameBadge.init_class() - - def split_to_ints(list_str): return tuple([int(x) for x in re.split(r'[\s,]+', list_str)]) From 2e51a018d288c08025af789289e68ed9f4385423 Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 13 Nov 2023 07:20:29 +0100 Subject: [PATCH 10/14] Rearranging code: file renaming for better "importability" --- led-badge-11x44.py => lednamebadge.py | 0 tests/{test_led-badge-11x44.py => test_lednamebadge_write.py} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename led-badge-11x44.py => lednamebadge.py (100%) rename tests/{test_led-badge-11x44.py => test_lednamebadge_write.py} (100%) diff --git a/led-badge-11x44.py b/lednamebadge.py similarity index 100% rename from led-badge-11x44.py rename to lednamebadge.py diff --git a/tests/test_led-badge-11x44.py b/tests/test_lednamebadge_write.py similarity index 100% rename from tests/test_led-badge-11x44.py rename to tests/test_lednamebadge_write.py From e206fa903dac1f82349c9af4d391a3bb0e558a10 Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 13 Nov 2023 07:33:54 +0100 Subject: [PATCH 11/14] Rearranging code: corrections and splitting tests after file renaming --- led-badge-12x48.py | 1 - lednamebadge.py | 7 +++- tests/test_lednamebadge_create.py | 35 ++++++++++++++++++ tests/test_lednamebadge_write.py | 59 ++++++++----------------------- 4 files changed, 56 insertions(+), 46 deletions(-) delete mode 120000 led-badge-12x48.py create mode 100644 tests/test_lednamebadge_create.py diff --git a/led-badge-12x48.py b/led-badge-12x48.py deleted file mode 120000 index db1bcd2..0000000 --- a/led-badge-12x48.py +++ /dev/null @@ -1 +0,0 @@ -led-badge-11x44.py \ No newline at end of file diff --git a/lednamebadge.py b/lednamebadge.py index 71a09bf..d7d5d90 100755 --- a/lednamebadge.py +++ b/lednamebadge.py @@ -225,6 +225,7 @@ class SimpleTextAndIcons: u"ÀÅÄÉÈÊËÖÔÜÛÙŸ" char_offsets = {} + pass for i in range(len(charmap)): char_offsets[charmap[i]] = 11 * i # print(i, charmap[i], char_offset[charmap[i]]) @@ -522,7 +523,7 @@ or def split_to_ints(list_str): return tuple([int(x) for x in re.split(r'[\s,]+', list_str)]) -if __name__ == '__main__': +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]) @@ -606,3 +607,7 @@ if __name__ == '__main__': buf.extend((0,) * (64 - need_padding)) LedNameBadge.write(buf) + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/tests/test_lednamebadge_create.py b/tests/test_lednamebadge_create.py new file mode 100644 index 0000000..12de16a --- /dev/null +++ b/tests/test_lednamebadge_create.py @@ -0,0 +1,35 @@ +import datetime +from array import array +from unittest import TestCase + +from lednamebadge import SimpleTextAndIcons as testee + + +class Test(TestCase): + def test_bitmap_png(self): + creator = testee() + buf = creator.bitmap("resources/bitpatterns.png") + self.assertEqual((array('B', + [128, 64, 32, 16, 8, 4, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 64, 32, 0, 1, 2, 3, + 4, 5, 15, 31, 63, 127, 255]), + 3), buf) + + def test_bitmap_text(self): + creator = testee() + buf = creator.bitmap("/:HEART2:\\") + self.assertEqual((array('B', + [0, 0, 2, 6, 12, 24, 48, 96, 192, 128, 0, 0, 12, 30, 63, 63, 63, 31, 15, 7, 3, 1, 0, 96, + 240, 248, 248, 248, 240, 224, 192, 128, 0, 0, 128, 192, 96, 48, 24, 12, 6, 2, 0, 0]), + 4), buf) + + def test_preload(self): + creator = testee() + self.assertFalse(creator.are_preloaded_unused()) + creator.add_preload_img("resources/bitpatterns.png") + self.assertTrue(creator.are_preloaded_unused()) + buf = creator.bitmap("\x01") + self.assertFalse(creator.are_preloaded_unused()) + self.assertEqual((array('B', + [128, 64, 32, 16, 8, 4, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 64, 32, 0, 1, 2, 3, + 4, 5, 15, 31, 63, 127, 255]), + 3), buf) diff --git a/tests/test_lednamebadge_write.py b/tests/test_lednamebadge_write.py index efd8c0f..af62a59 100644 --- a/tests/test_lednamebadge_write.py +++ b/tests/test_lednamebadge_write.py @@ -1,9 +1,8 @@ import datetime -import importlib from array import array from unittest import TestCase -testee = importlib.import_module("led-badge-11x44") +from lednamebadge import LedNameBadge as testee class Test(TestCase): @@ -11,72 +10,44 @@ class Test(TestCase): self.test_date = datetime.datetime(2022, 11, 13, 17, 38, 24) def test_header_2msgs(self): - buf = testee.LedNameBadge.header((6, 7), (5, 3), (6, 2), (0, 1), (1, 0), 75, self.test_date) + buf = testee.header((6, 7), (5, 3), (6, 2), (0, 1), (1, 0), 75, self.test_date) self.assertEqual([119, 97, 110, 103, 0, 16, 254, 1, 70, 34, 34, 34, 34, 34, 34, 34, 0, 6, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 11, 13, 17, 38, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], buf) def test_header_8msgs(self): - buf = testee.LedNameBadge.header((1, 2, 3, 4, 5, 6, 7, 8), - (1, 2, 3, 4, 5, 6, 7, 8), - (1, 2, 3, 4, 5, 6, 7, 8), - (0, 1, 0, 1, 0, 1, 0, 1), - (1, 0, 1, 0, 1, 0, 1, 0), - 25, - self.test_date) + buf = testee.header((1, 2, 3, 4, 5, 6, 7, 8), + (1, 2, 3, 4, 5, 6, 7, 8), + (1, 2, 3, 4, 5, 6, 7, 8), + (0, 1, 0, 1, 0, 1, 0, 1), + (1, 0, 1, 0, 1, 0, 1, 0), + 25, + self.test_date) self.assertEqual([119, 97, 110, 103, 0, 64, 170, 85, 1, 18, 35, 52, 69, 86, 103, 120, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0, 0, 0, 0, 0, 0, 22, 11, 13, 17, 38, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], buf) def test_header_brightness(self): - buf = testee.LedNameBadge.header((6,), (4,), (4,), (0,), (0,), 25, self.test_date) + buf = testee.header((6,), (4,), (4,), (0,), (0,), 25, self.test_date) self.assertEqual([119, 97, 110, 103, 0, 64, 0, 0, 52, 52, 52, 52, 52, 52, 52, 52, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 11, 13, 17, 38, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], buf) - buf = testee.LedNameBadge.header((6,), (4,), (4,), (0,), (0,), 26, self.test_date) + buf = testee.header((6,), (4,), (4,), (0,), (0,), 26, self.test_date) self.assertEqual([119, 97, 110, 103, 0, 32, 0, 0, 52, 52, 52, 52, 52, 52, 52, 52, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 11, 13, 17, 38, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], buf) - buf = testee.LedNameBadge.header((6,), (4,), (4,), (0,), (0,), 60, self.test_date) + buf = testee.header((6,), (4,), (4,), (0,), (0,), 60, self.test_date) self.assertEqual([119, 97, 110, 103, 0, 16, 0, 0, 52, 52, 52, 52, 52, 52, 52, 52, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 11, 13, 17, 38, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], buf) - buf = testee.LedNameBadge.header((6,), (4,), (4,), (0,), (0,), 80, self.test_date) + buf = testee.header((6,), (4,), (4,), (0,), (0,), 80, self.test_date) self.assertEqual([119, 97, 110, 103, 0, 0, 0, 0, 52, 52, 52, 52, 52, 52, 52, 52, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 11, 13, 17, 38, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], buf) def test_header_date(self): - buf1 = testee.LedNameBadge.header((6,), (4,), (4,), (0,), (0,), 100, self.test_date) - buf2 = testee.LedNameBadge.header((6,), (4,), (4,), (0,), (0,), 100) + buf1 = testee.header((6,), (4,), (4,), (0,), (0,), 100, self.test_date) + buf2 = testee.header((6,), (4,), (4,), (0,), (0,), 100) self.assertEqual(buf1[0:38], buf2[0:38]) self.assertEqual(buf1[38 + 6:], buf2[38 + 6:]) self.assertNotEqual(buf1[38:38 + 6], buf2[38:38 + 6]) - - def test_bitmap_png(self): - creator = testee.SimpleTextAndIcons() - buf = creator.bitmap("resources/bitpatterns.png") - self.assertEqual((array('B', - [128, 64, 32, 16, 8, 4, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 64, 32, 0, 1, 2, 3, - 4, 5, 15, 31, 63, 127, 255]), - 3), buf) - - def test_bitmap_text(self): - creator = testee.SimpleTextAndIcons() - buf = creator.bitmap("/:HEART2:\\") - self.assertEqual((array('B', - [0, 0, 2, 6, 12, 24, 48, 96, 192, 128, 0, 0, 12, 30, 63, 63, 63, 31, 15, 7, 3, 1, 0, 96, - 240, 248, 248, 248, 240, 224, 192, 128, 0, 0, 128, 192, 96, 48, 24, 12, 6, 2, 0, 0]), - 4), buf) - - def test_preload(self): - creator = testee.SimpleTextAndIcons() - self.assertFalse(creator.are_preloaded_unused()) - creator.add_preload_img("resources/bitpatterns.png") - self.assertTrue(creator.are_preloaded_unused()) - buf = creator.bitmap("\x01") - self.assertFalse(creator.are_preloaded_unused()) - self.assertEqual((array('B', - [128, 64, 32, 16, 8, 4, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 64, 32, 0, 1, 2, 3, - 4, 5, 15, 31, 63, 127, 255]), - 3), buf) From 2d96e0d41adddb37e9f67175629be4075c5bdc21 Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 13 Nov 2023 07:48:39 +0100 Subject: [PATCH 12/14] Rearranging code: re-introduce old script names after file renaming, for compatibility --- led-badge-11x44.py | 3 +++ led-badge-12x48.py | 1 + lednamebadge.py | 1 + 3 files changed, 5 insertions(+) create mode 100644 led-badge-11x44.py create mode 120000 led-badge-12x48.py diff --git a/led-badge-11x44.py b/led-badge-11x44.py new file mode 100644 index 0000000..582349c --- /dev/null +++ b/led-badge-11x44.py @@ -0,0 +1,3 @@ +import lednamebadge + +lednamebadge.main() diff --git a/led-badge-12x48.py b/led-badge-12x48.py new file mode 120000 index 0000000..db1bcd2 --- /dev/null +++ b/led-badge-12x48.py @@ -0,0 +1 @@ +led-badge-11x44.py \ No newline at end of file diff --git a/lednamebadge.py b/lednamebadge.py index d7d5d90..c176410 100755 --- a/lednamebadge.py +++ b/lednamebadge.py @@ -402,6 +402,7 @@ class LedNameBadge: 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() From 012d4415bdc0aed4b9ff73493afab6ebfd6d7233 Mon Sep 17 00:00:00 2001 From: Ben Date: Sat, 18 Nov 2023 17:46:49 +0100 Subject: [PATCH 13/14] Rearranging code: cleanup, documentation, version number increment. --- README.md | 110 +++++++++++++++++++++++++++- lednamebadge.py | 117 +++++++++++++++++++++--------- photos/bitmap_data_all.png | Bin 0 -> 2620 bytes photos/bitmap_data_descr.puml | 35 +++++++++ photos/bitmap_data_onebyte.png | Bin 0 -> 3286 bytes photos/bitmap_data_onescene.png | Bin 0 -> 5932 bytes tests/run_tests.py | 8 ++ tests/test_lednamebadge_create.py | 1 - tests/test_lednamebadge_write.py | 28 ++++++- 9 files changed, 260 insertions(+), 39 deletions(-) create mode 100644 photos/bitmap_data_all.png create mode 100644 photos/bitmap_data_descr.puml create mode 100644 photos/bitmap_data_onebyte.png create mode 100644 photos/bitmap_data_onescene.png create mode 100644 tests/run_tests.py diff --git a/README.md b/README.md index 815c22d..d68cba7 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Led-Badge-44x11 -Upload tool for an led name tag with USB-HID interface +Upload tool for a LED name tag with USB-HID interface ![LED Mini Board](photos/blueBadge.jpg) @@ -25,7 +25,7 @@ In both configurations, the badge identifies itself on the USB as ## Warning There are many different versions of LED Badges on the market. -This one uses an USB-HID interface, while others use USB-Serial (see references below). +This one uses a USB-HID interface, while others use USB-Serial (see references below). ## Command Line Installation and Usage @@ -173,10 +173,116 @@ Example combining image and text: sudo ./led-badge-11x44.py "I:HEART2:you" +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 ### 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. +## 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. + +- create the header +- append your own content +- write to device + +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 + 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. + +Your own content has to be a byte array with the bitmap data for all scenes. Of course, it has to fit the given lengths. + +See the following graphic for better understanding: +![bitmap_data_onebyte.png](photos%2Fbitmap_data_onebyte.png) + +![bitmap_data_onescene.png](photos%2Fbitmap_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) + +Example: + +Let's say, you have 2 scenes, one is 11x32 pixels, one is 11x60 pixels. So, the first have 4 byte-columns and 44 bytes, +the second has to be padded with 4 empty bit-columns in the last byte-column to 11x64 pixels and has therefore +8 byte-columns and 88 bytes. + +We like to display both in mode 4, the first one with speed 3 and the second one with speed 2 and the second one shall +be displayed with ants. And we like to set the initial brightness to 50%. + +This would be achieved by these calls: + +```python +from lednamebadge import LedNameBadge + +buf = array('B') +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) +``` + +### Using the text generation + +You can also use the text/icon/graphic generation of this module to get the corresponding byte buffers. + +This is quite simple and just like with the command line usage. There is the additional option to create a bitmap just +and only from an image file by giving the filename instead of a message. + +```python +from lednamebadge import SimpleTextAndIcons + +creator = SimpleTextAndIcons() +scene_a_bitmap = creator.bitmap("Hello :HEART2: World!") +scene_b_bitmap = creator.bitmap("As you :gfx/bicycle3.png: like...") +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: + +```python +from lednamebadge import * + +creator = SimpleTextAndIcons() +scene_x_bitmap = creator.bitmap("Hello :HEART2: World!") +scene_y_bitmap = creator.bitmap("Complete example ahead.") +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(scene_x_bitmap[0]) +buf.extend(scene_y_bitmap[0]) +buf.extend(your_own_stuff.bytes) +LedNameBadge.write(buf) +``` + +## Development + +### Generating Plantuml graphics + +You will need PlantUML and potentially GraphViz dot to generate the diagrams from the *.puml files. + +Just run `plantuml "*.puml"` from the `photos` directory to regenerate all diagrams. + +### Running the unit tests + +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/ diff --git a/lednamebadge.py b/lednamebadge.py index c176410..dd0dc4a 100755 --- a/lednamebadge.py +++ b/lednamebadge.py @@ -48,6 +48,19 @@ # 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! + import argparse import os @@ -58,7 +71,7 @@ from array import array from datetime import datetime -__version = "0.12" +__version = "0.13" class SimpleTextAndIcons: @@ -225,7 +238,6 @@ class SimpleTextAndIcons: u"ÀÅÄÉÈÊËÖÔÜÛÙŸ" char_offsets = {} - pass for i in range(len(charmap)): char_offsets[charmap[i]] = 11 * i # print(i, charmap[i], char_offset[charmap[i]]) @@ -288,23 +300,25 @@ class SimpleTextAndIcons: 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 == True @staticmethod - def get_named_bitmaps_keys(): + def _get_named_bitmaps_keys(): return SimpleTextAndIcons.bitmap_named.keys() def bitmap_char(self, ch): - """ 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. + """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: @@ -318,7 +332,7 @@ class SimpleTextAndIcons: def bitmap_text(self, 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. @@ -350,7 +364,10 @@ class SimpleTextAndIcons: @staticmethod def bitmap_img(file): - """ returns a tuple of (buffer, length_in_byte_columns) + """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. """ from PIL import Image @@ -383,8 +400,8 @@ class SimpleTextAndIcons: 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. + """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) @@ -408,7 +425,7 @@ class LedNameBadge: pyhidapi.hid_init() _have_pyhidapi = True print("Pyhidapi detected") - except Exception: + except: try: import usb.core print("Pyusb detected") @@ -429,27 +446,47 @@ class LedNameBadge: or sudo apt-get install python3-usb """) - else: # windows? + else: # windows? print("""Please with Linux or MacOS or help us implement support for """ + sys.platform) sys.exit(1) @staticmethod - def _expand_tuple(l): - l = l + (l[-1],) * (8 - len(l)) # repeat last element - return l + 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 header(lengths, speeds, modes, blinks, ants, brightness=100, date=datetime.now()): - """ lengths[0] is the number of chars of the first text - - Speeds come in as 1..8, but are needed 0..7 here. + """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. """ - ants = LedNameBadge._expand_tuple(ants) - blinks = LedNameBadge._expand_tuple(blinks) - speeds = LedNameBadge._expand_tuple(speeds) - modes = LedNameBadge._expand_tuple(modes) + 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] @@ -461,6 +498,7 @@ or h[5] = 0x20 elif brightness <= 75: h[5] = 0x10 + # else default 100% == 0x00 for i in range(8): h[6] += blinks[i] << i @@ -473,18 +511,31 @@ or h[17 + (2 * i) - 1] = lengths[i] // 256 h[17 + (2 * i)] = lengths[i] % 256 - 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 + 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 write(buf): + """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. + """ + need_padding = len(buf) % 64 + if need_padding: + buf.extend((0,) * (64 - need_padding)) + if len(buf) > 8192: print("Writing more than 8192 bytes damages the display!") sys.exit(1) @@ -522,7 +573,7 @@ or def split_to_ints(list_str): - return tuple([int(x) for x in re.split(r'[\s,]+', list_str)]) + return [int(x) for x in re.split(r'[\s,]+', list_str)] def main(): parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter, @@ -540,7 +591,7 @@ def main(): 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:') + 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=""" @@ -590,7 +641,7 @@ def main(): else: print("Type: 11x44") - lengths = tuple([b[1] for b in msg_bitmaps]) + 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) @@ -603,10 +654,6 @@ def main(): for msg_bitmap in msg_bitmaps: buf.extend(msg_bitmap[0]) - need_padding = len(buf) % 64 - if need_padding: - buf.extend((0,) * (64 - need_padding)) - LedNameBadge.write(buf) diff --git a/photos/bitmap_data_all.png b/photos/bitmap_data_all.png new file mode 100644 index 0000000000000000000000000000000000000000..fbcee1d9cc8602c8a2bbbe2d7de421571276d1a8 GIT binary patch literal 2620 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 literal 0 HcmV?d00001 diff --git a/photos/bitmap_data_descr.puml b/photos/bitmap_data_descr.puml new file mode 100644 index 0000000..7268993 --- /dev/null +++ b/photos/bitmap_data_descr.puml @@ -0,0 +1,35 @@ +@startuml bitmap_data_onebyte +salt +title One byte +{# + { most significant bit 7 + leftmost pixel } | bit 6 | ... | bit 1 | { least significant bit 0 + rightmost pixel } +} +@enduml + +@startuml bitmap_data_onescene +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 ... | ... +} +@enduml + +@startuml bitmap_data_all +salt +title Complete bitmap data +{# + scene 0 == x bytes | ... | scene n == z bytes +} +@enduml diff --git a/photos/bitmap_data_onebyte.png b/photos/bitmap_data_onebyte.png new file mode 100644 index 0000000000000000000000000000000000000000..6aca0be614eb72b6f20075103abf3907483a6f8a GIT binary patch literal 3286 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 literal 0 HcmV?d00001 diff --git a/tests/run_tests.py b/tests/run_tests.py new file mode 100644 index 0000000..7d6c8c7 --- /dev/null +++ b/tests/run_tests.py @@ -0,0 +1,8 @@ +import sys +import unittest + +if __name__ == '__main__': + sys.path.append("..") + suite = unittest.TestLoader().discover(".") + unittest.TextTestRunner().run(suite) + diff --git a/tests/test_lednamebadge_create.py b/tests/test_lednamebadge_create.py index 12de16a..cff662e 100644 --- a/tests/test_lednamebadge_create.py +++ b/tests/test_lednamebadge_create.py @@ -1,4 +1,3 @@ -import datetime from array import array from unittest import TestCase diff --git a/tests/test_lednamebadge_write.py b/tests/test_lednamebadge_write.py index af62a59..ac5f664 100644 --- a/tests/test_lednamebadge_write.py +++ b/tests/test_lednamebadge_write.py @@ -1,5 +1,4 @@ import datetime -from array import array from unittest import TestCase from lednamebadge import LedNameBadge as testee @@ -9,6 +8,27 @@ class Test(TestCase): def setUp(self): self.test_date = datetime.datetime(2022, 11, 13, 17, 38, 24) + def test_prepare_collection_expand(self): + self.assertEqual((1, 1, 1, 1, 1, 1, 1, 1), testee._prepare_iterable((1,), 1, 8)) + self.assertEqual((1, 2, 3, 4, 4, 4, 4, 4), testee._prepare_iterable([1, 2, 3, 4], 1, 8)) + self.assertEqual((1, 2, 3, 4, 5, 6, 7, 8), testee._prepare_iterable((1, 2, 3, 4, 5, 6, 7, 8), 1, 8)) + # Weired, but possible: + self.assertEqual(('A', 'B', 'C', 'D', 'D', 'D', 'D', 'D'), testee._prepare_iterable("ABCD", 'A', 'Z')) + self.assertEqual((True, False, True, True, True, True, True, True), testee._prepare_iterable((True, False, True), False, True)) + + def test_prepare_collection_limit(self): + self.assertEqual((1, 8, 8, 8, 8, 8, 8, 8), testee._prepare_iterable([-1, 9], 1, 8)) + # Weired, but possible: + self.assertEqual(('C', 'C', 'C', 'D', 'E', 'F', 'F', 'F'), testee._prepare_iterable("ABCDEFGH", 'C', 'F')) + self.assertEqual((True, 1, True, True, True, True, True, True), testee._prepare_iterable((True, False, True), 1, 8)) + self.assertEqual((0, False, 0, 0, 0, 0, 0, 0), testee._prepare_iterable((True, False, True), -2, 0)) + + def test_prepare_collection_type(self): + with self.assertRaises(TypeError): + testee._prepare_iterable(4, 1, 8) + with self.assertRaises(TypeError): + testee._prepare_iterable([], 1, 8) + def test_header_2msgs(self): buf = testee.header((6, 7), (5, 3), (6, 2), (0, 1), (1, 0), 75, self.test_date) self.assertEqual([119, 97, 110, 103, 0, 16, 254, 1, 70, 34, 34, 34, 34, 34, 34, 34, 0, 6, 0, 7, 0, 0, 0, 0, 0, @@ -51,3 +71,9 @@ class Test(TestCase): self.assertEqual(buf1[0:38], buf2[0:38]) self.assertEqual(buf1[38 + 6:], buf2[38 + 6:]) self.assertNotEqual(buf1[38:38 + 6], buf2[38:38 + 6]) + + def test_header_type(self): + with self.assertRaises(TypeError): + testee.header(("nan",), (4,), (4,), (0,), (0,), 80, self.test_date) + with self.assertRaises(ValueError): + testee.header((370,380), (4,), (4,), (0,), (0,), 80, self.test_date) From d780517d37dd9c2db046fdda3a469aa26b74b531 Mon Sep 17 00:00:00 2001 From: Ben Date: Sat, 18 Nov 2023 17:58:28 +0100 Subject: [PATCH 14/14] Rearranging code: cleanup, documentation, version number increment. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d68cba7..38eaf7a 100644 --- a/README.md +++ b/README.md @@ -206,6 +206,7 @@ The method `header()` takes a number of parameters: Your own content has to be a byte array with the bitmap data for all scenes. Of course, it has to fit the given lengths. See the following graphic for better understanding: + ![bitmap_data_onebyte.png](photos%2Fbitmap_data_onebyte.png) ![bitmap_data_onescene.png](photos%2Fbitmap_data_onescene.png)