1
0
forked from me/IronOS-Meta

Cleaning up _more_

This commit is contained in:
Ben V. Brown
2022-02-14 21:59:50 +11:00
parent 70d788783f
commit a526c3bb0d
4 changed files with 83 additions and 130 deletions

View File

@@ -1,8 +1,12 @@
# Preview of the logos
||   TS100   |   TS80    |   TS80P    |   Pinecil    |   IronOS     ## Boot up logo's are logos or animations shown on boot of IronOS
-|-|-|-|-|-
static<br>(right)|<img src="/Bootup Logo/Logos/TS100.png" alt="TS100" width="200%">|<img src="/Bootup Logo/Logos/TS80.png" alt="TS80" width="100%">|<img src="/Bootup Logo/Logos/TS80P.png" alt="TS80P" width="100%">|<img src="/Bootup Logo/Logos/Pinecil.png" alt="Pinecil" width="100%">|<img src="/Bootup Logo/Logos/IronOS.png" alt="IronOS" width="100%"> These are programmed into the device just like the normal firmware.
static<br>(left)|<img src="/Bootup Logo/Logos/TS100_L.png" alt="TS100_L" width="200%">|<img src="/Bootup Logo/Logos/TS80_L.png" alt="TS80_L" width="100%">|<img src="/Bootup Logo/Logos/TS80P_L.png" alt="TS80P_L" width="100%">|<img src="/Bootup Logo/Logos/Pinecil_L.png" alt="Pinecil_L" width="100%">|<img src="/Bootup Logo/Logos/IronOS_L.png" alt="IronOS_L" width="100%"> They can be (re)programmed as many times as desired after flashing the normal firmware.
animated|||||<img src="https://user-images.githubusercontent.com/53649486/153309202-ff517235-c81f-48e7-9ca2-990d0bb0764f.gif" alt="IronOS" width="100%">
notes|||||&emsp;&emsp;&ensp;^^^^^^^^<br>*(This loops just for<br>demonstration&nbsp;purposes,<br>the real thing<br>only plays once.)*
### 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

View File

