Rebuilt the network view in Svelte

This commit is contained in:
David Carley
2022-07-04 16:19:23 -07:00
parent 10df5ada62
commit 9710d56779
52 changed files with 11186 additions and 867 deletions

View File

@@ -1,30 +1,3 @@
################################################################################
# #
# This file is part of the Buildbotics firmware. #
# #
# Copyright (c) 2015 - 2018, Buildbotics LLC #
# All rights reserved. #
# #
# This file ("the software") is free software: you can redistribute it #
# and/or modify it under the terms of the GNU General Public License, #
# version 2 as published by the Free Software Foundation. You should #
# have received a copy of the GNU General Public License, version 2 #
# along with the software. If not, see <http://www.gnu.org/licenses/>. #
# #
# The software is distributed in the hope that it will be useful, but #
# WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU #
# Lesser General Public License for more details. #
# #
# You should have received a copy of the GNU Lesser General Public #
# License along with the software. If not, see #
# <http://www.gnu.org/licenses/>. #
# #
# For information regarding this software email: #
# "Joseph Coffland" <joseph@buildbotics.com> #
# #
################################################################################
import os
import json
import tornado
@@ -36,50 +9,26 @@ from tornado.web import HTTPError
from tornado import gen
import bbctrl
import iw_parse
def call_get_output(cmd):
p = subprocess.Popen(cmd, stdout = subprocess.PIPE)
p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
s = p.communicate()[0].decode('utf-8')
if p.returncode: raise HTTPError(400, 'Command failed')
if p.returncode:
raise HTTPError(400, 'Command failed')
return s
def get_username():
return call_get_output(['getent', 'passwd', '1001']).split(':')[0]
def set_username(username):
if subprocess.call(['usermod', '-l', username, get_username()]):
raise HTTPError(400, 'Failed to set username to "%s"' % username)
def check_password(password):
# Get current password
s = call_get_output(['getent', 'shadow', get_username()])
current = s.split(':')[1].split('$')
# Check password type
if len(current) < 2 or current[1] != '1':
raise HTTPError(401, "Password invalid")
# Check current password
cmd = ['openssl', 'passwd', '-salt', current[2], '-1', password]
s = call_get_output(cmd).strip()
if s.split('$') != current: raise HTTPError(401, 'Wrong password')
class RebootHandler(bbctrl.APIHandler):
def put_ok(self):
self.get_ctrl().lcd.goodbye('Rebooting...')
subprocess.Popen('reboot')
class ShutdownHandler(bbctrl.APIHandler):
def put_ok(self):
subprocess.Popen(['shutdown','-h','now'])
subprocess.Popen(['shutdown', '-h', 'now'])
class LogHandler(bbctrl.RequestHandler):
@@ -87,7 +36,6 @@ class LogHandler(bbctrl.RequestHandler):
with open(self.get_ctrl().log.get_path(), 'r') as f:
self.write(f.read())
def set_default_headers(self):
fmt = socket.gethostname() + '-%Y%m%d.log'
filename = datetime.date.today().strftime(fmt)
@@ -102,14 +50,16 @@ class MessageAckHandler(bbctrl.APIHandler):
class BugReportHandler(bbctrl.RequestHandler):
def get(self):
import tarfile, io
import tarfile
import io
buf = io.BytesIO()
tar = tarfile.open(mode = 'w:bz2', fileobj = buf)
tar = tarfile.open(mode='w:bz2', fileobj=buf)
def check_add(path, arcname = None):
def check_add(path, arcname=None):
if os.path.isfile(path):
if arcname is None: arcname = path
if arcname is None:
arcname = path
tar.add(path, self.basename + '/' + arcname)
def check_add_basename(path):
@@ -128,7 +78,6 @@ class BugReportHandler(bbctrl.RequestHandler):
self.write(buf.getvalue())
def set_default_headers(self):
fmt = socket.gethostname() + '-%Y%m%d-%H%M%S'
self.basename = datetime.datetime.now().strftime(fmt)
@@ -138,8 +87,6 @@ class BugReportHandler(bbctrl.RequestHandler):
class HostnameHandler(bbctrl.APIHandler):
def get(self): self.write_json(socket.gethostname())
def put(self):
if self.get_ctrl().args.demo:
raise HTTPError(400, 'Cannot set hostname in demo mode')
@@ -153,77 +100,59 @@ class HostnameHandler(bbctrl.APIHandler):
raise HTTPError(400, 'Failed to set hostname')
class WifiHandler(bbctrl.APIHandler):
class NetworkHandler(bbctrl.APIHandler):
def get(self):
data = {'ssid': '', 'channel': 0}
try:
data = json.loads(call_get_output(['config-wifi', '-j']))
except: pass
self.write_json(data)
ipAddresses = call_get_output(['hostname', '-I']).split()
except:
ipAddresses = ""
hostname = socket.gethostname()
try:
wifi = json.loads(call_get_output(['config-wifi', '-j']))
except:
wifi = {'enabled': False}
try:
lines = iw_parse.call_iwlist().decode("utf-8").split("\n")
wifi['networks'] = iw_parse.get_parsed_cells(lines)
except:
wifi['networks'] = []
self.write_json({
'ipAddresses': ipAddresses,
'hostname': hostname,
'wifi': wifi
})
def put(self):
if self.get_ctrl().args.demo:
raise HTTPError(400, 'Cannot configure WiFi in demo mode')
if 'mode' in self.json:
cmd = ['config-wifi', '-r']
mode = self.json['mode']
if not 'wifi' in self.json:
raise HTTPError(400, 'Payload is missing wifi config information')
if mode == 'disabled': cmd += ['-d']
elif 'ssid' in self.json:
cmd += ['-s', self.json['ssid']]
wifi = self.json['wifi']
if mode == 'ap':
cmd += ['-a']
if 'channel' in self.json:
cmd += ['-c', self.json['channel']]
cmd = ['config-wifi', '-r']
if 'pass' in self.json:
cmd += ['-p', self.json['pass']]
if not wifi['enabled']:
cmd += ['-d']
else:
if 'ssid' in wifi:
cmd += ['-s', wifi['ssid']]
if subprocess.call(cmd) == 0:
self.write_json('ok')
return
if 'password' in wifi:
cmd += ['-p', wifi['password']]
if subprocess.call(cmd) == 0:
self.write_json('ok')
return
raise HTTPError(400, 'Failed to configure wifi')
class UsernameHandler(bbctrl.APIHandler):
def get(self): self.write_json(get_username())
def put_ok(self):
if self.get_ctrl().args.demo:
raise HTTPError(400, 'Cannot set username in demo mode')
if 'username' in self.json: set_username(self.json['username'])
else: raise HTTPError(400, 'Missing "username"')
class PasswordHandler(bbctrl.APIHandler):
def put(self):
if self.get_ctrl().args.demo:
raise HTTPError(400, 'Cannot set password in demo mode')
if 'current' in self.json and 'password' in self.json:
check_password(self.json['current'])
# Set password
s = '%s:%s' % (get_username(), self.json['password'])
s = s.encode('utf-8')
p = subprocess.Popen(['chpasswd', '-c', 'MD5'],
stdin = subprocess.PIPE)
p.communicate(input = s)
if p.returncode == 0:
self.write_json('ok')
return
raise HTTPError(401, 'Failed to set password')
class ConfigLoadHandler(bbctrl.APIHandler):
def get(self):
self.write_json(self.get_ctrl().config.load())
@@ -238,7 +167,7 @@ class ConfigDownloadHandler(bbctrl.APIHandler):
'attachment; filename="%s"' % filename)
def get(self):
self.write_json(self.get_ctrl().config.load(), pretty = True)
self.write_json(self.get_ctrl().config.load(), pretty=True)
class ConfigSaveHandler(bbctrl.APIHandler):
@@ -252,14 +181,14 @@ class ConfigResetHandler(bbctrl.APIHandler):
class FirmwareUpdateHandler(bbctrl.APIHandler):
def prepare(self): pass
def put_ok(self):
if not 'firmware' in self.request.files:
raise HTTPError(401, 'Missing "firmware"')
firmware = self.request.files['firmware'][0]
if not os.path.exists('firmware'): os.mkdir('firmware')
if not os.path.exists('firmware'):
os.mkdir('firmware')
with open('firmware/update.tar.bz2', 'wb') as f:
f.write(firmware['body'])
@@ -284,20 +213,23 @@ class PathHandler(bbctrl.APIHandler):
future = preplanner.get_plan(filename)
try:
delta = datetime.timedelta(seconds = 1)
delta = datetime.timedelta(seconds=1)
data = yield gen.with_timeout(delta, future)
except gen.TimeoutError:
progress = preplanner.get_plan_progress(filename)
self.write_json(dict(progress = progress))
self.write_json(dict(progress=progress))
return
try:
if data is None: return
if data is None:
return
meta, positions, speeds = data
if dataType == '/positions': data = positions
elif dataType == '/speeds': data = speeds
if dataType == '/positions':
data = positions
elif dataType == '/speeds':
data = speeds
else:
self.get_ctrl().state.set_bounds(meta['bounds'])
self.write_json(meta)
@@ -316,12 +248,14 @@ class PathHandler(bbctrl.APIHandler):
self.write(chunk)
yield self.flush()
except tornado.iostream.StreamClosedError as e: pass
except tornado.iostream.StreamClosedError as e:
pass
class HomeHandler(bbctrl.APIHandler):
def put_ok(self, axis, action, *args):
if axis is not None: axis = ord(axis[1:2].lower())
if axis is not None:
axis = ord(axis[1:2].lower())
if action == '/set':
if not 'position' in self.json:
@@ -329,8 +263,10 @@ class HomeHandler(bbctrl.APIHandler):
self.get_ctrl().mach.home(axis, self.json['position'])
elif action == '/clear': self.get_ctrl().mach.unhome(axis)
else: self.get_ctrl().mach.home(axis)
elif action == '/clear':
self.get_ctrl().mach.unhome(axis)
else:
self.get_ctrl().mach.home(axis)
class StartHandler(bbctrl.APIHandler):
@@ -386,7 +322,7 @@ class ModbusReadHandler(bbctrl.APIHandler):
class ModbusWriteHandler(bbctrl.APIHandler):
def put_ok(self):
self.get_ctrl().mach.modbus_write(int(self.json['address']),
int(self.json['value']))
int(self.json['value']))
class JogHandler(bbctrl.APIHandler):
@@ -402,7 +338,8 @@ class JogHandler(bbctrl.APIHandler):
last = self.app.last_jog.get(id, 0)
self.app.last_jog[id] = ts
if ts < last: return # Out of order
if ts < last:
return # Out of order
self.get_ctrl().mach.jog(self.json)
@@ -413,17 +350,14 @@ class ClientConnection(object):
self.app = app
self.count = 0
def heartbeat(self):
self.timer = self.app.ioloop.call_later(3, self.heartbeat)
self.send({'heartbeat': self.count})
self.count += 1
def send(self, msg): raise HTTPError(400, 'Not implemented')
def on_open(self, id = None):
def on_open(self, id=None):
self.ctrl = self.app.get_ctrl(id)
self.ctrl.state.add_listener(self.send)
@@ -432,7 +366,6 @@ class ClientConnection(object):
self.heartbeat()
self.app.opened(self.ctrl)
def on_close(self):
self.app.ioloop.remove_timeout(self.timer)
self.ctrl.state.remove_listener(self.send)
@@ -440,7 +373,6 @@ class ClientConnection(object):
self.is_open = False
self.app.closed(self.ctrl)
def on_message(self, data):
self.ctrl.mach.mdi(data)
@@ -465,23 +397,24 @@ class SockJSConnection(ClientConnection, sockjs.tornado.SockJSConnection):
ClientConnection.__init__(self, session.server.app)
sockjs.tornado.SockJSConnection.__init__(self, session)
def send(self, msg):
try:
sockjs.tornado.SockJSConnection.send(self, msg)
except:
self.close()
def on_open(self, info):
cookie = info.get_cookie('client-id')
if cookie is None: self.send(dict(sid = '')) # Trigger client reset
if cookie is None:
self.send(dict(sid='')) # Trigger client reset
else:
id = cookie.value
ip = info.ip
if 'X-Real-IP' in info.headers: ip = info.headers['X-Real-IP']
self.app.get_ctrl(id).log.get('Web').info('Connection from %s' % ip)
if 'X-Real-IP' in info.headers:
ip = info.headers['X-Real-IP']
self.app.get_ctrl(id).log.get(
'Web').info('Connection from %s' % ip)
super().on_open(id)
@@ -499,10 +432,13 @@ class Web(tornado.web.Application):
# Init camera
if not args.disable_camera:
if self.args.demo: log = bbctrl.log.Log(args, ioloop, 'camera.log')
else: log = self.get_ctrl().log
if self.args.demo:
log = bbctrl.log.Log(args, ioloop, 'camera.log')
else:
log = self.get_ctrl().log
self.camera = bbctrl.Camera(ioloop, args, log)
else: self.camera = None
else:
self.camera = None
# Init controller
if not self.args.demo:
@@ -517,9 +453,7 @@ class Web(tornado.web.Application):
(r'/api/reboot', RebootHandler),
(r'/api/shutdown', ShutdownHandler),
(r'/api/hostname', HostnameHandler),
(r'/api/wifi', WifiHandler),
(r'/api/remote/username', UsernameHandler),
(r'/api/remote/password', PasswordHandler),
(r'/api/network', NetworkHandler),
(r'/api/config/load', ConfigLoadHandler),
(r'/api/config/download', ConfigDownloadHandler),
(r'/api/config/save', ConfigSaveHandler),
@@ -547,7 +481,7 @@ class Web(tornado.web.Application):
(r'/(.*)', StaticFileHandler,
{'path': bbctrl.get_resource('http/'),
'default_filename': 'index.html'}),
]
]
router = sockjs.tornado.SockJSRouter(SockJSConnection, '/sockjs')
router.app = self
@@ -555,7 +489,7 @@ class Web(tornado.web.Application):
tornado.web.Application.__init__(self, router.urls + handlers)
try:
self.listen(args.port, address = args.addr)
self.listen(args.port, address=args.addr)
except Exception as e:
raise Exception('Failed to bind %s:%d: %s' % (
@@ -563,33 +497,32 @@ class Web(tornado.web.Application):
print('Listening on http://%s:%d/' % (args.addr, args.port))
def opened(self, ctrl): ctrl.clear_timeout()
def closed(self, ctrl):
# Time out clients in demo mode
if self.args.demo: ctrl.set_timeout(self._reap_ctrl, ctrl)
if self.args.demo:
ctrl.set_timeout(self._reap_ctrl, ctrl)
def _reap_ctrl(self, ctrl):
ctrl.close()
del self.ctrls[ctrl.id]
def get_ctrl(self, id = None):
if not id or not self.args.demo: id = ''
def get_ctrl(self, id=None):
if not id or not self.args.demo:
id = ''
if not id in self.ctrls:
ctrl = bbctrl.Ctrl(self.args, self.ioloop, id)
self.ctrls[id] = ctrl
else: ctrl = self.ctrls[id]
else:
ctrl = self.ctrls[id]
return ctrl
# Override default logger
def log_request(self, handler):
log = self.get_ctrl(handler.get_cookie('client-id')).log.get('Web')
log.info("%d %s", handler.get_status(), handler._request_summary())