Files
boxtools/display.py
l_samenv d6e68aef7d utils.MainIf: rename 'getip' to 'poll'
+ change mechanism for getting hostname
2024-03-06 15:23:36 +01:00

293 lines
7.7 KiB
Python

import sys
import os
import time
import serial
import socket
import threading
import re
import queue
from utils import MainIf
from subprocess import Popen, PIPE
from configparser import ConfigParser
# display tty device
tty = '/dev/ttyS1'
TOOLS = '/home/l_samenv/boxtools'
STARTUP_TEXT = f'{TOOLS}/startup_display.txt'
ESC = 0x1b
ESCESC = bytes((ESC, ESC))
MODE_GRAPH = 0x20
MODE_CONSOLE = 0x21
SET_POS = 0x30
SET_FONT = 0x31
SET_COLOR = 0x32
CLEAR = 0x40
LINES = 0x41
RECT = 0x42
ICON = 0x43
TEXT = 0x44
COPYRECT = 0x45
PLOT = 0x46
TOUCH = 0x50
TOUCH_MODE = 0x51
SAVE_ATTRS = 0xa0
SEL_ATTRS = 0xc0
SET_STARTUP = 0xf2
IDENT = 0xf3
FONT_GEO = [(6, 8), (8, 16), (20, 40), (38,64), (16, 16), (12, 24)]
TOPGAP = 8 # upper part of display is not useable
HEIGHT = 120
WIDTH = 480
MAGIC = b'\xcb\xef\x20\x18'
mainif = MainIf()
def xy(x, y):
x = min(480, int(x))
return bytes([(min(127, y + TOPGAP) << 1) + (x >> 8), x % 256])
class Display:
fg = 15
bg = 0
touch = 0
bar = None
blink = False
menu = None
storage = None
def __init__(self, dev, timeout=0.5, daemon=False):
# self.event = threading.Event()
self.term = serial.Serial(dev, baudrate=115200, timeout=timeout)
self.storage = bytearray()
self.hostname = socket.gethostname()
if daemon:
todo_file = STARTUP_TEXT + '.todo'
try:
with open(todo_file) as f:
text = f.read()
print('new startup text:')
print(text)
except FileNotFoundError:
text = None
if text:
self.reset()
self.show(*text.split('\n')[0:3])
self.set_startup()
os.rename(todo_file, STARTUP_TEXT)
else:
self.storage = None
else:
os.system('systemctl stop display')
self.reset()
if daemon:
self.net_display(None)
def reset(self):
self.send(MODE_GRAPH)
self.font(2)
self.send(CLEAR, self.bg)
def color(self, fg=15, bg=0):
self.fg = fg
self.bg = bg
self.send(SET_COLOR, bg, bg, fg, fg)
def send(self, code, *args):
out = bytearray([code])
for arg in args:
if isinstance(arg, str):
out.extend(arg.encode('latin-1'))
elif hasattr(arg, '__len__'):
out.extend(arg)
else:
out.append(arg)
cmd = bytearray([ESC,ESC,len(out)]) + out
if self.storage is not None:
self.storage.extend(cmd)
self.term.write(cmd)
def set_startup(self):
if self.storage is None:
print('storage off')
return
data = self.storage
self.storage = None
self.send(SET_STARTUP, data)
def version(self):
self.term.write(bytes([ESC,ESC,1,0xf3]))
reply = self.term.read(4)
assert reply[0:2] == ESCESC
return self.term.read(reply[2]-1)
def font(self, size):
self.fontsize = size
self.send(SET_FONT, size)
self.colwid, self.rowhei = FONT_GEO[self.fontsize]
self.nrows = HEIGHT // self.rowhei
self.ncols = WIDTH // self.colwid
self.textbuffer = [" " * self.ncols] * self.nrows
self.color()
def text(self, text, row=0, left=0, right=None, font=2):
if font != self.fontsize:
self.font(font)
if right is None:
right = self.ncols
if right < 0:
right += self.ncols
if left < 0:
left += self.ncols
for line in text.split('\n'):
padded = line + " " * (right - left - len(line))
self.send(SET_POS, xy(left * self.colwid, TOPGAP + row * self.rowhei))
self.send(TEXT, padded.encode('latin-1'))
def show(self, *args):
self.send(CLEAR, self.bg)
if len(args) == 1:
self.text(args[0], 1)
else:
for row, line in enumerate(args):
if row < 3:
self.text(line, row)
def set_touch(self, on):
self.send(TOUCH_MODE, on)
self.touch = on
def gettouch(self):
if not self.touch:
self.set_touch(1)
while True:
ch2 = self.term.read(2)
while ch2 != ESCESC:
if len(ch2) < 2:
return
ch2 = ch2[1:2] + self.term.read(1)
length = self.term.read(1)
if not length:
return
data = self.term.read(length[0])
if len(data) < length[0]:
return
if data[0] == TOUCH and len(data) == 3:
x = (data[1] % 2) * 256 + data[2]
return x
print('skipped', data)
def menu_reboot(self, xtouch):
if xtouch is None:
return
if xtouch < 120:
self.show('reboot ...')
os.system('reboot now')
else:
self.std_display()
def menu_shutdown(self, xtouch):
if xtouch is None:
return
if xtouch < 120:
self.show('shutdown ...')
os.system('shutdown now')
else:
self.std_display()
def menu_main(self, xtouch):
if xtouch is None:
return
if xtouch < 160:
self.std_display()
elif xtouch < 320:
self.menu = self.menu_reboot
self.show('reboot?', '( OK ) (cancel)')
else:
self.menu = self.menu_shutdown
self.show('shutdown?', '( OK ) (cancel)')
def std_display(self):
self.menu = self.net_display
self.net_display(None)
def net_display(self, xtouch):
if xtouch is None:
hostip = mainif.ip or 'no network'
dotpos = hostip.find('.')
if dotpos < 0:
dotpos = len(hostip)
self.text(hostip.replace('.', ' ', 1), 0, 0, 15)
self.send(SET_COLOR, 0, 0, 0, 11 + self.blink) # yellow / blue
self.text('.', 0, dotpos, dotpos+1)
self.color()
self.text(mainif.hostname() or self.hostname, 1)
# self.event.set()
self.blink = not self.blink
else:
self.menu = self.menu_main
self.show('(cancel)(reboot)(shdown)')
def refresh(self):
mainif.poll()
func = self.menu or self.net_display
func(self.gettouch())
def bootmode(self):
self.send(0xf0, 0xcb, 0xef, 0x20, 0x18)
def pretty_version(v):
return '%c v%d.%d%s' % (v[0], v[2], v[3], ' test' if v[1] else '')
daemon = False
firmware = None
for arg in sys.argv[1:]:
if arg == '-d':
daemon = True
elif arg.endswith('.bin'):
firmware = arg
elif tty.startswith('/dev/'):
tty = arg
else:
raise ValueError(f'do not know how to tread argument: {arg}')
d = Display(tty, daemon=daemon and not firmware)
if firmware:
with open(firmware, 'rb') as f:
tag = f.read()[-8:]
if tag[:4] != MAGIC:
raise ValueError(f'{firmware} is not a valid firmware file')
hwversion = d.version()
if tag[4:] == hwversion:
# print('firmware is already', pretty_version(hwversion))
print('display version:', pretty_version(hwversion))
print('binfile version:', pretty_version(tag[4:]))
result = input('flash this (takes 1 min)? ').lower()
if result in ('y', 'yes'):
print('\ndo NOT interrupt')
d.bootmode()
d.term.close()
time.sleep(1.)
os.system(f'../stm32flash-0.7/stm32flash -R -v -b 115200 -w {firmware} {tty}')
if daemon:
while True:
d.refresh()