Merge pull request #1 from Ralim/logos
New logo format Animation support Github actions
43
.github/workflows/push.yml
vendored
Normal file
@@ -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: "-m"
|
||||||
|
model: "miniware"
|
||||||
|
- args: "-p"
|
||||||
|
model: "pinecil"
|
||||||
|
fail-fast: true
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Install dependencies (apk)
|
||||||
|
run: apk add --no-cache git python3 py3-pip zlib py3-pillow
|
||||||
|
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
|
||||||
|
- name: prep
|
||||||
|
run: mkdir -p /tmp/${{ matrix.model }}
|
||||||
|
|
||||||
|
- name: build all files for the device
|
||||||
|
run: cd Bootup\ Logos && ./run.sh /tmp/${{ matrix.model }}/ ${{matrix.args}}
|
||||||
|
|
||||||
|
- name: build logo erase file
|
||||||
|
run: cd Bootup\ Logos && python3 img2logo.py -E erase_stored_image /tmp/${{ matrix.model }}/ ${{matrix.args}}
|
||||||
|
|
||||||
|
- 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
|
||||||
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
*/__pycache__/*
|
||||||
|
*.hex
|
||||||
|
*.dfu
|
||||||
BIN
Bootup Logos/Images/IronOS.gif
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
BIN
Bootup Logos/Images/IronOS.png
Normal file
|
After Width: | Height: | Size: 458 B |
BIN
Bootup Logos/Images/IronOS_L.gif
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
BIN
Bootup Logos/Images/IronOS_L.png
Normal file
|
After Width: | Height: | Size: 300 B |
BIN
Bootup Logos/Images/Pinecil.png
Normal file
|
After Width: | Height: | Size: 258 B |
BIN
Bootup Logos/Images/Pinecil_L.png
Normal file
|
After Width: | Height: | Size: 259 B |
BIN
Bootup Logos/Images/TS100.png
Normal file
|
After Width: | Height: | Size: 251 B |
BIN
Bootup Logos/Images/TS100_L.png
Normal file
|
After Width: | Height: | Size: 245 B |
BIN
Bootup Logos/Images/TS80.png
Normal file
|
After Width: | Height: | Size: 262 B |
BIN
Bootup Logos/Images/TS80P.png
Normal file
|
After Width: | Height: | Size: 267 B |
BIN
Bootup Logos/Images/TS80P_L.png
Normal file
|
After Width: | Height: | Size: 263 B |
BIN
Bootup Logos/Images/TS80_L.png
Normal file
|
After Width: | Height: | Size: 262 B |
22
Bootup Logos/README.md
Normal file
@@ -0,0 +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.
|
||||||
|
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 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
|
||||||
318
Bootup Logos/img2logo.py
Executable file
@@ -0,0 +1,318 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# coding=utf-8
|
||||||
|
from __future__ import division
|
||||||
|
import argparse
|
||||||
|
import copy
|
||||||
|
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` (pip3 install pillow) or OS-specific package " "management tool.".format(error, sys.argv[0]))
|
||||||
|
|
||||||
|
VERSION_STRING = "1.0"
|
||||||
|
|
||||||
|
LCD_WIDTH = 96
|
||||||
|
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
|
||||||
|
DFU_TARGET_NAME = b"IronOS-dfu"
|
||||||
|
DFU_PINECIL_ALT = 0
|
||||||
|
DFU_PINECIL_VENDOR = 0x1209
|
||||||
|
DFU_PINECIL_PRODUCT = 0xDB42
|
||||||
|
|
||||||
|
|
||||||
|
class PinecilSettings:
|
||||||
|
IMAGE_ADDRESS = 0x0801F800
|
||||||
|
DFU_TARGET_NAME = b"Pinecil"
|
||||||
|
DFU_PINECIL_ALT = 0
|
||||||
|
DFU_PINECIL_VENDOR = 0x28E9
|
||||||
|
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 = []
|
||||||
|
|
||||||
|
# convert to LCD format
|
||||||
|
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
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
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 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
|
||||||
|
|
||||||
|
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, changing another pixel in that column on that row is "free"
|
||||||
|
"""
|
||||||
|
|
||||||
|
frameData = []
|
||||||
|
frameTiming = None
|
||||||
|
for framenum in range(0, imageIn.n_frames):
|
||||||
|
imageIn.seek(framenum)
|
||||||
|
image = imageIn
|
||||||
|
|
||||||
|
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 or frameTiming == 0:
|
||||||
|
frameTiming = frameDuration_ms
|
||||||
|
print(f"Found {len(frameData)} frames, interval {frameTiming}ms")
|
||||||
|
# We have no mangled the image into our frambuffers
|
||||||
|
|
||||||
|
# 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 = [DATA_PROGRAMMED_MARKER]
|
||||||
|
outputData.append(frameTiming)
|
||||||
|
outputData.extend(get_screen_blob([0x00] * (LCD_NUM_BYTES), frameData[0]))
|
||||||
|
|
||||||
|
"""
|
||||||
|
Format for each frame block is:
|
||||||
|
[length][ [delta block][delta block][delta block][delta block] ]
|
||||||
|
Where [delta block] is just [index,new value]
|
||||||
|
|
||||||
|
OR
|
||||||
|
[0xFF][Full frame data]
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
print(f"Frame {id} encoded to {len(frameBlob)} bytes")
|
||||||
|
outputData.extend(frameBlob)
|
||||||
|
return outputData
|
||||||
|
|
||||||
|
|
||||||
|
def img2hex(
|
||||||
|
input_filename,
|
||||||
|
preview_filename=None,
|
||||||
|
threshold=128,
|
||||||
|
dither=False,
|
||||||
|
negative=False,
|
||||||
|
isPinecil=False,
|
||||||
|
make_erase_image=False,
|
||||||
|
output_filename_base="out",
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Convert 'input_filename' image file into Intel hex format with data
|
||||||
|
formatted for display on LCD and file object.
|
||||||
|
Input image is converted from color or grayscale to black-and-white,
|
||||||
|
and resized to fit LCD screen as necessary.
|
||||||
|
Optionally write resized/thresholded/black-and-white preview image
|
||||||
|
to file specified by name.
|
||||||
|
Optional `threshold' argument 8 bit value; grayscale pixels greater than
|
||||||
|
this become 1 (white) in output, less than become 0 (black).
|
||||||
|
Unless optional `dither', in which case PIL grayscale-to-black/white
|
||||||
|
dithering algorithm used.
|
||||||
|
Optional `negative' inverts black/white regardless of input image type
|
||||||
|
or other options.
|
||||||
|
"""
|
||||||
|
if make_erase_image:
|
||||||
|
data = [0xFF] * 1024
|
||||||
|
else:
|
||||||
|
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:
|
||||||
|
pad = [0] * (LCD_PAGE_SIZE - len(data))
|
||||||
|
data.extend(pad)
|
||||||
|
deviceSettings = MiniwareSettings
|
||||||
|
if isPinecil:
|
||||||
|
deviceSettings = PinecilSettings
|
||||||
|
# Generate both possible outputs
|
||||||
|
output_name = output_filename_base + os.path.basename(input_filename)
|
||||||
|
DFUOutput.writeFile(
|
||||||
|
output_name + ".dfu",
|
||||||
|
data,
|
||||||
|
deviceSettings.IMAGE_ADDRESS,
|
||||||
|
deviceSettings.DFU_TARGET_NAME,
|
||||||
|
deviceSettings.DFU_PINECIL_ALT,
|
||||||
|
deviceSettings.DFU_PINECIL_PRODUCT,
|
||||||
|
deviceSettings.DFU_PINECIL_VENDOR,
|
||||||
|
)
|
||||||
|
HexOutput.writeFile(output_name + ".hex", data, deviceSettings.IMAGE_ADDRESS)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_commandline():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||||
|
description="Convert image file for display on IronOS OLED at startup",
|
||||||
|
)
|
||||||
|
|
||||||
|
def zero_to_255(text):
|
||||||
|
value = int(text)
|
||||||
|
if not 0 <= value <= 255:
|
||||||
|
raise argparse.ArgumentTypeError("must be integer from 0 to 255 ")
|
||||||
|
return value
|
||||||
|
|
||||||
|
parser.add_argument("input_filename", help="input image file")
|
||||||
|
|
||||||
|
parser.add_argument("output_filename", help="output file base name")
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"-P",
|
||||||
|
"--preview",
|
||||||
|
help="filename of image preview",
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"-n",
|
||||||
|
"--negative",
|
||||||
|
action="store_true",
|
||||||
|
help="photo negative: exchange black and white " "in output",
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"-t",
|
||||||
|
"--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",
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"-d",
|
||||||
|
"--dither",
|
||||||
|
action="store_true",
|
||||||
|
help="use dithering (speckling) to convert gray or " "color to black and white",
|
||||||
|
)
|
||||||
|
|
||||||
|
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")
|
||||||
|
parser.add_argument("-m", "--miniware", action="store_true", help="generate files for miniware")
|
||||||
|
parser.add_argument(
|
||||||
|
"-v",
|
||||||
|
"--version",
|
||||||
|
action="version",
|
||||||
|
version="%(prog)s version " + VERSION_STRING,
|
||||||
|
help="print version info",
|
||||||
|
)
|
||||||
|
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
args = parse_commandline()
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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,
|
||||||
|
preview_filename=args.preview,
|
||||||
|
threshold=args.threshold,
|
||||||
|
dither=args.dither,
|
||||||
|
negative=args.negative,
|
||||||
|
make_erase_image=args.erase,
|
||||||
|
isPinecil=args.pinecil,
|
||||||
|
)
|
||||||
68
Bootup Logos/output_dfu.py
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import struct, zlib
|
||||||
|
|
||||||
|
|
||||||
|
class DFUOutput:
|
||||||
|
|
||||||
|
DFU_PREFIX_SIZE = 11
|
||||||
|
DFU_SUFFIX_SIZE = 16
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def compute_crc(cls, data):
|
||||||
|
return 0xFFFFFFFF & -zlib.crc32(data) - 1
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def writeFile(
|
||||||
|
cls,
|
||||||
|
file_name: str,
|
||||||
|
data_in: bytearray,
|
||||||
|
data_address: int,
|
||||||
|
tagetName: str,
|
||||||
|
alt_number: int,
|
||||||
|
product_id: int,
|
||||||
|
vendor_id: int,
|
||||||
|
):
|
||||||
|
data: bytearray = bytearray(data_in)
|
||||||
|
|
||||||
|
data = struct.pack("<2I", data_address, len(data)) + data
|
||||||
|
data = (
|
||||||
|
struct.pack(
|
||||||
|
"<6sBI255s2I",
|
||||||
|
b"Target",
|
||||||
|
alt_number,
|
||||||
|
1,
|
||||||
|
tagetName,
|
||||||
|
len(data),
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
+ data
|
||||||
|
)
|
||||||
|
data = (
|
||||||
|
struct.pack(
|
||||||
|
"<5sBIB",
|
||||||
|
b"DfuSe",
|
||||||
|
1,
|
||||||
|
cls.DFU_PREFIX_SIZE + len(data) + cls.DFU_SUFFIX_SIZE,
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
+ data
|
||||||
|
)
|
||||||
|
data += struct.pack(
|
||||||
|
"<4H3sB",
|
||||||
|
0,
|
||||||
|
product_id,
|
||||||
|
vendor_id,
|
||||||
|
0x011A,
|
||||||
|
b"UFD",
|
||||||
|
cls.DFU_SUFFIX_SIZE,
|
||||||
|
)
|
||||||
|
crc = cls.compute_crc(data)
|
||||||
|
data += struct.pack("<I", crc)
|
||||||
|
with open(file_name, "wb") as output:
|
||||||
|
output.write(data)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import sys
|
||||||
|
|
||||||
|
print("DO NOT CALL THIS FILE DIRECTLY")
|
||||||
|
sys.exit(1)
|
||||||
104
Bootup Logos/output_hex.py
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
import zlib
|
||||||
|
|
||||||
|
|
||||||
|
class HexOutput:
|
||||||
|
"""
|
||||||
|
Supports writing a blob of data out in the Intel Hex format
|
||||||
|
"""
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def split16(cls, word):
|
||||||
|
"""return high and low byte of 16-bit word value as tuple"""
|
||||||
|
return (word >> 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)
|
||||||
4
Bootup Logos/run.sh
Executable file
@@ -0,0 +1,4 @@
|
|||||||
|
#! /bin/sh
|
||||||
|
echo $1
|
||||||
|
echo $2
|
||||||
|
find Images/ -type f -exec python3 img2logo.py {} "$1" "$2" \;
|
||||||
@@ -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.
|
||||||
|
|||||||