Прошло 2 года.

This commit is contained in:
Кобелев Андрей Андреевич
2026-03-10 22:54:23 +03:00
parent c7636ebd6f
commit a111352dc5
313 changed files with 274971 additions and 1409 deletions

21
lib/epdiy/scripts/LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 Mr. PK
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

105
lib/epdiy/scripts/README.md Normal file
View File

@@ -0,0 +1,105 @@
## Scripts in this folder are for adding addtional capabilities to epdiy.
## imgconvert.py
#### usage:
python3 imgconvert.py [-h] -i INPUTFILE -n NAME -o OUTPUTFILE [-maxw MAX_WIDTH]
[-maxh MAX_HEIGHT] [-nd] [-l GRAY_LEVELS]
**optional arguments:**
* **-h, --help** show this help message and exit
* **-i INPUTFILE**
* **-n NAME**
* **-o OUTPUTFILE**
* **-maxw MAX_WIDTH**
* **-maxh MAX_HEIGHT**
* **-nd** disable dithering
* **-l GRAY_LEVELS** configure the amount of colors
==========================================================
## fontconvert.py
#### usage:
python3 fontconvert.py [-h] [--compress] [--additional-intervals ADDITIONAL_INTERVALS]
[--string STRING]
name size fontstack [fontstack ...]
Generate a header file from a font to be used with epdiy.
**positional arguments:**
* **name** name of the font to be used in epdiy.
* **size** font size to use.
* **fontstack** list of font files, ordered by descending priority. This is not actually implemented as yet. Please just use one file for now.
**optional arguments:**
* **-h**, --help show this help message and exit
* **--compress** compress glyph bitmaps.
* **--additional-intervals** ADDITIONAL_INTERVALS
Additional code point intervals to export as min,max. This argument
can be repeated.
* **--string STRING** A quoted string of all required characters. The intervals are will be made from these characters if they exist in the ttf file. Missing characters will warn about their abscence.
####example:
1. Download a ttf from where you like to a directory. As in: "~/Downloads/any_old_ttf.ttf"
in the download directory
2. Run
`python3 fontconvert.py my_font 30 ~/Downloads/any_old_ttf.ttf --string '/0123456789:;@ABCDEFGH[\]^_`abcdefgh\{|}~¡¢£¤¥¦§¨©ª' > fonts.h`
* you will need to use special escapes for characters like ' or " This is system dependant though.
3. copy fonts.h into your app folder or where ever your app can find it.
4. include it into your project with
`#include fonts.h`
Then use it just like any other font file in epdiy.
**To run this script the freetype module needs to be installed. This can be done with `pip install freetype-py` You will be warned if it is not accessible by the script.**
==========================================================
##waveform_hdrgen.py
####usage:
waveform_hdrgen.py [-h] [--list-modes] [--temperature-range TEMPERATURE_RANGE]
[--export-modes EXPORT_MODES]
name
**positional arguments:**
name name of the waveform object.
**optional arguments:**
* **-h, --help** show this help message and exit
* **--list-modes** list the available modes for tis file.
* **--temperature-range TEMPERATURE_RANGE**
only export waveforms in the temperature range of min,max °C.
* **--export-modes EXPORT_MODES**
comma-separated list of waveform mode IDs to export.

View File

