added first control box

- added python display daemon
- on control boxes, the uplink is the leftmost plug by default
This commit is contained in:
2024-01-10 15:14:40 +01:00
parent fdef0b69cf
commit 7f7993ef96
4 changed files with 309 additions and 23 deletions

234
display.py Normal file
View File

@ -0,0 +1,234 @@
import sys
import os
import time
import serial
import socket
import threading
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
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
thread = None
bar = None
blinkchar = ' '
menu = None
def __init__(self, dev='/dev/ttyS1', timeout=0.5):
self.event = threading.Event()
self.term = serial.Serial(dev, baudrate=115200, timeout=timeout)
self.gethost(False)
self.reset()
def reset(self):
self.send(MODE_GRAPH)
self.font(2)
self.send(CLEAR, self.bg)
def start(self):
if self.thread is None or not self.thread.is_alive():
self.thread = threading.Thread(target=self.gethostthread)
self.thread.daemon = True
self.thread.start()
self.event.wait(1) # wait for thread to be started
print('refresh')
self.refresh()
def color(self, fg=15, bg=0):
self.fg = fg
self.bg = bg
def send(self, code, *args):
out = [code]
for arg in args:
if isinstance(arg, bytes):
out.extend(list(arg))
elif isinstance(arg, str):
out.append(arg.encode('latin-1'))
else:
out.append(arg)
self.term.write(bytes([ESC,ESC,len(out)] + out))
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.send(SET_COLOR, self.bg, self.bg, self.fg, self.fg)
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 = ''
if self.hostip and truehostname:
self.hostname = socket.gethostbyaddr(self.hostip)[0]
else:
self.hostname = 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.'
self.text(hostip.replace('.', self.blinkchar, 1), 0, 0, 15)
self.text(self.hostname, 1)
self.event.set()
self.blinkchar = ' ' if self.blinkchar == '.' else '.'
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)
d = Display()
if len(sys.argv) == 1:
d.start()
while True:
d.refresh()

View File

