1
0
forked from me/IronOS-Meta

Rebuilding python into split classes

This commit is contained in:
Ben V. Brown
2022-02-13 15:08:38 +11:00
parent cc3cf039d2
commit de30580e6a
4 changed files with 307 additions and 190 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
*/__pycache__/*
*.hex
*.dfu

View File

@@ -1,135 +1,59 @@
#!/usr/bin/env python #!/usr/bin/env python
# coding=utf-8 # coding=utf-8
from __future__ import division 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: try:
from PIL import Image, ImageOps from PIL import Image, ImageOps
except ImportError as error: except ImportError as error:
raise ImportError("{}: {} requres Python Imaging Library (PIL). " raise ImportError(
"Install with `pip` or OS-specific package " "{}: {} requres Python Imaging Library (PIL). "
"management tool." "Install with `pip` (pip3 install pillow) or OS-specific package "
.format(error, sys.argv[0])) "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( VERSION_STRING = "1.0"
"<6sBI255s2I", b"Target", DFU_PINECIL_ALT, 1, DFU_TARGET_NAME, len(data), 1
) LCD_WIDTH = 96
+ data LCD_HEIGHT = 16
) LCD_NUM_BYTES = LCD_WIDTH * LCD_HEIGHT // 8
data = ( LCD_PAGE_SIZE = 1024
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("<I", crc)
file.write(data)
def img2hex(input_filename, class MiniwareSettings:
output_file, IMAGE_ADDRESS = 0x0800F800
preview_filename=None, DFU_TARGET_NAME = b"IronOS-dfu"
threshold=128, DFU_PINECIL_ALT = 0
dither=False, DFU_PINECIL_VENDOR = 0x1209
negative=False, DFU_PINECIL_PRODUCT = 0xDB42
binary=False):
class PinecilSettings:
IMAGE_ADDRESS = 0x0801F800
DFU_TARGET_NAME = b"Pinecil"
DFU_PINECIL_ALT = 0
DFU_PINECIL_VENDOR = 0x28E9
DFU_PINECIL_PRODUCT = 0x0189
def img2hex(
input_filename,
preview_filename=None,
threshold=128,
dither=False,
negative=False,
isPinecil=False,
output_filename_base="out",
):
""" """
Convert 'input_filename' image file into Intel hex format with data Convert 'input_filename' image file into Intel hex format with data
formatted for display on TS100 LCD and file object. formatted for display on LCD and file object.
Input image is converted from color or grayscale to black-and-white, Input image is converted from color or grayscale to black-and-white,
and resized to fit TS100 LCD screen as necessary. and resized to fit LCD screen as necessary.
Optionally write resized/thresholded/black-and-white preview image Optionally write resized/thresholded/black-and-white preview image
to file specified by name. to file specified by name.
Optional `threshold' argument 8 bit value; grayscale pixels greater than Optional `threshold' argument 8 bit value; grayscale pixels greater than
@@ -143,49 +67,49 @@ def img2hex(input_filename,
try: try:
image = Image.open(input_filename) image = Image.open(input_filename)
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))
# 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
# also resizing works better in luminance than black/white # also resizing works better in luminance than black/white
# also no information loss converting black/white to grayscale # also no information loss converting black/white to grayscale
if image.mode != 'L': if image.mode != "L":
image = image.convert('L') image = image.convert("L")
# Resize to lcd size using bicubic sampling
if image.size != (LCD_WIDTH, LCD_HEIGHT): if image.size != (LCD_WIDTH, LCD_HEIGHT):
image = image.resize((LCD_WIDTH, LCD_HEIGHT), Image.BICUBIC) image = image.resize((LCD_WIDTH, LCD_HEIGHT), Image.BICUBIC)
if negative: if negative:
image = ImageOps.invert(image) image = ImageOps.invert(image)
threshold = 255 - threshold # have to invert threshold threshold = 255 - threshold # have to invert threshold as well
if dither: if dither:
image = image.convert('1') image = image.convert("1")
else: else:
image = image.point(lambda pixel: 0 if pixel < threshold else 1, '1') image = image.point(lambda pixel: 0 if pixel < threshold else 1, "1")
if preview_filename: if preview_filename:
image.save(preview_filename) image.save(preview_filename)
''' DEBUG """ DEBUG
for row in range(LCD_HEIGHT): for row in range(LCD_HEIGHT):
for column in range(LCD_WIDTH): for column in range(LCD_WIDTH):
if image.getpixel((column, row)): sys.stderr.write('1') if image.getpixel((column, row)): sys.stderr.write('1')
else: sys.stderr.write('0') else: sys.stderr.write('0')
sys.stderr.write('\n') sys.stderr.write('\n')
''' """
# 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_PADDED_SIZE data = [0] * LCD_PAGE_SIZE
# magic/undocumented/required header in endian-reverse byte order # magic/required header in endian-reverse byte order
data[0] = 0x55 data[0] = 0x55
data[1] = 0xAA data[1] = 0xAA
data[2] = 0x0D data[2] = 0x0D
data[3] = 0xF0 data[3] = 0xF0
# convert to TS100 LCD format # convert to LCD format
for ndx in range(LCD_WIDTH * 16 // 8): for ndx in range(LCD_WIDTH * 16 // 8):
bottom_half_offset = 0 if ndx < LCD_WIDTH else 8 bottom_half_offset = 0 if ndx < LCD_WIDTH else 8
byte = 0 byte = 0
@@ -194,18 +118,29 @@ def img2hex(input_filename,
byte |= 1 << y byte |= 1 << y
# store in endian-reversed byte order # store in endian-reversed byte order
data[4 + ndx + (1 if ndx % 2 == 0 else -1)] = byte data[4 + ndx + (1 if ndx % 2 == 0 else -1)] = byte
deviceSettings = MiniwareSettings
if binary: if isPinecil:
build_dfu(output_file, data) deviceSettings = PinecilSettings
else: # Generate both possible outputs
intel_hex(output_file, data, 0x0800F800) DFUOutput.writeFile(
output_filename_base + ".dfu",
data,
deviceSettings.IMAGE_ADDRESS,
deviceSettings.DFU_TARGET_NAME,
deviceSettings.DFU_PINECIL_ALT,
deviceSettings.DFU_PINECIL_PRODUCT,
deviceSettings.DFU_PINECIL_VENDOR,
)
HexOutput.writeFile(
output_filename_base + ".hex", data, deviceSettings.IMAGE_ADDRESS
)
def parse_commandline(): def parse_commandline():
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter, formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description="Convert image file for display on TS100 LCD " description="Convert image file for display on LCD " "at startup",
"at startup") )
def zero_to_255(text): def zero_to_255(text):
value = int(text) value = int(text)
@@ -213,79 +148,83 @@ def parse_commandline():
raise argparse.ArgumentTypeError("must be integer from 0 to 255 ") raise argparse.ArgumentTypeError("must be integer from 0 to 255 ")
return value return value
parser.add_argument('input_filename', parser.add_argument("input_filename", help="input image file")
help="input image file")
parser.add_argument('output_filename', parser.add_argument("output_filename", help="output file base name")
help="output Intel hex file")
parser.add_argument('-p', '--preview', parser.add_argument(
help="filename of image preview (same data as " "-p",
"Intel hex file, as will appear on TS100 LCD)") "--preview",
help="filename of image preview",
)
parser.add_argument('-n', '--negative', parser.add_argument(
action='store_true', "-n",
help="photo negative: exchange black and white " "--negative",
"in output") action="store_true",
help="photo negative: exchange black and white " "in output",
)
parser.add_argument('-t', '--threshold', parser.add_argument(
type=zero_to_255, "-t",
default=128, "--threshold",
help="0 to 255: gray (or color converted to gray) " type=zero_to_255,
"above this becomes white, below becomes black; " default=128,
"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('-d', '--dither', parser.add_argument(
action='store_true', "-d",
help="use dithering (speckling) to convert gray or " "--dither",
"color to black and white") action="store_true",
help="use dithering (speckling) to convert gray or " "color to black and white",
)
parser.add_argument('-f', '--force', parser.add_argument(
action='store_true', "-f", "--force", action="store_true", help="force overwriting of existing files"
help="force overwriting of existing files") )
parser.add_argument('-v', '--version', parser.add_argument(
action='version', "-v",
version="%(prog)s version " + VERSION_STRING, "--version",
help="print version info") action="version",
version="%(prog)s version " + VERSION_STRING,
help="print version info",
)
return parser.parse_args() return parser.parse_args()
if __name__ == "__main__": if __name__ == "__main__":
import argparse
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("Won't overwrite existing file \"{}\" (use --force " sys.stderr.write(
"option to override)\n" 'Won\'t overwrite existing file "{}" (use --force '
.format(args.output_filename)) "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("Won't overwrite existing file \"{}\" (use --force " sys.stderr.write(
"option to override)\n" 'Won\'t overwrite existing file "{}" (use --force '
.format(args.preview)) "option to override)\n".format(args.preview)
)
sys.exit(1) sys.exit(1)
try: try:
if args.output_filename[-4:] == ".dfu": img2hex(
with open(args.output_filename, 'wb') as output: args.input_filename,
img2hex(args.input_filename, args.preview,
output, args.threshold,
args.preview, args.dither,
args.threshold, args.negative,
args.dither, output_filename_base=args.output_filename,
args.negative, isPinecil=False,
True) )
else:
with open(args.output_filename, 'w', newline='\r\n') as output:
img2hex(args.input_filename,
output,
args.preview,
args.threshold,
args.dither,
args.negative)
except BaseException as error: except BaseException as error:
sys.stderr.write("Error converting file: {}\n".format(error)) sys.stderr.write("Error converting file: {}\n".format(error))
sys.exit(1) sys.exit(1)

View File

@@ -0,0 +1,71 @@
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: 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 = 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
View 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)