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
# 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]))
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 = '0.03'
VERSION_STRING = "1.0"
LCD_WIDTH = 96
LCD_HEIGHT = 16
LCD_NUM_BYTES = LCD_WIDTH * LCD_HEIGHT // 8
LCD_PADDED_SIZE = 1024
LCD_PAGE_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
class MiniwareSettings:
IMAGE_ADDRESS = 0x0800F800
DFU_TARGET_NAME = b"IronOS-dfu"
DFU_PINECIL_ALT = 0
DFU_PINECIL_VENDOR = 0x28e9
DFU_PINECIL_PRODUCT = 0x0189
DFU_LOGO_ADDRESS = 0x0801F800
DFU_PINECIL_VENDOR = 0x1209
DFU_PINECIL_PRODUCT = 0xDB42
class PinecilSettings:
IMAGE_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
DFU_PINECIL_ALT = 0
DFU_PINECIL_VENDOR = 0x28E9
DFU_PINECIL_PRODUCT = 0x0189
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("<I", crc)
file.write(data)
def img2hex(input_filename,
output_file,
def img2hex(
input_filename,
preview_filename=None,
threshold=128,
dither=False,
negative=False,
binary=False):
isPinecil=False,
output_filename_base="out",
):
"""
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,
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
to file specified by name.
Optional `threshold' argument 8 bit value; grayscale pixels greater than
@@ -143,49 +67,49 @@ def img2hex(input_filename,
try:
image = Image.open(input_filename)
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
# 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')
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
threshold = 255 - threshold # have to invert threshold as well
if dither:
image = image.convert('1')
image = image.convert("1")
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:
image.save(preview_filename)
''' DEBUG
""" 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')
sys.stderr.write('\n')
'''
"""
# 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[1] = 0xAA
data[2] = 0x0D
data[3] = 0xF0
# convert to TS100 LCD format
# convert to LCD format
for ndx in range(LCD_WIDTH * 16 // 8):
bottom_half_offset = 0 if ndx < LCD_WIDTH else 8
byte = 0
@@ -194,18 +118,29 @@ def img2hex(input_filename,
byte |= 1 << y
# store in endian-reversed byte order
data[4 + ndx + (1 if ndx % 2 == 0 else -1)] = byte
if binary:
build_dfu(output_file, data)
else:
intel_hex(output_file, data, 0x0800F800)
deviceSettings = MiniwareSettings
if isPinecil:
deviceSettings = PinecilSettings
# Generate both possible outputs
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():
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description="Convert image file for display on TS100 LCD "
"at startup")
description="Convert image file for display on LCD " "at startup",
)
def zero_to_255(text):
value = int(text)
@@ -213,79 +148,83 @@ def parse_commandline():
raise argparse.ArgumentTypeError("must be integer from 0 to 255 ")
return value
parser.add_argument('input_filename',
help="input image file")
parser.add_argument("input_filename", help="input image file")
parser.add_argument('output_filename',
help="output Intel hex file")
parser.add_argument("output_filename", help="output file base name")
parser.add_argument('-p', '--preview',
help="filename of image preview (same data as "
"Intel hex file, as will appear on TS100 LCD)")
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(
"-n",
"--negative",
action="store_true",
help="photo negative: exchange black and white " "in output",
)
parser.add_argument('-t', '--threshold',
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")
"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(
"-d",
"--dither",
action="store_true",
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('-v', '--version',
action='version',
parser.add_argument(
"-v",
"--version",
action="version",
version="%(prog)s version " + VERSION_STRING,
help="print version info")
help="print version info",
)
return parser.parse_args()
if __name__ == "__main__":
import argparse
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)
try:
if args.output_filename[-4:] == ".dfu":
with open(args.output_filename, 'wb') as output:
img2hex(args.input_filename,
output,
img2hex(
args.input_filename,
args.preview,
args.threshold,
args.dither,
args.negative,
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)
output_filename_base=args.output_filename,
isPinecil=False,
)
except BaseException as error:
sys.stderr.write("Error converting file: {}\n".format(error))
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)