Compare commits
61 Commits
v2022.02.1
...
a7ac4df252
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7ac4df252 | ||
|
|
22ce26026d | ||
|
|
4193362fce | ||
|
|
4261a7e5a2 | ||
|
|
d4c6978bfe | ||
|
|
ee6c5e911a | ||
|
|
97756fe21c | ||
|
|
29e96bb25b | ||
|
|
853b20eabc | ||
|
|
501ac131b8 | ||
|
|
474fc5923b | ||
|
|
b5e55ee1f1 | ||
|
|
5ec3a16c3f | ||
|
|
b313a84e10 | ||
|
|
3b02e3ea6f | ||
|
|
c10a130329 | ||
|
|
6e9ed602a2 | ||
|
|
d39d58d79a | ||
|
|
248b2b5806 | ||
|
|
9eed4b2dd4 | ||
|
|
1a5c7ac5f6 | ||
|
|
92f4d50a73 | ||
|
|
c252b67c9a | ||
|
|
2dd99ff5aa | ||
|
|
bd70b17cd6 | ||
|
|
2942212437 | ||
|
|
3c62bb7fe1 | ||
|
|
32e4db09ac | ||
|
|
fbf0a8ac63 | ||
|
|
f4e5717e34 | ||
|
|
466022c12f | ||
|
|
cb9c811bd9 | ||
|
|
ca0ed073e5 | ||
|
|
a744ef1467 | ||
|
|
1d5bcd7bb0 | ||
|
|
7fdc4dc679 | ||
|
|
8b344d7f0b | ||
|
|
041e107aec | ||
|
|
3658be86dd | ||
|
|
12b02f1ae2 | ||
|
|
08da9885b3 | ||
|
|
de8a9c89c8 | ||
|
|
f8a77bdc5f | ||
|
|
5e388107e7 | ||
|
|
fa8cf78cc8 | ||
|
|
6c9fa9519d | ||
|
|
dd9c77f6b0 | ||
|
|
1a2a27b3a9 | ||
|
|
29c63fc6f8 | ||
|
|
806e6a266d | ||
|
|
388e97cee4 | ||
|
|
0181963db6 | ||
|
|
124fc0ccfe | ||
|
|
595d2c7353 | ||
|
|
1a5dae2283 | ||
|
|
bcef360fe0 | ||
|
|
36c3cf0167 | ||
|
|
3eed987ce2 | ||
|
|
0de493954a | ||
|
|
f5d5eade81 | ||
|
|
1224b02750 |
19
.github/workflows/push.yml
vendored
@@ -10,17 +10,18 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- args: "-m"
|
- model: "miniware"
|
||||||
model: "miniware"
|
- model: "pinecilv1"
|
||||||
- args: "-p"
|
- model: "pinecilv2"
|
||||||
model: "pinecil"
|
- model: "mhp30"
|
||||||
|
- model: "s60"
|
||||||
fail-fast: true
|
fail-fast: true
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Install dependencies (apk)
|
- name: Install dependencies (apk)
|
||||||
run: apk add --no-cache git python3 py3-pip zlib py3-pillow
|
run: apk add --no-cache git python3 py3-pip zlib py3-pillow py3-intelhex
|
||||||
|
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
@@ -28,13 +29,13 @@ jobs:
|
|||||||
run: mkdir -p /tmp/${{ matrix.model }}
|
run: mkdir -p /tmp/${{ matrix.model }}
|
||||||
|
|
||||||
- name: build all files for the device
|
- name: build all files for the device
|
||||||
run: cd Bootup\ Logos && ./run.sh /tmp/${{ matrix.model }}/ ${{matrix.args}}
|
run: cd Bootup\ Logos && ./run.sh /tmp/${{ matrix.model }}/ -m ${{matrix.model}}
|
||||||
|
|
||||||
- name: build logo erase file
|
- name: build logo erase file
|
||||||
run: cd Bootup\ Logos && python3 img2logo.py -E erase_stored_image /tmp/${{ matrix.model }}/ ${{matrix.args}}
|
run: cd Bootup\ Logos && python3 img2logo.py -E erase_stored_image /tmp/${{ matrix.model }}/ -m ${{matrix.model}}
|
||||||
|
|
||||||
- name: Archive artifacts
|
- name: Archive artifacts
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: ${{ matrix.model }}
|
name: ${{ matrix.model }}
|
||||||
path: |
|
path: |
|
||||||
|
|||||||
63
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
---
|
||||||
|
name: "release"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- "main"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
name: "Release"
|
||||||
|
runs-on: "ubuntu-22.04"
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Install dependencies (apk)
|
||||||
|
run: sudo apt update && sudo apt-get install -y git python3 python3-pillow py3-intelhex
|
||||||
|
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
|
||||||
|
- name: prep
|
||||||
|
run: |
|
||||||
|
mkdir -p /tmp/pinecilv1 && \
|
||||||
|
mkdir -p /tmp/pinecilv2 && \
|
||||||
|
mkdir -p /tmp/miniware && \
|
||||||
|
mkdir -p /tmp/mhp30 && \
|
||||||
|
mkdir -p /tmp/s60
|
||||||
|
|
||||||
|
- name: build all files for the device
|
||||||
|
run: |
|
||||||
|
cd Bootup\ Logos && \
|
||||||
|
./run.sh /tmp/pinecilv1/ -m pinecilv1 && \
|
||||||
|
./run.sh /tmp/pinecilv2/ -m pinecilv2 && \
|
||||||
|
./run.sh /tmp/miniware/ -m miniware && \
|
||||||
|
./run.sh /tmp/mhp30/ -m mhp30 && \
|
||||||
|
./run.sh /tmp/s60/ -m s60
|
||||||
|
|
||||||
|
- name: build logo erase file
|
||||||
|
run: |
|
||||||
|
cd Bootup\ Logos && \
|
||||||
|
python3 img2logo.py -E erase_stored_image /tmp/pinecilv1/ -m pinecilv1 && \
|
||||||
|
python3 img2logo.py -E erase_stored_image /tmp/pinecilv2/ -m pinecilv2 && \
|
||||||
|
python3 img2logo.py -E erase_stored_image /tmp/miniware/ -m miniware && \
|
||||||
|
python3 img2logo.py -E erase_stored_image /tmp/mhp30/ -m mhp30 && \
|
||||||
|
python3 img2logo.py -E erase_stored_image /tmp/s60/ -m s60
|
||||||
|
|
||||||
|
- name: compress logo files
|
||||||
|
run: |
|
||||||
|
zip -rj pinecilv1.zip /tmp/pinecilv1/* && \
|
||||||
|
zip -rj miniware.zip /tmp/miniware/* && \
|
||||||
|
zip -rj pinecilv2.zip /tmp/pinecilv2/* && \
|
||||||
|
zip -rj mhp30.zip /tmp/mhp30/* && \
|
||||||
|
zip -rj s60_s60p.zip /tmp/s60/*
|
||||||
|
|
||||||
|
- uses: "marvinpinto/action-automatic-releases@latest"
|
||||||
|
with:
|
||||||
|
repo_token: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
|
automatic_release_tag: "latest"
|
||||||
|
prerelease: false
|
||||||
|
title: "Release"
|
||||||
|
files: |
|
||||||
|
*.zip
|
||||||
BIN
Bootup Logos/Images/CRT_horror_vacui.gif
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 9.0 KiB |
|
Before Width: | Height: | Size: 300 B |
BIN
Bootup Logos/Images/Logo1b.png
Normal file
|
After Width: | Height: | Size: 269 B |
|
Before Width: | Height: | Size: 259 B |
|
Before Width: | Height: | Size: 245 B |
|
Before Width: | Height: | Size: 263 B |
|
Before Width: | Height: | Size: 262 B |
BIN
Bootup Logos/Images/ad_maiora.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
Bootup Logos/Images/arcade_galaga.png
Normal file
|
After Width: | Height: | Size: 417 B |
BIN
Bootup Logos/Images/arcade_pac_man.png
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
BIN
Bootup Logos/Images/bender.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
Bootup Logos/Images/doggie_bone.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
Bootup Logos/Images/f1.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
Bootup Logos/Images/hack_the_planet.gif
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
Bootup Logos/Images/hack_the_planet.png
Normal file
|
After Width: | Height: | Size: 876 B |
BIN
Bootup Logos/Images/halo_master_chief_helmet.png
Normal file
|
After Width: | Height: | Size: 299 B |
BIN
Bootup Logos/Images/horror_vacui_IronOS.png
Normal file
|
After Width: | Height: | Size: 804 B |
BIN
Bootup Logos/Images/lot_of_tin_is_too_few_tin_IT.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
Bootup Logos/Images/majorTom.png
Normal file
|
After Width: | Height: | Size: 302 B |
BIN
Bootup Logos/Images/open_sw_hw_IronOS_logos.png
Normal file
|
After Width: | Height: | Size: 9.5 KiB |
BIN
Bootup Logos/Images/science.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
Bootup Logos/Images/skulls.png
Normal file
|
After Width: | Height: | Size: 191 B |
BIN
Bootup Logos/Images/terminal.gif
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
Bootup Logos/Images/wh_40k.png
Normal file
|
After Width: | Height: | Size: 329 B |
BIN
Bootup Logos/Images/wwii_kilroy.png
Normal file
|
After Width: | Height: | Size: 407 B |
@@ -1,15 +1,19 @@
|
|||||||
|
# Boot up Logos
|
||||||
|
|
||||||
## Boot up logo's are logos or animations shown on boot of IronOS
|
## 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.
|
They can be (re)programmed as many times as desired, after flashing the normal firmware.
|
||||||
|
|
||||||
### Data storage format
|
### 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 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 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:
|
The next byte indicates the frame timing in milliseconds, or `0` to indicate: Only show the first frame for the whole bootloader duration (still image mode).
|
||||||
|
|
||||||
|
After the OLED buffer is cleared to black, every frame is encoded as either:
|
||||||
|
|
||||||
### Full frame updates
|
### Full frame updates
|
||||||
|
|
||||||
@@ -18,5 +22,44 @@ Then the OLED buffer is cleared to black, then every frame is encoded as either:
|
|||||||
### Delta frame update
|
### Delta frame update
|
||||||
|
|
||||||
`[count of updates][[index,data][index,data][index,data][index,data]]`
|
`[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
|
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.
|
||||||
|
|
||||||
|
## Logos preview
|
||||||
|
|
||||||
|
**Static logos**
|
||||||
|
|Logo |Filename |Note |
|
||||||
|
|:-------------:|:-------------:|:-----:|
|
||||||
|
||ad_maiora.png|English: "Towards greater things"|
|
||||||
|
||arcade_galaga.png||
|
||||||
|
||arcade_pac_man.png||
|
||||||
|
||bender.png||
|
||||||
|
||doggie_bone.png||
|
||||||
|
||f1.png||
|
||||||
|
||halo_master_chief_helmet.png||
|
||||||
|
||horror_vacui_IronOS.png||
|
||||||
|
||IronOS.png||
|
||||||
|
||Logo1b.png||
|
||||||
|
||lot_of_tin_is_too_few_tin_IT.png|English: "A lot of tin is too few tin"|
|
||||||
|
||majorTom.png||
|
||||||
|
||open_sw_hw_IronOS_logos.png||
|
||||||
|
||Pinecil.png||
|
||||||
|
||science.png||
|
||||||
|
||skulls.png||
|
||||||
|
||TS100.png||
|
||||||
|
||TS80.png||
|
||||||
|
||TS80P.png||
|
||||||
|
||wh_40k.png||
|
||||||
|
||wwii_kilroy.png||
|
||||||
|
||hack_the_planet.png||
|
||||||
|
|
||||||
|
**Animated logos**
|
||||||
|
|Logo \* |Filename |Note |
|
||||||
|
|:-------------:|:-------------:|:-----:|
|
||||||
|
| |CRT_horror_vacui.gif||
|
||||||
|
||IronOS.gif||
|
||||||
|
||terminal.gif||
|
||||||
|
||hack_the_planet.gif||
|
||||||
|
|
||||||
|
_* Click the individual logo to see its animation, if you missed it here._
|
||||||
|
|||||||
@@ -4,14 +4,19 @@ from __future__ import division
|
|||||||
import argparse
|
import argparse
|
||||||
import copy
|
import copy
|
||||||
import os, sys
|
import os, sys
|
||||||
|
from typing import Optional
|
||||||
|
from intelhex import IntelHex
|
||||||
from output_hex import HexOutput
|
from output_hex import HexOutput
|
||||||
from output_dfu import DFUOutput
|
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). " "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"
|
VERSION_STRING = "1.0"
|
||||||
|
|
||||||
@@ -22,30 +27,73 @@ LCD_PAGE_SIZE = 1024
|
|||||||
|
|
||||||
DATA_PROGRAMMED_MARKER = 0xAA
|
DATA_PROGRAMMED_MARKER = 0xAA
|
||||||
FULL_FRAME_MARKER = 0xFF
|
FULL_FRAME_MARKER = 0xFF
|
||||||
|
EMPTY_FRAME_MARKER = (
|
||||||
|
0xFE # If this marker is used to start a frame, the frame is a 0-length delta frame
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class MiniwareSettings:
|
class MiniwareSettings:
|
||||||
IMAGE_ADDRESS = 0x0800F800
|
IMAGE_ADDRESS = 0x0800F800
|
||||||
DFU_TARGET_NAME = b"IronOS-dfu"
|
DFU_TARGET_NAME = b"IronOS-dfu"
|
||||||
DFU_PINECIL_ALT = 0
|
DFU_ALT = 0
|
||||||
DFU_PINECIL_VENDOR = 0x1209
|
DFU_VENDOR = 0x1209
|
||||||
DFU_PINECIL_PRODUCT = 0xDB42
|
DFU_PRODUCT = 0xDB42
|
||||||
|
MINIMUM_HEX_SIZE = 4096
|
||||||
|
|
||||||
|
|
||||||
|
class S60Settings:
|
||||||
|
IMAGE_ADDRESS = 0x08000000 + (62 * 1024)
|
||||||
|
DFU_TARGET_NAME = b"IronOS-dfu"
|
||||||
|
DFU_ALT = 0
|
||||||
|
DFU_VENDOR = 0x1209
|
||||||
|
DFU_PRODUCT = 0xDB42
|
||||||
|
MINIMUM_HEX_SIZE = 1024
|
||||||
|
|
||||||
|
|
||||||
|
class TS101Settings:
|
||||||
|
IMAGE_ADDRESS = 0x08000000 + (99 * 1024)
|
||||||
|
DFU_TARGET_NAME = b"IronOS-dfu"
|
||||||
|
DFU_ALT = 0
|
||||||
|
DFU_VENDOR = 0x1209
|
||||||
|
DFU_PRODUCT = 0xDB42
|
||||||
|
MINIMUM_HEX_SIZE = 1024
|
||||||
|
|
||||||
|
|
||||||
|
class MHP30Settings:
|
||||||
|
IMAGE_ADDRESS = 0x08000000 + (126 * 1024)
|
||||||
|
DFU_TARGET_NAME = b"IronOS-dfu"
|
||||||
|
DFU_ALT = 0
|
||||||
|
DFU_VENDOR = 0x1209
|
||||||
|
DFU_PRODUCT = 0xDB42
|
||||||
|
MINIMUM_HEX_SIZE = 4096
|
||||||
|
|
||||||
|
|
||||||
class PinecilSettings:
|
class PinecilSettings:
|
||||||
IMAGE_ADDRESS = 0x0801F800
|
IMAGE_ADDRESS = 0x0801F800
|
||||||
DFU_TARGET_NAME = b"Pinecil"
|
DFU_TARGET_NAME = b"Pinecil"
|
||||||
DFU_PINECIL_ALT = 0
|
DFU_ALT = 0
|
||||||
DFU_PINECIL_VENDOR = 0x28E9
|
DFU_VENDOR = 0x28E9
|
||||||
DFU_PINECIL_PRODUCT = 0x0189
|
DFU_PRODUCT = 0x0189
|
||||||
|
MINIMUM_HEX_SIZE = 1024
|
||||||
|
|
||||||
|
|
||||||
def still_image_to_bytes(image: Image, negative: bool, dither: bool, threshold: int, preview_filename):
|
class Pinecilv2Settings:
|
||||||
|
IMAGE_ADDRESS = 1016 * 1024 # its 2 4k erase pages inset
|
||||||
|
DFU_TARGET_NAME = b"Pinecilv2"
|
||||||
|
DFU_ALT = 0
|
||||||
|
DFU_VENDOR = 0x28E9 # These are ignored by blisp so doesnt matter what we use
|
||||||
|
DFU_PRODUCT = 0x0189 # These are ignored by blisp so doesnt matter what we use
|
||||||
|
MINIMUM_HEX_SIZE = 1024
|
||||||
|
|
||||||
|
|
||||||
|
def still_image_to_bytes(
|
||||||
|
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
|
||||||
# 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 greyscale
|
||||||
if image.mode != "L":
|
if image.mode != "L":
|
||||||
image = image.convert("L")
|
image = image.convert("L")
|
||||||
# Resize to lcd size using bicubic sampling
|
# Resize to lcd size using bicubic sampling
|
||||||
@@ -103,7 +151,9 @@ def get_screen_blob(previous_frame: bytearray, this_frame: bytearray):
|
|||||||
"""
|
"""
|
||||||
outputData = []
|
outputData = []
|
||||||
delta = calculate_frame_delta_encode(previous_frame, this_frame)
|
delta = calculate_frame_delta_encode(previous_frame, this_frame)
|
||||||
if len(delta) < (len(this_frame)):
|
if len(delta) == 0:
|
||||||
|
outputData.append(EMPTY_FRAME_MARKER)
|
||||||
|
elif len(delta) < (len(this_frame)):
|
||||||
outputData.append(len(delta))
|
outputData.append(len(delta))
|
||||||
outputData.extend(delta)
|
outputData.extend(delta)
|
||||||
# print("delta encoded frame")
|
# print("delta encoded frame")
|
||||||
@@ -114,7 +164,9 @@ def get_screen_blob(previous_frame: bytearray, this_frame: bytearray):
|
|||||||
return outputData
|
return outputData
|
||||||
|
|
||||||
|
|
||||||
def animated_image_to_bytes(imageIn: Image, negative: bool, dither: bool, threshold: int):
|
def animated_image_to_bytes(
|
||||||
|
imageIn: Image, negative: bool, dither: bool, threshold: int, flip_frames
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
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
|
||||||
@@ -133,23 +185,42 @@ def animated_image_to_bytes(imageIn: Image, negative: bool, dither: bool, thresh
|
|||||||
for framenum in range(0, imageIn.n_frames):
|
for framenum in range(0, imageIn.n_frames):
|
||||||
imageIn.seek(framenum)
|
imageIn.seek(framenum)
|
||||||
image = imageIn
|
image = imageIn
|
||||||
|
if flip_frames:
|
||||||
|
image = image.rotate(180)
|
||||||
|
|
||||||
frameb = still_image_to_bytes(image, negative, dither, threshold, None)
|
frameb = still_image_to_bytes(image, negative, dither, threshold, None)
|
||||||
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 frameTiming is None:
|
||||||
frameDuration_ms = 255
|
|
||||||
if frameTiming is None or frameTiming == 0:
|
|
||||||
frameTiming = frameDuration_ms
|
frameTiming = frameDuration_ms
|
||||||
|
else:
|
||||||
|
delta = frameDuration_ms / frameTiming
|
||||||
|
if delta > 1.05 or delta < 0.95:
|
||||||
|
print(
|
||||||
|
f"ERROR: You have a frame that is different to the first frame time. Mixed rates are not supported"
|
||||||
|
)
|
||||||
|
sys.exit(-1)
|
||||||
print(f"Found {len(frameData)} frames, interval {frameTiming}ms")
|
print(f"Found {len(frameData)} frames, interval {frameTiming}ms")
|
||||||
# We have no mangled the image into our frambuffers
|
frameTiming = frameTiming / 5
|
||||||
|
if frameTiming <= 0 or frameTiming > 254:
|
||||||
|
newTiming = max(frameTiming, 1)
|
||||||
|
newTiming = min(newTiming, 254)
|
||||||
|
|
||||||
|
print(
|
||||||
|
f"Inter frame delay {frameTiming} is out of range, and is being adjusted to {newTiming*5}"
|
||||||
|
)
|
||||||
|
frameTiming = newTiming
|
||||||
|
|
||||||
|
# We have now mangled the image into our framebuffers
|
||||||
|
|
||||||
# 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]
|
outputData = [DATA_PROGRAMMED_MARKER]
|
||||||
outputData.append(frameTiming)
|
outputData.append(int(frameTiming))
|
||||||
outputData.extend(get_screen_blob([0x00] * (LCD_NUM_BYTES), frameData[0]))
|
first_frame = get_screen_blob([0x00] * (LCD_NUM_BYTES), frameData[0])
|
||||||
|
outputData.extend(first_frame)
|
||||||
|
print(f"Frame 1 encoded to {len(first_frame)} bytes")
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Format for each frame block is:
|
Format for each frame block is:
|
||||||
@@ -164,31 +235,34 @@ def animated_image_to_bytes(imageIn: Image, negative: bool, dither: bool, thresh
|
|||||||
if (len(outputData) + len(frameBlob)) > LCD_PAGE_SIZE:
|
if (len(outputData) + len(frameBlob)) > LCD_PAGE_SIZE:
|
||||||
print(f"Truncating animation after {id} frames as we are out of space")
|
print(f"Truncating animation after {id} frames as we are out of space")
|
||||||
break
|
break
|
||||||
print(f"Frame {id} encoded to {len(frameBlob)} bytes")
|
print(f"Frame {id + 1} encoded to {len(frameBlob)} bytes")
|
||||||
outputData.extend(frameBlob)
|
outputData.extend(frameBlob)
|
||||||
|
print(f"Total size used: {len(outputData)} of 1024 bytes")
|
||||||
return outputData
|
return outputData
|
||||||
|
|
||||||
|
|
||||||
def img2hex(
|
def img2hex(
|
||||||
input_filename,
|
input_filename,
|
||||||
|
device_model_name: str,
|
||||||
|
merge_hex_file: Optional[str],
|
||||||
preview_filename=None,
|
preview_filename=None,
|
||||||
threshold=128,
|
threshold=128,
|
||||||
dither=False,
|
dither=False,
|
||||||
negative=False,
|
negative=False,
|
||||||
isPinecil=False,
|
|
||||||
make_erase_image=False,
|
make_erase_image=False,
|
||||||
output_filename_base="out",
|
output_filename_base="out",
|
||||||
|
flip=False,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
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 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 greyscale to black-and-white,
|
||||||
and resized to fit 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; greyscale pixels greater than
|
||||||
this become 1 (white) in output, less than become 0 (black).
|
this become 1 (white) in output, less than become 0 (black).
|
||||||
Unless optional `dither', in which case PIL grayscale-to-black/white
|
Unless optional `dither', in which case PIL greyscale-to-black/white
|
||||||
dithering algorithm used.
|
dithering algorithm used.
|
||||||
Optional `negative' inverts black/white regardless of input image type
|
Optional `negative' inverts black/white regardless of input image type
|
||||||
or other options.
|
or other options.
|
||||||
@@ -202,32 +276,120 @@ def img2hex(
|
|||||||
raise IOError('error reading image file "{}": {}'.format(input_filename, e))
|
raise IOError('error reading image file "{}": {}'.format(input_filename, e))
|
||||||
|
|
||||||
if getattr(image, "is_animated", False):
|
if getattr(image, "is_animated", False):
|
||||||
data = animated_image_to_bytes(image, negative, dither, threshold)
|
data = animated_image_to_bytes(image, negative, dither, threshold, flip)
|
||||||
else:
|
else:
|
||||||
|
if flip:
|
||||||
|
image = image.rotate(180)
|
||||||
# magic/required header
|
# magic/required header
|
||||||
data = [DATA_PROGRAMMED_MARKER, 0x00] # Timing value of 0
|
data = [DATA_PROGRAMMED_MARKER, 0x00] # Timing value of 0
|
||||||
image_bytes = still_image_to_bytes(image, negative, dither, threshold, preview_filename)
|
image_bytes = still_image_to_bytes(
|
||||||
|
image, negative, dither, threshold, preview_filename
|
||||||
|
)
|
||||||
data.extend(get_screen_blob([0] * LCD_NUM_BYTES, image_bytes))
|
data.extend(get_screen_blob([0] * LCD_NUM_BYTES, image_bytes))
|
||||||
|
|
||||||
# Pad up to the full page size
|
# Pad up to the full page size
|
||||||
if len(data) < LCD_PAGE_SIZE:
|
if len(data) < LCD_PAGE_SIZE:
|
||||||
pad = [0] * (LCD_PAGE_SIZE - len(data))
|
pad = [0] * (LCD_PAGE_SIZE - len(data))
|
||||||
data.extend(pad)
|
data.extend(pad)
|
||||||
deviceSettings = MiniwareSettings
|
|
||||||
if isPinecil:
|
# Set device settings depending on input `-m` argument
|
||||||
|
device_name = device_model_name.lower()
|
||||||
|
if (
|
||||||
|
device_name == "miniware"
|
||||||
|
or device_name == "ts100"
|
||||||
|
or device_name == "ts80"
|
||||||
|
or device_name == "ts80p"
|
||||||
|
):
|
||||||
|
deviceSettings = MiniwareSettings
|
||||||
|
elif device_name == "pinecilv1" or device_name == "pinecil":
|
||||||
deviceSettings = PinecilSettings
|
deviceSettings = PinecilSettings
|
||||||
# Generate both possible outputs
|
elif device_name == "pinecilv2":
|
||||||
output_name = output_filename_base + os.path.basename(input_filename)
|
deviceSettings = Pinecilv2Settings
|
||||||
DFUOutput.writeFile(
|
elif device_name == "ts101":
|
||||||
output_name + ".dfu",
|
deviceSettings = TS101Settings
|
||||||
data,
|
if merge_hex_file is None:
|
||||||
deviceSettings.IMAGE_ADDRESS,
|
print(
|
||||||
deviceSettings.DFU_TARGET_NAME,
|
"For the TS101 for compatibility with bugs in the Miniware Loader, you must merge the main firmware with the logo to flash it"
|
||||||
deviceSettings.DFU_PINECIL_ALT,
|
)
|
||||||
deviceSettings.DFU_PINECIL_PRODUCT,
|
exit(1)
|
||||||
deviceSettings.DFU_PINECIL_VENDOR,
|
elif device_name == "s60":
|
||||||
|
deviceSettings = S60Settings
|
||||||
|
elif device_name == "mhp30":
|
||||||
|
deviceSettings = MHP30Settings
|
||||||
|
else:
|
||||||
|
print("Could not determine device type")
|
||||||
|
sys.exit(-1)
|
||||||
|
|
||||||
|
# Split name from extension so we can mangle in the _L suffix for flipped images
|
||||||
|
split_name = os.path.splitext(os.path.basename(input_filename))
|
||||||
|
|
||||||
|
if flip:
|
||||||
|
base = split_name[0]
|
||||||
|
ext = split_name[1]
|
||||||
|
base = base + "_L"
|
||||||
|
split_name = [base, ext]
|
||||||
|
output_name = output_filename_base + split_name[0] + split_name[1]
|
||||||
|
|
||||||
|
# If a file has been specified for merging, we want to splice our image data with it
|
||||||
|
if merge_hex_file is not None:
|
||||||
|
read_merge_write(merge_hex_file, data, deviceSettings, output_name)
|
||||||
|
else:
|
||||||
|
DFUOutput.writeFile(
|
||||||
|
output_name + ".dfu",
|
||||||
|
data,
|
||||||
|
deviceSettings.IMAGE_ADDRESS,
|
||||||
|
deviceSettings.DFU_TARGET_NAME,
|
||||||
|
deviceSettings.DFU_ALT,
|
||||||
|
deviceSettings.DFU_PRODUCT,
|
||||||
|
deviceSettings.DFU_VENDOR,
|
||||||
|
)
|
||||||
|
|
||||||
|
HexOutput.writeFile(
|
||||||
|
output_name + ".hex",
|
||||||
|
data,
|
||||||
|
deviceSettings.IMAGE_ADDRESS,
|
||||||
|
deviceSettings.MINIMUM_HEX_SIZE,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def read_merge_write(
|
||||||
|
merge_filename: str, image_data: list[int], deviceSettings, output_filename: str
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Reads in the merge filename as the base object, then inserts the image data.
|
||||||
|
Then pad-fills the empty space in the binary
|
||||||
|
"""
|
||||||
|
base_hex_file = IntelHex(merge_filename)
|
||||||
|
logo_hex_file = IntelHex()
|
||||||
|
logo_hex_file.frombytes(image_data, deviceSettings.IMAGE_ADDRESS)
|
||||||
|
# Merge in the image data, error if collision
|
||||||
|
base_hex_file.merge(logo_hex_file, overlap="error")
|
||||||
|
binary_base = base_hex_file.minaddr()
|
||||||
|
base_hex_file.padding = 0xFF
|
||||||
|
binary_blob = base_hex_file.tobinarray(start=binary_base)
|
||||||
|
print(
|
||||||
|
f"Post-merge output image starts at 0x{binary_base:x}, len {len(binary_blob)}"
|
||||||
)
|
)
|
||||||
HexOutput.writeFile(output_name + ".hex", data, deviceSettings.IMAGE_ADDRESS)
|
DFUOutput.writeFile(
|
||||||
|
output_filename + ".dfu",
|
||||||
|
binary_blob,
|
||||||
|
binary_base,
|
||||||
|
deviceSettings.DFU_TARGET_NAME,
|
||||||
|
deviceSettings.DFU_ALT,
|
||||||
|
deviceSettings.DFU_PRODUCT,
|
||||||
|
deviceSettings.DFU_VENDOR,
|
||||||
|
)
|
||||||
|
# Gap fill any missing segments
|
||||||
|
# This is required for the TS101 bootloader
|
||||||
|
segments = base_hex_file.segments()
|
||||||
|
for seg_pair in zip(segments, segments[1:]):
|
||||||
|
start = seg_pair[0][1]
|
||||||
|
end = seg_pair[1][0]
|
||||||
|
filler = [0xFE] * (end - start)
|
||||||
|
base_hex_file.frombytes(filler, start)
|
||||||
|
|
||||||
|
with open(output_filename + ".hex", "w") as output:
|
||||||
|
base_hex_file.write_hex_file(output, eolstyle="CRLF")
|
||||||
|
|
||||||
|
|
||||||
def parse_commandline():
|
def parse_commandline():
|
||||||
@@ -252,11 +414,17 @@ def parse_commandline():
|
|||||||
help="filename of image preview",
|
help="filename of image preview",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"-M",
|
||||||
|
"--merge",
|
||||||
|
help="filename of another hex file to merge with, creating a combined firmware",
|
||||||
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-n",
|
"-n",
|
||||||
"--negative",
|
"--negative",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="photo negative: exchange black and white " "in output",
|
help="photo negative: exchange black and white in output",
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
@@ -264,14 +432,16 @@ 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) " "above this becomes white, below becomes black; " "ignored if using --dither",
|
help="0 to 255: grey (or color converted to grey) "
|
||||||
|
"above this becomes white, below becomes black; "
|
||||||
|
"ignored if using --dither",
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-d",
|
"-d",
|
||||||
"--dither",
|
"--dither",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="use dithering (speckling) to convert gray or " "color to black and white",
|
help="use dithering (speckling) to convert grey or " "color to black and white",
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
@@ -281,8 +451,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("-p", "--pinecil", action="store_true", help="generate files for Pinecil")
|
parser.add_argument("-m", "--model", help="device model name")
|
||||||
parser.add_argument("-m", "--miniware", action="store_true", help="generate files for miniware")
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-v",
|
"-v",
|
||||||
"--version",
|
"--version",
|
||||||
@@ -299,20 +468,36 @@ if __name__ == "__main__":
|
|||||||
args = parse_commandline()
|
args = parse_commandline()
|
||||||
|
|
||||||
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 ' "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)
|
sys.exit(1)
|
||||||
|
|
||||||
if args.miniware == False and args.pinecil == False:
|
print(f"Converting {args.input_filename} => {args.output_filename}")
|
||||||
sys.stderr.write("You must provide --miniware or --pinecil to select your model")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
img2hex(
|
img2hex(
|
||||||
|
merge_hex_file=args.merge,
|
||||||
input_filename=args.input_filename,
|
input_filename=args.input_filename,
|
||||||
output_filename_base=args.output_filename,
|
output_filename_base=args.output_filename,
|
||||||
|
device_model_name=args.model,
|
||||||
preview_filename=args.preview,
|
preview_filename=args.preview,
|
||||||
threshold=args.threshold,
|
threshold=args.threshold,
|
||||||
dither=args.dither,
|
dither=args.dither,
|
||||||
negative=args.negative,
|
negative=args.negative,
|
||||||
make_erase_image=args.erase,
|
make_erase_image=args.erase,
|
||||||
isPinecil=args.pinecil,
|
flip=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
img2hex(
|
||||||
|
merge_hex_file=args.merge,
|
||||||
|
input_filename=args.input_filename,
|
||||||
|
output_filename_base=args.output_filename,
|
||||||
|
device_model_name=args.model,
|
||||||
|
preview_filename=args.preview,
|
||||||
|
threshold=args.threshold,
|
||||||
|
dither=args.dither,
|
||||||
|
negative=args.negative,
|
||||||
|
make_erase_image=args.erase,
|
||||||
|
flip=True,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ class HexOutput:
|
|||||||
INTELHEX_END_OF_FILE_RECORD = 0x01
|
INTELHEX_END_OF_FILE_RECORD = 0x01
|
||||||
INTELHEX_EXTENDED_LINEAR_ADDRESS_RECORD = 0x04
|
INTELHEX_EXTENDED_LINEAR_ADDRESS_RECORD = 0x04
|
||||||
INTELHEX_BYTES_PER_LINE = 16
|
INTELHEX_BYTES_PER_LINE = 16
|
||||||
INTELHEX_MINIMUM_SIZE = 4096
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def split16(cls, word):
|
def split16(cls, word):
|
||||||
@@ -53,20 +52,19 @@ class HexOutput:
|
|||||||
) # low 8 bits
|
) # low 8 bits
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def writeFile(cls, file_name: str, data: bytearray, data_address: int):
|
def writeFile(
|
||||||
|
cls,
|
||||||
|
file_name: str,
|
||||||
|
data: bytearray,
|
||||||
|
data_address: int,
|
||||||
|
minimum_hex_file_size: int,
|
||||||
|
):
|
||||||
"""write block of data in Intel hex format"""
|
"""write block of data in Intel hex format"""
|
||||||
with open(file_name, "w", newline="\r\n") as output:
|
with open(file_name, "w", newline="\r\n") as output:
|
||||||
|
|
||||||
def write(generator):
|
def write(generator):
|
||||||
output.write("".join(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_lo = data_address & 0xFFFF
|
||||||
address_hi = (data_address >> 16) & 0xFFFF
|
address_hi = (data_address >> 16) & 0xFFFF
|
||||||
|
|
||||||
@@ -79,7 +77,7 @@ class HexOutput:
|
|||||||
)
|
)
|
||||||
|
|
||||||
size_written = 0
|
size_written = 0
|
||||||
while size_written < cls.INTELHEX_MINIMUM_SIZE:
|
while size_written < minimum_hex_file_size:
|
||||||
offset = address_lo
|
offset = address_lo
|
||||||
for line_start in range(0, len(data), cls.INTELHEX_BYTES_PER_LINE):
|
for line_start in range(0, len(data), cls.INTELHEX_BYTES_PER_LINE):
|
||||||
write(
|
write(
|
||||||
@@ -90,8 +88,6 @@ class HexOutput:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
size_written += 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
|
offset += cls.INTELHEX_BYTES_PER_LINE
|
||||||
|
|
||||||
write(cls.intel_hex_line(cls.INTELHEX_END_OF_FILE_RECORD, 0, ()))
|
write(cls.intel_hex_line(cls.INTELHEX_END_OF_FILE_RECORD, 0, ()))
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#! /bin/sh
|
#! /bin/sh
|
||||||
echo $1
|
echo $1
|
||||||
echo $2
|
echo $2
|
||||||
find Images/ -type f -exec python3 img2logo.py {} "$1" "$2" \;
|
set -e
|
||||||
|
find Images/ -type f -exec python3 img2logo.py {} "$1" "$2" "$3" \;
|
||||||
|
|||||||
3351
Firmware/Miniware/MHP30_2.11.hex
Normal file
5
Firmware/Miniware/README.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Miniware firmware directory
|
||||||
|
|
||||||
|
This directory contains original proprietary firmware developed by Miniware for the purpose of backup & for easy reference in the documentation.
|
||||||
|
|
||||||
|
**DISCLAIMER**: All copyrights & authorship of this firmware belong to Miniware.
|
||||||
1984
Firmware/Miniware/TS100_2.20.hex
Normal file
2933
Firmware/Miniware/TS101_2.01.hex
Normal file
3014
Firmware/Miniware/TS80P_1.30.hex
Normal file
2013
Firmware/Miniware/TS80_1.07.hex
Normal file
5
Firmware/Miniware/md5.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
dda37ecf0c711781848b509c76e5c438 MHP30_2.11.hex
|
||||||
|
b835a6010fd0f876df1a5762e2317136 TS100_2.20.hex
|
||||||
|
06ac39c5ee21003e1a422bbb9e985de9 TS101_2.01.hex
|
||||||
|
abcdc8e9058931220c3b9cf9a19c58b2 TS80_1.07.hex
|
||||||
|
48e3d1be8f6a417596048f963e1ae1f6 TS80P_1.30.hex
|
||||||
5
Firmware/README.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Firmware directory
|
||||||
|
|
||||||
|
This directory contains original proprietary firmware of supported hardware for the purpose of backup & for easy reference in the documentation.
|
||||||
|
|
||||||
|
**DISCLAIMER**: All copyrights & authorship belong to their original holders (such as Miniware, Sequre, etc.)
|
||||||
17
README.md
@@ -2,7 +2,22 @@
|
|||||||
|
|
||||||
Storing meta information for IronOS.
|
Storing meta information for IronOS.
|
||||||
This are things that are not part of the core "OS".
|
This are things that are not part of the core "OS".
|
||||||
This includes photographs of hardware, datasheets, schematics and of course **bootup logos**.
|
This includes photographs of hardware, datasheets, schematics, original proprietary firmware and of course **bootup logos**.
|
||||||
|
|
||||||
This repository uses github actions to automagically build the logos for each device.
|
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.
|
Periodically a "release" will be tagged and pre-compiled logo's will be put there as well to make it easy.
|
||||||
|
|
||||||
|
# Boot-Up Logos
|
||||||
|
|
||||||
|
The IronOS firmware supports a user created bootup logo.
|
||||||
|
By default, there is _not_ one included in the firmware. This means that once flashed they generally stay. If you want no logo again, you would have to flash a blank image to the bootup logo.
|
||||||
|
|
||||||
|
- Safe & Fun: will not over write your firmware
|
||||||
|
- Easy install: use dfu tool just like updating firmware (or Pine64 Updater if you have a Pinecil).
|
||||||
|
|
||||||
|
## Generating the Logo files
|
||||||
|
|
||||||
|
There are community logo's already converted and ready to use in [IronOS-Meta/releases](https://github.com/Ralim/IronOS-Meta/releases).
|
||||||
|
Download the zip for Pinecil or Miniware and then install using the instructions on the [main IronOS documentation](https://ralim.github.io/IronOS/Logo/).
|
||||||
|
|
||||||
|
Alternatively if you want to make your own logo files, there is also documentation on how best to do this in the [main IronOS documentation](https://ralim.github.io/IronOS/Logo/).
|
||||||
|
|||||||