@@ -0,0 +1,160 @@
#!env python3
import json
from modenames import mode_names, mode_id
import sys
"""
This script generates json files to generate waveforms from.
"""
FRAME_TIMES_WHITE_TO_GL16 = {
"ED097OC4": [3.0, 3.0, 2.0, 2.0, 3.0, 3.0, 3.0, 4.0, 4.0, 5.0, 5.0, 5.0, 10.0, 20.0, 30.0],
"ED097OC1": [3.0, 3.0, 2.0, 2.0, 3.0, 3.0, 3.0, 4.0, 4.0, 5.0, 5.0, 5.0, 10.0, 20.0, 30.0],
"ED060SC4": [3.0, 3.0, 2.0, 2.0, 3.0, 3.0, 3.0, 4.0, 4.0, 5.0, 5.0, 5.0, 10.0, 20.0, 30.0],
"ED060XC3": [5.0, 4.0, 3.0, 3.0, 3.0, 3.0, 4.0, 4.0, 4.0, 5.0, 5.0, 5.0, 10.0, 20.0, 120.0],
"ED047TC1": [3.0, 3.0, 2.0, 2.0, 3.0, 3.0, 3.0, 4.0, 4.0, 5.0, 5.0, 5.0, 10.0, 20.0, 30.0],
"ED097TC2": [1.5, .8, .8, .8, .8, .8, 1.0, 1.0, 1.0, 1.0, 2.0, 2.0, 5.0, 10.0, 20.0],
"ED060SCT": [1.5, .8, .8, .8, .8, .8, 1.0, 1.0, 1.0, 1.0, 2.0, 2.0, 5.0, 10.0, 20.0],
"ED133UT2": [6.0, 3.0, 2.0, 2.0, 2.0, 2.0, 2.0, 3.0, 3.0, 3.0, 5.0, 5.0, 10.0, 20.0, 30.0],
}
FRAME_TIMES_BLACK_TO_GL16 = {
"ED097OC4": [1.0, 1.0, .8, .8, .8, .8, .8, 1.0, 1.0, 1.5, 1.5, 2.0, 2.0, 10.0, 30.0],
"ED097OC1": [1.0, 1.0, .8, .8, .8, .8, .8, 1.0, 1.0, 1.5, 1.5, 2.0, 2.0, 10.0, 30.0],
"ED060SC4": [1.0, 1.0, .8, .8, .8, .8, .8, .8, .8, 1.0, 1.0, 2.0, 2.0, 10.0, 30.0],
"ED060XC3": [1.0, 1.0, .5, .5, .5, .5, .5, .5, .5, 1.0, 1.0, 2.0, 2.0, 10.0, 30.0],
"ED047TC1": [1.0, 1.0, .8, .8, .8, .8, .8, 1.0, 1.0, 1.5, 1.5, 2.0, 2.0, 10.0, 30.0],
"ED097TC2": [1.5, .8, .4, .3, .3, .3, .3, .6, .6, .6, .6, 1.5, 2.0, 5.0, 15.0],
"ED060SCT": [1.5, .8, .4, .3, .3, .3, .3, .6, .6, .6, .6, 1.5, 2.0, 5.0, 15.0],
"ED133UT2": [2.0, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 2.0, 3.0, 3.0, 5.0, 10.0, 20.0]
}
FRAME_TIMES_DU = {
"ED097OC4": [100, 100, 100, 100],
"ED097OC1": [100, 100, 100, 100],
"ED060SC4": [100, 100, 100, 100],
"ED060XC3": [100, 100, 100, 100],
"ED047TC1": [100, 100, 100, 100, 100],
"ED097TC2": [100, 100, 100, 100, 100],
"ED060SCT": [100, 100, 100, 100, 100],
"ED133UT2": [100, 100, 100, 100]
}
def generate_frame(lut_func):
phase = []
for t in range(0, 32):
line = []
for f in range(0, 32):
line.append(lut_func(t, f))
phase.append(line)
return phase
def generate_mode_x_to_GL16(display, mode):
"""
Generates LUTs for drawing successive black frames
on a white screen.
"""
frame_times = None
dirfunc = None
if mode_names[mode] == "MODE_EPDIY_WHITE_TO_GL16":
frame_times = FRAME_TIMES_WHITE_TO_GL16[display]
def dirfunc(f, t, frame):
return (t < (30 - 2 * frame) and f == 30)
dirfunc = dirfunc
elif mode_names[mode] == "MODE_EPDIY_BLACK_TO_GL16":
frame_times = FRAME_TIMES_BLACK_TO_GL16[display]
def dirfunc(f, t, frame):
return 2 * (t > (2 * frame) and f == 0)
dirfunc = dirfunc
else:
raise ValueError(f"Cannot generate {mode} here.")
phases = []
for frame in range(15):
phase = generate_frame(lambda t, f: dirfunc(f, t, frame))
phases.append(phase)
return {"mode": mode, "ranges": [{"index": 0, "phases": phases, "phase_times": frame_times}]}
def generate_mode_GC16(display):
"""
Do arbitrary transitions by going to full black, then the desired value.
"""
phases = []
for frame in range(15):
phase = generate_frame(lambda t, f: f >= (30 - 2 * frame))
phases.append(phase)
for frame in range(15):
phase = generate_frame(lambda t, f: 2 * (t > (2 * frame)))
phases.append(phase)
frame_times = FRAME_TIMES_WHITE_TO_GL16[display] + FRAME_TIMES_BLACK_TO_GL16[display]
return {"mode": mode_id("MODE_GC16"), "ranges": [{"index": 0, "phases": phases, "phase_times": frame_times}]}
def generate_mode_GL16(display):
"""
Do non-flashing arbitrary transitions by going to full black, then the desired value.
"""
phases = []
for frame in range(15):
phase = generate_frame(lambda t, f: f >= (30 - 2 * frame))
phase[30][30] = 0
phase[0][0] = 0
phases.append(phase)
for frame in range(15):
phase = generate_frame(lambda t, f: 2 * (t > (2 * frame)))
phase[30][30] = 0
phase[0][0] = 0
phases.append(phase)
frame_times = FRAME_TIMES_WHITE_TO_GL16[display] + FRAME_TIMES_BLACK_TO_GL16[display]
return {"mode": mode_id("MODE_GL16"), "ranges": [{"index": 0, "phases": phases, "phase_times": frame_times}]}
def generate_du(display):
"""
Generate the "direct update" mode for a display.
"""
frame_times = FRAME_TIMES_DU[display];
num_phases = len(frame_times)
phases = []
for frame in range(num_phases):
def lutfunc(t, f):
if t == 0 and f > 0:
return 1
elif t == 30 and f < 30:
return 2
else:
return 0
phase = generate_frame(lutfunc)
phases.append(phase)
return {"mode": mode_id("MODE_DU"), "ranges": [{"index": 0, "phases": phases, "phase_times": frame_times}]}
def generate_epdiy_waveform(display):
# we say that these temperature waveforms are valid in ~room temperature.
default_temperature_range = {"from": 20, "to": 30}
white_to_gl16 = generate_mode_x_to_GL16(display, mode_id("MODE_EPDIY_WHITE_TO_GL16"))
black_to_gl16 = generate_mode_x_to_GL16(display, mode_id("MODE_EPDIY_BLACK_TO_GL16"))
gc16 = generate_mode_GC16(display)
gl16 = generate_mode_GL16(display)
du = generate_du(display)
temperature_ranges = {"range_bounds": [default_temperature_range]}
return {"temperature_ranges": temperature_ranges, "modes": [
du,
gc16,
gl16,
white_to_gl16,
black_to_gl16
]}
waveform = generate_epdiy_waveform(sys.argv[1])
print (json.dumps(waveform))

