293 lines
7.8 KiB
Python
293 lines
7.8 KiB
Python
import sys
|
|
import os
|
|
import time
|
|
import serial
|
|
import socket
|
|
import threading
|
|
import re
|
|
import queue
|
|
from utils import MainIf
|
|
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'
|
|
|
|
|
|
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)
|
|
if reply[0:2] != ESCESC:
|
|
print('make sure the display is not in test mode - touch through it first!')
|
|
raise ValueError(f'reply is not starting with ESCESC: {reply!r}')
|
|
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 treat argument: {arg}')
|
|
|
|
|
|
if not firmware:
|
|
mainif = MainIf()
|
|
|
|
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('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()
|
|
|