Merge pull request #71 from dacarley/5

6 - Improved uploading, date/time/timezone setting, etc.
This commit is contained in:
David A. Carley
2022-08-23 15:33:31 -07:00
committed by GitHub
81 changed files with 2931 additions and 3657 deletions

View File

@@ -4,7 +4,7 @@ module.exports = {
attached: function () {
this.svelteComponent = SvelteComponents.createComponent(
"AdminNetworkView",
document.getElementById("svelte-root")
document.getElementById("admin-network")
);
},

View File

@@ -4,7 +4,6 @@ const api = require("./api");
const cookie = require("./cookie")("bbctrl-");
const Sock = require("./sock");
SvelteComponents.initNetworkInfo();
SvelteComponents.createComponent("DialogHost",
document.getElementById("svelte-dialog-host")
);
@@ -136,6 +135,7 @@ module.exports = new Vue({
watch: {
display_units: function (value) {
localStorage.setItem("display_units", value);
SvelteComponents.setDisplayUnits(value);
},
},
@@ -214,6 +214,10 @@ module.exports = new Vue({
ready: function () {
$(window).on("hashchange", this.parse_hash);
this.connect();
SvelteComponents.registerControllerMethods({
dispatch: (...args) => this.$dispatch(...args)
});
},
methods: {
@@ -289,15 +293,6 @@ module.exports = new Vue({
update_object(this.config, config, true);
this.parse_hash();
if (!this.devModChecked) {
this.devModChecked = true;
if (this.config.devmode) {
SvelteComponents.createComponent("Devmode",
document.getElementById("svelte-devmode-host")
);
}
}
if (!this.checkedUpgrade) {
this.checkedUpgrade = true;

View File

@@ -9,6 +9,7 @@ module.exports = {
data: function () {
return {
current_time: "",
mach_units: this.$root.state.metric ? "METRIC" : "IMPERIAL",
mdi: '',
last_file: undefined,
@@ -19,22 +20,6 @@ module.exports = {
history: [],
speed_override: 1,
feed_override: 1,
manual_home: {
x: false,
y: false,
z: false,
a: false,
b: false,
c: false
},
position_msg: {
x: false,
y: false,
z: false,
a: false,
b: false,
c: false
},
jog_incr_amounts: {
"METRIC": {
fine: 0.1,
@@ -49,7 +34,6 @@ module.exports = {
large: 5,
}
},
axis_position: 0,
jog_incr: localStorage.getItem("jog_incr") || 'small',
jog_step: cookie.get_bool('jog-step'),
jog_adjust: parseInt(cookie.get('jog-adjust', 2)),
@@ -253,10 +237,18 @@ module.exports = {
ready: function () {
this.load();
setInterval(() => {
this.current_time = new Date().toLocaleTimeString();
}, 1000);
SvelteComponents.registerControllerMethods({
stop: (...args) => this.stop(...args),
send: (...args) => this.send(...args),
goto_zero: (...args) => this.goto_zero(...args)
goto_zero: (...args) => this.goto_zero(...args),
isAxisHomed: (axis) => this[axis].homed,
unhome: (...args) => this.unhome(...args),
set_position: (...args) => this.set_position(...args),
set_home: (...args) => this.set_home(...args)
});
},
@@ -398,18 +390,13 @@ module.exports = {
return;
}
const fd = new FormData();
fd.append('gcode', file);
try {
await api.upload('file', fd);
this.last_file_time = undefined; // Force reload
this.$broadcast('gcode-reload', file.name);
} catch (err) {
api.alert('Upload failed', err)
}
SvelteComponents.showDialog("Upload", {
file,
onComplete: () => {
this.last_file_time = undefined; // Force reload
this.$broadcast('gcode-reload', file.name);
}
});
},
delete_current: function () {
@@ -433,23 +420,20 @@ module.exports = {
} else if (this[axis].homingMode != 'manual') {
api.put('home/' + axis);
} else {
this.manual_home[axis] = true;
SvelteComponents.showDialog("ManualHomeAxis", { axis });
}
},
set_home: function (axis, position) {
this.manual_home[axis] = false;
api.put('home/' + axis + '/set', { position: parseFloat(position) });
},
unhome: function (axis) {
this.position_msg[axis] = false;
api.put('home/' + axis + '/clear');
},
show_set_position: function (axis) {
this.axis_position = 0;
this.position_msg[axis] = true;
SvelteComponents.showDialog("SetAxisPosition", { axis });
},
show_toolpath_msg: function (axis) {
@@ -457,7 +441,6 @@ module.exports = {
},
set_position: function (axis, position) {
this.position_msg[axis] = false;
api.put('position/' + axis, { 'position': parseFloat(position) });
},
@@ -530,7 +513,7 @@ module.exports = {
this.send(JSON.stringify(data));
},
showProbeDialog: function(probeType) {
showProbeDialog: function (probeType) {
SvelteComponents.showDialog("Probe", { probeType });
}
},

View File

@@ -1,31 +1,14 @@
'use strict'
module.exports = {
template: '#settings-view-template',
props: ['config', 'template'],
template: "#settings-view-template",
computed: {
display_units: {
cache: false,
get: function () {
return this.$root.display_units;
},
set: function (value) {
this.$root.display_units = value;
}
},
attached: function () {
this.svelteComponent = SvelteComponents.createComponent(
"SettingsView",
document.getElementById("settings")
);
},
events: {
'input-changed': function () {
this.$dispatch('config-changed');
return false;
}
},
methods: {
showScreenRotationDialog: function () {
SvelteComponents.showDialog("ScreenRotation");
}
detached: function() {
this.svelteComponent.$destroy();
}
}
};

View File

@@ -19,7 +19,6 @@ html(lang="en")
body(v-cloak)
#svelte-dialog-host
#svelte-devmode-host
#overlay(v-if="status != 'connected'")
span {{status}}

View File

@@ -1,3 +1,2 @@
script#admin-network-view-template(type="text/x-template")
#admin-network
#svelte-root
#admin-network

View File

@@ -150,60 +150,18 @@ script#control-view-template(type="text/x-template")
th.actions
button.pure-button(:disabled="!can_set_axis",
title=`Set {{'${axis}' | upper}} axis position.`,
@click=`show_set_position('${axis}')`,style="height:60px;width:60px")
@click=`show_set_position('${axis}')`, style="height:60px;width:60px")
.fa.fa-cog
button.pure-button(:disabled="!can_set_axis",
title=`Zero {{'${axis}' | upper}} axis offset.`,
@click=`zero('${axis}')`,style="height:60px;width:60px")
@click=`zero('${axis}')`, style="height:60px;width:60px")
.fa.fa-map-marker
button.pure-button(:disabled="!is_idle", @click=`home('${axis}')`,
title=`Home {{'${axis}' | upper}} axis.`,style="height:60px;width:60px")
title=`Home {{'${axis}' | upper}} axis.`, style="height:60px;width:60px")
.fa.fa-home
message(:show.sync=`position_msg['${axis}']`)
h3(slot="header") Set {{'#{axis}' | upper}} axis position
div(slot="body")
.pure-form
.pure-control-group
label Position
input(v-model="axis_position",
@keyup.enter=`set_position('${axis}', axis_position)`)
p
div(slot="footer")
button.pure-button(@click=`position_msg['${axis}'] = false`)
| Cancel
button.pure-button(v-if=`${axis}.homed`,
@click=`unhome('${axis}')`) Unhome
button.pure-button.button-success(
@click=`set_position('${axis}', axis_position)`) Set
message(:show.sync=`manual_home['${axis}']`)
h3(slot="header") Manually home {{'#{axis}' | upper}} axis
div(slot="body")
p Set axis absolute position.
.pure-form
.pure-control-group
label Absolute
input(v-model="axis_position",
@keyup.enter=`set_home('${axis}', axis_position)`)
p
div(slot="footer")
button.pure-button(@click=`manual_home['${axis}'] = false`)
| Cancel
button.pure-button.button-success(
title=`Home {{'${axis}' | upper}} axis.`,
@click=`set_home('${axis}', axis_position)`) Set
tr(style="vertical-align: top;")
td
@@ -265,6 +223,11 @@ script#control-view-template(type="text/x-template")
td
table.info
tr
th Current Time
td
span {{current_time}}
tr
th Remaining
td(title="Total run time (days:hours:mins:secs)").
@@ -273,12 +236,7 @@ script#control-view-template(type="text/x-template")
tr
th ETA
td.eta {{eta}}
tr
th Line
td
| {{0 <= state.line ? state.line : 0 | number}}
span(v-if="toolpath.lines")
| &nbsp;of {{toolpath.lines | number}}
tr
th Progress
td.progress

View File

@@ -1,53 +1,2 @@
script#settings-view-template(type="text/x-template")
#settings
h1 Settings
.pure-form.pure-form-aligned
fieldset
h2 Screen
.pure-control-group
label(for="screen-rotation")
button.pure-button(name="screen-rotation", @click="showScreenRotationDialog") Change Screen Rotation
fieldset
h2 Probe Dimensions
templated-input(v-for="templ in template.probe", v-if="$key !== 'probe-diameter'", :name="$key"
:model.sync="config.probe[$key]", :template="templ")
fieldset
h2 GCode
templated-input(v-for="templ in template.gcode", :name="$key",
:model.sync="config.gcode[$key]", :template="templ")
fieldset
h2 Path Accuracy
templated-input(name="max-deviation",
:model.sync="config.settings['max-deviation']",
:template="template.settings['max-deviation']")
p.
Lower #[tt max-deviation] to follow the programmed path more precisely
but at a slower speed.
p.
In order to improve traversal speed, the path planner may merge
consecutive moves or round off sharp corners if doing so would deviate
from the program path by less than #[tt max-deviation].
- var base = '//linuxcnc.org/docs/html/gcode/g-code.html'
p.
GCode commands
#[a(href=base + "#gcode:g61", target="_blank") G61, G61.1] and
#[a(href=base + "#gcode:g64", target="_blank") G64] also affect path
planning accuracy.
h2 Cornering Speed (Advanced)
templated-input(name="junction-accel",
:model.sync="config.settings['junction-accel']",
:template="template.settings['junction-accel']")
p.
Junction acceleration limits the cornering speed the planner will
allow. Increasing this value will allow for faster traversal of
corners but may cause the planner to violate axis jerk limits and
stall the motors. Use with caution.
#settings

View File

@@ -1,207 +0,0 @@
################################################################################
# #
# 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 sys
import traceback
import signal
import bbctrl
import bbctrl.Cmd as Cmd
class AVREmu(object):
def __init__(self, ctrl):
self.ctrl = ctrl
self.log = ctrl.log.get('AVREmu')
self.avrOut = None
self.avrIn = None
self.i2cOut = None
self.read_cb = None
self.write_cb = None
self.pid = None
def close(self):
# Close pipes
def _close(fd, withHandle):
if fd is None: return
try:
if withHandle: self.ctrl.ioloop.remove_handler(fd)
except: pass
try:
os.close(fd)
except: pass
_close(self.avrOut, True)
_close(self.avrIn, True)
_close(self.i2cOut, False)
self.avrOut, self.avrIn, self.i2cOut = None, None, None
# Kill process and wait for it
if self.pid is not None:
os.kill(self.pid, signal.SIGKILL)
os.waitpid(self.pid, 0)
self.pid = None
def _start(self):
try:
self.close()
# Create pipes
stdinFDs = os.pipe()
stdoutFDs = os.pipe()
i2cFDs = os.pipe()
self.pid = os.fork()
if not self.pid:
# Dup child ends
os.dup2(stdinFDs[0], 0)
os.dup2(stdoutFDs[1], 1)
os.dup2(i2cFDs[0], 3)
# Close orig fds
os.close(stdinFDs[0])
os.close(stdoutFDs[1])
os.close(i2cFDs[0])
# Close parent ends
os.close(stdinFDs[1])
os.close(stdoutFDs[0])
os.close(i2cFDs[1])
cmd = ['bbemu']
if self.ctrl.args.fast_emu: cmd.append('--fast')
os.execvp(cmd[0], cmd)
os._exit(1) # In case of failure
# Parent, close child ends
os.close(stdinFDs[0])
os.close(stdoutFDs[1])
os.close(i2cFDs[0])
# Non-blocking IO
os.set_blocking(stdinFDs[1], False)
os.set_blocking(stdoutFDs[0], False)
os.set_blocking(i2cFDs[1], False)
self.avrOut = stdinFDs[1]
self.avrIn = stdoutFDs[0]
self.i2cOut = i2cFDs[1]
ioloop = self.ctrl.ioloop
ioloop.add_handler(self.avrOut, self._avr_write_handler,
ioloop.WRITE | ioloop.ERROR)
ioloop.add_handler(self.avrIn, self._avr_read_handler,
ioloop.READ | ioloop.ERROR)
self.write_enabled = True
except Exception:
self.close()
self.log.exception('Internal error: Failed to start bbemu')
def set_handlers(self, read_cb, write_cb):
if self.read_cb is not None or self.write_cb is not None:
raise Exception('AVR handler already set')
self.read_cb = read_cb
self.write_cb = write_cb
self._start()
def enable_write(self, enable):
if self.avrOut is None: return
flags = self.ctrl.ioloop.WRITE if enable else 0
self.ctrl.ioloop.update_handler(self.avrOut, flags)
self.write_enabled = enable
def _avr_write(self, data):
try:
length = os.write(self.avrOut, data)
self.continue_write = length and length == len(data)
return length
except BlockingIOError: pass
except BrokenPipeError: pass
return 0
def _avr_write_handler(self, fd, events):
if self.avrOut is None: return
if events & self.ctrl.ioloop.ERROR:
self._start()
return
try:
while True:
self.continue_write = False
self.write_cb(self._avr_write)
if not self.continue_write: break
except Exception as e:
self.log.warning('AVR write handler error: %s',
traceback.format_exc())
def _avr_read_handler(self, fd, events):
if self.avrIn is None: return
if events & self.ctrl.ioloop.ERROR:
self._start()
return
try:
data = os.read(self.avrIn, 4096)
if data is not None: self.read_cb(data)
except Exception as e:
self.log.warning('AVR read handler error: %s %s' %
(data, traceback.format_exc()))
def i2c_command(self, cmd, byte = None, word = None, block = None):
if byte is not None: data = chr(byte)
elif word is not None: data = word
elif block is not None: data = block
else: data = ''
try:
if self.i2cOut is not None:
os.write(self.i2cOut, bytes(cmd + data + '\n', 'utf-8'))
except BrokenPipeError: pass

View File

@@ -25,10 +25,7 @@ class Ctrl(object):
self.log.get('Ctrl').info('Starting %s' % self.id)
try:
if args.demo:
self.avr = bbctrl.AVREmu(self)
else:
self.avr = bbctrl.AVR(self)
self.avr = bbctrl.AVR(self)
self.i2c = bbctrl.I2C(args.i2c_port, args.demo)
self.mach = bbctrl.Mach(self, self.avr)

View File

@@ -1,34 +1,8 @@
################################################################################
# #
# 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 tempfile
import bbctrl
import glob
import html
import tornado
from tornado import gen
from tornado.web import HTTPError
@@ -36,17 +10,32 @@ from tornado.web import HTTPError
def safe_remove(path):
try:
os.unlink(path)
except OSError: pass
except OSError:
pass
@tornado.web.stream_request_body
class FileHandler(bbctrl.APIHandler):
def prepare(self): pass
def prepare(self):
if self.request.method == 'PUT':
self.request.connection.set_max_body_size(2 ** 30)
self.uploadFilename = self.request.path.split('/')[-1] \
.replace('\\', '/') \
.replace('#', '-') \
.replace('?', '-')
self.uploadFile = tempfile.NamedTemporaryFile("wb")
def data_received(self, data):
if self.request.method == 'PUT':
self.uploadFile.write(data)
def delete_ok(self, filename):
if not filename:
# Delete everything
for path in glob.glob(self.get_upload('*')): safe_remove(path)
for path in glob.glob(self.get_upload('*')):
safe_remove(path)
self.get_ctrl().preplanner.delete_all_plans()
self.get_ctrl().state.clear_files()
@@ -57,26 +46,29 @@ class FileHandler(bbctrl.APIHandler):
self.get_ctrl().preplanner.delete_plans(filename)
self.get_ctrl().state.remove_file(filename)
def put_ok(self, *args):
gcode = self.request.files['gcode'][0]
filename = os.path.basename(gcode['filename'].replace('\\', '/'))
filename = filename.replace('#', '-').replace('?', '-')
if not os.path.exists(self.get_upload()):
os.mkdir(self.get_upload())
if not os.path.exists(self.get_upload()): os.mkdir(self.get_upload())
filename = self.get_upload(self.uploadFilename).encode('utf8')
safe_remove(filename)
os.link(self.uploadFile.name, filename)
with open(self.get_upload(filename).encode('utf8'), 'wb') as f:
f.write(gcode['body'])
os.sync()
self.uploadFile.close()
self.get_ctrl().preplanner.invalidate(filename)
self.get_ctrl().state.add_file(filename)
self.get_log('FileHandler').info('GCode received: ' + filename)
del(self.uploadFile)
self.get_ctrl().preplanner.invalidate(self.uploadFilename)
self.get_ctrl().state.add_file(self.uploadFilename)
self.get_log('FileHandler').info(
'GCode received: ' + self.uploadFilename)
del(self.uploadFilename)
@gen.coroutine
def get(self, filename):
if not filename: raise HTTPError(400, 'Missing filename')
if not filename:
raise HTTPError(400, 'Missing filename')
filename = os.path.basename(filename)
try:
@@ -84,6 +76,7 @@ class FileHandler(bbctrl.APIHandler):
self.write(f.read())
except Exception:
self.get_ctrl().state.select_file('')
raise HTTPError(400, "Unable to read file - doesn't appear to be GCode.")
raise HTTPError(
400, "Unable to read file - doesn't appear to be GCode.")
self.get_ctrl().state.select_file(filename)

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,24 @@
import traceback
import copy
import json
import uuid
import os
import socket
import bbctrl
import iw_parse
from tornado import gen
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
def call_get_output(cmd):
p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
s = p.communicate()[0].decode('utf-8')
if p.returncode:
raise HTTPError(400, 'Command failed')
return s
class UploadChangeHandler(FileSystemEventHandler):
def __init__(self, state):
self.state = state
@@ -64,6 +76,36 @@ class State(object):
self), self.ctrl.get_upload(), recursive=True)
observer.start()
self._updateNetworkInfo()
@gen.coroutine
def _updateNetworkInfo(self):
try:
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.set('networkInfo', {
'ipAddresses': ipAddresses,
'hostname': hostname,
'wifi': wifi
})
self.timeout = self.ctrl.ioloop.call_later(5, self._updateNetworkInfo)
def reset(self):
# Unhome all motors
for i in range(4):
@@ -81,8 +123,6 @@ class State(object):
if not os.path.exists(upload):
os.mkdir(upload)
from shutil import copy
copy(bbctrl.get_resource('http/buildbotics.nc'), upload)
for path in os.listdir(upload):
if os.path.isfile(upload + '/' + path):
@@ -170,8 +210,11 @@ class State(object):
return name
def has(self, name): return self.resolve(name) in self.vars
def set_callback(self, name, cb): self.callbacks[self.resolve(name)] = cb
def has(self, name):
return self.resolve(name) in self.vars
def set_callback(self, name, cb):
self.callbacks[self.resolve(name)] = cb
def set(self, name, value):
name = self.resolve(name)
@@ -233,7 +276,8 @@ class State(object):
self.listeners.append(listener)
listener(self.vars)
def remove_listener(self, listener): self.listeners.remove(listener)
def remove_listener(self, listener):
self.listeners.remove(listener)
def set_machine_vars(self, vars):
# Record all machine vars, indexed or otherwise
@@ -284,7 +328,8 @@ class State(object):
if motor_axis == axis.lower() and self.vars.get('%dme' % motor, 0):
return motor
def is_axis_homed(self, axis): return self.get('%s_homed' % axis, False)
def is_axis_homed(self, axis):
return self.get('%s_homed' % axis, False)
def is_axis_enabled(self, axis):
motor = self.find_motor(axis)

View File

@@ -1,6 +1,5 @@
import os
import re
import json
import tornado
import sockjs.tornado
import datetime
@@ -10,7 +9,6 @@ from tornado.web import HTTPError
from tornado import gen
import bbctrl
import iw_parse
def call_get_output(cmd):
@@ -23,11 +21,17 @@ def call_get_output(cmd):
class RebootHandler(bbctrl.APIHandler):
def put_ok(self):
subprocess.Popen('reboot')
subprocess.Popen(['plymouth', 'show-splash'])
subprocess.Popen(['plymouth', 'change-mode', '--shutdown'])
subprocess.Popen(['killall', 'xinit'])
subprocess.Popen(['reboot'])
class ShutdownHandler(bbctrl.APIHandler):
def put_ok(self):
subprocess.Popen(['plymouth', 'show-splash'])
subprocess.Popen(['plymouth', 'change-mode', '--shutdown'])
subprocess.Popen(['killall', 'xinit'])
subprocess.Popen(['shutdown', '-h', 'now'])
@@ -101,31 +105,6 @@ class HostnameHandler(bbctrl.APIHandler):
class NetworkHandler(bbctrl.APIHandler):
def get(self):
try:
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')
@@ -267,14 +246,6 @@ class HomeHandler(bbctrl.APIHandler):
self.get_ctrl().mach.home(axis)
class DevmodeHandler(bbctrl.APIHandler):
def put_ok(self, command, *args):
if command == "/probe":
self.get_ctrl().mach.fake_probe_contact()
else:
raise HTTPError(400, 'Not implemented')
class StartHandler(bbctrl.APIHandler):
def put_ok(self): self.get_ctrl().mach.start()
@@ -383,13 +354,34 @@ class ScreenRotationHandler(bbctrl.APIHandler):
text = config.read()
text = transformationMatrixPattern.sub(r'\1\2\3\5', text)
if rotated:
text = matchIsTouchscreenPattern.sub(r'\1\2\3\2Option "TransformationMatrix" "-1 0 1 0 -1 1 0 0 1"\1\4', text)
text = matchIsTouchscreenPattern.sub(
r'\1\2\3\2Option "TransformationMatrix" "-1 0 1 0 -1 1 0 0 1"\1\4', text)
with open("/usr/share/X11/xorg.conf.d/40-libinput.conf", 'wt') as config:
config.write(text)
subprocess.run('reboot')
class TimeHandler(bbctrl.APIHandler):
def get(self):
timeinfo = call_get_output(['timedatectl'])
timezones = call_get_output(
['timedatectl', 'list-timezones', '--no-pager'])
self.get_log('TimeHandler').info(
'Time stuff: {}, {}'.format(timeinfo, timezones))
self.write_json({
'timeinfo': timeinfo,
'timezones': timezones
})
def put_ok(self):
datetime = self.json['datetime']
timezone = self.json['timezone']
subprocess.Popen(['timedatectl', 'set-time', datetime])
subprocess.Popen(['timedatectl', 'set-timezone', timezone])
# Base class for Web Socket connections
class ClientConnection(object):
def __init__(self, app):
@@ -509,7 +501,6 @@ class Web(tornado.web.Application):
(r'/api/file(/[^/]+)?', bbctrl.FileHandler),
(r'/api/path/([^/]+)((/positions)|(/speeds))?', PathHandler),
(r'/api/home(/[xyzabcXYZABC]((/set)|(/clear))?)?', HomeHandler),
(r'/api/devmode((/probe))?', DevmodeHandler),
(r'/api/start', StartHandler),
(r'/api/estop', EStopHandler),
(r'/api/clear', ClearHandler),
@@ -526,6 +517,7 @@ class Web(tornado.web.Application):
(r'/api/jog', JogHandler),
(r'/api/video', bbctrl.VideoHandler),
(r'/api/screen-rotation', ScreenRotationHandler),
(r'/api/time', TimeHandler),
(r'/(.*)', StaticFileHandler,
{'path': bbctrl.get_resource('http/'),
'default_filename': 'index.html'}),
@@ -545,7 +537,8 @@ class Web(tornado.web.Application):
print('Listening on http://%s:%d/' % (args.addr, args.port))
def opened(self, ctrl): ctrl.clear_timeout()
def opened(self, ctrl):
ctrl.clear_timeout()
def closed(self, ctrl):
# Time out clients in demo mode

View File

@@ -1,33 +1,5 @@
#!/usr/bin/env python3
################################################################################
# #
# 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 sys
import signal
import tornado
@@ -53,13 +25,11 @@ from bbctrl.Comm import Comm
from bbctrl.CommandQueue import CommandQueue
from bbctrl.Camera import Camera, VideoHandler
from bbctrl.AVR import AVR
from bbctrl.AVREmu import AVREmu
from bbctrl.IOLoop import IOLoop
from bbctrl.MonitorTemp import MonitorTemp
import bbctrl.Cmd as Cmd
import bbctrl.v4l2 as v4l2
import bbctrl.Log as log
import bbctrl.ObjGraph as ObjGraph
ctrl = None
@@ -69,7 +39,7 @@ def get_resource(path):
return resource_filename(Requirement.parse('bbctrl'), 'bbctrl/' + path)
def on_exit(sig = 0, func = None):
def on_exit(sig=0, func=None):
global ctrl
print('Exit handler triggered: signal = %d', sig)
@@ -85,76 +55,46 @@ def time_str():
return datetime.datetime.now().strftime('%Y%m%d-%H:%M:%S')
class Debugger:
def __init__(self, ioloop, freq = 60 * 15, depth = 100):
self.ioloop = ioloop
self.freq = freq
self.depth = depth
self._callback()
def _callback(self):
with open('bbctrl-debug-%s.log' % time_str(), 'w') as log:
def line(name):
log.write('==== ' + name + ' ' + '=' * (74 - len(name)) + '\n')
line('Common')
ObjGraph.show_most_common_types(limit = self.depth, file = log)
log.write('\n')
line('Growth')
ObjGraph.show_growth(limit = self.depth, file = log)
log.write('\n')
line('New IDs')
ObjGraph.get_new_ids(limit = self.depth, file = log)
log.flush()
self.ioloop.call_later(self.freq, self._callback)
def parse_args():
parser = argparse.ArgumentParser(
description = 'Buildbotics Machine Controller')
description='Buildbotics Machine Controller')
parser.add_argument('-p', '--port', default = 80,
type = int, help = 'HTTP port')
parser.add_argument('-a', '--addr', metavar = 'IP', default = '0.0.0.0',
help = 'HTTP address to bind')
parser.add_argument('-s', '--serial', default = '/dev/ttyAMA0',
help = 'Serial device')
parser.add_argument('-b', '--baud', default = 230400, type = int,
help = 'Serial baud rate')
parser.add_argument('--i2c-port', default = 1, type = int,
help = 'I2C port')
parser.add_argument('--avr-addr', default = 0x2b, type = int,
help = 'AVR I2C address')
parser.add_argument('--pwr-addr', default = 0x60, type = int,
help = 'Power AVR I2C address')
parser.add_argument('-v', '--verbose', action = 'store_true',
help = 'Verbose output')
parser.add_argument('-l', '--log', metavar = "FILE",
help = 'Set a log file')
parser.add_argument('--disable-camera', action = 'store_true',
help = 'Disable the camera')
parser.add_argument('--width', default = 640, type = int,
help = 'Camera width')
parser.add_argument('--height', default = 480, type = int,
help = 'Camera height')
parser.add_argument('--fps', default = 15, type = int,
help = 'Camera frames per second')
parser.add_argument('--camera-clients', default = 4,
help = 'Maximum simultaneous camera clients')
parser.add_argument('--demo', action = 'store_true',
help = 'Enter demo mode')
parser.add_argument('--debug', default = 0, type = int,
help = 'Enable debug mode and set frequency in seconds')
parser.add_argument('--fast-emu', action = 'store_true',
help = 'Enter demo mode')
parser.add_argument('--client-timeout', default = 5 * 60, type = int,
help = 'Demo client timeout in seconds')
parser.add_argument('-p', '--port', default=80,
type=int, help='HTTP port')
parser.add_argument('-a', '--addr', metavar='IP', default='0.0.0.0',
help='HTTP address to bind')
parser.add_argument('-s', '--serial', default='/dev/ttyAMA0',
help='Serial device')
parser.add_argument('-b', '--baud', default=230400, type=int,
help='Serial baud rate')
parser.add_argument('--i2c-port', default=1, type=int,
help='I2C port')
parser.add_argument('--avr-addr', default=0x2b, type=int,
help='AVR I2C address')
parser.add_argument('--pwr-addr', default=0x60, type=int,
help='Power AVR I2C address')
parser.add_argument('-v', '--verbose', action='store_true',
help='Verbose output')
parser.add_argument('-l', '--log', metavar="FILE",
help='Set a log file')
parser.add_argument('--disable-camera', action='store_true',
help='Disable the camera')
parser.add_argument('--width', default=640, type=int,
help='Camera width')
parser.add_argument('--height', default=480, type=int,
help='Camera height')
parser.add_argument('--fps', default=15, type=int,
help='Camera frames per second')
parser.add_argument('--camera-clients', default=4,
help='Maximum simultaneous camera clients')
parser.add_argument('--demo', action='store_true',
help='Enter demo mode')
parser.add_argument('--debug', default=0, type=int,
help='Enable debug mode and set frequency in seconds')
parser.add_argument('--fast-emu', action='store_true',
help='Enter demo mode')
parser.add_argument('--client-timeout', default=5 * 60, type=int,
help='Demo client timeout in seconds')
return parser.parse_args()
@@ -170,16 +110,15 @@ def run():
# Create ioloop
ioloop = tornado.ioloop.IOLoop.current()
# Set ObjGraph signal handler
if args.debug: Debugger(ioloop, args.debug)
# Start server
web = Web(args, ioloop)
try:
ioloop.start()
except KeyboardInterrupt: on_exit()
except KeyboardInterrupt:
on_exit()
if __name__ == '__main__': run()
if __name__ == '__main__':
run()

View File

@@ -1,403 +0,0 @@
G21
(File: 'buildbotics_logo.tpl')
G0 Z3
F1600
M3 S10000
M6 T2
G0 X59.25 Y5.85
G1 Z-1.5
G1 X61.68 Y6.7
G1 X63.86 Y8.07
G1 X65.68 Y9.89
G1 X67.05 Y12.07
G1 X67.9 Y14.5
G1 X68.2 Y17.09
G1 Y56.6
G1 X67.73 Y59.04
G1 X50.8
G1 Y34.9
G1 X50.65 Y34.55
G1 X50.3 Y34.4
G1 X23.46
G1 X23.1 Y34.55
G1 X22.96 Y34.9
G1 X22.98 Y49.88
G1 X22.96 Y59.05
G1 X22.41
G1 X19.26
G1 X6.04
G1 X5.56 Y56.53
G1 Y17.09
G1 X5.85 Y14.5
G1 X6.7 Y12.07
G1 X8.07 Y9.89
G1 X9.89 Y8.07
G1 X12.07 Y6.7
G1 X14.5 Y5.85
G1 X17.09 Y5.56
G1 X56.67
G1 X59.25 Y5.85
G0 Z3
G0 X64.26 Y64.72
G1 Z-1.5
G1 X61.78 Y66.52
G1 X58.91 Y67.68
G1 X56.54 Y68.08
G1 X17.22
G1 X14.84 Y67.68
G1 X11.97 Y66.52
G1 X9.49 Y64.72
G1 X8.08 Y63.16
G1 X27.35
G1 X27.89 Y63.45
G1 X27.96 Y63.48
G1 X31.48 Y64.75
G1 X31.52 Y64.76
G1 X31.56 Y64.77
G1 X35.19 Y65.41
G1 X35.26
G1 X35.97 Y65.44
G1 X36.04 Y65.45
G1 X36.07
G1 X36.82 Y65.44
G1 X36.83
G1 X36.89
G1 X36.95
G1 X36.97
G1 X37.72 Y65.43
G1 X37.74
G1 X37.8
G1 X37.81
G1 X37.88
G1 X37.89
G1 X38.65 Y65.38
G1 X38.68
G1 X38.75 Y65.37
G1 X39.38 Y65.32
G1 X39.44 Y65.31
G1 X42.68 Y64.64
G1 X42.76 Y64.62
G1 X45.87 Y63.44
G1 X45.93 Y63.41
G1 X46.4 Y63.16
G1 X65.67
G1 X64.26 Y64.72
G0 Z3
G0 X36.88 Y9.4
G1 Z-1.5
G1 X37.31 Y9.64
G1 X39.58 Y13.48
G1 X39.63 Y13.6
G1 X39.65 Y13.73
G1 Y27.54
G1 X41.67
G1 Y25.39
G1 X41.75 Y25.12
G1 X41.97 Y24.93
G1 X46.41 Y22.92
G1 Y19.97
G1 X45.44
G1 X45.08 Y19.82
G1 X44.94 Y19.47
G1 Y13.73
G1 X45.08 Y13.38
G1 X45.44 Y13.23
G1 X49.94 Y13.24
G1 X50.29 Y13.39
G1 X50.44 Y13.74
G1 Y19.47
G1 X50.29 Y19.83
G1 X49.93 Y19.97
G1 X48.92
G1 Y23.61
G1 X48.84 Y23.88
G1 X48.63 Y24.06
G1 X44.19 Y26.12
G1 Y27.54
G1 X49.22
G1 X49.33 Y27.56
G1 X49.44 Y27.6
G1 X50.13 Y27.94
G1 X50.25 Y28.02
G1 X50.34 Y28.13
G1 X50.73 Y28.77
G1 X50.78 Y28.89
G1 X50.8 Y29.03
G1 Y33.05
G1 Y34.25
G1 Y34.65
G1 X50.66 Y35.01
G1 X50.3 Y35.15
G1 X23.46
G1 X23.1 Y35.01
G1 X22.96 Y34.65
G1 Y29.07
G1 X22.97 Y28.94
G1 X23.02 Y28.82
G1 X23.4 Y28.17
G1 X23.49 Y28.06
G1 X23.6 Y27.98
G1 X24.29 Y27.6
G1 X24.4 Y27.56
G1 X24.52 Y27.54
G1 X25.55
G1 Y26.4
G1 X23.4 Y25.52
G1 X23.17 Y25.33
G1 X23.09 Y25.06
G1 Y17.54
G1 X23.23 Y17.19
G1 X23.59 Y17.04
G1 X24.6
G1 Y10.36
G1 X24.62 Y10.23
G1 X24.66 Y10.11
G1 X24.8 Y9.88
G1 X24.88 Y9.77
G1 X24.99 Y9.68
G1 X25.25 Y9.54
G1 X25.37 Y9.5
G1 X25.49 Y9.48
G1 X26.53
G1 X26.65 Y9.49
G1 X26.76 Y9.54
G1 X27.01 Y9.66
G1 X27.12 Y9.74
G1 X27.21 Y9.85
G1 X27.35 Y10.09
G1 X27.41 Y10.22
G1 X27.43 Y10.35
G1 Y10.43
G1 X27.47 Y17.04
G1 X28.57
G1 X28.92 Y17.19
G1 X29.07 Y17.54
G1 Y24.64
G1 X30.72 Y25.3
G1 X30.95 Y25.49
G1 X31.03 Y25.77
G1 X31.02 Y27.54
G1 X34.03
G1 Y13.73
G1 X34.05 Y13.59
G1 X34.1 Y13.47
G1 X36.45 Y9.64
G1 X36.88 Y9.4
G0 Z3
G0 X49.94 Y10.82
G1 Z-1.5
G1 X50.29 Y10.97
G1 X50.44 Y11.32
G1 Y13.34
G1 X50.29 Y13.69
G1 X49.94 Y13.84
G1 X45.44
G1 X45.08 Y13.69
G1 X44.94 Y13.34
G1 Y11.32
G1 X45.08 Y10.97
G1 X45.44 Y10.82
G1 X49.94
G0 Z3
G0 X48.46 Y9.7
G1 Z-1.5
G1 X48.59 Y9.72
G1 X48.71 Y9.77
G1 X50.03 Y10.53
G1 X50.21 Y10.71
G1 X50.28 Y10.96
G1 X50.14 Y11.31
G1 X49.78 Y11.46
G1 X45.62
G1 X45.14 Y11.09
G1 X45.37 Y10.53
G1 X46.69 Y9.77
G1 X46.81 Y9.72
G1 X46.94 Y9.7
G1 X48.46
G0 Z3
G0 X50.3 Y34.4
G1 Z-1.5
G1 X50.66 Y34.55
G1 X50.8 Y34.9
G1 Y49.88
G1 X50.79 Y59.52
G1 X50.76 Y59.69
G1 X50.67 Y59.84
G1 X50.09 Y60.52
G1 X50.06 Y60.55
G1 X50.02 Y60.58
G1 X47.89 Y62.26
G1 X47.85 Y62.28
G1 X47.81 Y62.31
G1 X44.46 Y64.03
G1 X44.41 Y64.05
G1 X44.37 Y64.06
G1 X40.69 Y65.1
G1 X40.62 Y65.12
G1 X37.86 Y65.45
G1 X37.8 Y65.46
G1 X36.89 Y65.44
G1 X36.83
G1 X36.82
G1 X36.07 Y65.45
G1 X36.04 Y65.44
G1 X35.97
G1 X35.1 Y65.41
G1 X35.04 Y65.4
G1 X32.44 Y64.99
G1 X32.37 Y64.97
G1 X28.94 Y63.9
G1 X28.89 Y63.88
G1 X28.85 Y63.86
G1 X25.74 Y62.2
G1 X25.7 Y62.18
G1 X25.66 Y62.15
G1 X23.68 Y60.56
G1 X23.65 Y60.53
G1 X23.62 Y60.5
G1 X23.08 Y59.87
G1 X22.99 Y59.71
G1 X22.96 Y59.54
G1 X22.98 Y49.88
G1 X22.96 Y34.9
G1 X23.1 Y34.55
G1 X23.46 Y34.4
G1 X50.3
G0 Z3
G0 X55.2 Y43.67
G1 Z-1.5
G1 Y51.34
G1 X55.11 Y51.94
G1 X54.83 Y52.88
G1 X54.39 Y53.88
G1 X53.8 Y54.85
G1 X53.09 Y55.74
G1 X52.28 Y56.47
G1 X51.41 Y56.98
G1 X51.07 Y57.09
G1 Y43.67
G1 X55.2
G0 Z3
G0 X22.69 Y43.63
G1 Z-1.5
G1 Y57.09
G1 X22.35 Y56.98
G1 X21.47 Y56.47
G1 X20.67 Y55.74
G1 X19.95 Y54.85
G1 X19.36 Y53.88
G1 X18.92 Y52.88
G1 X18.64 Y51.94
G1 X18.55 Y51.34
G1 Y43.63
G1 X22.69
G0 Z3
G0 X28.55 Y35.84
G1 Z-0.99
G1 X30.11 Y36.15
G1 X31.43 Y37.03
G1 X32.32 Y38.35
G1 X32.63 Y39.91
G1 X32.32 Y41.47
G1 X31.43 Y42.79
G1 X30.11 Y43.68
G1 X28.55 Y43.99
G1 X26.99 Y43.68
G1 X25.67 Y42.79
G1 X24.79 Y41.47
G1 X24.48 Y39.91
G1 X24.79 Y38.35
G1 X25.67 Y37.03
G1 X26.99 Y36.15
G1 X28.55 Y35.84
G0 Z3
G0 X45.33 Y35.93
G1 Z-0.99
G1 X46.88 Y36.24
G1 X48.21 Y37.12
G1 X49.09 Y38.45
G1 X49.4 Y40
G1 X49.09 Y41.56
G1 X48.21 Y42.88
G1 X46.88 Y43.77
G1 X45.33 Y44.08
G1 X43.77 Y43.77
G1 X42.45 Y42.88
G1 X41.56 Y41.56
G1 X41.25 Y40
G1 X41.56 Y38.45
G1 X42.45 Y37.12
G1 X43.77 Y36.24
G1 X45.33 Y35.93
G0 Z3
G0 X45.2 Y39.12
G1 Z-0.99
G1 X45.7 Y39.19
G1 X46.07 Y39.52
G1 X46.22 Y40
G1 X46.07 Y40.49
G1 X45.7 Y40.81
G1 X45.2 Y40.89
G1 X44.74 Y40.68
G1 X44.47 Y40.26
G1 Y39.75
G1 X44.74 Y39.33
G1 X45.2 Y39.12
G0 Z3
G0 X28.43 Y39.03
G1 Z-0.99
G1 X28.92 Y39.1
G1 X29.3 Y39.43
G1 X29.44 Y39.91
G1 X29.3 Y40.4
G1 X28.92 Y40.72
G1 X28.43 Y40.8
G1 X27.97 Y40.59
G1 X27.7 Y40.16
G1 Y39.66
G1 X27.97 Y39.24
G1 X28.43 Y39.03
G0 Z3
G0 X55.76 Y0
G1 Z-1.5
G1 X59.27 Y0.35
G1 X62.65 Y1.37
G1 X65.76 Y3.03
G1 X68.49 Y5.27
G1 X70.73 Y8
G1 X72.39 Y11.11
G1 X73.42 Y14.49
G1 X73.76 Y18
G1 Y55.69
G1 X73.42 Y59.2
G1 X72.39 Y62.57
G1 X70.73 Y65.69
G1 X68.49 Y68.41
G1 X65.76 Y70.65
G1 X62.65 Y72.31
G1 X59.27 Y73.34
G1 X55.76 Y73.69
G1 X18
G1 X14.49 Y73.34
G1 X11.11 Y72.31
G1 X8 Y70.65
G1 X5.27 Y68.41
G1 X3.03 Y65.69
G1 X1.37 Y62.57
G1 X0.35 Y59.2
G1 X0 Y55.69
G1 Y18
G1 X0.35 Y14.49
G1 X1.37 Y11.11
G1 X3.03 Y8
G1 X5.27 Y5.27
G1 X8 Y3.03
G1 X11.11 Y1.37
G1 X14.49 Y0.35
G1 X18 Y0
G1 X55.76
G0 Z3
M5
G0 X40 Y75
M2

View File

@@ -502,14 +502,14 @@
},
"probe-fast-seek": {
"type": "float",
"unit": "mm/m",
"unit": "mm/min",
"min": 0,
"max": 1000,
"default": 200
},
"probe-slow-seek": {
"type": "float",
"unit": "mm/m",
"unit": "mm/min",
"min": 0,
"max": 1000,
"default": 25

View File

@@ -1,8 +0,0 @@
[Plymouth Theme]
Name=buildbotics
Description=Buildbotics boot splash
ModuleName=script
[script]
ImageDir=/usr/share/plymouth/themes/buildbotics
ScriptFile=/usr/share/plymouth/themes/buildbotics/buildbotics.script

View File

@@ -1,32 +0,0 @@
screenW = Window.GetWidth();
screenH = Window.GetHeight();
image = Image("splash.png");
imageW = image.GetWidth();
imageH = image.GetHeight();
scaleX = imageW / screenW;
scaleY = imageH / screenH;
flag = 1;
if (scaleX > 1 || scaleY > 1) {
if (scaleX > scaleY) {
resized = image.Scale(screenW, imageH / scaleX);
imageX = 0;
imageY = (screenH - ((imageH * screenW) / imageW)) / 2;
} else {
resized = image.Scale(imageW / scaleY, screenH);
imageX = (screenW - ((imageW * screenH) / imageH)) / 2;
imageY = 0;
}
} else {
resized = image.Scale(imageW, imageH);
imageX = (screenW - imageW) / 2;
imageY = (screenH - imageH) / 2;
}
sprite = Sprite(resized);
sprite.SetPosition(imageX, imageY, -100);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -11,18 +11,20 @@
"check": "svelte-check --tsconfig ./tsconfig.json"
},
"devDependencies": {
"@sveltejs/kit": "^1.0.0-next.357",
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.49",
"@sveltejs/kit": "^1.0.0-next.392",
"@sveltejs/vite-plugin-svelte": "^1.0.1",
"@tsconfig/svelte": "^3.0.0",
"node-sass": "^7.0.1",
"polyfill-object.fromentries": "^1.0.1",
"smui-theme": "^6.0.0-beta.16",
"svelte": "^3.48.0",
"string.prototype.matchall": "^4.0.7",
"svelte": "^3.49.0",
"svelte-check": "^2.8.0",
"svelte-material-ui": "^6.0.0-beta.16",
"svelte-preprocess": "^4.10.7",
"svelte-tiny-virtual-list": "^2.0.5",
"tslib": "^2.4.0",
"typescript": "^4.7.4",
"vite": "^2.9.13"
"vite": "^3.0.2"
}
}

View File

@@ -0,0 +1,131 @@
<script lang="ts">
import configTemplate from "../../../resources/config-template.json";
import { Config, DisplayUnits } from "$lib/ConfigStore";
import { ControllerMethods } from "$lib/RegisterControllerMethods";
import { onMount } from "svelte";
type Template = {
type?: string;
values?: (string | number)[];
unit?: "string";
iunit?: "string";
min?: number;
max?: number;
step?: number;
help?: string;
default?: string | number;
scale?: number;
};
export let key: string;
let keyParts: string[];
let template: Template;
let name: string;
let title: string;
let units: string;
let value;
onMount(() => {
keyParts = (key || "").split(".");
template = getTemplate();
name = keyParts[keyParts.length - 1];
title = getTitle();
value = getValue();
});
$: metric = $DisplayUnits === "METRIC";
$: if (template) {
units = metric || !template.iunit ? template.unit : template.iunit;
}
function getTemplate(): Template {
let template = configTemplate;
for (const part of keyParts) {
template = template[part];
}
return template as Template;
}
function getTitle(): string {
const help = template.help ? `${template.help}\n` : "";
return `${help}Default: ${template.default} ${template.unit || ""}`;
}
function getValue(): string | number {
let value: any = $Config;
for (const part of keyParts) {
value = value[part];
}
if (template.scale) {
if (metric) {
return Number.parseFloat(value.toFixed(3));
}
return Number.parseFloat((value / template.scale).toFixed(4));
}
return value;
}
function onChange() {
Config.update((config) => {
let target = config;
for (const part of keyParts.slice(0, -1)) {
target = target[part];
}
target[keyParts[keyParts.length - 1]] = value;
return config;
});
ControllerMethods.dispatch("config-changed");
}
</script>
{#if template}
<div class="pure-control-group" {title}>
<label for={name}>{name}</label>
{#if template.values}
<select {name} bind:value on:change={onChange}>
{#each template.values as opt}
<option value={opt} disabled={opt === "-----"}>
{opt}
</option>
{/each}
</select>
{:else if template.type === "bool"}
<input {name} type="checkbox" bind:value on:input={onChange} />
{:else if template.type === "float"}
<input
{name}
type="number"
min={template.min}
max={template.max}
step={template.step || "any"}
bind:value
on:input={onChange}
/>
{:else if template.type === "int"}
<input
{name}
type="number"
min={template.min}
max={template.max}
bind:value
on:input={onChange}
/>
{:else if template.type === "string"}
<input {name} type="text" bind:value on:input={onChange} />
{:else if template.type == "text"}
<textarea {name} bind:value on:input={onChange} />
{/if}
<label for="" class="units">{units || ""}</label>
<slot name="extra" />
</div>
{/if}

View File

@@ -1,33 +0,0 @@
<script lang="ts">
import * as api from "$lib/api";
import { onMount } from "svelte";
onMount(() => {
document.body.style.backgroundColor = "black";
const layout = document.querySelector("#layout") as HTMLElement;
layout.style.backgroundColor = "white";
layout.style.width = "1280px";
layout.style.height = "720px";
layout.style.overflowY = "scroll";
});
</script>
<div class="devmode">
<button on:click={() => api.PUT("devmode/probe")}> Probe Contact </button>
</div>
<style lang="scss">
.devmode {
background-color: greenyellow;
z-index: 20000000;
position: absolute;
top: 10px;
left: 400px;
width: 500px;
height: 100px;
padding: 6px;
display: flex;
align-items: flex-start;
justify-items: space-between;
}
</style>

View File

@@ -0,0 +1,97 @@
<script lang="ts">
import configTemplate from "../../../resources/config-template.json";
import ScreenRotationDialog from "$dialogs/ScreenRotationDialog.svelte";
import ConfigTemplatedInput from "./ConfigTemplatedInput.svelte";
import SetTimeDialog from "$dialogs/SetTimeDialog.svelte";
import Button, { Label } from "@smui/button";
const gcodeURL = "https://linuxcnc.org/docs/html/gcode/g-code.html";
let showScreenRotationDialog = false;
let showSetTimeDialog = false;
</script>
<ScreenRotationDialog bind:open={showScreenRotationDialog} />
<SetTimeDialog bind:open={showSetTimeDialog} />
<h1>Settings</h1>
<div class="pure-form pure-form-aligned">
<h2>User Interface</h2>
<fieldset>
<div class="pure-control-group">
<label for="screen-rotation" />
<Button
name="screen-rotation"
touch
variant="raised"
on:click={() => (showScreenRotationDialog = true)}
>
<Label>Change Screen Rotation</Label>
</Button>
</div>
<div class="pure-control-group">
<label for="set-time" />
<Button
name="set-time"
touch
variant="raised"
on:click={() => (showSetTimeDialog = true)}
>
<Label>Change Time & Timezone</Label>
</Button>
</div>
</fieldset>
<fieldset>
<h2>Probe Dimensions</h2>
{#each Object.keys(configTemplate.probe) as key}
{#if key !== "probe-diameter"}
<ConfigTemplatedInput key={`probe.${key}`} />
{/if}
{/each}
</fieldset>
<fieldset>
<h2>GCode</h2>
{#each Object.keys(configTemplate.gcode) as key}
<ConfigTemplatedInput key={`gcode.${key}`} />
{/each}
</fieldset>
<h2>Path Accuracy</h2>
<fieldset>
<ConfigTemplatedInput key={`settings.max-deviation`} />
</fieldset>
<p>
Lower <tt>max-deviation</tt> to follow the programmed path more precisely but
at a slower speed.
</p>
<p>
In order to improve traversal speed, the path planner may merge consecutive
moves or round off sharp corners if doing so would deviate from the program
path by less than <tt>max-deviation</tt>.
</p>
<p>
GCode commands
<a href={`${gcodeURL}#gcode:g61`} target="_blank">G61, G61.1</a>
and <a href={`${gcodeURL}#gcode:g64`} target="_blank"> G64</a> also affect path
planning accuracy.
</p>
<h2>Cornering Speed (Advanced)</h2>
<fieldset>
<ConfigTemplatedInput key={`settings.junction-accel`} />
</fieldset>
<p>
Junction acceleration limits the cornering speed the planner will allow.
Increasing this value will allow for faster traversal of corners but may
cause the planner to violate axis jerk limits and stall the motors. Use with
caution.
</p>
</div>

View File

@@ -3,6 +3,10 @@
import HomeMachineDialog from "$dialogs/HomeMachineDialog.svelte";
import ProbeDialog from "$dialogs/ProbeDialog.svelte";
import ScreenRotationDialog from "$dialogs/ScreenRotationDialog.svelte";
import UploadDialog from "$dialogs/UploadDialog.svelte";
import SetTimeDialog from "./SetTimeDialog.svelte";
import ManualHomeAxisDialog from "./ManualHomeAxisDialog.svelte";
import SetAxisPositionDialog from "./SetAxisPositionDialog.svelte";
const HomeMachineDialogProps = writable<HomeMachineDialogPropsType>();
type HomeMachineDialogPropsType = {
@@ -16,11 +20,35 @@
probeType: "xyz" | "z";
};
const ScreenRotationDialogProps = writable<ProbeDialogPropsType>();
const ScreenRotationDialogProps = writable<ScreenRotationDialogPropsType>();
type ScreenRotationDialogPropsType = {
open: boolean;
};
const UploadDialogProps = writable<UploadDialogPropsType>();
type UploadDialogPropsType = {
open: boolean;
file: File;
onComplete: () => void;
};
const SetTimeDialogProps = writable<SetTimeDialogPropsType>();
type SetTimeDialogPropsType = {
open: boolean;
};
const ManualHomeAxisDialogProps = writable<ManualHomeAxisDialogPropsType>();
type ManualHomeAxisDialogPropsType = {
open: boolean;
axis: string;
};
const SetAxisPositionDialogProps = writable<SetAxisPositionDialogPropsType>();
type SetAxisPositionDialogPropsType = {
open: boolean;
axis: string;
};
export function showDialog(
dialog: "HomeMachine",
props: Omit<HomeMachineDialogPropsType, "open">
@@ -31,6 +59,31 @@
props: Omit<ProbeDialogPropsType, "open">
);
export function showDialog(
dialog: "ScreenRotation",
props: Omit<ScreenRotationDialogPropsType, "open">
);
export function showDialog(
dialog: "Upload",
props: Omit<UploadDialogPropsType, "open">
);
export function showDialog(
dialog: "SetTime",
props: Omit<SetTimeDialogPropsType, "open">
);
export function showDialog(
dialog: "ManualHomeAxis",
props: Omit<ManualHomeAxisDialogPropsType, "open">
);
export function showDialog(
dialog: "SetAxisPosition",
props: Omit<SetAxisPositionDialogPropsType, "open">
);
export function showDialog(dialog: string, props: any) {
switch (dialog) {
case "HomeMachine":
@@ -45,6 +98,22 @@
ScreenRotationDialogProps.set({ ...props, open: true });
break;
case "Upload":
UploadDialogProps.set({ ...props, open: true });
break;
case "SetTime":
SetTimeDialogProps.set({ ...props, open: true });
break;
case "ManualHomeAxis":
ManualHomeAxisDialogProps.set({ ...props, open: true });
break;
case "SetAxisPosition":
SetAxisPositionDialogProps.set({ ...props, open: true });
break;
default:
throw new Error(`Unknown dialog '${dialog}`);
}
@@ -54,3 +123,7 @@
<HomeMachineDialog {...$HomeMachineDialogProps} />
<ProbeDialog {...$ProbeDialogProps} />
<ScreenRotationDialog {...$ScreenRotationDialogProps} />
<UploadDialog {...$UploadDialogProps} />
<SetTimeDialog {...$SetTimeDialogProps} />
<ManualHomeAxisDialog {...$ManualHomeAxisDialogProps} />
<SetAxisPositionDialog {...$SetAxisPositionDialogProps} />

View File

@@ -8,6 +8,7 @@
<Dialog
bind:open
scrimClickAction=""
aria-labelledby="home-machine-dialog-title"
aria-describedby="home-machine-dialog-content"
>

View File

@@ -0,0 +1,46 @@
<script lang="ts">
import Dialog, { Title, Content, Actions } from "@smui/dialog";
import TextField from "@smui/textfield";
import Button, { Label } from "@smui/button";
import { ControllerMethods } from "$lib/RegisterControllerMethods";
export let open: boolean;
export let axis = "";
let value = 0;
function onConfirm() {
ControllerMethods.set_home(axis, value);
}
</script>
<Dialog
bind:open
scrimClickAction=""
aria-labelledby="manual-home-axis-dialog-title"
aria-describedby="manual-home-axis-dialog-content"
>
<Title id="manual-home-axis-dialog-title"
>Manually Home {axis.toUpperCase()} Axis</Title
>
<Content id="manual-home-axis-dialog-content">
<p>Set axis absolute position</p>
<TextField
label="Absolute"
type="number"
bind:value
variant="filled"
style="width: 100%;"
/>
</Content>
<Actions>
<Button>
<Label>Cancel</Label>
</Button>
<Button defaultAction on:click={onConfirm}>
<Label>Set</Label>
</Button>
</Actions>
</Dialog>

View File

@@ -1,5 +1,18 @@
<script type="ts" context="module">
import { get, writable, type Writable } from "svelte/store";
<script type="ts">
import DimensionInput from "$components/DimensionInput.svelte";
import Dialog, { Title, Content, Actions } from "@smui/dialog";
import Button, { Label } from "@smui/button";
import { waitForChange } from "$lib/StoreHelpers";
import { ControllerMethods } from "$lib/RegisterControllerMethods";
import { Config } from "$lib/ConfigStore";
import { writable, type Writable } from "svelte/store";
import {
probingActive,
probeContacted,
probingComplete,
probingFailed,
probingStarted,
} from "$lib/ControllerState";
type Step =
| "None"
@@ -19,49 +32,8 @@
};
const cancelled = writable(false);
const probingActive = writable(false);
const probeContacted = writable(false);
const probingStarted = writable(false);
const probingFailed = writable(false);
const probingComplete = writable(false);
const userAcknowledged = writable(false);
export function handleControllerStateUpdate(state: Record<string, any>) {
if (!get(probingActive)) {
return;
}
switch (true) {
case state.pw === 0:
probeContacted.set(true);
break;
case state.log?.msg === "Switch not found":
probingFailed.set(true);
break;
case state.cycle !== "idle":
probingStarted.set(true);
break;
case state.cycle === "idle":
if (get(probingStarted)) {
probingStarted.set(false);
probingComplete.set(true);
}
break;
}
}
</script>
<script type="ts">
import DimensionInput from "$components/DimensionInput.svelte";
import Dialog, { Title, Content, Actions } from "@smui/dialog";
import Button, { Label } from "@smui/button";
import { waitForChange } from "$lib/StoreHelpers";
import { ControllerMethods } from "$lib/RegisterControllerMethods";
import { Config } from "$lib/ConfigStore";
const cutterDiameterOptions = [
{ value: 0.5, label: '1/2 "', metric: false },
{ value: 0.25, label: '1/4 "', metric: false },

View File

@@ -0,0 +1,64 @@
<script lang="ts">
import Dialog, { Title, Content, Actions } from "@smui/dialog";
import TextField from "@smui/textfield";
import Button, { Label } from "@smui/button";
import { ControllerMethods } from "$lib/RegisterControllerMethods";
export let open: boolean;
export let axis = "";
let value = 0;
let homed = false;
let wasOpen = false;
$: if (open != wasOpen) {
if (open) {
homed = ControllerMethods.isAxisHomed(axis);
}
wasOpen = open;
}
function onUnhome() {
ControllerMethods.unhome(axis);
}
function onConfirm() {
ControllerMethods.set_position(axis, value);
}
</script>
<Dialog
bind:open
scrimClickAction=""
aria-labelledby="set-axis-position-dialog-title"
aria-describedby="set-axis-position-dialog-content"
>
<Title id="set-axis-position-dialog-title"
>Set {axis.toUpperCase()} Axis Position</Title
>
<Content id="set-axis-position-dialog-content">
<TextField
label="Position"
type="number"
bind:value
spellcheck="false"
variant="filled"
style="width: 100%;"
/>
</Content>
<Actions>
<Button>
<Label>Cancel</Label>
</Button>
{#if homed}
<Button on:click={onUnhome}>
<Label>Unhome</Label>
</Button>
{/if}
<Button defaultAction on:click={onConfirm}>
<Label>Set</Label>
</Button>
</Actions>
</Dialog>

View File

@@ -0,0 +1,240 @@
<script lang="ts">
import Dialog, { Title, Content, Actions } from "@smui/dialog";
import Button, { Label } from "@smui/button";
import TextField from "@smui/textfield";
import CircularProgress from "@smui/circular-progress";
import VirtualList from "svelte-tiny-virtual-list";
import * as api from "$lib/api";
const itemHeight = 35;
type Timezone = {
label: string;
value: string;
};
export let open = false;
let value = "";
let wasOpen = false;
let loading = true;
let timezones: Timezone[] = [];
let currentTimezoneIndex: number;
let selectedTimezoneIndex: number;
let networkTimeSynchronized: boolean;
$: if (open != wasOpen) {
if (!wasOpen) {
loadData();
}
wasOpen = open;
}
async function loadData() {
loading = true;
const result = await api.GET("time");
parseTimezones(result.timezones);
parseTimeinfo(result.timeinfo);
value = getDateTimeValueString();
loading = false;
}
function parseTimeinfo(str: string) {
const matches = Array.from(str.matchAll(/\s*([^:]+):\s+(.+)/gm));
let currentTimezoneValue;
for (const match of matches) {
let [, label, value] = match;
switch (label) {
case "Time zone":
currentTimezoneValue = value.split(" ")[0];
break;
case "NTP synchronized":
networkTimeSynchronized = value === "yes";
break;
}
}
currentTimezoneIndex = timezones.findIndex(
(tz) => tz.value === currentTimezoneValue
);
selectedTimezoneIndex = currentTimezoneIndex;
}
function parseTimezones(str: string) {
const matches = Array.from(str.matchAll(/\s*(\S+)\s*/gm));
timezones = [];
for (let [, value] of matches) {
timezones.push({
label: value.replace(/_/g, " "),
value,
});
}
// Sort alphabetically, but with the current timezone at the top of the list
timezones.sort((a, b) => {
switch (true) {
case a.value === "UTC":
return -1;
case b.value === "UTC":
return 1;
default:
return a.value.localeCompare(b.value);
}
});
}
function getDateTimeValueString() {
const date = new Date();
const year = date.getFullYear().toString().padStart(2, "0");
const month = (date.getMonth() + 1).toString().padStart(2, "0");
const day = date.getDate().toString().padStart(2, "0");
const hour = date.getHours().toString().padStart(2, "0");
const minute = date.getMinutes().toString().padStart(2, "0");
return `${year}-${month}-${day}T${hour}:${minute}:00`;
}
async function onConfirm() {
const date = new Date(value);
const year = date.getFullYear().toString().padStart(2, "0");
const month = (date.getMonth() + 1).toString().padStart(2, "0");
const day = date.getDate().toString().padStart(2, "0");
const hour = date.getHours().toString().padStart(2, "0");
const minute = date.getMinutes().toString().padStart(2, "0");
await api.PUT("time", {
datetime: `${year}-${month}-${day} ${hour}:${minute}:00`,
timezone: timezones[selectedTimezoneIndex].value,
});
}
</script>
<Dialog
bind:open
scrimClickAction=""
aria-labelledby="set-time-dialog-title"
aria-describedby="set-time-dialog-content"
>
<Title id="set-time-dialog-title">Adjust Clock & Timezone</Title>
<Content id="set-time-dialog-content">
{#if loading}
<div style="display: flex; justify-content: center">
<CircularProgress style="height: 32px; width: 32px;" indeterminate />
</div>
{:else}
{#if networkTimeSynchronized}
<p>
Because this controller is connected to the internet, the time is
managed automatically, and cannot be manually set.
</p>
{:else}
<p>
Because this controller is not connected to the internet, you can
manually set the time.
</p>
<p>
Note: any time the controller is turned off, the time will need to be
reset. If you connect the controller to the internet, the time will be
managed automatically.
</p>
<Label>Date & Time</Label>
<TextField
bind:value
label="Time"
type="datetime-local"
variant="filled"
style="width: 100%;"
/>
{/if}
<p>
To display your local time correctly, the controller must know what
timezone it is in.
</p>
<div class="timezones-container" style="margin-top: 20px;">
<Label>Select your timezone</Label>
<VirtualList
width="100%"
height={itemHeight * 6}
itemCount={timezones.length}
itemSize={itemHeight}
scrollToIndex={currentTimezoneIndex}
scrollToAlignment="center"
>
<div
slot="item"
let:index
let:style
{style}
class="timezone"
class:selected={index === selectedTimezoneIndex}
on:click={() => (selectedTimezoneIndex = index)}
>
{timezones[index].label}
</div>
</VirtualList>
</div>
{/if}
</Content>
<Actions>
<Button>
<Label>Cancel</Label>
</Button>
<Button
defaultAction
disabled={selectedTimezoneIndex === -1}
on:click={onConfirm}
>
<Label>Confirm</Label>
</Button>
</Actions>
</Dialog>
<style lang="scss">
@use "sass:color";
$primary: #0078e7;
$very-dark: #555;
$text: #777;
$grey: #bbb;
$light: #ddd;
.timezones-container {
:global {
.virtual-list-wrapper {
border: 1px solid #ccc;
border-radius: 3px;
overflow-x: hidden;
overflow-y: scroll;
}
}
.timezone {
font-size: 14px;
display: flex;
align-items: center;
margin: 0;
padding-left: 10px;
&.selected {
color: $primary;
background-color: color.adjust($primary, $lightness: 50%);
font-weight: bold;
}
}
}
</style>

View File

@@ -0,0 +1,78 @@
<script lang="ts">
import Dialog, { Title, Content, Actions } from "@smui/dialog";
import Button, { Label } from "@smui/button";
import LinearProgress from "@smui/linear-progress";
export let open = false;
export let file: File;
export let onComplete: () => void;
let wasOpen = false;
let xhr;
let progress;
$: if (open != wasOpen) {
if (!wasOpen) {
beginUpload();
}
wasOpen = open;
}
$: if (!open) {
xhr = undefined;
}
async function beginUpload() {
progress = 0;
xhr = new XMLHttpRequest();
xhr.upload.onload = () => {
open = false;
if (onComplete) {
onComplete();
}
};
xhr.upload.onerror = () => {
open = false;
alert("Upload failed.");
};
xhr.upload.onabort = () => {
open = false;
};
xhr.upload.onprogress = (event) => {
progress = event.loaded / event.total;
};
xhr.open("PUT", `/api/file/${encodeURIComponent(file.name)}`);
xhr.send(file);
}
function onCancel() {
xhr.abort();
}
</script>
<Dialog
bind:open
scrimClickAction=""
aria-labelledby="upload-dialog-title"
aria-describedby="upload-dialog-content"
>
<Title id="upload-dialog-title">
Uploading {#if file}{file.name}...{/if}
</Title>
<Content id="upload-dialog-content">
<LinearProgress {progress} />
</Content>
<Actions>
<Button on:click={onCancel}>
<Label>Cancel</Label>
</Button>
</Actions>
</Dialog>

View File

@@ -1,7 +1,14 @@
import { writable } from "svelte/store";
type DisplayUnits = "METRIC" | "IMPERIAL";
export const Config = writable<Record<string, any>>({});
export const DisplayUnits = writable<DisplayUnits>();
export function handleConfigUpdate(config: Record<string, any>) {
Config.set(config);
}
export function setDisplayUnits(value: DisplayUnits) {
DisplayUnits.set(value);
}

View File

@@ -0,0 +1,36 @@
import { get, writable } from "svelte/store";
import { processNetworkInfo } from "./NetworkInfo";
export const networkInfo = writable({});
export const probingActive = writable(false);
export const probeContacted = writable(false);
export const probingStarted = writable(false);
export const probingFailed = writable(false);
export const probingComplete = writable(false);
export function handleControllerStateUpdate(state: Record<string, any>) {
if (state.networkInfo) {
processNetworkInfo(state.networkInfo);
delete state.networkInfo;
}
if (get(probingActive)) {
if (state.pw === 0) {
probeContacted.set(true);
}
if (state.log?.msg === "Switch not found") {
probingFailed.set(true);
}
if (state.cycle !== "idle") {
probingStarted.set(true);
}
if (state.cycle === "idle" && get(probingStarted)) {
probingStarted.set(false);
probingComplete.set(true);
}
}
}

View File

@@ -1,5 +1,4 @@
import { readable } from "svelte/store";
import * as api from "$lib/api";
import { writable } from "svelte/store";
export type WifiNetwork = {
Quality: string;
@@ -34,60 +33,43 @@ const empty: NetworkInfo = {
}
}
export const networkInfo = readable<NetworkInfo>(empty, (set) => {
getNetworkInfo();
const networkInfoIntervalId = setInterval(getNetworkInfo, 5000);
export const networkInfo = writable<NetworkInfo>(empty);
async function getNetworkInfo() {
const networksByName: Record<string, WifiNetwork> = {}
export function processNetworkInfo(rawNetworkInfo: NetworkInfo) {
const now = Date.now();
const networksByName: Record<string, WifiNetwork> = {}
try {
const networkInfo: NetworkInfo = await api.GET("network");
const now = Date.now();
for (let network of networkInfo.wifi.networks) {
if (network.Name) {
network.lastSeen = now;
network.active = networkInfo.wifi.ssid === network.Name;
networksByName[network.Name] = network;
}
}
for (let network of Object.values(networksByName)) {
if (network.lastSeen - now > 30000) {
delete networksByName[network.Name];
}
}
set({
ipAddresses: networkInfo.ipAddresses,
hostname: networkInfo.hostname,
wifi: {
ssid: networkInfo.wifi.ssid,
networks: Object.values(networksByName).sort((a, b) => {
switch (true) {
case a.active:
return -1;
case b.active:
return 1;
default:
return a.Name.localeCompare(b.Name);
}
})
}
});
} catch (error) {
console.debug("Failed to fetch network info", error);
for (let network of rawNetworkInfo.wifi.networks) {
if (network.Name) {
network.lastSeen = now;
network.active = rawNetworkInfo.wifi.ssid === network.Name;
networksByName[network.Name] = network;
}
}
return () => {
clearInterval(networkInfoIntervalId);
for (let network of Object.values(networksByName)) {
if (network.lastSeen - now > 30000) {
delete networksByName[network.Name];
}
}
})
export function init() {
return networkInfo.subscribe(() => ({}));
networkInfo.set({
ipAddresses: rawNetworkInfo.ipAddresses,
hostname: rawNetworkInfo.hostname,
wifi: {
ssid: rawNetworkInfo.wifi.ssid,
networks: Object.values(networksByName).sort((a, b) => {
switch (true) {
case a.active:
return -1;
case b.active:
return 1;
default:
return a.Name.localeCompare(b.Name);
}
})
}
});
}

View File

@@ -2,10 +2,18 @@ type ControllerMethods = {
stop: () => void;
send: (gcode: string) => void;
goto_zero: (x: number, y: number, z: number, a: number) => void;
dispatch: (event: string, ...args: any[]) => void;
isAxisHomed: (axis: string) => boolean;
unhome: (axis: string) => void;
set_position: (axis: string, value: number) => void;
set_home: (axis: string, value: number) => void;
}
export let ControllerMethods: ControllerMethods;
export function registerControllerMethods(methods: ControllerMethods) {
ControllerMethods = methods;
export function registerControllerMethods(methods: Partial<ControllerMethods>) {
ControllerMethods = {
...ControllerMethods,
...methods
};
}

View File

@@ -1,11 +1,13 @@
import 'polyfill-object.fromentries';
import matchAll from "string.prototype.matchall";
matchAll.shim();
import AdminNetworkView from '$components/AdminNetworkView.svelte';
import SettingsView from '$components/SettingsView.svelte';
import DialogHost, { showDialog } from "$dialogs/DialogHost.svelte";
import Devmode from "$components/Devmode.svelte";
import { handleConfigUpdate } from '$lib/ConfigStore';
import { init as initNetworkInfo } from '$lib/NetworkInfo';
import { handleControllerStateUpdate } from "$dialogs/ProbeDialog.svelte";
import { handleConfigUpdate, setDisplayUnits } from '$lib/ConfigStore';
import { handleControllerStateUpdate } from "$lib/ControllerState";
import { registerControllerMethods } from "$lib/RegisterControllerMethods";
export function createComponent(component: string, target: HTMLElement, props: Record<string, any>) {
@@ -13,21 +15,21 @@ export function createComponent(component: string, target: HTMLElement, props: R
case "AdminNetworkView":
return new AdminNetworkView({ target, props });
case "SettingsView":
return new SettingsView({ target, props });
case "DialogHost":
return new DialogHost({ target, props });
case "Devmode":
return new Devmode({target, props});
default:
throw new Error("Unknown component");
}
}
export {
initNetworkInfo,
showDialog,
handleControllerStateUpdate,
handleConfigUpdate,
registerControllerMethods,
setDisplayUnits
};

View File

@@ -15,6 +15,7 @@ export default defineConfig({
}
},
build: {
minify: false,
target: "chrome60",
lib: {
entry: resolve(__dirname, 'src/main.ts'),