@@ -11,11 +11,7 @@ from output_dfu import DFUOutput
try: try:
from PIL import Image, ImageOps from PIL import Image, ImageOps
except ImportError as error: except ImportError as error:
raise ImportError( raise ImportError("{}: {} requres Python Imaging Library (PIL). " "Install with `pip` (pip3 install pillow) or OS-specific package " "management tool.".format(error, sys.argv[0]))
"{}: {} 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" VERSION_STRING = "1.0"
@@ -24,6 +20,9 @@ LCD_HEIGHT = 16
LCD_NUM_BYTES = LCD_WIDTH * LCD_HEIGHT // 8 LCD_NUM_BYTES = LCD_WIDTH * LCD_HEIGHT // 8
LCD_PAGE_SIZE = 1024 LCD_PAGE_SIZE = 1024
DATA_PROGRAMMED_MARKER = 0xAA
FULL_FRAME_MARKER = 0xFF
class MiniwareSettings: class MiniwareSettings:
IMAGE_ADDRESS = 0x0800F800 IMAGE_ADDRESS = 0x0800F800
@@ -41,9 +40,7 @@ class PinecilSettings:
DFU_PINECIL_PRODUCT = 0x0189 DFU_PINECIL_PRODUCT = 0x0189
def still_image_to_bytes( def still_image_to_bytes(image: Image, negative: bool, dither: bool, threshold: int, preview_filename):
image: Image, negative: bool, dither: bool, threshold: int, preview_filename
):
# convert to luminance # convert to luminance
# do even if already black/white because PIL can't invert 1-bit so # do even if already black/white because PIL can't invert 1-bit so
# can't just pass thru in case --negative flag # can't just pass thru in case --negative flag
@@ -67,11 +64,7 @@ def still_image_to_bytes(
if preview_filename: if preview_filename:
image.save(preview_filename) image.save(preview_filename)
# pad to this size (also will be repeated in output Intel hex file) # pad to this size (also will be repeated in output Intel hex file)
data = [0] * LCD_PAGE_SIZE data = []
# magic/required header
data[0] = 0xAA # Indicates programmed page
data[1] = 0xBB
# convert to LCD format # convert to LCD format
for ndx in range(LCD_WIDTH * LCD_HEIGHT // 8): for ndx in range(LCD_WIDTH * LCD_HEIGHT // 8):
@@ -80,7 +73,15 @@ def still_image_to_bytes(
for y in range(8): for y in range(8):
if image.getpixel((ndx % LCD_WIDTH, y + bottom_half_offset)): if image.getpixel((ndx % LCD_WIDTH, y + bottom_half_offset)):
byte |= 1 << y 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 return data
@@ -93,9 +94,27 @@ def calculate_frame_delta_encode(previous_frame: bytearray, this_frame: bytearra
return damage return damage
def animated_image_to_bytes( def get_screen_blob(previous_frame: bytearray, this_frame: bytearray):
imageIn: Image, negative: bool, dither: bool, threshold: int """
): 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 Convert the gif into our best effort startup animation
We are delta-encoding on a byte by byte basis 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 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. 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. 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 = [] frameData = []
frameTiming = None frameTiming = None
for framenum in range(0, imageIn.n_frames): for framenum in range(0, imageIn.n_frames):
print(f"Frame {framenum}") print(f"Frame {framenum}")
imageIn.seek(framenum) imageIn.seek(framenum)
image = imageIn 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: frameb = still_image_to_bytes(image, negative, dither, threshold, None)
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
frameData.append(frameb) frameData.append(frameb)
# Store inter-frame duration # Store inter-frame duration
frameDuration_ms = image.info["duration"] frameDuration_ms = image.info["duration"]
if frameDuration_ms > 255: if frameDuration_ms > 255:
frameDuration_ms = 255 frameDuration_ms = 255
if frameTiming is None: if frameTiming is None or frameTiming == 0:
frameTiming = frameDuration_ms 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 # 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 # 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 # First we always start with a full first frame; future optimisation to check if we should or not
outputData = [DATA_PROGRAMMED_MARKER]
bytes_black = sum([1 if x == 0 else 0 for x in frameData[0]]) outputData.append(frameTiming)
if bytes_black > 96: outputData.extend(get_screen_blob([0x00] * (LCD_NUM_BYTES), frameData[0]))
# 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
""" """
Format for each frame block is: Format for each frame block is:
@@ -184,26 +160,13 @@ def animated_image_to_bytes(
OR OR
[0xFF][Full frame data] [0xFF][Full frame data]
""" """
for frame in range(1, len(frameData)): for id in range(1, len(frameData)):
data = [] frameBlob = get_screen_blob(frameData[id - 1], frameData[id])
if len(frameDeltas[frame]) > LCD_NUM_BYTES: if (len(outputData) + len(frameBlob)) > LCD_PAGE_SIZE:
data.append(0xFF) print(f"Truncating animation after {id} frames as we are out of space")
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"
)
break break
outputData.extend(data) print(f"Frame {id} encoded to {len(frameBlob)} bytes")
if len(outputData) < 1024: outputData.extend(frameBlob)
pad = [0] * (1024 - len(outputData))
outputData.extend(pad)
return outputData return outputData
@@ -237,22 +200,19 @@ def img2hex(
except BaseException as e: except BaseException as e:
raise IOError('error reading image file "{}": {}'.format(input_filename, 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: if make_erase_image:
data = [0xFF] * 1024 data = [0xFF] * 1024
elif getattr(image, "is_animated", False): elif getattr(image, "is_animated", False):
data = animated_image_to_bytes(image, negative, dither, threshold) data = animated_image_to_bytes(image, negative, dither, threshold)
else: else:
data = still_image_to_bytes( # magic/required header
image, negative, dither, threshold, preview_filename 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 deviceSettings = MiniwareSettings
if isPinecil: if isPinecil:
deviceSettings = PinecilSettings deviceSettings = PinecilSettings
@@ -266,9 +226,7 @@ def img2hex(
deviceSettings.DFU_PINECIL_PRODUCT, deviceSettings.DFU_PINECIL_PRODUCT,
deviceSettings.DFU_PINECIL_VENDOR, deviceSettings.DFU_PINECIL_VENDOR,
) )
HexOutput.writeFile( HexOutput.writeFile(output_filename_base + ".hex", data, deviceSettings.IMAGE_ADDRESS)
output_filename_base + ".hex", data, deviceSettings.IMAGE_ADDRESS
)
def parse_commandline(): def parse_commandline():
@@ -305,9 +263,7 @@ def parse_commandline():
"--threshold", "--threshold",
type=zero_to_255, type=zero_to_255,
default=128, default=128,
help="0 to 255: gray (or color converted to gray) " help="0 to 255: gray (or color converted to gray) " "above this becomes white, below becomes black; " "ignored if using --dither",
"above this becomes white, below becomes black; "
"ignored if using --dither",
) )
parser.add_argument( parser.add_argument(
@@ -317,9 +273,7 @@ def parse_commandline():
help="use dithering (speckling) to convert gray or " "color to black and white", help="use dithering (speckling) to convert gray or " "color to black and white",
) )
parser.add_argument( parser.add_argument("-f", "--force", action="store_true", help="force overwriting of existing files")
"-f", "--force", action="store_true", help="force overwriting of existing files"
)
parser.add_argument( parser.add_argument(
"-E", "-E",
"--erase", "--erase",
@@ -327,9 +281,7 @@ def parse_commandline():
help="generate a logo erase file instead of a logo", help="generate a logo erase file instead of a logo",
) )
parser.add_argument( parser.add_argument("-p", "--pinecil", action="store_true", help="generate files for Pinecil")
"-p", "--pinecil", action="store_true", help="generate files for Pinecil"
)
parser.add_argument( parser.add_argument(
"-v", "-v",
"--version", "--version",
@@ -346,17 +298,11 @@ if __name__ == "__main__":
args = parse_commandline() args = parse_commandline()
if os.path.exists(args.output_filename) and not args.force: if os.path.exists(args.output_filename) and not args.force:
sys.stderr.write( sys.stderr.write('Won\'t overwrite existing file "{}" (use --force ' "option to override)\n".format(args.output_filename))
'Won\'t overwrite existing file "{}" (use --force '
"option to override)\n".format(args.output_filename)
)
sys.exit(1) sys.exit(1)
if args.preview and os.path.exists(args.preview) and not args.force: if args.preview and os.path.exists(args.preview) and not args.force:
sys.stderr.write( sys.stderr.write('Won\'t overwrite existing file "{}" (use --force ' "option to override)\n".format(args.preview))
'Won\'t overwrite existing file "{}" (use --force '
"option to override)\n".format(args.preview)
)
sys.exit(1) sys.exit(1)
img2hex( img2hex(

View File

@@ -14,17 +14,14 @@ class DFUOutput:
def writeFile( def writeFile(
cls, cls,
file_name: str, file_name: str,
data: bytearray, data_in: bytearray,
data_address: int, data_address: int,
tagetName: str, tagetName: str,
alt_number: int, alt_number: int,
product_id: int, product_id: int,
vendor_id: int, vendor_id: int,
): ):
data: bytearray = b"" data: bytearray = bytearray(data_in)
for byte in data:
data += byte.to_bytes(1, byteorder="big")
data = struct.pack("<2I", data_address, len(data)) + data data = struct.pack("<2I", data_address, len(data)) + data
data = ( data = (

View File

@@ -1,2 +1,8 @@
# IronOS-Meta # 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.