Files
boxtools/display.py

280 lines
7.2 KiB
Python

import sys
import os
import time
import serial
import socket
import threading
# display tty device
tty = '/dev/ttyS1'
STARTUP_TEXT = '/root/aputools/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)]
TOPGAP = 8 # upper part of display is not useable
HEIGHT = 120
WIDTH = 480
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()
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
self.gethost(False)
self.reset()
if daemon:
threading.Thread(target=self.gethostthread, daemon=True).start()
self.event.wait(1) # wait for thread to be started
self.refresh()
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 gethost(self, truehostname):
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
s.connect(('8.8.8.8', 80)) # 8.8.8.8: google DNS
self.hostip = s.getsockname()[0]
except Exception as e:
self.hostip = ''
hostname = None
if self.hostip and truehostname:
try:
hostname = socket.gethostbyaddr(self.hostip)[0]
except Exception:
pass
self.hostname = hostname or socket.gethostname()
def gethostthread(self):
self.gethost(True)
self.event.set()
time.sleep(1)
while True:
self.event.clear()
self.gethost(True)
self.event.wait(1)
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]
print(x)
return x
print('skipped', data)
def menu_reboot(self, x):
if x is None:
return
if x < 120:
self.show('reboot ...')
os.system('reboot now')
else:
self.std_display()
def menu_shutdown(self, x):
if x is None:
return
if x < 120:
self.show('shutdown ...')
os.system('shutdown now')
else:
self.std_display()
def menu_main(self, x):
if x is None:
return
print(x)
if x < 160:
self.std_display()
elif x < 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, x):
if x is None:
hostip = self.hostip 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(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):
func = self.menu or self.net_display
func(self.gettouch())
def bootmode(self):
self.send(0xf0, 0xcb, 0xef, 0x20, 0x18)
daemon = False
for arg in sys.argv[1:]:
if arg == '-d':
daemon = True
else:
tty = arg
d = Display(tty, daemon=daemon)
if daemon:
while True:
d.refresh()