added first control box
- added python display daemon - on control boxes, the uplink is the leftmost plug by default
This commit is contained in:
234
display.py
Normal file
234
display.py
Normal 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()
|
||||||
|
|
83
install.py
83
install.py
@ -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':
|
||||||
|
9
servercfg/linse-box1_5b265c.cfg
Normal file
9
servercfg/linse-box1_5b265c.cfg
Normal 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
|
@ -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"
|
||||||
|
Reference in New Issue
Block a user