View File

@@ -0,0 +1,299 @@
#!python3
import sys
# inclusive unicode code point intervals
# must not overlap and be in ascending order
# modify intervals here
# however if the "string" command line argument is used these are ignored
intervals = [
(32, 126),
(160, 255),
# punctuation
(0x2010, 0x205F),
# arrows
(0x2190, 0x21FF),
# math
#(0x2200, 0x22FF),
# symbols
(0x2300, 0x23FF),
# box drawing
#(0x2500, 0x259F),
# geometric shapes
(0x25A0, 0x25FF),
# misc symbols
(0x2600, 0x26F0),
(0x2700, 0x27BF),
# powerline symbols
#(0xE0A0, 0xE0A2),
#(0xE0B0, 0xE0B3),
#(0x1F600, 0x1F680),
]
try:
import freetype
except ImportError as error:
sys.exit("To run this script the freetype module needs to be installed.\nThis can be done using:\npip install freetype-py")
import zlib
import sys
import re
import math
import argparse
from collections import namedtuple
#see https://freetype-py.readthedocs.io/en/latest/ for documentation
parser = argparse.ArgumentParser(description="Generate a header file from a font to be used with epdiy.")
parser.add_argument("name", action="store", help="name of the font.")
parser.add_argument("size", type=int, help="font size to use.")
parser.add_argument("fontstack", action="store", nargs='+', help="list of font files, ordered by descending priority. This is not actually implemented please just use one file for now.")
parser.add_argument("--compress", dest="compress", action="store_true", help="compress glyph bitmaps.")
parser.add_argument("--additional-intervals", dest="additional_intervals", action="append", help="Additional code point intervals to export as min,max. This argument can be repeated.")
parser.add_argument("--string", action="store", help="A string of all required characters. intervals are made up of this" )
args = parser.parse_args()
command_line = ""
prev_arg = ""
for arg in sys.argv:
# ~ if prev_arg == "--string":
# ~ command_line = command_line + " '" + arg +"'"
# ~ else:
command_line = command_line + " " + arg
# ~ prev_arg = arg
# ~ print (command_line)
GlyphProps = namedtuple("GlyphProps", ["width", "height", "advance_x", "left", "top", "compressed_size", "data_offset", "code_point"])
font_stack = [freetype.Face(f) for f in args.fontstack]
font_files = args.fontstack
face_index = 0
font_file = font_files[face_index]
compress = args.compress
size = args.size
font_name = args.name
for face in font_stack:
# shift by 6 bytes, because sizes are given as 6-bit fractions
# the display has about 150 dpi.
face.set_char_size(size << 6, size << 6, 150, 150)
# assign intervals from argument parrameters ie. handle the string arg
if args.string != None:
font_file = font_files[face_index]
string = " " + args.string # always add space to the string it is easily forgotten
chars = sorted(set(string))
#make array of code pointscode_ponts.append(ord(char))
code_points = list()
intervals = [] # empty the intevals array NB. if you want to allways add default characters comment out this line
# go through the sorted characters and make the intervals
for char in chars:
if( face.get_char_index(ord(char)) != 0 ):
# this character is in the font file so add it to the new string.
code_points.append(ord(char))
else:
print("The character ", char, " is not available in ", font_file, file=sys.stderr)
lower = code_points[0]
len_x = len(code_points)
x = 0
while x < len_x:
# ~ print ("loop value x = ", x , file=sys.stderr)
a = code_points[x];
b = a;
if( x < len_x - 1):
b = code_points[x + 1];
if( a == b - 1 ):
# ~ print("sequential", a, b, file=sys.stderr)
if( lower == -1):
lower = a
else:
# ~ print("non sequential", a, b , file=sys.stderr)
if( lower == -1):
# ~ print("single character")
interval = (a , a)
else:
interval = (lower, a)
# ~ print("interval", interval , file=sys.stderr)
intervals.append(interval)
lower = -1
x = x + 1
# base intervals are assigned dditional intervals from arguments
add_ints = []
if args.additional_intervals != None:
add_ints = [tuple([int(n, base=0) for n in i.split(",")]) for i in args.additional_intervals]
intervals = sorted(intervals + add_ints)
# ~ print("Intervals are now: ", intervals, file=sys.stderr)
def norm_floor(val):
return int(math.floor(val / (1 << 6)))
def norm_ceil(val):
return int(math.ceil(val / (1 << 6)))
for face in font_stack:
# shift by 6 bytes, because sizes are given as 6-bit fractions
# the display has about 150 dpi.
face.set_char_size(size << 6, size << 6, 150, 150)
def chunks(l, n):
for i in range(0, len(l), n):
yield l[i:i + n]
total_size = 0
total_packed = 0
all_glyphs = []
# new globals
total_chars = 0
ascender = 0
descender = 100
f_height = 0
def load_glyph(code_point):
global face_index
face_index = 0
while face_index < len(font_stack):
face = font_stack[face_index]
glyph_index = face.get_char_index(code_point)
if glyph_index > 0:
face.load_glyph(glyph_index, freetype.FT_LOAD_RENDER)
#count characters found and find bounds of characters
global ascender
if ascender < face.size.ascender:
ascender = face.size.ascender
global descender
if descender > face.size.descender:
descender = face.size.descender
global f_height
if f_height < face.size.height:
f_height = face.size.height
global total_chars
total_chars += 1
return face
break
face_index += 1
# this needs work
# this needs to be handled better to show failed character and continue not just die a questionable death
# this appears to have been designed to combine several font files
# but that is not clear to the end user and this then looks like a bug
print (f"falling back to font {face_index} for {chr(code_point)}.", file=sys.stderr)
raise ValueError(f"code point {code_point} not found in font stack!")
for i_start, i_end in intervals:
for code_point in range(i_start, i_end + 1):
# handle missing characters in font file
if( face.get_char_index(code_point) == 0 ):
print("Character ", chr(code_point), "(", code_point, ") is not in ", font_file, file=sys.stderr)
continue
face = load_glyph(code_point)
bitmap = face.glyph.bitmap
pixels = []
px = 0
for i, v in enumerate(bitmap.buffer):
y = i / bitmap.width
x = i % bitmap.width
if x % 2 == 0:
px = (v >> 4)
else:
px = px | (v & 0xF0)
pixels.append(px);
px = 0
# eol
if x == bitmap.width - 1 and bitmap.width % 2 > 0:
pixels.append(px)
px = 0
packed = bytes(pixels);
total_packed += len(packed)
compressed = packed
if compress:
compressed = zlib.compress(packed)
glyph = GlyphProps(
width = bitmap.width,
height = bitmap.rows,
advance_x = norm_floor(face.glyph.advance.x),
left = face.glyph.bitmap_left,
top = face.glyph.bitmap_top,
compressed_size = len(compressed),
data_offset = total_size,
code_point = code_point,
)
total_size += len(compressed)
all_glyphs.append((glyph, compressed))
# pipe seems to be a good heuristic for the "real" descender
# face = load_glyph(ord('|'))
# removed as max descender and assender are handled above
glyph_data = []
glyph_props = []
for index, glyph in enumerate(all_glyphs):
props, compressed = glyph
glyph_data.extend([b for b in compressed])
glyph_props.append(props)
print("", file=sys.stderr)
print(f"Original font file {font_file} as {font_name} using {total_chars} characters", file=sys.stderr)
print("total", total_packed, file=sys.stderr)
print("compressed", total_size, file=sys.stderr)
print("#pragma once")
print("#include \"epdiy.h\"")
# add font file origin and characters at the head of the output file
print("/*")
print ( "Created with")
print(command_line)
print(f"As '{font_name}' with available {total_chars} characters")
for i, g in enumerate(glyph_props):
print (f"{chr(g.code_point)}", end ="" )
print("")
print("*/")
print(f"const uint8_t {font_name}_Bitmaps[{len(glyph_data)}] = {{")
for c in chunks(glyph_data, 16):
print (" " + " ".join(f"0x{b:02X}," for b in c))
print ("};");
print ('// GlyphProps[width, height, advance_x, left, top, compressed_size, data_offset, code_point]')
print(f"const EpdGlyph {font_name}_Glyphs[] = {{")
for i, g in enumerate(glyph_props):
print (" { " + ", ".join([f"{a}" for a in list(g[:-1])]),"},", f"// '{chr(g.code_point) if g.code_point != 92 else '<backslash>'}'")
print ("};");
print(f"const EpdUnicodeInterval {font_name}_Intervals[] = {{")
offset = 0
for i_start, i_end in intervals:
print (f" {{ 0x{i_start:X}, 0x{i_end:X}, 0x{offset:X} }},")
offset += i_end - i_start + 1
print ("};");
print(f"const EpdFont {font_name} = {{")
print(f" {font_name}_Bitmaps, // (*bitmap) Glyph bitmap pointer, all concatenated together")
print(f" {font_name}_Glyphs, // glyphs Glyph array")
print(f" {font_name}_Intervals, // intervals Valid unicode intervals for this font")
print(f" {len(intervals)}, // interval_count Number of unicode intervals.intervals")
print(f" {1 if compress else 0}, // compressed Does this font use compressed glyph bitmaps?")
print(f" {norm_ceil(f_height)}, // advance_y Newline distance (y axis)")
print(f" {norm_ceil(ascender)}, // ascender Maximal height of a glyph above the base line")
print(f" {norm_floor(descender)}, // descender Maximal height of a glyph below the base line")
print("};")
print("/*")
print("Included intervals")
for i_start, i_end in intervals:
print (f" ( {i_start}, {i_end}), ie. '{chr(i_start)}' - '{chr(i_end)}'")
print("Included intervals", file=sys.stderr)
for i_start, i_end in intervals:
print (f" ( {i_start}, {i_end}), ie. '{chr(i_start)}' - '{chr(i_end)}'", file=sys.stderr)
print("")
print("*/")

