From cc3cf039d240f1bbbeec0dad94e9aec2ad3c0233 Mon Sep 17 00:00:00 2001 From: "Ben V. Brown" Date: Sun, 13 Feb 2022 10:34:35 +1100 Subject: [PATCH 01/17] Copy over images --- Bootup Logos/Images/IronOS.gif | Bin 0 -> 26202 bytes Bootup Logos/Images/IronOS.png | Bin 0 -> 458 bytes Bootup Logos/Images/IronOS_L.gif | Bin 0 -> 26238 bytes Bootup Logos/Images/IronOS_L.png | Bin 0 -> 300 bytes Bootup Logos/Images/Pinecil.png | Bin 0 -> 258 bytes Bootup Logos/Images/Pinecil_L.png | Bin 0 -> 259 bytes Bootup Logos/Images/TS100.png | Bin 0 -> 251 bytes Bootup Logos/Images/TS100_L.png | Bin 0 -> 245 bytes Bootup Logos/Images/TS80.png | Bin 0 -> 262 bytes Bootup Logos/Images/TS80P.png | Bin 0 -> 267 bytes Bootup Logos/Images/TS80P_L.png | Bin 0 -> 263 bytes Bootup Logos/Images/TS80_L.png | Bin 0 -> 262 bytes Bootup Logos/README.md | 8 + Bootup Logos/img2logo.py | 291 ++++++++++++++++++++++++++++++ 14 files changed, 299 insertions(+) create mode 100644 Bootup Logos/Images/IronOS.gif create mode 100644 Bootup Logos/Images/IronOS.png create mode 100644 Bootup Logos/Images/IronOS_L.gif create mode 100644 Bootup Logos/Images/IronOS_L.png create mode 100644 Bootup Logos/Images/Pinecil.png create mode 100644 Bootup Logos/Images/Pinecil_L.png create mode 100644 Bootup Logos/Images/TS100.png create mode 100644 Bootup Logos/Images/TS100_L.png create mode 100644 Bootup Logos/Images/TS80.png create mode 100644 Bootup Logos/Images/TS80P.png create mode 100644 Bootup Logos/Images/TS80P_L.png create mode 100644 Bootup Logos/Images/TS80_L.png create mode 100644 Bootup Logos/README.md create mode 100644 Bootup Logos/img2logo.py diff --git a/Bootup Logos/Images/IronOS.gif b/Bootup Logos/Images/IronOS.gif new file mode 100644 index 0000000000000000000000000000000000000000..52c2a42512e800bfc5cbc7b8e292512ca318b91d GIT binary patch literal 26202 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 Z00;mC00IC3fB--MAOH{m2>eF`{tYb>0iyr_ literal 0 HcmV?d00001 diff --git a/Bootup Logos/Images/IronOS.png b/Bootup Logos/Images/IronOS.png new file mode 100644 index 0000000000000000000000000000000000000000..a728fadc3cb5ec224ee6df151eb818c5c64037e0 GIT binary patch literal 458 zcmV;*0X6=KP)AHIP0004zNklCCbldFo|Re*0=)vYo@(H0Fa_~HQb*wbMxQ6p%wFK?wtvt zu`K}n*wRC2fh%!Q4hoEMj%6rebtitr(%#$xU=%`=Cvh175ql#U^fF`bi+=n3BiV>Z zMN%MV&_CL!3!zO~$(J}F?1Bh;GpEu` zUa_jWZu6uKAnfnfK+kRg&`n}zywdTVxpdF^0r+AXACQp|j{pDw07*qoM6N<$f|_>E AKL7v# literal 0 HcmV?d00001 diff --git a/Bootup Logos/Images/IronOS_L.gif b/Bootup Logos/Images/IronOS_L.gif new file mode 100644 index 0000000000000000000000000000000000000000..b7f08857bf37403d0f841d893b0d0b7c1306bc91 GIT binary patch literal 26238 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 literal 0 HcmV?d00001 diff --git a/Bootup Logos/Images/IronOS_L.png b/Bootup Logos/Images/IronOS_L.png new file mode 100644 index 0000000000000000000000000000000000000000..befd3aa045eca2893fa42d41ae204b2353047c5e GIT binary patch literal 300 zcmeAS@N?(olHy`uVBq!ia0vp^2|z5s!VDy(r=*_(QjEnx?oJHr&dIz4a#+$GeH|GX zHuiJ>Nn{1`*#dk*T!Hle|NocXoPQU{;wdE{bbMdP|}jIM%d`O_Cd)e)^}0|({lG|60ZZWJq q5%@x8nOer-?SEISFO$#TEdFJSz~Ruv3;2LeXYh3Ob6Mw<&;$TMl5qL} literal 0 HcmV?d00001 diff --git a/Bootup Logos/Images/Pinecil.png b/Bootup Logos/Images/Pinecil.png new file mode 100644 index 0000000000000000000000000000000000000000..29d7ce11310bf51c9eea991b0762f53eb69fd6d4 GIT binary patch literal 258 zcmeAS@N?(olHy`uVBq!ia0vp^2|z5s!VDy(r=*_(QjEnx?oJHr&dIz4a#+$GeH|GX zHuiJ>Nn{1`*#dk*T!Hle|NocXoPQU{;wn$y<2oh1REk{Tx`!JK~zn yPCv_1$dOK*+Vj6_U3_Ntr6(0PGF)m@7cpzc^7#b66!`^o5QC?ypUXO@geCwlYFwuP literal 0 HcmV?d00001 diff --git a/Bootup Logos/Images/Pinecil_L.png b/Bootup Logos/Images/Pinecil_L.png new file mode 100644 index 0000000000000000000000000000000000000000..00e50f7ee68c53d5bbc67abf25ecc334917c243e GIT binary patch literal 259 zcmeAS@N?(olHy`uVBq!ia0vp^2|z5s!VDy(r=*_(QjEnx?oJHr&dIz4a#+$GeH|GX zHuiJ>Nn{1`*#dk*T!Hle|NocXoPQU{;wjI08Q@=KyQh48V+5govBR=KO zgyk$|byb~9LKo{>^mC^78qRF%VO_l1vxHISEuW9zjmy`8E@JR>^>bP0l+XkK34B_# literal 0 HcmV?d00001 diff --git a/Bootup Logos/Images/TS100.png b/Bootup Logos/Images/TS100.png new file mode 100644 index 0000000000000000000000000000000000000000..4af79035b99949ebe95175b6778d8a316d353d61 GIT binary patch literal 251 zcmeAS@N?(olHy`uVBq!ia0vp^2|z5s!VDy(r=*_(QjEnx?oJHr&dIz4a#+$GeH|GX zHuiJ>Nn{1`*#dk*T!Hle|NocXoPQU{;w+i+ZC3kqrnN>9m)j){uGago@{El7 z!QEk8;f&h99;sd4&bdThF}FU4#aX;1v-=3oiQ8Kz7i~8`&guQ-NY(>BnS+OqOnVu2 sxUzYI(a)x7{axi({wEbLUd*6pd4#Xvf&Y{%K$kFhy85}Sb4q9e02mQlumAu6 literal 0 HcmV?d00001 diff --git a/Bootup Logos/Images/TS100_L.png b/Bootup Logos/Images/TS100_L.png new file mode 100644 index 0000000000000000000000000000000000000000..82c5ba2aaa5cd39a8ca33903cabc5ae17f74e0aa GIT binary patch literal 245 zcmeAS@N?(olHy`uVBq!ia0vp^2|z5s!VDy(r=*_(QjEnx?oJHr&dIz4a#+$GeH|GX zHuiJ>Nn{1`*#dk*T!Hle|NocXoPQU{;wNT^vI!P9F`7 z=4&wEXqLY4J3g%ONn{1`*#dk*T!Hle|NocXoPQU{;wL2Qf1%p6bol}ndNn{1`*#dk*T!Hle|NocXoPQU{;w(IoxgufFxtliudBlWT1zm91tBjCT0J@h|#7#=_!>3+FYdhbIb&h;TmjSL1E2 z%+pn=6*ur)bM{q?>W$q7slo^JBAR}sctx#r4ZkD1<-2G1Ca)bALJi*Y@jhh`wQ0Qb zc7qf1BTJ2wPl_1SzbyQB=<(l=PqXeE?$G4?`LR~^o=^d!?G7Gy2j-f5pvxFMUHx3v IIVCg!0E5|J%>V!Z literal 0 HcmV?d00001 diff --git a/Bootup Logos/Images/TS80P_L.png b/Bootup Logos/Images/TS80P_L.png new file mode 100644 index 0000000000000000000000000000000000000000..beae7b9b296cdd66acc8ed481c864b53a74d4465 GIT binary patch literal 263 zcmeAS@N?(olHy`uVBq!ia0vp^2|z5s!VDy(r=*_(QjEnx?oJHr&dIz4a#+$GeH|GX zHuiJ>Nn{1`*#dk*T!Hle|NocXoPQU{;wNU@fk)aq^1zDZ!R2`ldB!SvT;#U$nSE$1C3U$$#dIampZ2mK?;k^7o8%6LYdd({W5d=M0$s)6>FVdQ&MBb@ E0OJo{=Kufz literal 0 HcmV?d00001 diff --git a/Bootup Logos/Images/TS80_L.png b/Bootup Logos/Images/TS80_L.png new file mode 100644 index 0000000000000000000000000000000000000000..e2127a93f44bde1c75cc5f4dcda1191a2c7e2617 GIT binary patch literal 262 zcmeAS@N?(olHy`uVBq!ia0vp^2|z5s!VDy(r=*_(QjEnx?oJHr&dIz4a#+$GeH|GX zHuiJ>Nn{1`*#dk*T!Hle|NocXoPQU{;wDBsh2xg>sJ6q~CGUzp9& zs@Y{KCz?0$d+G`=3(;+EnlOLTgiyZ&i5a)AnjC)MyK?ry*EfZwB&No-y*AX~v2?y- zaX){a;O4`xIj23o%BSpU&c57!K{jLeqxWplnrvnZ(right)|TS100|TS80|TS80P|Pinecil|IronOS +static
(left)|TS100_L|TS80_L|TS80P_L|Pinecil_L|IronOS_L +animated|||||IronOS +notes|||||   ^^^^^^^^
*(This loops just for
demonstration purposes,
the real thing
only plays once.)* diff --git a/Bootup Logos/img2logo.py b/Bootup Logos/img2logo.py new file mode 100644 index 0000000..3e5239f --- /dev/null +++ b/Bootup Logos/img2logo.py @@ -0,0 +1,291 @@ +#!/usr/bin/env python +# coding=utf-8 +from __future__ import division +import os, sys, struct, zlib + + +try: + from PIL import Image, ImageOps +except ImportError as error: + raise ImportError("{}: {} requres Python Imaging Library (PIL). " + "Install with `pip` or OS-specific package " + "management tool." + .format(error, sys.argv[0])) + +VERSION_STRING = '0.03' + +LCD_WIDTH = 96 +LCD_HEIGHT = 16 +LCD_NUM_BYTES = LCD_WIDTH * LCD_HEIGHT // 8 +LCD_PADDED_SIZE = 1024 + +INTELHEX_DATA_RECORD = 0x00 +INTELHEX_END_OF_FILE_RECORD = 0x01 +INTELHEX_EXTENDED_LINEAR_ADDRESS_RECORD = 0x04 +INTELHEX_BYTES_PER_LINE = 16 +INTELHEX_MINIMUM_SIZE = 4096 + +DFU_PINECIL_ALT = 0 +DFU_PINECIL_VENDOR = 0x28e9 +DFU_PINECIL_PRODUCT = 0x0189 +DFU_LOGO_ADDRESS = 0x0801F800 +DFU_TARGET_NAME = b"Pinecil" +DFU_PREFIX_SIZE = 11 +DFU_SUFFIX_SIZE = 16 + +def split16(word): + """return high and low byte of 16-bit word value as tuple""" + return (word >> 8) & 0xff, word & 0xff + + +def compute_crc(data): + return 0xFFFFFFFF & -zlib.crc32(data) - 1 + + +def intel_hex_line(record_type, offset, data): + """generate a line of data in Intel hex format""" + # length, address offset, record type + record_length = len(data) + yield ':{:02X}{:04X}{:02X}'.format(record_length, offset, record_type) + + # data + for byte in data: + yield "{:02X}".format(byte) + + # compute and write checksum (now using unix style line endings for DFU3.45 compatibility + yield "{:02X}\n".format((((sum(data, # sum data ... + record_length # ... and other ... + + sum(split16(offset)) # ... fields ... + + record_type) # ... on line + & 0xff) # low 8 bits + ^ 0xff) # two's ... + + 1) # ... complement + & 0xff) # low 8 bits + + +def intel_hex(file, bytes_, start_address=0x0): + """write block of data in Intel hex format""" + def write(generator): + file.write(''.join(generator)) + + if len(bytes_) % INTELHEX_BYTES_PER_LINE != 0: + raise ValueError("Program error: Size of LCD data is not evenly divisible by {}" + .format(INTELHEX_BYTES_PER_LINE)) + + address_lo = start_address & 0xffff + address_hi = (start_address >> 16) & 0xffff + + write(intel_hex_line(INTELHEX_EXTENDED_LINEAR_ADDRESS_RECORD, 0, + split16(address_hi))) + + size_written = 0 + while size_written < INTELHEX_MINIMUM_SIZE: + offset = address_lo + for line_start in range(0, len(bytes_), INTELHEX_BYTES_PER_LINE): + write(intel_hex_line(INTELHEX_DATA_RECORD, offset, + bytes_[line_start:line_start + INTELHEX_BYTES_PER_LINE])) + size_written += INTELHEX_BYTES_PER_LINE + if size_written >= INTELHEX_MINIMUM_SIZE: + break + offset += INTELHEX_BYTES_PER_LINE + + write(intel_hex_line(INTELHEX_END_OF_FILE_RECORD, 0, ())) + + +def build_dfu(file, bytes_): + data = b"" + for byte in bytes_: + data += byte.to_bytes(1, byteorder="big") + + data = ( + struct.pack("<2I", DFU_LOGO_ADDRESS, len(data)) + data + ) + data = ( + struct.pack( + "<6sBI255s2I", b"Target", DFU_PINECIL_ALT, 1, DFU_TARGET_NAME, len(data), 1 + ) + + data + ) + data = ( + struct.pack( + "<5sBIB", b"DfuSe", 1, DFU_PREFIX_SIZE + len(data) + DFU_SUFFIX_SIZE, 1 + ) + + data + ) + data += struct.pack("<4H3sB", 0, DFU_PINECIL_PRODUCT, DFU_PINECIL_VENDOR, 0x011A, b"UFD", DFU_SUFFIX_SIZE) + crc = compute_crc(data) + data += struct.pack(" Date: Sun, 13 Feb 2022 15:08:38 +1100 Subject: [PATCH 02/17] Rebuilding python into split classes --- .gitignore | 3 + Bootup Logos/img2logo.py | 319 +++++++++++++++---------------------- Bootup Logos/output_dfu.py | 71 +++++++++ Bootup Logos/output_hex.py | 104 ++++++++++++ 4 files changed, 307 insertions(+), 190 deletions(-) create mode 100644 .gitignore create mode 100644 Bootup Logos/output_dfu.py create mode 100644 Bootup Logos/output_hex.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..699b26b --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*/__pycache__/* +*.hex +*.dfu diff --git a/Bootup Logos/img2logo.py b/Bootup Logos/img2logo.py index 3e5239f..e4bc7fb 100644 --- a/Bootup Logos/img2logo.py +++ b/Bootup Logos/img2logo.py @@ -1,135 +1,59 @@ #!/usr/bin/env python # coding=utf-8 from __future__ import division -import os, sys, struct, zlib +import argparse +import os, sys +from output_hex import HexOutput +from output_dfu import DFUOutput try: from PIL import Image, ImageOps except ImportError as error: - raise ImportError("{}: {} requres Python Imaging Library (PIL). " - "Install with `pip` or OS-specific package " - "management tool." - .format(error, sys.argv[0])) - -VERSION_STRING = '0.03' - -LCD_WIDTH = 96 -LCD_HEIGHT = 16 -LCD_NUM_BYTES = LCD_WIDTH * LCD_HEIGHT // 8 -LCD_PADDED_SIZE = 1024 - -INTELHEX_DATA_RECORD = 0x00 -INTELHEX_END_OF_FILE_RECORD = 0x01 -INTELHEX_EXTENDED_LINEAR_ADDRESS_RECORD = 0x04 -INTELHEX_BYTES_PER_LINE = 16 -INTELHEX_MINIMUM_SIZE = 4096 - -DFU_PINECIL_ALT = 0 -DFU_PINECIL_VENDOR = 0x28e9 -DFU_PINECIL_PRODUCT = 0x0189 -DFU_LOGO_ADDRESS = 0x0801F800 -DFU_TARGET_NAME = b"Pinecil" -DFU_PREFIX_SIZE = 11 -DFU_SUFFIX_SIZE = 16 - -def split16(word): - """return high and low byte of 16-bit word value as tuple""" - return (word >> 8) & 0xff, word & 0xff - - -def compute_crc(data): - return 0xFFFFFFFF & -zlib.crc32(data) - 1 - - -def intel_hex_line(record_type, offset, data): - """generate a line of data in Intel hex format""" - # length, address offset, record type - record_length = len(data) - yield ':{:02X}{:04X}{:02X}'.format(record_length, offset, record_type) - - # data - for byte in data: - yield "{:02X}".format(byte) - - # compute and write checksum (now using unix style line endings for DFU3.45 compatibility - yield "{:02X}\n".format((((sum(data, # sum data ... - record_length # ... and other ... - + sum(split16(offset)) # ... fields ... - + record_type) # ... on line - & 0xff) # low 8 bits - ^ 0xff) # two's ... - + 1) # ... complement - & 0xff) # low 8 bits - - -def intel_hex(file, bytes_, start_address=0x0): - """write block of data in Intel hex format""" - def write(generator): - file.write(''.join(generator)) - - if len(bytes_) % INTELHEX_BYTES_PER_LINE != 0: - raise ValueError("Program error: Size of LCD data is not evenly divisible by {}" - .format(INTELHEX_BYTES_PER_LINE)) - - address_lo = start_address & 0xffff - address_hi = (start_address >> 16) & 0xffff - - write(intel_hex_line(INTELHEX_EXTENDED_LINEAR_ADDRESS_RECORD, 0, - split16(address_hi))) - - size_written = 0 - while size_written < INTELHEX_MINIMUM_SIZE: - offset = address_lo - for line_start in range(0, len(bytes_), INTELHEX_BYTES_PER_LINE): - write(intel_hex_line(INTELHEX_DATA_RECORD, offset, - bytes_[line_start:line_start + INTELHEX_BYTES_PER_LINE])) - size_written += INTELHEX_BYTES_PER_LINE - if size_written >= INTELHEX_MINIMUM_SIZE: - break - offset += INTELHEX_BYTES_PER_LINE - - write(intel_hex_line(INTELHEX_END_OF_FILE_RECORD, 0, ())) - - -def build_dfu(file, bytes_): - data = b"" - for byte in bytes_: - data += byte.to_bytes(1, byteorder="big") - - data = ( - struct.pack("<2I", DFU_LOGO_ADDRESS, len(data)) + data + raise ImportError( + "{}: {} requres Python Imaging Library (PIL). " + "Install with `pip` (pip3 install pillow) or OS-specific package " + "management tool.".format(error, sys.argv[0]) ) - data = ( - struct.pack( - "<6sBI255s2I", b"Target", DFU_PINECIL_ALT, 1, DFU_TARGET_NAME, len(data), 1 - ) - + data - ) - data = ( - struct.pack( - "<5sBIB", b"DfuSe", 1, DFU_PREFIX_SIZE + len(data) + DFU_SUFFIX_SIZE, 1 - ) - + data - ) - data += struct.pack("<4H3sB", 0, DFU_PINECIL_PRODUCT, DFU_PINECIL_VENDOR, 0x011A, b"UFD", DFU_SUFFIX_SIZE) - crc = compute_crc(data) - data += struct.pack("> 8) & 0xFF, word & 0xFF + + @classmethod + def compute_crc(cls, data): + return 0xFFFFFFFF & -zlib.crc32(data) - 1 + + @classmethod + def intel_hex_line(cls, record_type, offset, data): + """generate a line of data in Intel hex format""" + # length, address offset, record type + record_length = len(data) + yield ":{:02X}{:04X}{:02X}".format(record_length, offset, record_type) + + # data + for byte in data: + yield "{:02X}".format(byte) + + # compute and write checksum (now using unix style line endings for DFU3.45 compatibility + yield "{:02X}\n".format( + ( + ( + ( + sum( + data, # sum data ... + record_length # ... and other ... + + sum(cls.split16(offset)) # ... fields ... + + record_type, + ) # ... on line + & 0xFF + ) # low 8 bits + ^ 0xFF + ) # two's ... + + 1 + ) # ... complement + & 0xFF + ) # low 8 bits + + @classmethod + def writeFile(cls, file_name: str, data: bytearray, data_address: int): + """write block of data in Intel hex format""" + with open(file_name, "w", newline="\r\n") as output: + + def write(generator): + output.write("".join(generator)) + + if len(data) % cls.INTELHEX_BYTES_PER_LINE != 0: + raise ValueError( + "Program error: Size of LCD data is not evenly divisible by {}".format( + cls.INTELHEX_BYTES_PER_LINE + ) + ) + + address_lo = data_address & 0xFFFF + address_hi = (data_address >> 16) & 0xFFFF + + write( + cls.intel_hex_line( + cls.INTELHEX_EXTENDED_LINEAR_ADDRESS_RECORD, + 0, + cls.split16(address_hi), + ) + ) + + size_written = 0 + while size_written < cls.INTELHEX_MINIMUM_SIZE: + offset = address_lo + for line_start in range(0, len(data), cls.INTELHEX_BYTES_PER_LINE): + write( + cls.intel_hex_line( + cls.INTELHEX_DATA_RECORD, + offset, + data[line_start : line_start + cls.INTELHEX_BYTES_PER_LINE], + ) + ) + size_written += cls.INTELHEX_BYTES_PER_LINE + if size_written >= cls.INTELHEX_MINIMUM_SIZE: + break + offset += cls.INTELHEX_BYTES_PER_LINE + + write(cls.intel_hex_line(cls.INTELHEX_END_OF_FILE_RECORD, 0, ())) + + +if __name__ == "__main__": + import sys + + print("DO NOT CALL THIS FILE DIRECTLY") + sys.exit(1) From 19d46aee44337f965c5b01a032957d4cac089257 Mon Sep 17 00:00:00 2001 From: "Ben V. Brown" Date: Sun, 13 Feb 2022 15:14:43 +1100 Subject: [PATCH 03/17] Pinecil toggle --- Bootup Logos/img2logo.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Bootup Logos/img2logo.py b/Bootup Logos/img2logo.py index e4bc7fb..17b992d 100644 --- a/Bootup Logos/img2logo.py +++ b/Bootup Logos/img2logo.py @@ -153,7 +153,7 @@ def parse_commandline(): parser.add_argument("output_filename", help="output file base name") parser.add_argument( - "-p", + "-P", "--preview", help="filename of image preview", ) @@ -186,6 +186,9 @@ def parse_commandline(): "-f", "--force", action="store_true", help="force overwriting of existing files" ) + parser.add_argument( + "-p", "--pinecil", action="store_true", help="generate files for Pinecil" + ) parser.add_argument( "-v", "--version", @@ -223,7 +226,7 @@ if __name__ == "__main__": args.dither, args.negative, output_filename_base=args.output_filename, - isPinecil=False, + isPinecil=args.pinecil, ) except BaseException as error: sys.stderr.write("Error converting file: {}\n".format(error)) From 3ee201cb44b36e8e5f3149b730a8dd4ba4cfc62c Mon Sep 17 00:00:00 2001 From: "Ben V. Brown" Date: Sun, 13 Feb 2022 19:05:59 +1100 Subject: [PATCH 04/17] Scratching out an idea for an animated boot --- Bootup Logos/img2logo.py | 201 ++++++++++++++++++++++++++++----------- 1 file changed, 147 insertions(+), 54 deletions(-) diff --git a/Bootup Logos/img2logo.py b/Bootup Logos/img2logo.py index 17b992d..991edfd 100644 --- a/Bootup Logos/img2logo.py +++ b/Bootup Logos/img2logo.py @@ -2,6 +2,7 @@ # coding=utf-8 from __future__ import division import argparse +import copy import os, sys from output_hex import HexOutput @@ -40,6 +41,137 @@ class PinecilSettings: DFU_PINECIL_PRODUCT = 0x0189 +def still_image_to_bytes( + image: Image, negative: bool, dither: bool, threshold: int, preview_filename +): + # convert to luminance + # do even if already black/white because PIL can't invert 1-bit so + # can't just pass thru in case --negative flag + # also resizing works better in luminance than black/white + # also no information loss converting black/white to grayscale + if image.mode != "L": + image = image.convert("L") + # Resize to lcd size using bicubic sampling + if image.size != (LCD_WIDTH, LCD_HEIGHT): + image = image.resize((LCD_WIDTH, LCD_HEIGHT), Image.BICUBIC) + + if negative: + image = ImageOps.invert(image) + threshold = 255 - threshold # have to invert threshold as well + + if dither: + image = image.convert("1") + else: + image = image.point(lambda pixel: 0 if pixel < threshold else 1, "1") + + if preview_filename: + image.save(preview_filename) + # pad to this size (also will be repeated in output Intel hex file) + data = [0] * LCD_PAGE_SIZE + + # magic/required header in endian-reverse byte order + data[0] = 0x55 + data[1] = 0xAA + data[2] = 0x0D + data[3] = 0xF0 + + # convert to LCD format + for ndx in range(LCD_WIDTH * 16 // 8): + bottom_half_offset = 0 if ndx < LCD_WIDTH else 8 + byte = 0 + for y in range(8): + if image.getpixel((ndx % LCD_WIDTH, y + bottom_half_offset)): + byte |= 1 << y + # store in endian-reversed byte order + data[4 + ndx + (1 if ndx % 2 == 0 else -1)] = byte + return data + + +def animated_image_to_bytes( + imageIn: Image, negative: bool, dither: bool, threshold: int +): + """ + Convert the gif into our best effort startup animation + We are delta-encoding on a byte by byte basis + + So we convert every frame into its binary representation + The compare these to figure out the encoding + + The naïve implementation would save the frame 5 times + But if we delta encode; we can make far more frames of animation for _some_ types of animations. + This means reveals are better than moves. + Data is stored in the byte blobs, so if you change one pixel in upper or lower row, changing another pixel in that column on that row is "free" + """ + frameData = [] + frameTimings = [] + for framenum in range(0, imageIn.n_frames): + print(f"Frame {framenum}") + imageIn.seek(framenum) + image = imageIn + if image.mode != "L": + image = image.convert("L") + # Resize to lcd size using bicubic sampling + if image.size != (LCD_WIDTH, LCD_HEIGHT): + image = image.resize((LCD_WIDTH, LCD_HEIGHT), Image.BICUBIC) + + if negative: + image = ImageOps.invert(image) + threshold = 255 - threshold # have to invert threshold as well + + if dither: + image = image.convert("1") + else: + image = image.point(lambda pixel: 0 if pixel < threshold else 1, "1") + + frameb = [0] * LCD_WIDTH * (LCD_HEIGHT // 8) + for ndx in range(LCD_WIDTH * 16 // 8): + bottom_half_offset = 0 if ndx < LCD_WIDTH else 8 + byte = 0 + for y in range(8): + if image.getpixel((ndx % LCD_WIDTH, y + bottom_half_offset)): + byte |= 1 << y + # store in endian-reversed byte order + frameb[ndx] = byte + frameData.append(frameb) + frameDuration_ms = image.info["duration"] + if frameDuration_ms > 255: + frameDuration_ms = 255 + frameTimings.append(frameDuration_ms) + print(f"Found {len(frameTimings)} frames") + # We have no mangled the image into our frambuffers + # Now create the "deltas" for each frame + frameDeltas = [[]] + for frame in range(1, len(frameData)): + damage = [] + for i in range(0, len(frameData[frame])): + if frameData[frame][i] != frameData[frame - 1][i]: + damage.append(i) + damage.append(frameData[frame][i]) + frameDeltas.append(damage) + print(damage) + # Now we can build our output data blob + # First we always start with a full first frame; future optimisation to check if we should or not + outputData = [0xAA, 0xBB, frameTimings[0]] + outputData.extend(frameData[0]) + # Now we delta encode all following frames + + """ + Format for each frame block is: + [duration in ms, max of 255][length][ [delta block][delta block][delta block][delta block] ] + Where [delta block] is just [index,new value] + """ + for frame in range(1, len(frameData)): + outputData.append(frameTimings[frame]) + outputData.append(len(frameDeltas[frame])) + outputData.extend(frameDeltas[frame]) + + if len(outputData) > 1024: + raise Exception( + f"Too many frames, required too much space. You used {len(outputData)} of 1024 bytes" + ) + return outputData + + def img2hex( input_filename, preview_filename=None, @@ -69,29 +201,6 @@ def img2hex( except BaseException as e: raise IOError('error reading image file "{}": {}'.format(input_filename, e)) - # convert to luminance - # do even if already black/white because PIL can't invert 1-bit so - # can't just pass thru in case --negative flag - # also resizing works better in luminance than black/white - # also no information loss converting black/white to grayscale - if image.mode != "L": - image = image.convert("L") - # Resize to lcd size using bicubic sampling - if image.size != (LCD_WIDTH, LCD_HEIGHT): - image = image.resize((LCD_WIDTH, LCD_HEIGHT), Image.BICUBIC) - - if negative: - image = ImageOps.invert(image) - threshold = 255 - threshold # have to invert threshold as well - - if dither: - image = image.convert("1") - else: - image = image.point(lambda pixel: 0 if pixel < threshold else 1, "1") - - if preview_filename: - image.save(preview_filename) - """ DEBUG for row in range(LCD_HEIGHT): for column in range(LCD_WIDTH): @@ -99,25 +208,13 @@ def img2hex( else: sys.stderr.write('0') sys.stderr.write('\n') """ + if getattr(image, "is_animated", False): + data = animated_image_to_bytes(image, negative, dither, threshold) + else: + data = still_image_to_bytes( + image, negative, dither, threshold, preview_filename + ) - # pad to this size (also will be repeated in output Intel hex file) - data = [0] * LCD_PAGE_SIZE - - # magic/required header in endian-reverse byte order - data[0] = 0x55 - data[1] = 0xAA - data[2] = 0x0D - data[3] = 0xF0 - - # convert to LCD format - for ndx in range(LCD_WIDTH * 16 // 8): - bottom_half_offset = 0 if ndx < LCD_WIDTH else 8 - byte = 0 - for y in range(8): - if image.getpixel((ndx % LCD_WIDTH, y + bottom_half_offset)): - byte |= 1 << y - # store in endian-reversed byte order - data[4 + ndx + (1 if ndx % 2 == 0 else -1)] = byte deviceSettings = MiniwareSettings if isPinecil: deviceSettings = PinecilSettings @@ -218,16 +315,12 @@ if __name__ == "__main__": ) sys.exit(1) - try: - img2hex( - args.input_filename, - args.preview, - args.threshold, - args.dither, - args.negative, - output_filename_base=args.output_filename, - isPinecil=args.pinecil, - ) - except BaseException as error: - sys.stderr.write("Error converting file: {}\n".format(error)) - sys.exit(1) + img2hex( + args.input_filename, + args.preview, + args.threshold, + args.dither, + args.negative, + output_filename_base=args.output_filename, + isPinecil=args.pinecil, + ) From 22207921b1ca68df6547eeeac588cc2466c929f4 Mon Sep 17 00:00:00 2001 From: "Ben V. Brown" Date: Mon, 14 Feb 2022 20:17:57 +1100 Subject: [PATCH 05/17] More refactoring of animation support --- Bootup Logos/img2logo.py | 90 ++++++++++++++++++++++++++++------------ 1 file changed, 63 insertions(+), 27 deletions(-) mode change 100644 => 100755 Bootup Logos/img2logo.py diff --git a/Bootup Logos/img2logo.py b/Bootup Logos/img2logo.py old mode 100644 new mode 100755 index 991edfd..30b7809 --- a/Bootup Logos/img2logo.py +++ b/Bootup Logos/img2logo.py @@ -70,23 +70,29 @@ def still_image_to_bytes( data = [0] * LCD_PAGE_SIZE # magic/required header in endian-reverse byte order - data[0] = 0x55 - data[1] = 0xAA - data[2] = 0x0D - data[3] = 0xF0 + data[0] = 0xAA + data[1] = 0xBB # convert to LCD format - for ndx in range(LCD_WIDTH * 16 // 8): + for ndx in range(LCD_WIDTH * LCD_HEIGHT // 8): bottom_half_offset = 0 if ndx < LCD_WIDTH else 8 byte = 0 for y in range(8): if image.getpixel((ndx % LCD_WIDTH, y + bottom_half_offset)): byte |= 1 << y - # store in endian-reversed byte order - data[4 + ndx + (1 if ndx % 2 == 0 else -1)] = byte + data[2 + ndx] = byte return data +def calculate_frame_delta_encode(previous_frame: bytearray, this_frame: bytearray): + damage = [] + for i in range(0, len(this_frame)): + if this_frame[i] != previous_frame[i]: + damage.append(i) + damage.append(this_frame[i]) + return damage + + def animated_image_to_bytes( imageIn: Image, negative: bool, dither: bool, threshold: int ): @@ -124,7 +130,7 @@ def animated_image_to_bytes( image = image.point(lambda pixel: 0 if pixel < threshold else 1, "1") frameb = [0] * LCD_WIDTH * (LCD_HEIGHT // 8) - for ndx in range(LCD_WIDTH * 16 // 8): + for ndx in range(LCD_WIDTH * LCD_HEIGHT // 8): bottom_half_offset = 0 if ndx < LCD_WIDTH else 8 byte = 0 for y in range(8): @@ -141,34 +147,61 @@ def animated_image_to_bytes( # We have no mangled the image into our frambuffers # Now create the "deltas" for each frame frameDeltas = [[]] + for frame in range(1, len(frameData)): - damage = [] - for i in range(0, len(frameData[frame])): - if frameData[frame][i] != frameData[frame - 1][i]: - damage.append(i) - damage.append(frameData[frame][i]) - frameDeltas.append(damage) - print(damage) + + frameDeltas.append( + calculate_frame_delta_encode(frameData[frame - 1], frameData[frame]) + ) + # Now we can build our output data blob # First we always start with a full first frame; future optimisation to check if we should or not - outputData = [0xAA, 0xBB, frameTimings[0]] - outputData.extend(frameData[0]) + + bytes_black = sum([1 if x == 0 else 0 for x in frameData[0]]) + if bytes_black > 96: + # It will take less room to delta encode first frame + outputData = [0xAA, 0xCC, frameTimings[0]] + delta = calculate_frame_delta_encode([0x00] * (LCD_NUM_BYTES), frameData[0]) + if len(delta) > (LCD_NUM_BYTES / 2): + raise Exception("BUG: Shouldn't delta encode more than 50%% of the screen") + outputData.append(len(delta)) + outputData.extend(delta) + print("delta encoded first frame") + else: + outputData = [0xAA, 0xDD, frameTimings[0]] + outputData.extend(frameData[0]) + print("Used full encoded first frame") + # Now we delta encode all following frames """ Format for each frame block is: [duration in ms, max of 255][length][ [delta block][delta block][delta block][delta block] ] Where [delta block] is just [index,new value] + + OR + [duration in ms, max of 255][0xFF][Full frame data] """ for frame in range(1, len(frameData)): - outputData.append(frameTimings[frame]) - outputData.append(len(frameDeltas[frame])) - outputData.extend(frameDeltas[frame]) + data = [frameTimings[frame]] + if len(frameDeltas[frame]) > LCD_NUM_BYTES: + data.append(0xFF) + data.extend(frameData[frame]) + print(f"Frame {frame} full encodes to {len(data)} bytes") + else: + data.append(len(frameDeltas[frame])) + data.extend(frameDeltas[frame]) - if len(outputData) > 1024: - raise Exception( - f"Too many frames, required too much space. You used {len(outputData)} of 1024 bytes" - ) + print(f"Frame {frame} delta encodes to {len(data)} bytes") + if len(outputData) + len(data) > 1024: + print( + f"Animation truncated, frame {frame} and onwards out of {len(frameData)} discarded" + ) + break + outputData.extend(data) + if len(outputData) < 1024: + pad = [0] * (1024 - len(outputData)) + outputData.extend(pad) return outputData @@ -179,6 +212,7 @@ def img2hex( dither=False, negative=False, isPinecil=False, + make_erase_image=False, output_filename_base="out", ): """ @@ -204,11 +238,13 @@ def img2hex( """ DEBUG for row in range(LCD_HEIGHT): for column in range(LCD_WIDTH): - if image.getpixel((column, row)): sys.stderr.write('1') - else: sys.stderr.write('0') + if image.getpixel((column, row)): sys.stderr.write('█') + else: sys.stderr.write(' ') sys.stderr.write('\n') """ - if getattr(image, "is_animated", False): + if make_erase_image: + data = [0xFF] * 1024 + elif getattr(image, "is_animated", False): data = animated_image_to_bytes(image, negative, dither, threshold) else: data = still_image_to_bytes( From 24f805cdac775812f8119425bad0432b108aac02 Mon Sep 17 00:00:00 2001 From: "Ben V. Brown" Date: Mon, 14 Feb 2022 21:05:30 +1100 Subject: [PATCH 06/17] test? --- .github/workflows/push.yml | 43 ++++++++++++++++++++++++++++++++++++++ Bootup Logos/img2logo.py | 41 ++++++++++++++++++++++-------------- 2 files changed, 68 insertions(+), 16 deletions(-) create mode 100644 .github/workflows/push.yml diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml new file mode 100644 index 0000000..9c18ee5 --- /dev/null +++ b/.github/workflows/push.yml @@ -0,0 +1,43 @@ +name: CI + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-20.04 + container: + image: alpine:3.15 + strategy: + matrix: + include: + - args: "" + model: "miniware" + - args: "-p" + model: "pinecil" + fail-fast: true + + steps: + - name: Install dependencies (apk) + run: apk add --no-cache git python3 py3-pip + + - name: Install dependencies (py) + run: pip3 install IntelHex + + - uses: actions/checkout@v2 + with: + submodules: true + + - name: prep + run: mkdir -p /tmp/${{ matrix.model }} + + - name: build test + run: cd Bootup\ Logos && python3 img2logo.py ${{matrix.args}} Images/IronOS.gif /tmp/${{ matrix.model }}/IronOS.gif + + - name: Archive artifacts + uses: actions/upload-artifact@v2 + with: + name: ${{ matrix.model }} + path: | + /tmp/${{ matrix.model }}/*.hex + /tmp/${{ matrix.model }}/*.dfu + if-no-files-found: error diff --git a/Bootup Logos/img2logo.py b/Bootup Logos/img2logo.py index 30b7809..f7ee3f9 100755 --- a/Bootup Logos/img2logo.py +++ b/Bootup Logos/img2logo.py @@ -69,8 +69,8 @@ def still_image_to_bytes( # pad to this size (also will be repeated in output Intel hex file) data = [0] * LCD_PAGE_SIZE - # magic/required header in endian-reverse byte order - data[0] = 0xAA + # magic/required header + data[0] = 0xAA # Indicates programmed page data[1] = 0xBB # convert to LCD format @@ -109,7 +109,7 @@ def animated_image_to_bytes( Data is stored in the byte blobs, so if you change one pixel in upper or lower row, changing another pixel in that column on that row is "free" """ frameData = [] - frameTimings = [] + frameTiming = None for framenum in range(0, imageIn.n_frames): print(f"Frame {framenum}") imageIn.seek(framenum) @@ -139,11 +139,13 @@ def animated_image_to_bytes( # store in endian-reversed byte order frameb[ndx] = byte frameData.append(frameb) + # Store inter-frame duration frameDuration_ms = image.info["duration"] if frameDuration_ms > 255: frameDuration_ms = 255 - frameTimings.append(frameDuration_ms) - print(f"Found {len(frameTimings)} frames") + if frameTiming is None: + frameTiming = frameDuration_ms + print(f"Found {len(frameData)} frames") # We have no mangled the image into our frambuffers # Now create the "deltas" for each frame frameDeltas = [[]] @@ -160,7 +162,7 @@ def animated_image_to_bytes( bytes_black = sum([1 if x == 0 else 0 for x in frameData[0]]) if bytes_black > 96: # It will take less room to delta encode first frame - outputData = [0xAA, 0xCC, frameTimings[0]] + outputData = [0xAA, 0xCC, frameTiming] delta = calculate_frame_delta_encode([0x00] * (LCD_NUM_BYTES), frameData[0]) if len(delta) > (LCD_NUM_BYTES / 2): raise Exception("BUG: Shouldn't delta encode more than 50%% of the screen") @@ -168,7 +170,7 @@ def animated_image_to_bytes( outputData.extend(delta) print("delta encoded first frame") else: - outputData = [0xAA, 0xDD, frameTimings[0]] + outputData = [0xAA, 0xDD, frameTiming] outputData.extend(frameData[0]) print("Used full encoded first frame") @@ -176,14 +178,14 @@ def animated_image_to_bytes( """ Format for each frame block is: - [duration in ms, max of 255][length][ [delta block][delta block][delta block][delta block] ] + [length][ [delta block][delta block][delta block][delta block] ] Where [delta block] is just [index,new value] OR - [duration in ms, max of 255][0xFF][Full frame data] + [0xFF][Full frame data] """ for frame in range(1, len(frameData)): - data = [frameTimings[frame]] + data = [] if len(frameDeltas[frame]) > LCD_NUM_BYTES: data.append(0xFF) data.extend(frameData[frame]) @@ -272,7 +274,7 @@ def img2hex( def parse_commandline(): parser = argparse.ArgumentParser( formatter_class=argparse.ArgumentDefaultsHelpFormatter, - description="Convert image file for display on LCD " "at startup", + description="Convert image file for display on IronOS OLED at startup", ) def zero_to_255(text): @@ -318,6 +320,12 @@ def parse_commandline(): parser.add_argument( "-f", "--force", action="store_true", help="force overwriting of existing files" ) + parser.add_argument( + "-E", + "--erase", + action="store_true", + help="generate a logo erase file instead of a logo", + ) parser.add_argument( "-p", "--pinecil", action="store_true", help="generate files for Pinecil" @@ -352,11 +360,12 @@ if __name__ == "__main__": sys.exit(1) img2hex( - args.input_filename, - args.preview, - args.threshold, - args.dither, - args.negative, + input_filename=args.input_filename, + preview_filename=args.preview, + threshold=args.threshold, + dither=args.dither, + negative=args.negative, + make_erase_image=args.erase, output_filename_base=args.output_filename, isPinecil=args.pinecil, ) From 68baf800524e483d201895ca1612508b524e39ca Mon Sep 17 00:00:00 2001 From: "Ben V. Brown" Date: Mon, 14 Feb 2022 21:07:06 +1100 Subject: [PATCH 07/17] Update push.yml --- .github/workflows/push.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 9c18ee5..51b14b6 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -21,7 +21,7 @@ jobs: run: apk add --no-cache git python3 py3-pip - name: Install dependencies (py) - run: pip3 install IntelHex + run: pip3 install pillow - uses: actions/checkout@v2 with: From 167d5300df794e0c08e4b36887d1a9f43006c861 Mon Sep 17 00:00:00 2001 From: "Ben V. Brown" Date: Mon, 14 Feb 2022 21:16:06 +1100 Subject: [PATCH 08/17] Update push.yml --- .github/workflows/push.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 51b14b6..b9e3aa9 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -18,7 +18,7 @@ jobs: steps: - name: Install dependencies (apk) - run: apk add --no-cache git python3 py3-pip + run: apk add --no-cache git python3 py3-pip zlib - name: Install dependencies (py) run: pip3 install pillow From 70d788783f4ceb2b773816c5e074cd1ef7a86026 Mon Sep 17 00:00:00 2001 From: "Ben V. Brown" Date: Mon, 14 Feb 2022 21:17:43 +1100 Subject: [PATCH 09/17] Update push.yml --- .github/workflows/push.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index b9e3aa9..9a61efb 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -18,10 +18,7 @@ jobs: steps: - name: Install dependencies (apk) - run: apk add --no-cache git python3 py3-pip zlib - - - name: Install dependencies (py) - run: pip3 install pillow + run: apk add --no-cache git python3 py3-pip zlib py3-pillow - uses: actions/checkout@v2 with: From a526c3bb0d81f31c762c5861961ae6db1edd0db6 Mon Sep 17 00:00:00 2001 From: "Ben V. Brown" Date: Mon, 14 Feb 2022 21:59:50 +1100 Subject: [PATCH 10/17] Cleaning up _more_ --- Bootup Logos/README.md | 18 ++-- Bootup Logos/img2logo.py | 180 +++++++++++++------------------------ Bootup Logos/output_dfu.py | 7 +- README.md | 8 +- 4 files changed, 83 insertions(+), 130 deletions(-) diff --git a/Bootup Logos/README.md b/Bootup Logos/README.md index 55f3431..40687a8 100644 --- a/Bootup Logos/README.md +++ b/Bootup Logos/README.md @@ -1,8 +1,12 @@ -# Preview of the logos -||   TS100   |   TS80    |   TS80P    |   Pinecil    |   IronOS     --|-|-|-|-|- -static
(right)|TS100|TS80|TS80P|Pinecil|IronOS -static
(left)|TS100_L|TS80_L|TS80P_L|Pinecil_L|IronOS_L -animated|||||IronOS -notes|||||   ^^^^^^^^
*(This loops just for
demonstration purposes,
the real thing
only plays once.)* +## Boot up logo's are logos or animations shown on boot of IronOS + +These are programmed into the device just like the normal firmware. +They can be (re)programmed as many times as desired after flashing the normal firmware. + + +### Data storage format + +The data is stored into the second last page of flash, this gives 1024 bytes of space for the entire payload of bootup logo data. + +The first byte is marked purely to \ No newline at end of file diff --git a/Bootup Logos/img2logo.py b/Bootup Logos/img2logo.py index f7ee3f9..ebed819 100755 --- a/Bootup Logos/img2logo.py +++ b/Bootup Logos/img2logo.py @@ -11,11 +11,7 @@ from output_dfu import DFUOutput try: from PIL import Image, ImageOps except ImportError as error: - raise ImportError( - "{}: {} requres Python Imaging Library (PIL). " - "Install with `pip` (pip3 install pillow) or OS-specific package " - "management tool.".format(error, sys.argv[0]) - ) + raise ImportError("{}: {} requres Python Imaging Library (PIL). " "Install with `pip` (pip3 install pillow) or OS-specific package " "management tool.".format(error, sys.argv[0])) VERSION_STRING = "1.0" @@ -24,6 +20,9 @@ LCD_HEIGHT = 16 LCD_NUM_BYTES = LCD_WIDTH * LCD_HEIGHT // 8 LCD_PAGE_SIZE = 1024 +DATA_PROGRAMMED_MARKER = 0xAA +FULL_FRAME_MARKER = 0xFF + class MiniwareSettings: IMAGE_ADDRESS = 0x0800F800 @@ -41,9 +40,7 @@ class PinecilSettings: DFU_PINECIL_PRODUCT = 0x0189 -def still_image_to_bytes( - image: Image, negative: bool, dither: bool, threshold: int, preview_filename -): +def still_image_to_bytes(image: Image, negative: bool, dither: bool, threshold: int, preview_filename): # convert to luminance # do even if already black/white because PIL can't invert 1-bit so # can't just pass thru in case --negative flag @@ -67,11 +64,7 @@ def still_image_to_bytes( if preview_filename: image.save(preview_filename) # pad to this size (also will be repeated in output Intel hex file) - data = [0] * LCD_PAGE_SIZE - - # magic/required header - data[0] = 0xAA # Indicates programmed page - data[1] = 0xBB + data = [] # convert to LCD format for ndx in range(LCD_WIDTH * LCD_HEIGHT // 8): @@ -80,7 +73,15 @@ def still_image_to_bytes( for y in range(8): if image.getpixel((ndx % LCD_WIDTH, y + bottom_half_offset)): byte |= 1 << y - data[2 + ndx] = byte + data.append(byte) + + """ DEBUG + for row in range(LCD_HEIGHT): + for column in range(LCD_WIDTH): + if image.getpixel((column, row)): sys.stderr.write('█') + else: sys.stderr.write(' ') + sys.stderr.write('\n') + """ return data @@ -93,9 +94,27 @@ def calculate_frame_delta_encode(previous_frame: bytearray, this_frame: bytearra return damage -def animated_image_to_bytes( - imageIn: Image, negative: bool, dither: bool, threshold: int -): +def get_screen_blob(previous_frame: bytearray, this_frame: bytearray): + """ + Given two screens, returns the smaller representation + Either a full screen update + OR + A delta encoded form + """ + outputData = [] + delta = calculate_frame_delta_encode(previous_frame, this_frame) + if len(delta) < (len(this_frame)): + outputData.append(len(delta)) + outputData.extend(delta) + # print("delta encoded frame") + else: + outputData.append(FULL_FRAME_MARKER) + outputData.extend(this_frame) + # print("full encoded frame") + return outputData + + +def animated_image_to_bytes(imageIn: Image, negative: bool, dither: bool, threshold: int): """ Convert the gif into our best effort startup animation We are delta-encoding on a byte by byte basis @@ -106,75 +125,32 @@ def animated_image_to_bytes( The naïve implementation would save the frame 5 times But if we delta encode; we can make far more frames of animation for _some_ types of animations. This means reveals are better than moves. - Data is stored in the byte blobs, so if you change one pixel in upper or lower row, changing another pixel in that column on that row is "free" + Data is stored in the byte blobs, so if you change one pixel, changing another pixel in that column on that row is "free" """ + frameData = [] frameTiming = None for framenum in range(0, imageIn.n_frames): print(f"Frame {framenum}") imageIn.seek(framenum) image = imageIn - if image.mode != "L": - image = image.convert("L") - # Resize to lcd size using bicubic sampling - if image.size != (LCD_WIDTH, LCD_HEIGHT): - image = image.resize((LCD_WIDTH, LCD_HEIGHT), Image.BICUBIC) - if negative: - image = ImageOps.invert(image) - threshold = 255 - threshold # have to invert threshold as well - - if dither: - image = image.convert("1") - else: - image = image.point(lambda pixel: 0 if pixel < threshold else 1, "1") - - frameb = [0] * LCD_WIDTH * (LCD_HEIGHT // 8) - for ndx in range(LCD_WIDTH * LCD_HEIGHT // 8): - bottom_half_offset = 0 if ndx < LCD_WIDTH else 8 - byte = 0 - for y in range(8): - if image.getpixel((ndx % LCD_WIDTH, y + bottom_half_offset)): - byte |= 1 << y - # store in endian-reversed byte order - frameb[ndx] = byte + frameb = still_image_to_bytes(image, negative, dither, threshold, None) frameData.append(frameb) # Store inter-frame duration frameDuration_ms = image.info["duration"] if frameDuration_ms > 255: frameDuration_ms = 255 - if frameTiming is None: + if frameTiming is None or frameTiming == 0: frameTiming = frameDuration_ms - print(f"Found {len(frameData)} frames") + print(f"Found {len(frameData)} frames, interval {frameTiming}ms") # We have no mangled the image into our frambuffers - # Now create the "deltas" for each frame - frameDeltas = [[]] - - for frame in range(1, len(frameData)): - - frameDeltas.append( - calculate_frame_delta_encode(frameData[frame - 1], frameData[frame]) - ) # Now we can build our output data blob # First we always start with a full first frame; future optimisation to check if we should or not - - bytes_black = sum([1 if x == 0 else 0 for x in frameData[0]]) - if bytes_black > 96: - # It will take less room to delta encode first frame - outputData = [0xAA, 0xCC, frameTiming] - delta = calculate_frame_delta_encode([0x00] * (LCD_NUM_BYTES), frameData[0]) - if len(delta) > (LCD_NUM_BYTES / 2): - raise Exception("BUG: Shouldn't delta encode more than 50%% of the screen") - outputData.append(len(delta)) - outputData.extend(delta) - print("delta encoded first frame") - else: - outputData = [0xAA, 0xDD, frameTiming] - outputData.extend(frameData[0]) - print("Used full encoded first frame") - - # Now we delta encode all following frames + outputData = [DATA_PROGRAMMED_MARKER] + outputData.append(frameTiming) + outputData.extend(get_screen_blob([0x00] * (LCD_NUM_BYTES), frameData[0])) """ Format for each frame block is: @@ -184,26 +160,13 @@ def animated_image_to_bytes( OR [0xFF][Full frame data] """ - for frame in range(1, len(frameData)): - data = [] - if len(frameDeltas[frame]) > LCD_NUM_BYTES: - data.append(0xFF) - data.extend(frameData[frame]) - print(f"Frame {frame} full encodes to {len(data)} bytes") - else: - data.append(len(frameDeltas[frame])) - data.extend(frameDeltas[frame]) - - print(f"Frame {frame} delta encodes to {len(data)} bytes") - if len(outputData) + len(data) > 1024: - print( - f"Animation truncated, frame {frame} and onwards out of {len(frameData)} discarded" - ) + for id in range(1, len(frameData)): + frameBlob = get_screen_blob(frameData[id - 1], frameData[id]) + if (len(outputData) + len(frameBlob)) > LCD_PAGE_SIZE: + print(f"Truncating animation after {id} frames as we are out of space") break - outputData.extend(data) - if len(outputData) < 1024: - pad = [0] * (1024 - len(outputData)) - outputData.extend(pad) + print(f"Frame {id} encoded to {len(frameBlob)} bytes") + outputData.extend(frameBlob) return outputData @@ -237,22 +200,19 @@ def img2hex( except BaseException as e: raise IOError('error reading image file "{}": {}'.format(input_filename, e)) - """ DEBUG - for row in range(LCD_HEIGHT): - for column in range(LCD_WIDTH): - if image.getpixel((column, row)): sys.stderr.write('█') - else: sys.stderr.write(' ') - sys.stderr.write('\n') - """ if make_erase_image: data = [0xFF] * 1024 elif getattr(image, "is_animated", False): data = animated_image_to_bytes(image, negative, dither, threshold) else: - data = still_image_to_bytes( - image, negative, dither, threshold, preview_filename - ) + # magic/required header + data = [DATA_PROGRAMMED_MARKER, 0xBB] + data.extend(still_image_to_bytes(image, negative, dither, threshold, preview_filename)) + # Pad up to the full page size + if len(data) < LCD_PAGE_SIZE: + pad = [0] * (LCD_PAGE_SIZE - len(data)) + data.extend(pad) deviceSettings = MiniwareSettings if isPinecil: deviceSettings = PinecilSettings @@ -266,9 +226,7 @@ def img2hex( deviceSettings.DFU_PINECIL_PRODUCT, deviceSettings.DFU_PINECIL_VENDOR, ) - HexOutput.writeFile( - output_filename_base + ".hex", data, deviceSettings.IMAGE_ADDRESS - ) + HexOutput.writeFile(output_filename_base + ".hex", data, deviceSettings.IMAGE_ADDRESS) def parse_commandline(): @@ -305,9 +263,7 @@ def parse_commandline(): "--threshold", type=zero_to_255, default=128, - help="0 to 255: gray (or color converted to gray) " - "above this becomes white, below becomes black; " - "ignored if using --dither", + help="0 to 255: gray (or color converted to gray) " "above this becomes white, below becomes black; " "ignored if using --dither", ) parser.add_argument( @@ -317,9 +273,7 @@ def parse_commandline(): help="use dithering (speckling) to convert gray or " "color to black and white", ) - parser.add_argument( - "-f", "--force", action="store_true", help="force overwriting of existing files" - ) + parser.add_argument("-f", "--force", action="store_true", help="force overwriting of existing files") parser.add_argument( "-E", "--erase", @@ -327,9 +281,7 @@ def parse_commandline(): help="generate a logo erase file instead of a logo", ) - parser.add_argument( - "-p", "--pinecil", action="store_true", help="generate files for Pinecil" - ) + parser.add_argument("-p", "--pinecil", action="store_true", help="generate files for Pinecil") parser.add_argument( "-v", "--version", @@ -346,17 +298,11 @@ if __name__ == "__main__": args = parse_commandline() if os.path.exists(args.output_filename) and not args.force: - sys.stderr.write( - 'Won\'t overwrite existing file "{}" (use --force ' - "option to override)\n".format(args.output_filename) - ) + sys.stderr.write('Won\'t overwrite existing file "{}" (use --force ' "option to override)\n".format(args.output_filename)) sys.exit(1) if args.preview and os.path.exists(args.preview) and not args.force: - sys.stderr.write( - 'Won\'t overwrite existing file "{}" (use --force ' - "option to override)\n".format(args.preview) - ) + sys.stderr.write('Won\'t overwrite existing file "{}" (use --force ' "option to override)\n".format(args.preview)) sys.exit(1) img2hex( diff --git a/Bootup Logos/output_dfu.py b/Bootup Logos/output_dfu.py index 2ee8057..5194a60 100644 --- a/Bootup Logos/output_dfu.py +++ b/Bootup Logos/output_dfu.py @@ -14,17 +14,14 @@ class DFUOutput: def writeFile( cls, file_name: str, - data: bytearray, + data_in: bytearray, data_address: int, tagetName: str, alt_number: int, product_id: int, vendor_id: int, ): - data: bytearray = b"" - - for byte in data: - data += byte.to_bytes(1, byteorder="big") + data: bytearray = bytearray(data_in) data = struct.pack("<2I", data_address, len(data)) + data data = ( diff --git a/README.md b/README.md index 52d7885..e30bc91 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,8 @@ # IronOS-Meta -Storing meta information about devices that dont need to be in the main repo + +Storing meta information for IronOS. +This are things that are not part of the core "OS". +This includes photographs of hardware, datasheets, schematics and of course **bootup logos**. + +This repository uses github actions to automagically build the logos for each device. +Periodically a "release" will be tagged and pre-compiled logo's will be put there as well to make it easy. From 90b2e2c31158051853b2ba6bca17450464fa95f3 Mon Sep 17 00:00:00 2001 From: "Ben V. Brown" Date: Mon, 14 Feb 2022 22:17:48 +1100 Subject: [PATCH 11/17] Refactor more --- .github/workflows/push.yml | 2 +- Bootup Logos/README.md | 18 ++++++++++++++---- Bootup Logos/img2logo.py | 18 +++++++----------- Bootup Logos/run.sh | 4 ++++ 4 files changed, 26 insertions(+), 16 deletions(-) create mode 100755 Bootup Logos/run.sh diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 9a61efb..45588e3 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -28,7 +28,7 @@ jobs: run: mkdir -p /tmp/${{ matrix.model }} - name: build test - run: cd Bootup\ Logos && python3 img2logo.py ${{matrix.args}} Images/IronOS.gif /tmp/${{ matrix.model }}/IronOS.gif + run: cd Bootup\ Logos && ./run.sh /tmp/${{ matrix.model }}/ ${{matrix.args}} - name: Archive artifacts uses: actions/upload-artifact@v2 diff --git a/Bootup Logos/README.md b/Bootup Logos/README.md index 40687a8..95dd36f 100644 --- a/Bootup Logos/README.md +++ b/Bootup Logos/README.md @@ -1,12 +1,22 @@ - ## Boot up logo's are logos or animations shown on boot of IronOS -These are programmed into the device just like the normal firmware. +These are programmed into the device just like the normal firmware. They can be (re)programmed as many times as desired after flashing the normal firmware. - ### Data storage format The data is stored into the second last page of flash, this gives 1024 bytes of space for the entire payload of bootup logo data. -The first byte is marked purely to \ No newline at end of file +The first byte is marked purely to indicate that the page is programmed and which revision of the boot logo logic it is +The next byte indicates the frame timing in milliseconds, or `0` to indicate only show first frame for whole bootloader duration (still image mode) +Then the OLED buffer is cleared to black, then every frame is encoded as either: + +### Full frame updates + +`[0xFF][Full framebuffer of data]` + +### Delta frame update + +`[count of updates][[index,data][index,data][index,data][index,data]]` +Where index is byte location into screen buffer, and data is the new byte to plonk down there +This just overwrites individual bytes in the output buffer diff --git a/Bootup Logos/img2logo.py b/Bootup Logos/img2logo.py index ebed819..fd21e87 100755 --- a/Bootup Logos/img2logo.py +++ b/Bootup Logos/img2logo.py @@ -131,7 +131,6 @@ def animated_image_to_bytes(imageIn: Image, negative: bool, dither: bool, thresh frameData = [] frameTiming = None for framenum in range(0, imageIn.n_frames): - print(f"Frame {framenum}") imageIn.seek(framenum) image = imageIn @@ -206,8 +205,9 @@ def img2hex( data = animated_image_to_bytes(image, negative, dither, threshold) else: # magic/required header - data = [DATA_PROGRAMMED_MARKER, 0xBB] - data.extend(still_image_to_bytes(image, negative, dither, threshold, preview_filename)) + data = [DATA_PROGRAMMED_MARKER, 0x00] # Timing value of 0 + image_bytes = still_image_to_bytes(image, negative, dither, threshold, preview_filename) + data.extend(get_screen_blob([0] * LCD_NUM_BYTES, image_bytes)) # Pad up to the full page size if len(data) < LCD_PAGE_SIZE: @@ -217,8 +217,9 @@ def img2hex( if isPinecil: deviceSettings = PinecilSettings # Generate both possible outputs + output_name = output_filename_base + os.path.basename(input_filename) DFUOutput.writeFile( - output_filename_base + ".dfu", + output_name + ".dfu", data, deviceSettings.IMAGE_ADDRESS, deviceSettings.DFU_TARGET_NAME, @@ -226,7 +227,7 @@ def img2hex( deviceSettings.DFU_PINECIL_PRODUCT, deviceSettings.DFU_PINECIL_VENDOR, ) - HexOutput.writeFile(output_filename_base + ".hex", data, deviceSettings.IMAGE_ADDRESS) + HexOutput.writeFile(output_name + ".hex", data, deviceSettings.IMAGE_ADDRESS) def parse_commandline(): @@ -273,7 +274,6 @@ def parse_commandline(): help="use dithering (speckling) to convert gray or " "color to black and white", ) - parser.add_argument("-f", "--force", action="store_true", help="force overwriting of existing files") parser.add_argument( "-E", "--erase", @@ -297,21 +297,17 @@ if __name__ == "__main__": args = parse_commandline() - if os.path.exists(args.output_filename) and not args.force: - sys.stderr.write('Won\'t overwrite existing file "{}" (use --force ' "option to override)\n".format(args.output_filename)) - sys.exit(1) - if args.preview and os.path.exists(args.preview) and not args.force: sys.stderr.write('Won\'t overwrite existing file "{}" (use --force ' "option to override)\n".format(args.preview)) sys.exit(1) img2hex( input_filename=args.input_filename, + output_filename_base=args.output_filename, preview_filename=args.preview, threshold=args.threshold, dither=args.dither, negative=args.negative, make_erase_image=args.erase, - output_filename_base=args.output_filename, isPinecil=args.pinecil, ) diff --git a/Bootup Logos/run.sh b/Bootup Logos/run.sh new file mode 100755 index 0000000..0f08031 --- /dev/null +++ b/Bootup Logos/run.sh @@ -0,0 +1,4 @@ +#! /bin/sh +echo $1 +echo $2 +find Images/ -type f -exec python img2logo.py {} "$1" "$2" \; From 6644d9550ceb51fdfd014ba37d884fcee36318c1 Mon Sep 17 00:00:00 2001 From: "Ben V. Brown" Date: Mon, 14 Feb 2022 22:23:02 +1100 Subject: [PATCH 12/17] Make erase image as well --- .github/workflows/push.yml | 5 ++++- Bootup Logos/img2logo.py | 24 ++++++++++++------------ Bootup Logos/run.sh | 2 +- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 45588e3..68b3f9e 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -27,9 +27,12 @@ jobs: - name: prep run: mkdir -p /tmp/${{ matrix.model }} - - name: build test + - name: build logo's run: cd Bootup\ Logos && ./run.sh /tmp/${{ matrix.model }}/ ${{matrix.args}} + - name: build logo's + run: cd Bootup\ Logos && python3 img2logo.py -E dummy.png /tmp/${{ matrix.model }}/ + - name: Archive artifacts uses: actions/upload-artifact@v2 with: diff --git a/Bootup Logos/img2logo.py b/Bootup Logos/img2logo.py index fd21e87..806b097 100755 --- a/Bootup Logos/img2logo.py +++ b/Bootup Logos/img2logo.py @@ -193,21 +193,21 @@ def img2hex( Optional `negative' inverts black/white regardless of input image type or other options. """ - - try: - image = Image.open(input_filename) - except BaseException as e: - raise IOError('error reading image file "{}": {}'.format(input_filename, e)) - if make_erase_image: data = [0xFF] * 1024 - elif getattr(image, "is_animated", False): - data = animated_image_to_bytes(image, negative, dither, threshold) else: - # magic/required header - data = [DATA_PROGRAMMED_MARKER, 0x00] # Timing value of 0 - image_bytes = still_image_to_bytes(image, negative, dither, threshold, preview_filename) - data.extend(get_screen_blob([0] * LCD_NUM_BYTES, image_bytes)) + try: + image = Image.open(input_filename) + except BaseException as e: + raise IOError('error reading image file "{}": {}'.format(input_filename, e)) + + if getattr(image, "is_animated", False): + data = animated_image_to_bytes(image, negative, dither, threshold) + else: + # magic/required header + data = [DATA_PROGRAMMED_MARKER, 0x00] # Timing value of 0 + image_bytes = still_image_to_bytes(image, negative, dither, threshold, preview_filename) + data.extend(get_screen_blob([0] * LCD_NUM_BYTES, image_bytes)) # Pad up to the full page size if len(data) < LCD_PAGE_SIZE: diff --git a/Bootup Logos/run.sh b/Bootup Logos/run.sh index 0f08031..2959aa3 100755 --- a/Bootup Logos/run.sh +++ b/Bootup Logos/run.sh @@ -1,4 +1,4 @@ #! /bin/sh echo $1 echo $2 -find Images/ -type f -exec python img2logo.py {} "$1" "$2" \; +find Images/ -type f -exec python3 img2logo.py {} "$1" "$2" \; From d0bec69096e554c564955ecf509374ce95351151 Mon Sep 17 00:00:00 2001 From: "Ben V. Brown" Date: Mon, 14 Feb 2022 22:23:17 +1100 Subject: [PATCH 13/17] Update push.yml --- .github/workflows/push.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 68b3f9e..63b5293 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -31,7 +31,7 @@ jobs: run: cd Bootup\ Logos && ./run.sh /tmp/${{ matrix.model }}/ ${{matrix.args}} - name: build logo's - run: cd Bootup\ Logos && python3 img2logo.py -E dummy.png /tmp/${{ matrix.model }}/ + run: cd Bootup\ Logos && python3 img2logo.py -E erase_stored_image /tmp/${{ matrix.model }}/ - name: Archive artifacts uses: actions/upload-artifact@v2 From b5d59fbc150a57d531d7ebb49416811af0de29ac Mon Sep 17 00:00:00 2001 From: discip <53649486+discip@users.noreply.github.com> Date: Mon, 14 Feb 2022 21:20:24 +0100 Subject: [PATCH 14/17] replacing IronOS.gif --- Bootup Logos/Images/IronOS.gif | Bin 26202 -> 9165 bytes Bootup Logos/Images/IronOS_L.gif | Bin 26238 -> 9177 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/Bootup Logos/Images/IronOS.gif b/Bootup Logos/Images/IronOS.gif index 52c2a42512e800bfc5cbc7b8e292512ca318b91d..fc9daa00c1bb1a1245850c9b2e53691d0c9fcaf6 100644 GIT binary patch delta 209 zcmcb0hViWX20>;XK?WuU9R?ssU=Wx*LG&=A;O0OtKIX};vX5X)GsUM+CgZ2c+Nw?U z)n#iU7eq~!dV6Z($~#*$p2tm%y0-4t3ZHr|zRv6VAqyh1#nNtD=*Q3KJ6X52Ms#J$ zG*8XoZL1|VJUdprmCY`5>!%k-{EeTpT8Dj&y;3D9>?vpE6;06U8lU?`$>8Jqf49jINm%~QP50K1!D(256HBV zw6@64s&e-}xvq_fv}JJ%VN}CFyEHwX_EDM{Uh^@FclXE4L#dy(IaU^RGpqn|GP{=6 ztXJT@1Z)H(lUEP0Rn_|S;gO2#7dP;@Bd zX;lH5AkRwyIaYHX-cb)lU9D0Rfnan4@xV}9P1(qU^M3^_Ukzxgn&qeMbiq=mS8%31WGF7EC#n)fqq(4sO< zTx(a@@{5(oppzHkGuf;@AL*J4+d|#vg)a9m)36_I{H+k%z&DMR}#xPeay>aORevNOk8vJCvMuIYCV zf?eI>LyP72N`9ps+7WZJf3vPDV?W|4r#808f#CvioL*Ai-9vT^(G=1sy68(Dcqm8@ z%tDoh+-C0k{e7rQvB#-1EzNb?C4ph}PihY6Q+O8rq%>`A zc}UFFwMz{gH?upj7};BoU&^$7-lsR3S7gLf+L5$VKm3B5)- z(0sni>K5A+*TZ+%^n+&Gj$llCG?>ufNe=PqcFPI)R1BQ*EP+KOYbTgt!ij}}i7=KY z7fF9${%%I#711^DFFM@b#V1}gx$j%(zg&Nw1h5_;`;I)g)hT(g`$Y@G(nx!rPo97> zU5)n>v+;#|mTi7xS`I{dybM-v2y#G4JV_^k%rtB-R$S`;k~(CWRp^|H1aos0GNZcc zW8pNS-og~>;ojdyL5k)@gN)198-ostSkUHB;BObt)9tE0QV}h!FgiIMrchgq;oNYT zvB_DC5#PhG6RJ0;MUGd-s05y4>&3IX==9oZ!S9tUECY-n$Y*%z4qqcFtNCd*EJY7@ z?2uox8hZM_g)U5py=za^s-TY17BFI-Z)@>X29o2wk`icD7{zF)+@y84aQ|;ur|Khb z2R=#>5$%t{*Ly**)Ujb!)Y?DRIGL4=HK%NWcMPJ#o;MU7{#^-|W9(gS_VKA}zyY2w~H0{!Z<864h_<{_@k2o&9@e6!5w(oDMHhb6$^n z-E&RfMr6R6-zzE)VP{TRsbIu;jzO7}#MojFHjE#P3Ey7o*$@}_38pY#)}4ExO-_j} zoF1a3OboXtSiJLXPTIiun6ra5b~&uKzq z^R%2j;e+&xxXy3lzQ1O8lYq4VvUuzKx3c{zbDe1bpJ96<4thpam+}g$=J_PGJ!dVz&Q~2?V|!C%k3{=7Q*P2A(zN_|&tBlu4KD zXu@`?lo#EeZCAexQS1*JC5HhY`?@T>I&3GA6E?ubb&k)w*k!=J5VaSLrTv0*8i_pvZ6~C~C1%>RA(VxCK;?XQL`k;Yx9D79vID z`qq--c*7T!=D@~Z)J{rY zwuC zt{~V#o>8rWa6tH~vm%_VV3VOz^n`J!Go2_E6%S=6nah>xQzC{-&vYC55IhWfE~jl$ zW3sY9BitxL&yZrjL2n@z9O!Y-`-GvmGPjJj*4bpvx5?Kp5y_urKYxr=k@z}aKhr}6 zvzHaIiJ{I9qT|%fH5=l?hdcEC+IF8)14`az?ZNqZt3RBqtDa(e<@6oFh5eQp4Mhly zL#Bz*OqxZk4xFM+w!?&F>bj;ICmNp3nbkwy^uU`4>$Vm36?i5&Dz(M3Td7(lpWc5d zLmQkGC2>v%eJ6nB@^Se+sQ+5|r_p->tDUd diff --git a/Bootup Logos/Images/IronOS_L.gif b/Bootup Logos/Images/IronOS_L.gif index b7f08857bf37403d0f841d893b0d0b7c1306bc91..b40f3d883d7068d759cc453782aaa51ec96e3af4 100644 GIT binary patch delta 244 zcmex&hViER20>;XK?WuU9R?ssU=W!6pZ6%E;ARIkKIX|$vQJ=4U&U82rkiSb{if$Z z3w_$%TC+ly1unCl$kp0;dAaY>;PpbWo}%f2o02!Ptz9)a$9qG@<5hnp%yV5fYFzgE zI!i?3)~3kG?@X;Psk`rN{Q1u0kBGsIjm>ZGslJrZi`#C@s=x2&_GbYzTjaBCS6y5h zcakHJecLQ?!?VlU-1Gl=eEslzdXnYkZ{3nAChvBwNnZ5j_Vw*9dl!^np4xr2B5>Zh f_eRg8_EsHgU{L(Y!U^?5iR%xj!#7Wie9Hp>7Vv8h delta 2170 zcma)+X*k>I8pdfbgla2A?G&jcsG?4*glT7D35wJbOQ}6*$KFt)brccOBom5BbL@%= z(j=BKr%h`Y#*(R}%nU)TEo$k}(UFeM=$Y$W*ZF_?e|ewZec$i>yrZz)UctI!fTKWx zLjn+iz0X(RsDOYdf}{qJvuewd0QkP_Bow#js9pq1ym2bEA!#^PN#sYB+2QoAc7Jyw zy4*h9)cM^r_BsyPOzWc#>spacduB%tA-{S1!FmGs53>lu%Sk5O^Z|L%m5pUZJu(+ zY6T`}CdgE|mtCd%q&3D>!CO#sgV|`r-&Iw$~jAe z>N7p}VN<=m`3*nzWqcMPNfp2r+)uN0Pdf;6SR})`zPJr52=;lIVOY|EI|RjsR}QFK zkpwOkUy_s3=*cqpq8+DgR0_`pU!4^~N1wFELW9>JuU!0l$olE!a8yj_K)Fn+POJ_A z)nA5)T|+d1P%xS+?SVw%Tdg5dFl|?;ssS=q?x-pGn>BOMvq6#TAPsa4v#B*7H>{lq z*{bDyi&mGsoA0_b^;}eiBorQ};nPWadd)`*O%KkYK(m6X(Qn&R4g`O|2c};@P*l;n z?q5$+MURQN->)lrmVQ*-_Cn`n-;~S~MyTPN11D2*6fR!#vL=?c5jCabYHkkjEc3Md zTrZ49fnkLPku!fj9P$VuU^pzC!piT3>HrNSjjx z&$7+-h}L$d!_j>)@`&aBYB%IiGN*vBLBv1qME-0-ST{%VQ?1fl%CkK8H8VV!G# zX*!HC*B3 zn)DF#kWHwH?!zrFQ116^+E5J7;aP{)%M;0zcZj;d%E%4dZ*@$^=kZ+yO z4l@?$RyX{8su(B&cXJil6B2f3Sb}(cq9ty+os4xUzt~@HMO8{fug&74lZy3q;-e$R zZ_}jCn(q{?)XF99PA(UQa`7yctKh!(y6gQ|s$Ketqkj}e;1GR4TmnFvkxIbW5R1lH z&WVG2LUejdueaVMn}K*Ag_tND=Z&^{eW*RWB&*}~*qvsfab9}OwJlF+i{Y`YJJB4m zjFhos=nfKyf-K14bEijY!{}?jo#4&}=Tqc%6blnJc%9XWTggeormxDLLT7Un#v9jZ zeR%&I2c>J42f8cxdM^`fn6U1P+7Sw7yOsPWnp69&Y1=mKA+-sDn-$*ty&Ny7K&6X$3DPk8;zvrbY^<4?DHoR(EB;}Efm#~7 zm2_llDFHp-+gAdW5Iu;egcZWR+fAp|K&*GDZnWoX^rUNe5xJhQ__FD(yM%q_Xaq( z;;?}i9D!ui{@;U`E>=dTjuZn!_e2V-mAk*|9b z@6oZF*m&NR?h2#H4RPk8-{Umsnaz0*uwR9RtlcDs5$D8}1gXqK6!5E~?Fjy1M|XWH z?|&HjFV4ubY=f*nIbYYj(dowBfu?TqIqZE-I9X-dAr~@QJCA#!1h(k*j2*9PGG*<0 zdx1M=tI{u0l?@LX+H2_cr^lHeVx^<*4BOcd^XQ|S+e7kw4?j^nJ!o!Jl{vP9`QSnj zJ3AUS$>Gv_aYy#tc=H#bQN=~GZ|CfKG1(qUbvS*H^09s_i`-lBJZ=|(#?G#mU4`|j zJQ{loA=SB1{T*TYK=f(b^n!TtiZSNGB8MD%!~hu8G|&56qxd4(CV4t@Y!$hk{o{`w z&h$0s0ln%MfTJKd5!2Hay*o3@wpWKfM>=G&fZQZ Date: Tue, 15 Feb 2022 18:15:38 +1100 Subject: [PATCH 15/17] arg for miniware --- .github/workflows/push.yml | 2 +- Bootup Logos/img2logo.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 63b5293..9863586 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -10,7 +10,7 @@ jobs: strategy: matrix: include: - - args: "" + - args: "-m" model: "miniware" - args: "-p" model: "pinecil" diff --git a/Bootup Logos/img2logo.py b/Bootup Logos/img2logo.py index 806b097..ba30177 100755 --- a/Bootup Logos/img2logo.py +++ b/Bootup Logos/img2logo.py @@ -282,6 +282,7 @@ def parse_commandline(): ) parser.add_argument("-p", "--pinecil", action="store_true", help="generate files for Pinecil") + parser.add_argument("-m", "--miniware", action="store_true", help="generate files for miniware") parser.add_argument( "-v", "--version", @@ -301,6 +302,10 @@ if __name__ == "__main__": sys.stderr.write('Won\'t overwrite existing file "{}" (use --force ' "option to override)\n".format(args.preview)) sys.exit(1) + if args.miniware == False and args.pinecil == False: + sys.stderr.write("You must provide --miniware or --pinecil to select your model") + sys.exit(1) + img2hex( input_filename=args.input_filename, output_filename_base=args.output_filename, From 62a13b427b352b5a2366de65183f961c4132cb4f Mon Sep 17 00:00:00 2001 From: "Ben V. Brown" Date: Tue, 15 Feb 2022 18:19:27 +1100 Subject: [PATCH 16/17] Update push.yml --- .github/workflows/push.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 9863586..c036439 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -31,7 +31,7 @@ jobs: run: cd Bootup\ Logos && ./run.sh /tmp/${{ matrix.model }}/ ${{matrix.args}} - name: build logo's - run: cd Bootup\ Logos && python3 img2logo.py -E erase_stored_image /tmp/${{ matrix.model }}/ + run: cd Bootup\ Logos && python3 img2logo.py -E erase_stored_image /tmp/${{ matrix.model }}/ ${{matrix.args}} - name: Archive artifacts uses: actions/upload-artifact@v2 From 6ea1572543530f61ea1ee00e3ae5bce0c9ab57a2 Mon Sep 17 00:00:00 2001 From: "Ben V. Brown" Date: Tue, 15 Feb 2022 18:19:57 +1100 Subject: [PATCH 17/17] Update push.yml --- .github/workflows/push.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index c036439..883953c 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -27,10 +27,10 @@ jobs: - name: prep run: mkdir -p /tmp/${{ matrix.model }} - - name: build logo's + - name: build all files for the device run: cd Bootup\ Logos && ./run.sh /tmp/${{ matrix.model }}/ ${{matrix.args}} - - name: build logo's + - name: build logo erase file run: cd Bootup\ Logos && python3 img2logo.py -E erase_stored_image /tmp/${{ matrix.model }}/ ${{matrix.args}} - name: Archive artifacts