Created
January 8, 2026 15:10
-
-
Save NicolaiSoeborg/81a65703d16a81d24d6b225bfe646ba1 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import asyncio | |
| from datetime import datetime | |
| from bleak import BleakClient, BleakScanner | |
| #devs = await BleakScanner.discover(timeout=10.0, service_uuids=['0000fee0-0000-1000-8000-00805f9b34fb']) | |
| #assert len(devs) == 1 | |
| device_address = 'XX:XX:XX:XX:XX:XX' | |
| # From https://github.com/fossasia/badgemagic-app/blob/development/lib/bademagic_module/utils/data_to_bytearray_converter.dart | |
| char_codes = { | |
| '0': "007CC6CEDEF6E6C6C67C00", | |
| '1': "0018387818181818187E00", | |
| '2': "007CC6060C183060C6FE00", | |
| '3': "007CC606063C0606C67C00", | |
| '4': "000C1C3C6CCCFE0C0C1E00", | |
| '5': "00FEC0C0FC060606C67C00", | |
| '6': "007CC6C0C0FCC6C6C67C00", | |
| '7': "00FEC6060C183030303000", | |
| '8': "007CC6C6C67CC6C6C67C00", | |
| '9': "007CC6C6C67E0606C67C00", | |
| '#': "006C6CFE6C6CFE6C6C0000", | |
| '&': "00386C6C3876DCCCCC7600", | |
| '_': "00000000000000000000FF", | |
| '-': "0000000000FE0000000000", | |
| '?': "007CC6C60C181800181800", | |
| '@': "00003C429DA5ADB6403C00", | |
| '(': "000C183030303030180C00", | |
| ')': "0030180C0C0C0C0C183000", | |
| '=': "0000007E00007E00000000", | |
| '+': "00000018187E1818000000", | |
| '!': "00183C3C3C181800181800", | |
| '\'': "1818081000000000000000", | |
| ':': "0000001818000018180000", | |
| '%': "006092966C106CD2920C00", | |
| '/': "000002060C183060C08000", | |
| '"': "6666222200000000000000", | |
| '[': "003C303030303030303C00", | |
| ']': "003C0C0C0C0C0C0C0C3C00", | |
| ' ': "0000000000000000000000", | |
| '*': "000000663CFF3C66000000", | |
| ',': "0000000000000030301020", | |
| '.': "0000000000000000303000", | |
| '$': "107CD6D6701CD6D67C1010", | |
| '~': "0076DC0000000000000000", | |
| '{': "000E181818701818180E00", | |
| '}': "00701818180E1818187000", | |
| '<': "00060C18306030180C0600", | |
| '>': "006030180C060C18306000", | |
| '^': "386CC60000000000000000", | |
| '`': "1818100800000000000000", | |
| ';': "0000001818000018180810", | |
| '\\': "0080C06030180C06020000", | |
| '|': "0018181818001818181800", | |
| 'a': "00000000780C7CCCCC7600", | |
| 'b': "00E060607C666666667C00", | |
| 'c': "000000007CC6C0C0C67C00", | |
| 'd': "001C0C0C7CCCCCCCCC7600", | |
| 'e': "000000007CC6FEC0C67C00", | |
| 'f': "001C363078303030307800", | |
| 'g': "00000076CCCCCC7C0CCC78", | |
| 'h': "00E060606C76666666E600", | |
| 'i': "0018180038181818183C00", | |
| 'j': "0C0C001C0C0C0C0CCCCC78", | |
| 'k': "00E06060666C78786CE600", | |
| 'l': "0038181818181818183C00", | |
| 'm': "00000000ECFED6D6D6C600", | |
| 'n': "00000000DC666666666600", | |
| 'o': "000000007CC6C6C6C67C00", | |
| 'p': "000000DC6666667C6060F0", | |
| 'q': "0000007CCCCCCC7C0C0C1E", | |
| 'r': "00000000DE76606060F000", | |
| 's': "000000007CC6701CC67C00", | |
| 't': "00103030FC303030341800", | |
| 'u': "00000000CCCCCCCCCC7600", | |
| 'v': "00000000C6C6C66C381000", | |
| 'w': "00000000C6D6D6D6FE6C00", | |
| 'x': "00000000C66C38386CC600", | |
| 'y': "000000C6C6C6C67E060CF8", | |
| 'z': "00000000FE8C183062FE00", | |
| 'A': "00386CC6C6FEC6C6C6C600", | |
| 'B': "00FC6666667C666666FC00", | |
| 'C': "007CC6C6C0C0C0C6C67C00", | |
| 'D': "00FC66666666666666FC00", | |
| 'E': "00FE66626878686266FE00", | |
| 'F': "00FE66626878686060F000", | |
| 'G': "007CC6C6C0C0CEC6C67E00", | |
| 'H': "00C6C6C6C6FEC6C6C6C600", | |
| 'I': "003C181818181818183C00", | |
| 'J': "001E0C0C0C0C0CCCCC7800", | |
| 'K': "00E6666C6C786C6C66E600", | |
| 'L': "00F060606060606266FE00", | |
| 'M': "0082C6EEFED6C6C6C6C600", | |
| 'N': "0086C6E6F6DECEC6C6C600", | |
| 'O': "007CC6C6C6C6C6C6C67C00", | |
| 'P': "00FC6666667C606060F000", | |
| 'Q': "007CC6C6C6C6C6D6DE7C06", | |
| 'R': "00FC6666667C6C6666E600", | |
| 'S': "007CC6C660380CC6C67C00", | |
| 'T': "007E7E5A18181818183C00", | |
| 'U': "00C6C6C6C6C6C6C6C67C00", | |
| 'V': "00C6C6C6C6C6C66C381000", | |
| 'W': "00C6C6C6C6D6FEEEC68200", | |
| 'X': "00C6C66C7C387C6CC6C600", | |
| 'Y': "00666666663C1818183C00", | |
| 'Z': "00FEC6860C183062C6FE00", | |
| 'Á': "0810386cc6c6fec6c6c600", | |
| 'À': "2010386cc6c6fec6c6c600", | |
| 'Â': "1028386CC6C6FEC6C6C600", | |
| 'Ä': "2800386CC6C6FEC6C6C600", | |
| 'Å': "1028107CC6C6FEC6C6C600", | |
| 'É': "0810FE626878686266FE00", | |
| 'È': "2010FE626878686266FE00", | |
| 'Ê': "1028FE626878686266FE00", | |
| 'Ë': "2800FE626878686266FE00", | |
| 'Ě': "2810FE626878686266FE00", | |
| 'Í': "04083C1818181818183C00", | |
| 'Ì': "10083C1818181818183C00", | |
| 'Î': "08143C1818181818183C00", | |
| 'Ï': "14003C1818181818183C00", | |
| 'Ó': "08107CC6C6C6C6C6C67C00", | |
| 'Ò': "20107CC6C6C6C6C6C67C00", | |
| 'Ô': "10287CC6C6C6C6C6C67C00", | |
| 'Ö': "28007CC6C6C6C6C6C67C00", | |
| 'Ő': "14287CC6C6C6C6C6C67C00", | |
| 'Ú': "0810C6C6C6C6C6C6C67C00", | |
| 'Ù': "2010C6C6C6C6C6C6C67C00", | |
| 'Û': "1028C6C6C6C6C6C6C67C00", | |
| 'Ü': "2800C6C6C6C6C6C6C67C00", | |
| 'Ű': "1428C6C6C6C6C6C6C67C00", | |
| 'Ů': "102810C6C6C6C6C6C67C00", | |
| 'Ý': "04086666663C1818183C00", | |
| 'Ÿ': "14006666663C1818183C00", | |
| 'á': "00000810780C7CCCCC7600", | |
| 'à': "00002010780C7CCCCC7600", | |
| 'â': "00102800780C7CCCCC7600", | |
| 'ä': "00002800780C7CCCCC7600", | |
| 'å': "00102810780C7CCCCC7600", | |
| 'é': "000008107CC6FEC0C67C00", | |
| 'è': "000020107CC6FEC0C67C00", | |
| 'ê': "001028007CC6FEC0C67C00", | |
| 'ë': "000028007CC6FEC0C67C00", | |
| 'ě': "000028107CC6FEC0C67C00", | |
| 'í': "0000081038181818183C00", | |
| 'ì': "0000201038181818183C00", | |
| 'î': "0008140038181818183C00", | |
| 'ï': "0000140038181818183C00", | |
| 'ó': "000008107CC6C6C6C67C00", | |
| 'ò': "000020107CC6C6C6C67C00", | |
| 'ô': "001028007CC6C6C6C67C00", | |
| 'ö': "000028007CC6C6C6C67C00", | |
| 'ő': "000014287CC6C6C6C67C00", | |
| 'ú': "00000810CCCCCCCCCC7600", | |
| 'ù': "00002010CCCCCCCCCC7600", | |
| 'û': "00102800CCCCCCCCCC7600", | |
| 'ü': "00002800CCCCCCCCCC7600", | |
| 'ű': "00001428CCCCCCCCCC7600", | |
| 'ů': "00102810CCCCCCCCCC7600", | |
| 'ý': "000810C6C6C6C67E060CF8", | |
| 'ÿ': "002800C6C6C6C67E060CF8", | |
| 'Ç': "007CC6C6C0C0C0C67C1030", | |
| 'ç': "000000007CC6C0467C1030", | |
| 'Ñ': "342CC6E6F6DECEC6C6C600", | |
| 'ñ': "00342C00DC666666666600", | |
| 'Č': "28107CC6C6C0C0C6C67C00", | |
| 'č': "000028107CC6C0C0C67C00", | |
| 'Ď': "2810FC666666666666FC00", | |
| 'ď': "02061C0C7CCCCCCCCC7600", | |
| 'Ň': "2810C6E6F6DECEC6C6C600", | |
| 'ň': "00002810DC666666666600", | |
| 'Ř': "2810FC66667C6C6666E600", | |
| 'ř': "00002810DE76606060F000", | |
| 'Š': "28107CC6E0380CC6C67C00", | |
| 'š': "000028107CC6701CC67C00", | |
| 'Ť': "14087E7E5A181818183C00", | |
| 'ť': "00143430FC303030341800", | |
| 'Ž': "2810FE860C183062C6FE00", | |
| 'ž': "00002810FE8C183062FE00", | |
| } | |
| char_codes = {k: bytes.fromhex(v) for k, v in char_codes.items()} | |
| def format_message(msg_str): | |
| msg_bytes = b''.join(char_codes[c] for c in msg_str) | |
| MAX_PAGES = 8 | |
| # Which pages have these flags | |
| flash = bytes([0b00000000]) # flash full screen | |
| marquee = bytes([0b00000000]) # pixel border around badge | |
| options = bytes([0b00000100]) * MAX_PAGES | |
| # ^ 0 => rtl, 1 => left-to-right | |
| # ^ scroll from bottom to top | |
| # ^^ scroll jump? | |
| # ^^^^ speed 0b1111 => slowest?! | |
| # page 0 has a size, rest of page has no size? | |
| msg_len = len(msg_str) | |
| sizes = bytes([(msg_len >> 8) & 0xFF, msg_len & 0xFF] + [0, 0] * (MAX_PAGES - 1)) | |
| now = datetime.now() | |
| time_bytes = bytes([ | |
| now.year & 0xFF, now.month + 1, now.day, | |
| now.hour, now.minute, now.second | |
| ]) | |
| payload = bytes([0x77, 0x61, 0x6e, 0x67, 0x00, 0x00]) | |
| payload += flash + marquee + options + sizes | |
| payload += b"\x00"*6 + time_bytes + b"\x00"*20 | |
| payload += msg_bytes | |
| payload = payload.ljust((len(payload) + 15) // 16 * 16, b'\x00') | |
| yield from [payload[i:i+16] for i in range(0, len(payload), 16)] | |
| async def main(): | |
| print(f"Connecting to {device_address}...", end='', flush=True) | |
| client = BleakClient(device_address) | |
| await client.connect() | |
| print(" connected!") | |
| for chunk in format_message("HELLO"): | |
| await client.write_gatt_char("0000fee1-0000-1000-8000-00805f9b34fb", chunk, response=False) | |
| if __name__ == "__main__": | |
| asyncio.run(main()) |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Passive bleak scanning: