feat: work to i2c screen (wip)
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
error.glsl
|
||||
forge_default_save.txt
|
||||
build
|
||||
tmp*
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 72 KiB |
@@ -71,7 +71,7 @@ run: $(BUILD_DIR)/$(TARGET)
|
||||
|
||||
.PHONY: start-bg
|
||||
start-bg:
|
||||
@python3 $(PROJECT_DIR)/i2c.py&
|
||||
@python3 $(PROJECT_DIR)/i2c.py >> $(PROJECT_DIR)/python.log&
|
||||
|
||||
.PHONY: start-fg
|
||||
start-fg:
|
||||
|
||||
Binary file not shown.
@@ -33,6 +33,7 @@ echo 'make -C /opt/forge-invar start-weston' >> ~/.bashrc
|
||||
* https://www.exploding-shed.com/synth-diy-guides/standards-of-eurorack/eurorack-dimensions/
|
||||
* https://electricalflux.com/mcu-raspberry/i2c-oled-display-raspberry-pi-5-wiring-code
|
||||
* https://www.raspberrypi.com/documentation/computers/configuration.html
|
||||
* https://int10h.org/oldschool-pc-fonts/fontlist/font?ibm_bios
|
||||
|
||||
## TODO
|
||||
|
||||
|
||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,106 @@
|
||||
from PIL import Image, ImageDraw, ImageFont, ImageMath
|
||||
|
||||
# TODO clean
|
||||
|
||||
font = ImageFont.truetype("./TinyAndChunkyRegular.ttf", 5)
|
||||
font2 = ImageFont.truetype("./Px437_IBM_BIOS.ttf", 8)
|
||||
|
||||
im0 = Image.new("1", (128, 64), "black")
|
||||
|
||||
draw = ImageDraw.Draw(im0)
|
||||
|
||||
draw.text((26, 24), "F.O.R.G.E.", fill="white", font=font2)
|
||||
draw.rectangle((24, 35, 104, 40), outline="white", fill="black")
|
||||
|
||||
im0.save("i2c_loading.png")
|
||||
|
||||
im = Image.new("1", (128, 64), "black")
|
||||
|
||||
draw = ImageDraw.Draw(im)
|
||||
|
||||
|
||||
# tempo
|
||||
draw.text((7, -2), "TEMPO:", fill="white", font=font)
|
||||
draw.rectangle((6, 6, 60, 11), outline="white", fill="black")
|
||||
|
||||
# time
|
||||
draw.text((68, -2), "TIME:", fill="white", font=font)
|
||||
draw.rectangle((67, 6, 121, 11), outline="white", fill="black")
|
||||
|
||||
# pipes
|
||||
draw.rectangle((26, 22, 78, 24), fill="white")
|
||||
draw.rectangle((26, 46, 78, 48), fill="white")
|
||||
draw.rectangle((78, 34, 128, 36), fill="white")
|
||||
draw.rectangle((76, 23, 78, 46), fill="white")
|
||||
|
||||
# squares
|
||||
draw.rectangle((12, 16, 26, 30), outline="white", fill="black")
|
||||
draw.rectangle((41, 16, 55, 30), outline="white", fill="black")
|
||||
|
||||
draw.rectangle((12, 40, 26, 54), outline="white", fill="black")
|
||||
draw.rectangle((41, 40, 55, 54), outline="white", fill="black")
|
||||
|
||||
draw.rectangle((70, 28, 84, 42), outline="white", fill="black")
|
||||
draw.rectangle((99, 28, 113, 42), outline="white", fill="black")
|
||||
|
||||
selected_bounds = [
|
||||
(10, 14, 28, 32),
|
||||
(10, 38, 28, 56),
|
||||
(39, 14, 57, 32),
|
||||
(39, 38, 57, 56),
|
||||
(0, 0, 0, 0),
|
||||
(97, 26, 115, 44),
|
||||
]
|
||||
|
||||
# page
|
||||
draw.circle((55, 61), 2, outline="white", fill="black")
|
||||
draw.circle((63, 61), 2, outline="white", fill="black")
|
||||
draw.circle((71, 61), 2, outline="white", fill="black")
|
||||
|
||||
im.save("i2c_background.png")
|
||||
|
||||
# ---
|
||||
|
||||
# tempo
|
||||
draw.text((42, -2), "240", fill="white", font=font)
|
||||
draw.rectangle((6, 7, 6 + 53 * 1, 10), fill="white", width=0)
|
||||
|
||||
# time
|
||||
draw.text((94, -2), "1234", fill="white", font=font)
|
||||
draw.rectangle((67, 7, 67 + 53 * 1, 10), fill="white", width=0)
|
||||
|
||||
draw.circle((55, 61), 2, fill="white")
|
||||
|
||||
# selected
|
||||
|
||||
for i in range(len(selected_bounds)):
|
||||
if selected_bounds[i][0] > 0:
|
||||
draw.rectangle(selected_bounds[i], outline="white", fill=None)
|
||||
|
||||
im_overlay = Image.new("1", (128, 64), "black")
|
||||
draw2 = ImageDraw.Draw(im_overlay)
|
||||
|
||||
|
||||
# shaders
|
||||
draw2.text((16, 20), "0", fill="white", font=font2)
|
||||
draw2.text((45, 20), "2", fill="white", font=font2)
|
||||
draw2.text((16, 44), "1", fill="white", font=font2)
|
||||
draw2.text((45, 44), "3", fill="white", font=font2)
|
||||
draw2.text((74, 32), "K", fill="white", font=font2)
|
||||
draw2.text((103, 32), "6", fill="white", font=font2)
|
||||
|
||||
im = ImageMath.lambda_eval(lambda args: args["convert"](args["a"] ^ args["b"], "1"), a=im, b=im_overlay)
|
||||
|
||||
# ---
|
||||
|
||||
im = im.resize((1280, 640)).convert("RGB")
|
||||
|
||||
draw2 = ImageDraw.Draw(im)
|
||||
|
||||
for x in range(1, 128):
|
||||
draw2.line((x * 10, 0, x * 10, 640), fill=(50, 50, 50))
|
||||
for y in range(1, 64):
|
||||
draw2.line((0, y * 10, 1280, y * 10), fill=(50, 50, 50))
|
||||
|
||||
|
||||
im.resize((1280, 640)).save("tmp.png")
|
||||
@@ -5,38 +5,64 @@
|
||||
import collections
|
||||
import pathlib
|
||||
import sys
|
||||
import time
|
||||
|
||||
from luma.core.error import DeviceNotFoundError
|
||||
from luma.core.interface.serial import i2c
|
||||
from luma.core.render import canvas
|
||||
from luma.oled.device import sh1106
|
||||
from luma.oled.device import device, sh1106
|
||||
from PIL import Image, ImageDraw, ImageFont, ImageMath
|
||||
|
||||
TARGET_FILE = pathlib.Path(__file__).parent / "forge_default_save.txt"
|
||||
TARGET_FILE_BACKUP = pathlib.Path(__file__).parent / "forge_default_save.txt.bck"
|
||||
|
||||
ESTIMATED_BOOT_TIME_SECONDS = 18.0
|
||||
|
||||
data: dict[str, float] = collections.defaultdict(lambda: 0)
|
||||
|
||||
def readfile():
|
||||
font1 = ImageFont.truetype("./TinyAndChunkyRegular.ttf", 5)
|
||||
font2 = ImageFont.truetype("./Px437_IBM_BIOS.ttf", 8)
|
||||
loading_img = Image.open("i2c_loading.png").convert("1")
|
||||
background_img = Image.open("i2c_background.png").convert("1")
|
||||
blank_im = Image.new("1", (128, 64), "black")
|
||||
|
||||
selected_bounds = [
|
||||
(10, 14, 28, 32),
|
||||
(10, 38, 28, 56),
|
||||
(39, 14, 57, 32),
|
||||
(39, 38, 57, 56),
|
||||
(0, 0, 0, 0),
|
||||
(97, 26, 115, 44),
|
||||
]
|
||||
|
||||
|
||||
def try_restore_backup() -> bool:
|
||||
if TARGET_FILE_BACKUP.exists() and readfile(TARGET_FILE_BACKUP):
|
||||
TARGET_FILE.write_text(TARGET_FILE_BACKUP.read_text())
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def readfile(file: pathlib.Path = TARGET_FILE) -> bool:
|
||||
try:
|
||||
content = TARGET_FILE.read_text()
|
||||
content = file.read_text()
|
||||
except: # noqa: E722
|
||||
return
|
||||
TARGET_FILE_BACKUP.write_text(content)
|
||||
return False
|
||||
for line in content.splitlines():
|
||||
key, value = line.split("=", maxsplit=2)
|
||||
data[key] = float(value)
|
||||
if "time" in data:
|
||||
TARGET_FILE_BACKUP.write_text(content)
|
||||
return "time" in data
|
||||
|
||||
def main():
|
||||
if TARGET_FILE_BACKUP.exists() and TARGET_FILE.exists():
|
||||
TARGET_FILE.write_text(TARGET_FILE_BACKUP.read_text())
|
||||
|
||||
def open_device() -> device:
|
||||
# Pin definitions: I2C Bus 1 (SDA=GPIO2, SCL=GPIO3)
|
||||
# Default SSD1306 I2C address is 0x3C
|
||||
serial_interface = i2c(port=1, address=0x3C)
|
||||
|
||||
try:
|
||||
# Initialize the device. If using SH1106, change ssd1306 to sh1106
|
||||
device = sh1106(serial_interface, width=128, height=64, rotate=0)
|
||||
return sh1106(serial_interface, width=128, height=64, rotate=2)
|
||||
except DeviceNotFoundError:
|
||||
print("Fatal: OLED not found at 0x3C. Check wiring and run i2cdetect -y 1")
|
||||
sys.exit(1)
|
||||
@@ -49,13 +75,106 @@ def main():
|
||||
print(f"Fatal OS Error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def print_loading(restored: bool, t0: float) -> Image.Image:
|
||||
im = loading_img.copy()
|
||||
draw = ImageDraw.Draw(im)
|
||||
|
||||
now = time.time()
|
||||
|
||||
percent = min(1.0, (now - t0) / ESTIMATED_BOOT_TIME_SECONDS)
|
||||
|
||||
draw.rectangle((24, 35, 24 + 80 * percent, 40), fill="white")
|
||||
|
||||
if not restored:
|
||||
draw.circle((4, 4), 1, fill="white")
|
||||
|
||||
return im
|
||||
|
||||
|
||||
def print_monitor() -> Image.Image:
|
||||
im = background_img.copy()
|
||||
draw = ImageDraw.Draw(im)
|
||||
|
||||
im_overlay = blank_im.copy()
|
||||
draw2 = ImageDraw.Draw(im_overlay)
|
||||
|
||||
# tempo
|
||||
draw.text((42, -2), f"{int(data['tempo'])}", fill="white", font=font1)
|
||||
draw.rectangle(
|
||||
(6, 7, 6 + 53 * (data["beats"] - int(data["beats"])), 10), fill="white", width=0
|
||||
)
|
||||
|
||||
# time
|
||||
draw.text((94, -2), f"{int(data['time'])}", fill="white", font=font1)
|
||||
draw.rectangle(
|
||||
(67, 7, 67 + 53 * (data["time"] - int(data["time"])), 10), fill="white", width=0
|
||||
)
|
||||
|
||||
# page
|
||||
draw.circle((55 + 8 * int(data["page"]), 61), 2, fill="white")
|
||||
|
||||
# selected
|
||||
selected = int(data["selected"])
|
||||
draw.rectangle(selected_bounds[selected], outline="white", fill=None)
|
||||
|
||||
# TODO active fx/src
|
||||
|
||||
# fx/mix values
|
||||
# todo random and time based
|
||||
fxa_value = 1 - data['value_18_x'] / 127
|
||||
draw.rectangle((41, 16 + round(14 * fxa_value), 55, 30), fill="white")
|
||||
fxb_value = 1 - data['value_25_x'] / 127
|
||||
draw.rectangle((41, 40 + round(14 * fxb_value), 55, 54), fill="white")
|
||||
mfx_value = 1 - data['value_32_x'] / 127
|
||||
draw.rectangle((99, 28 + round(14 * mfx_value), 113, 42), fill="white")
|
||||
mix_value = 1 - data['value_34_x'] / 127
|
||||
draw.rectangle((70, 28 + round(14 * mix_value), 84, 42), fill="white")
|
||||
|
||||
# active shaders
|
||||
draw2.text((16, 20), hex(int(data["state_0"]))[2].upper(), fill="white", font=font2)
|
||||
draw2.text((16, 44), hex(int(data["state_1"]))[2].upper(), fill="white", font=font2)
|
||||
draw2.text((45, 20), hex(int(data["state_2"]))[2].upper(), fill="white", font=font2)
|
||||
draw2.text((45, 44), hex(int(data["state_3"]))[2].upper(), fill="white", font=font2)
|
||||
draw2.text(
|
||||
(103, 32), hex(int(data["state_6"]))[2].upper(), fill="white", font=font2
|
||||
)
|
||||
mix_type = data['value_33_x'] == 0
|
||||
draw2.text((74, 32), "M" if mix_type else "K", fill="white", font=font2) # TODO
|
||||
|
||||
return ImageMath.lambda_eval(lambda args: args["convert"](args["a"] ^ args["b"], "1"), a=im, b=im_overlay)
|
||||
|
||||
|
||||
def main():
|
||||
restored = try_restore_backup() or readfile()
|
||||
|
||||
device = open_device()
|
||||
|
||||
initial = True
|
||||
started = False
|
||||
|
||||
t0 = time.time()
|
||||
|
||||
try:
|
||||
device.display(print_loading(restored, t0))
|
||||
|
||||
while True:
|
||||
readfile()
|
||||
with canvas(device) as draw:
|
||||
# draw.rectangle((0, 50, 127, 60), outline="white", fill="black")
|
||||
draw.text((0, 0), f"time={data['time']}", fill="white")
|
||||
draw.text((0, 10), f"beats={data['beats']}", fill="white")
|
||||
if not readfile():
|
||||
time.sleep(0.01)
|
||||
continue
|
||||
|
||||
if initial or started:
|
||||
if not started:
|
||||
started = "time" in data and data["time"] == 0
|
||||
if started:
|
||||
initial = False
|
||||
device.display(print_loading(restored, t0))
|
||||
else:
|
||||
device.display(print_monitor())
|
||||
|
||||
if started and data["time"] > 0:
|
||||
initial = False
|
||||
started = False
|
||||
except KeyboardInterrupt:
|
||||
print("Exiting gracefully...")
|
||||
except OSError as e:
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 301 B |
Binary file not shown.
|
After Width: | Height: | Size: 181 B |
Reference in New Issue
Block a user