@ -12,6 +12,7 @@ import sys
import os import os
import filecmp import filecmp
import shutil import shutil
import serial
from subprocess import Popen, PIPE from subprocess import Popen, PIPE
from glob import glob from glob import glob
from ipaddress import IPv4Interface from ipaddress import IPv4Interface
@ -26,17 +27,28 @@ CFGPATH = '/root/aputools/servercfg/%s_%6.6x.cfg'
COMMENT = "; please refer to README.md for help" COMMENT = "; please refer to README.md for help"
CONFIG_TEMPLATE = """[NETWORK] CONFIG_TEMPLATE = f"""[NETWORK]
%s {COMMENT}
enp1s0=192.168.127.254 enp1s0=192.168.127.254
enp2s0=192.168.2.2 enp2s0=192.168.2.2
enp3s0=192.168.3.3 enp3s0=192.168.3.3
enp4s0=dhcp enp4s0=dhcp
[ROUTER] [ROUTER]
%s {COMMENT}
3001=192.168.127.254:3001 3001=192.168.127.254:3001
""" % (COMMENT, COMMENT) """
CONTROLBOX_TEMPLATE = f"""[NETWORK]
{COMMENT}
enp1s0=dhcp
enp2s0=192.168.1.1
enp3s0=192.168.2.2
enp4s0=192.168.3.3
[DISPLAY]
{COMMENT}
"""
DHCP_HEADER = """ DHCP_HEADER = """
default-lease-time 600; default-lease-time 600;
@ -66,6 +78,19 @@ ExecStart = /usr/bin/python3 /home/l_samenv/frappy/bin/secop-server %s
[Install] [Install]
WantedBy = multi-user.target WantedBy = multi-user.target
""" """
DISPLAY_TEMPLATE = """[Unit]
Description = status display
After = network.target
[Service]
User = root
ExecStart = /usr/bin/python3 /root/aputools/display.py
[Install]
WantedBy = multi-user.target
"""
pip_requirements = { pip_requirements = {
'l_samenv': {}, 'l_samenv': {},
'root': {} 'root': {}
@ -95,6 +120,10 @@ def router(**opts):
return ROUTER_TEMPLATE return ROUTER_TEMPLATE
def display(**opts):
return DISPLAY_TEMPLATE
def pip(): def pip():
for user, requirements in pip_requirements.items(): for user, requirements in pip_requirements.items():
if user == 'root': if user == 'root':
@ -118,7 +147,7 @@ def pip():
show.dirty = True show.dirty = True
SERVICES = dict(router=router, frappy=frappy) SERVICES = dict(router=router, frappy=frappy, display=display)
def unix_cmd(command, always=False, stdout=PIPE): def unix_cmd(command, always=False, stdout=PIPE):
@ -308,36 +337,50 @@ class Do:
os.remove(join(self.syspath, file)) os.remove(join(self.syspath, file))
IFNAMES = ['enp%ds0' % i for i in range(1,5)] netaddr_dict = {}
for netif in os.scandir('/sys/class/net'):
if netif.name != 'lo': # do not consider loopback interface
with open(os.path.join(netif.path, 'address')) as f:
netaddr_dict[netif.name] = netaddr = f.read().strip().lower()
def handle_config(): def handle_config():
netaddr_dict = {}
for ifname in IFNAMES:
with open('/sys/class/net/%s/address' % ifname) as f:
netaddr_dict[ifname] = f.read().strip().lower()
netaddr = netaddr_dict[IFNAMES[0]]
apuid = int(''.join(netaddr.split(':')[-3:]), 16) & 0xfffffc
cfgfile = None cfgfile = None
cfgfiles = [] cfgfiles = []
for i in range(4): shortids = {}
# goodie: look for mac addresses of all 4 ports for netif, addr in netaddr_dict.items():
cfgfiles += glob(CFGPATH % ('*', apuid + i)) shortid = int(''.join(addr.split(':')[-3:]), 16) & 0xffffff
cfgfiles += glob(CFGPATH % ('*', shortid))
shortids[netif] = shortid
with open('/etc/hostname') as f: with open('/etc/hostname') as f:
hostname = f.read().strip() hostname = f.read().strip()
if not cfgfiles: if not cfgfiles:
print('no cfg file found for %s' % hostname) disp = serial.Serial('/dev/ttyS1', baudrate=115200, timeout=1)
disp.write(b'\x1b\x1b\x01\xf3')
display_available = disp.read(8)[0:4] == b'\x1b\x1b\x05\xf3'
ids = sorted(shortids)
if display_available:
# leftmost interface labelled 'eth0'
apuid = shortids[ids[0]]
typ = 'controlbox'
template = CONTROLBOX_TEMPLATE
else:
# rightmost interface
apuid = shortid[idx[-1]]
typ = 'bare apu'
template = CONFIG_TEMPLATE
print('no cfg file found for this', typ, f'with id {apuid:%6.6x} (hostname={hostname})')
newname = input('enter host name: ') newname = input('enter host name: ')
if not newname: if not newname:
print('no hostname given') print('no hostname given')
return False return False
cfgfile = CFGPATH % (newname, apuid) cfgfile = CFGPATH % (newname, apuid)
with open(cfgfile, 'w') as f: with open(cfgfile, 'w') as f:
f.write(CONFIG_TEMPLATE) f.write(template)
elif len(cfgfiles) > 1: elif len(cfgfiles) > 1:
print('ERROR: ambiguous cfg files: %s' % ', '.join(cfgfiles)) print('ERROR: ambiguous cfg files: %s' % ', '.join(cfgfiles))
else: else:
cfgfile = cfgfiles[0] cfgfile = cfgfiles[0]
apuid = int(cfgfile.split('_')[-1].split('.')[0], 16)
if cfgfile != CFGPATH % (hostname, apuid): if cfgfile != CFGPATH % (hostname, apuid):
if doit: if doit:
os.system('sh ../sethostname.sh') os.system('sh ../sethostname.sh')
@ -354,8 +397,8 @@ def handle_config():
try: try:
parser.read(cfgfile) parser.read(cfgfile)
network = dict(parser['NETWORK']) network = dict(parser['NETWORK'])
for ifname in IFNAMES: for ifname, addr in netaddr_dict.items():
content = create_if(ifname, network.pop(ifname, 'off'), netaddr_dict[ifname], dhcp_server_cfg) content = create_if(ifname, network.pop(ifname, 'off'), addr, dhcp_server_cfg)
content = '\n'.join('%s=%s' % kv for kv in content.items()) content = '\n'.join('%s=%s' % kv for kv in content.items())
todo = write_when_new('/etc/sysconfig/network-scripts/ifcfg-%s' % ifname, content) todo = write_when_new('/etc/sysconfig/network-scripts/ifcfg-%s' % ifname, content)
if todo and more_info == 'NONE': if todo and more_info == 'NONE':

View File

@ -0,0 +1,9 @@
[NETWORK]
; please refer to README.md for help
enp1s0=dhcp
enp2s0=192.168.1.1
enp3s0=192.168.2.2
enp4s0=192.168.3.3
[DISPLAY]
; please refer to README.md for help

View File

@ -1,11 +1,11 @@
echo "Welcome to $HOSTNAME $(hostname -I)" echo "Welcome to $HOSTNAME $(hostname -I)"
echo "$(cat /sys/class/net/enp4s0/address)" echo "$(cat /sys/class/net/enp1s0/address) .. $(cat /sys/class/net/enp4s0/address)"
export EDITOR=nano export EDITOR=nano
function service_status () { function service_status () {
for name in $@; do \ for name in $@; do \
echo ${name} $(systemctl is-active ${name}) $(systemctl is-enabled ${name}); \ echo ${name} $(systemctl is-active ${name}) $(systemctl is-enabled ${name} 2> /dev/null); \
done | column -t | grep --color=always '\(disabled\|inactive\|$\)' done | column -t | grep --color=always '\(disabled\|inactive\|$\)'
} }
alias current='cat /root/aputools/current' alias current='cat /root/aputools/current'
service_status router frappy service_status router frappy display
echo "> current # show currently installed state" echo "> current # show currently installed state"