75
lib/epdiy/scripts/imgconvert.py Executable file
View File

@@ -0,0 +1,75 @@
#!python3
from PIL import Image
from argparse import ArgumentParser
import sys
import math
parser = ArgumentParser()
parser.add_argument('-i', action="store", dest="inputfile", required=True)
parser.add_argument('-n', action="store", dest="name", required=True)
parser.add_argument('-o', action="store", dest="outputfile", required=True)
parser.add_argument('-maxw', action="store", dest="max_width", default=1200, type=int)
parser.add_argument('-maxh', action="store", dest="max_height", default=825, type=int)
parser.add_argument('-nd', action="store_false", dest="dither", required=False, default=True)
parser.add_argument('-l', action="store", dest="levels", default=16, type=int)
args = parser.parse_args()
if args.levels > 16:
raise Exception("Maximum 16 levels of gray supported by this program")
if args.levels < 2:
# less than 2 doesn't make sense, there is at least on and off
arg.levels = 2
if args.max_height < 1:
raise Exception("Max height cannot be lower than 1")
if args.max_width < 1:
raise Exception("Max width cannot be lower than 1")
im = Image.open(args.inputfile)
if im.mode == 'I;16': # some pngs load like this
# so we fix it
im = im.point(lambda i:i*(1./256)).convert('L')
im.thumbnail((args.max_width, args.max_height), Image.LANCZOS)
if im.mode != 'L':
if im.mode != 'RGB':
im = im.convert(mode='RGB') # drop alpha
# now we can convert it to grayscale to get rid of colors
im = im.convert(mode='L')
# but the quant algorithm works much better on RGB, so go to RGB mode just for that (even though we're still grayscale)
im = im.convert(mode='RGB')
# prepare a palette for the levels of gray
paletteImage = Image.new(mode='P', size=[1,1])
# we make a palette that matches the indexed colors of the display, just repeating the same value for rgb
paletteColors = [round(i * 255.0 / (args.levels - 1)) for i in range(0,args.levels) for c in range(0,3)]
paletteImage.putpalette(paletteColors, rawmode='RGB')
im = im.quantize(colors=args.levels, palette=paletteImage, dither=Image.Dither.FLOYDSTEINBERG if args.dither else Image.Dither.NONE)
# now it's quantizied to the palette, 0 == dark, args.levels-1 == white
# Write out the output file.
with open(args.outputfile, 'w') as f:
f.write("const uint32_t {}_width = {};\n".format(args.name, im.size[0]))
f.write("const uint32_t {}_height = {};\n".format(args.name, im.size[1]))
f.write(
"const uint8_t {}_data[({}*{})/2] = {{\n".format(args.name, math.ceil(im.size[0] / 2) * 2, im.size[1])
)
for y in range(0, im.size[1]):
byte = 0
for x in range(0, im.size[0]):
l = im.getpixel((x, y))
if x % 2 == 0:
byte = l
else:
byte |= l << 4
f.write("0x{:02X}, ".format(byte))
if im.size[0] % 2 == 1:
f.write("0x{:02X}, ".format(byte))
f.write("\n\t")
f.write("};\n")

View File

@@ -0,0 +1,21 @@
mode_names = {
0: "MODE_INIT",
1: "MODE_DU",
2: "MODE_GC16",
3: "MODE_GC16_FAST",
4: "MODE_A2",
5: "MODE_GL16",
6: "MODE_GL16_FAST",
7: "MODE_DU4",
10: "MODE_GL4",
11: "MODE_GL16_INV",
0x10: "MODE_EPDIY_WHITE_TO_GL16",
0x11: "MODE_EPDIY_BLACK_TO_GL16",
0x3F: "MODE_UNKNOWN_WAVEFORM",
}
def mode_id(name):
for i, n in mode_names.items():
if name == n:
return i
return 0x3F

View File

@@ -0,0 +1,137 @@
#!env python3
import json
import sys
import argparse
from modenames import mode_names
parser = argparse.ArgumentParser()
parser.add_argument("--list-modes", help="list the available modes for tis file.", action = "store_true");
parser.add_argument("--temperature-range", help="only export waveforms in the temperature range of min,max °C.");
parser.add_argument("--export-modes", help="comma-separated list of waveform mode IDs to export.");
parser.add_argument("name", help="name of the waveform object.");
args = parser.parse_args()
waveforms = json.load(sys.stdin);
total_size = 0
def phase_to_c(phase,bits_per_pixel_c=4):
N1 = len(phase)
N2 = len(phase[0])
N = 2**bits_per_pixel_c
if N1%N != 0:
raise ValueError(f"first dimension of phases is {N1}. Allowed are multiples of {N}")
step1 = int(N1/N)
if N2%N != 0:
raise ValueError(f"second dimension of phases is {N2}. Allowed are multiples of {N}")
step2 = int(N2/N)
targets = []
for t in range(0, N1, step1):
chunk = 0
line = []
i = 0
for f in range(0, N2, step2):
fr = phase[t][f]
chunk = (chunk << 2) | fr
i += 1
if i == 4:
i = 0
line.append(chunk)
chunk = 0
targets.append(line)
return targets
def list_to_c(l):
if isinstance(l, list):
children = [list_to_c(c) for c in l]
return "{" + ",".join(children) + "}"
elif isinstance(l, int):
return f"0x{l:02x}"
else:
assert(false)
if args.list_modes:
for mode in waveforms["modes"]:
print(f"""{mode["mode"]}: {mode_names[mode["mode"]]}""" )
sys.exit(0)
tmin = -100
tmax = 1000
if args.temperature_range:
tmin, tmax = map(int, args.temperature_range.split(","))
modes = []
mode_filter = [wm["mode"] for wm in waveforms["modes"]]
if args.export_modes:
mode_filter = list(map(int, args.export_modes.split(",")))
mode_filter = [m for m in mode_filter if any([wm["mode"] == m for wm in waveforms["modes"]])]
num_modes = len(mode_filter)
temp_intervals = []
for bounds in waveforms["temperature_ranges"]["range_bounds"]:
if bounds["to"] < tmin or bounds["from"] > tmax:
continue
temp_intervals.append(f"{{ .min = {bounds['from']}, .max = {bounds['to']} }}")
modes = []
num_ranges = -1
for m_index, mode in enumerate(waveforms["modes"]):
if not mode["mode"] in mode_filter:
continue
ranges = []
for i, r in enumerate(mode["ranges"]):
bounds = waveforms["temperature_ranges"]["range_bounds"][i]
if bounds["to"] < tmin or bounds["from"] > tmax:
continue
phases = []
phase_count = len(r["phases"])
prev_phase = None
for phase in r["phases"]:
phases.append(phase_to_c(phase))
name = f"epd_wp_{args.name}_{mode['mode']}_{r['index']}"
phase_times= None
if r.get("phase_times"):
phase_times = [str(int(t * 10)) for t in r["phase_times"]]
print(f"const int {name}_times[{len(phase_times)}] = {{ {','.join(phase_times) } }};")
phase_times_str = f"&{name}_times[0]" if phase_times else "NULL"
print(f"const uint8_t {name}_data[{phase_count}][16][4] = {list_to_c(phases)};")
print(f"const EpdWaveformPhases {name} = {{ .phases = {phase_count}, .phase_times = {phase_times_str}, .luts = (const uint8_t*)&{name}_data[0] }};")
ranges.append(name)
assert(num_ranges < 0 or num_ranges == len(ranges))
num_ranges = len(ranges)
name = f"epd_wm_{args.name}_{mode['mode']}"
range_pointers = ','.join(['&' + n for n in ranges])
print(f"const EpdWaveformPhases* {name}_ranges[{len(ranges)}] = {{ {range_pointers} }};")
print(f"const EpdWaveformMode {name} = {{ .type = {mode['mode']}, .temp_ranges = {len(ranges)}, .range_data = &{name}_ranges[0] }};");
modes.append(name)
mode_pointers = ','.join(['&' + n for n in modes])
range_data = ",".join(temp_intervals)
print(f"const EpdWaveformTempInterval {args.name}_intervals[{len(temp_intervals)}] = {{ {range_data} }};");
print(f"const EpdWaveformMode* {args.name}_modes[{num_modes}] = {{ {mode_pointers} }};");
print(f"const EpdWaveform {args.name} = {{ .num_modes = {num_modes}, .num_temp_ranges = {num_ranges}, .mode_data = &{args.name}_modes[0], .temp_intervals = &{args.name}_intervals[0] }};");