Merge pull request #68 from dacarley/2
3 - New user experience for probing
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -20,9 +20,9 @@ __pycache__
|
|||||||
*.zip
|
*.zip
|
||||||
/rpi-share
|
/rpi-share
|
||||||
/rpi-root
|
/rpi-root
|
||||||
/package-lock.json
|
|
||||||
/src/bbserial/linux-rpi-raspberrypi-kernel*
|
/src/bbserial/linux-rpi-raspberrypi-kernel*
|
||||||
/src/bbserial/raspberrypi-kernel*
|
/src/bbserial/raspberrypi-kernel*
|
||||||
|
.vscode
|
||||||
*.elf
|
*.elf
|
||||||
*.hex
|
*.hex
|
||||||
|
null.d
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
include package.json README.md
|
include package.json README.md requirements.txt
|
||||||
graft scripts
|
graft scripts
|
||||||
|
graft python-packages
|
||||||
graft src/py/bbctrl/http
|
graft src/py/bbctrl/http
|
||||||
graft src/py/camotics
|
graft src/py/camotics
|
||||||
include src/avr/bbctrl-avr-firmware.hex
|
include src/avr/bbctrl-avr-firmware.hex
|
||||||
|
|||||||
17
Makefile
17
Makefile
@@ -27,8 +27,7 @@ BETA_VERSION := $(VERSION)-rc$(shell ./scripts/next-rc)
|
|||||||
BETA_PKG_NAME := bbctrl-$(BETA_VERSION)
|
BETA_PKG_NAME := bbctrl-$(BETA_VERSION)
|
||||||
|
|
||||||
SUBPROJECTS := avr boot pwr jig
|
SUBPROJECTS := avr boot pwr jig
|
||||||
WATCH := src/pug src/pug/templates src/stylus src/js src/resources Makefile
|
WATCH := src/pug src/pug/templates src/stylus src/js src/resources src/svelte-components src/static Makefile
|
||||||
WATCH += src/static
|
|
||||||
|
|
||||||
ifndef HOST
|
ifndef HOST
|
||||||
HOST=onefinity
|
HOST=onefinity
|
||||||
@@ -100,6 +99,9 @@ node_modules: package.json
|
|||||||
$(TARGET_DIR)/%: src/resources/%
|
$(TARGET_DIR)/%: src/resources/%
|
||||||
install -D $< $@
|
install -D $< $@
|
||||||
|
|
||||||
|
src/svelte-components/dist/%:
|
||||||
|
cd src/svelte-components && rm -rf dist && npm run build
|
||||||
|
|
||||||
$(TARGET_DIR)/index.html: build/templates.pug
|
$(TARGET_DIR)/index.html: build/templates.pug
|
||||||
$(TARGET_DIR)/index.html: $(wildcard src/static/js/*)
|
$(TARGET_DIR)/index.html: $(wildcard src/static/js/*)
|
||||||
$(TARGET_DIR)/index.html: $(wildcard src/static/css/*)
|
$(TARGET_DIR)/index.html: $(wildcard src/static/css/*)
|
||||||
@@ -108,9 +110,16 @@ $(TARGET_DIR)/index.html: $(wildcard src/js/*)
|
|||||||
$(TARGET_DIR)/index.html: $(wildcard src/stylus/*)
|
$(TARGET_DIR)/index.html: $(wildcard src/stylus/*)
|
||||||
$(TARGET_DIR)/index.html: src/resources/config-template.json
|
$(TARGET_DIR)/index.html: src/resources/config-template.json
|
||||||
$(TARGET_DIR)/index.html: $(wildcard src/resources/onefinity*defaults.json)
|
$(TARGET_DIR)/index.html: $(wildcard src/resources/onefinity*defaults.json)
|
||||||
|
$(TARGET_DIR)/index.html: $(wildcard src/svelte-components/dist/*)
|
||||||
|
|
||||||
$(TARGET_DIR)/%.html: src/pug/%.pug node_modules
|
FORCE:
|
||||||
@mkdir -p $(shell dirname $@)
|
|
||||||
|
$(TARGET_DIR)/%.html: src/pug/%.pug node_modules FORCE
|
||||||
|
cd src/svelte-components && rm -rf dist && npm run build
|
||||||
|
@mkdir -p $(TARGET_DIR)/svelte-components
|
||||||
|
cp src/svelte-components/dist/* $(TARGET_DIR)/svelte-components/
|
||||||
|
|
||||||
|
@mkdir -p $(TARGET_DIR)
|
||||||
$(PUG) -O pug-opts.js -P $< -o $(TARGET_DIR) || (rm -f $@; exit 1)
|
$(PUG) -O pug-opts.js -P $< -o $(TARGET_DIR) || (rm -f $@; exit 1)
|
||||||
|
|
||||||
pylint:
|
pylint:
|
||||||
|
|||||||
5569
package-lock.json
generated
Normal file
5569
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
14
package.json
14
package.json
@@ -1,16 +1,16 @@
|
|||||||
{
|
{
|
||||||
"name": "bbctrl",
|
"name": "bbctrl",
|
||||||
"version": "1.0.9",
|
"version": "1.0.10b1",
|
||||||
"homepage": "https://onefinitycnc.com/",
|
"homepage": "https://onefinitycnc.com/",
|
||||||
"repository": "https://github.com/OneFinityCNC/onefinity",
|
"repository": "https://github.com/OneFinityCNC/onefinity",
|
||||||
"license": "GPL-3.0+",
|
"license": "GPL-3.0+",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"browserify": "",
|
"browserify": "^17.0.0",
|
||||||
"jshint": "",
|
"jshint": "^2.13.4",
|
||||||
"jstransformer-escape-html": "",
|
"jstransformer-escape-html": "^1.1.0",
|
||||||
"jstransformer-stylus": "",
|
"jstransformer-scss": "^2.0.0",
|
||||||
|
"jstransformer-stylus": "^1.5.0",
|
||||||
"lodash.merge": "4.6.2",
|
"lodash.merge": "4.6.2",
|
||||||
"lodash.omit": "^4.5.0",
|
"pug-cli": "^1.0.0-alpha6"
|
||||||
"pug-cli": ""
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
python-packages/pathtools-0.1.2-py3-none-any.whl
Normal file
BIN
python-packages/pathtools-0.1.2-py3-none-any.whl
Normal file
Binary file not shown.
BIN
python-packages/watchdog-0.10.6-py3-none-any.whl
Normal file
BIN
python-packages/watchdog-0.10.6-py3-none-any.whl
Normal file
Binary file not shown.
1
requirements.txt
Normal file
1
requirements.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
watchdog==0.10.6
|
||||||
@@ -18,20 +18,23 @@ function query_config() {
|
|||||||
if [ -e $WLAN0_CFG ]; then
|
if [ -e $WLAN0_CFG ]; then
|
||||||
SSID=$(grep wpa-ssid $WLAN0_CFG |
|
SSID=$(grep wpa-ssid $WLAN0_CFG |
|
||||||
sed 's/^[[:space:]]*wpa-ssid "\([^"]*\)"/\1/')
|
sed 's/^[[:space:]]*wpa-ssid "\([^"]*\)"/\1/')
|
||||||
echo "{\"ssid\": \"$SSID\", \"mode\": \"client\"}"
|
# echo "{\"ssid\": \"$SSID\", \"mode\": \"client\"}"
|
||||||
|
echo "{\"ssid\": \"$SSID\"}"
|
||||||
|
|
||||||
else
|
else
|
||||||
if [ -e $HOSTAPD_CFG -a -e /etc/default/hostapd ]; then
|
# if [ -e $HOSTAPD_CFG -a -e /etc/default/hostapd ]; then
|
||||||
SSID=$(grep ^ssid= $HOSTAPD_CFG | sed 's/^ssid=\(.*\)$/\1/')
|
# SSID=$(grep ^ssid= $HOSTAPD_CFG | sed 's/^ssid=\(.*\)$/\1/')
|
||||||
CHANNEL=$(grep ^channel= $HOSTAPD_CFG |
|
# CHANNEL=$(grep ^channel= $HOSTAPD_CFG |
|
||||||
sed 's/^channel=\(.*\)$/\1/')
|
# sed 's/^channel=\(.*\)$/\1/')
|
||||||
|
|
||||||
echo -n "{\"ssid\": \"$SSID\", "
|
# echo -n "{\"ssid\": \"$SSID\", "
|
||||||
echo "\"channel\": $CHANNEL, \"mode\": \"ap\"}"
|
# echo "\"channel\": $CHANNEL, \"mode\": \"ap\"}"
|
||||||
|
|
||||||
else
|
# else
|
||||||
echo "{\"mode\": \"disabled\"}"
|
# echo "{\"mode\": \"disabled\"}"
|
||||||
fi
|
# fi
|
||||||
|
|
||||||
|
echo "{}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
3
scripts/download-dependencies.sh
Executable file
3
scripts/download-dependencies.sh
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash -ex
|
||||||
|
|
||||||
|
pip3 download -d python-packages -r requirements.txt
|
||||||
0
scripts/edit-boot-config
Normal file → Executable file
0
scripts/edit-boot-config
Normal file → Executable file
0
scripts/edit-config
Normal file → Executable file
0
scripts/edit-config
Normal file → Executable file
@@ -121,19 +121,21 @@ fi
|
|||||||
# Install rc.local
|
# Install rc.local
|
||||||
cp scripts/rc.local /etc/
|
cp scripts/rc.local /etc/
|
||||||
|
|
||||||
# Ensure that the watchdog python library is installed
|
|
||||||
pip3 list --format=columns | grep watchdog >/dev/null
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
pip3 install scripts/pathtools-0.1.2.tar.gz scripts/watchdog-v0.10.6.tar.gz
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Install bbctrl
|
# Install bbctrl
|
||||||
if $UPDATE_PY; then
|
if $UPDATE_PY; then
|
||||||
|
service bbctrl stop
|
||||||
|
|
||||||
rm -rf /usr/local/lib/python*/dist-packages/bbctrl-*
|
rm -rf /usr/local/lib/python*/dist-packages/bbctrl-*
|
||||||
|
|
||||||
|
# Ensure python dependencies are installed
|
||||||
|
pip3 install --no-index --find-links python-packages -r requirements.txt
|
||||||
|
|
||||||
./setup.py install --force
|
./setup.py install --force
|
||||||
service bbctrl restart
|
|
||||||
HTTP_DIR=$(find /usr/local/lib/ -type d -name "http")
|
HTTP_DIR=$(find /usr/local/lib/ -type d -name "http")
|
||||||
chmod 777 $HTTP_DIR
|
chmod 777 $HTTP_DIR
|
||||||
|
|
||||||
|
service bbctrl restart
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Expand the file system if necessary
|
# Expand the file system if necessary
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -1,5 +1,3 @@
|
|||||||
hostinfo.sh &
|
|
||||||
|
|
||||||
ratpoison &
|
ratpoison &
|
||||||
|
|
||||||
xset -dpms
|
xset -dpms
|
||||||
|
|||||||
54
setup.py
54
setup.py
@@ -5,26 +5,31 @@ import json
|
|||||||
|
|
||||||
pkg = json.load(open('package.json', 'r'))
|
pkg = json.load(open('package.json', 'r'))
|
||||||
|
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name = pkg['name'],
|
name=pkg['name'],
|
||||||
version = pkg['version'],
|
version=pkg['version'],
|
||||||
description = 'Buildbotics Machine Controller',
|
description='Buildbotics Machine Controller',
|
||||||
long_description = open('README.md', 'rt').read(),
|
long_description=open('README.md', 'rt').read(),
|
||||||
author = 'Joseph Coffland',
|
author='Joseph Coffland',
|
||||||
author_email = 'joseph@buildbotics.org',
|
author_email='joseph@buildbotics.org',
|
||||||
platforms = ['any'],
|
platforms=['any'],
|
||||||
license = pkg['license'],
|
license=pkg['license'],
|
||||||
url = pkg['homepage'],
|
url=pkg['homepage'],
|
||||||
package_dir = {'': 'src/py'},
|
package_dir={'': 'src/py'},
|
||||||
packages = ['bbctrl', 'inevent', 'lcd', 'camotics'],
|
packages=[
|
||||||
include_package_data = True,
|
'bbctrl',
|
||||||
entry_points = {
|
'inevent',
|
||||||
|
'lcd',
|
||||||
|
'camotics',
|
||||||
|
'iw_parse'
|
||||||
|
],
|
||||||
|
include_package_data=True,
|
||||||
|
entry_points={
|
||||||
'console_scripts': [
|
'console_scripts': [
|
||||||
'bbctrl = bbctrl:run'
|
'bbctrl = bbctrl:run'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
scripts = [
|
scripts=[
|
||||||
'scripts/update-bbctrl',
|
'scripts/update-bbctrl',
|
||||||
'scripts/upgrade-bbctrl',
|
'scripts/upgrade-bbctrl',
|
||||||
'scripts/sethostname',
|
'scripts/sethostname',
|
||||||
@@ -34,7 +39,14 @@ setup(
|
|||||||
'scripts/edit-config',
|
'scripts/edit-config',
|
||||||
'scripts/edit-boot-config',
|
'scripts/edit-boot-config',
|
||||||
'scripts/browser',
|
'scripts/browser',
|
||||||
],
|
],
|
||||||
install_requires = 'tornado sockjs-tornado pyserial pyudev smbus2 watchdog'.split(),
|
install_requires=[
|
||||||
zip_safe = False,
|
'tornado',
|
||||||
)
|
'sockjs-tornado',
|
||||||
|
'pyserial',
|
||||||
|
'pyudev',
|
||||||
|
'smbus2',
|
||||||
|
'watchdog'
|
||||||
|
],
|
||||||
|
zip_safe=False,
|
||||||
|
)
|
||||||
|
|||||||
@@ -49,18 +49,11 @@ module.exports = {
|
|||||||
configRestored: false,
|
configRestored: false,
|
||||||
confirmReset: false,
|
confirmReset: false,
|
||||||
configReset: false,
|
configReset: false,
|
||||||
latest: '',
|
|
||||||
autoCheckUpgrade: true,
|
autoCheckUpgrade: true,
|
||||||
reset_variant: ''
|
reset_variant: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
events: {
|
|
||||||
latest_version: function (version) {
|
|
||||||
this.latest = version
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
ready: function () {
|
ready: function () {
|
||||||
this.autoCheckUpgrade = this.config.admin['auto-check-upgrade']
|
this.autoCheckUpgrade = this.config.admin['auto-check-upgrade']
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,177 +1,14 @@
|
|||||||
/******************************************************************************\
|
|
||||||
|
|
||||||
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>
|
|
||||||
|
|
||||||
\******************************************************************************/
|
|
||||||
|
|
||||||
'use strict'
|
|
||||||
|
|
||||||
|
|
||||||
var api = require('./api');
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
template: '#admin-network-view-template',
|
template: "#admin-network-view-template",
|
||||||
props: ['config', 'state'],
|
|
||||||
|
|
||||||
|
attached: function () {
|
||||||
data: function () {
|
this.svelteComponent = SvelteComponents.createComponent(
|
||||||
return {
|
"AdminNetworkView",
|
||||||
hostnameSet: false,
|
document.getElementById("svelte-root")
|
||||||
usernameSet: false,
|
);
|
||||||
passwordSet: false,
|
|
||||||
redirectTimeout: 0,
|
|
||||||
hostname: '',
|
|
||||||
username: '',
|
|
||||||
current: '',
|
|
||||||
password: '',
|
|
||||||
password2: '',
|
|
||||||
wifi_mode: 'client',
|
|
||||||
wifi_ssid: '',
|
|
||||||
wifi_ch: undefined,
|
|
||||||
wifi_pass: '',
|
|
||||||
wifiConfirm: false,
|
|
||||||
rebooting: false
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
detached: function() {
|
||||||
ready: function () {
|
this.svelteComponent.$destroy();
|
||||||
api.get('hostname').done(function (hostname) {
|
|
||||||
this.hostname = hostname;
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
api.get('remote/username').done(function (username) {
|
|
||||||
this.username = username;
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
api.get('wifi').done(function (config) {
|
|
||||||
this.wifi_mode = config.mode;
|
|
||||||
this.wifi_ssid = config.ssid;
|
|
||||||
this.wifi_ch = config.channel;
|
|
||||||
}.bind(this));
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
redirect: function (hostname) {
|
|
||||||
if (0 < this.redirectTimeout) {
|
|
||||||
this.redirectTimeout -= 1;
|
|
||||||
setTimeout(function () {this.redirect(hostname)}.bind(this), 1000);
|
|
||||||
|
|
||||||
} else location.hostname = hostname;
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
set_hostname: function () {
|
|
||||||
api.put('hostname', {hostname: this.hostname}).done(function () {
|
|
||||||
this.redirectTimeout = 45;
|
|
||||||
this.hostnameSet = true;
|
|
||||||
|
|
||||||
api.put('reboot').always(function () {
|
|
||||||
if (String(location.hostname) == 'localhost') return;
|
|
||||||
|
|
||||||
var hostname = this.hostname;
|
|
||||||
if (String(location.hostname).endsWith('.local'))
|
|
||||||
hostname += '.local'
|
|
||||||
this.$dispatch('hostname-changed', hostname);
|
|
||||||
this.redirect(hostname);
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
}.bind(this)).fail(function (error) {
|
|
||||||
api.alert('Set hostname failed', error);
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
set_username: function () {
|
|
||||||
api.put('remote/username', {username: this.username}).done(function () {
|
|
||||||
this.usernameSet = true;
|
|
||||||
}.bind(this)).fail(function (error) {
|
|
||||||
api.alert('Set username failed', error);
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
set_password: function () {
|
|
||||||
if (this.password != this.password2) {
|
|
||||||
alert('Passwords to not match');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.password.length < 6) {
|
|
||||||
alert('Password too short');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
api.put('remote/password', {
|
|
||||||
current: this.current,
|
|
||||||
password: this.password
|
|
||||||
}).done(function () {
|
|
||||||
this.passwordSet = true;
|
|
||||||
}.bind(this)).fail(function (error) {
|
|
||||||
api.alert('Set password failed', error);
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
config_wifi: function () {
|
|
||||||
this.wifiConfirm = false;
|
|
||||||
|
|
||||||
if (!this.wifi_ssid.length) {
|
|
||||||
alert('SSID not set');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (32 < this.wifi_ssid.length) {
|
|
||||||
alert('SSID longer than 32 characters');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.wifi_pass.length && this.wifi_pass.length < 8) {
|
|
||||||
alert('WiFi password shorter than 8 characters');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (128 < this.wifi_pass.length) {
|
|
||||||
alert('WiFi password longer than 128 characters');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.rebooting = true;
|
|
||||||
|
|
||||||
var config = {
|
|
||||||
mode: this.wifi_mode,
|
|
||||||
channel: this.wifi_ch,
|
|
||||||
ssid: this.wifi_ssid,
|
|
||||||
pass: this.wifi_pass
|
|
||||||
}
|
|
||||||
|
|
||||||
api.put('wifi', config).fail(function (error) {
|
|
||||||
api.alert('Failed to configure WiFi', error);
|
|
||||||
this.rebooting = false;
|
|
||||||
}.bind(this))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -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>
|
|
||||||
|
|
||||||
\******************************************************************************/
|
|
||||||
|
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
398
src/js/app.js
398
src/js/app.js
@@ -1,36 +1,13 @@
|
|||||||
/******************************************************************************\
|
"use strict";
|
||||||
|
|
||||||
This file is part of the Buildbotics firmware.
|
const api = require("./api");
|
||||||
|
const cookie = require("./cookie")("bbctrl-");
|
||||||
|
const Sock = require("./sock");
|
||||||
|
|
||||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
SvelteComponents.initNetworkInfo();
|
||||||
All rights reserved.
|
SvelteComponents.createComponent("DialogHost",
|
||||||
|
document.getElementById("svelte-dialog-host")
|
||||||
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>
|
|
||||||
|
|
||||||
\******************************************************************************/
|
|
||||||
|
|
||||||
'use strict'
|
|
||||||
|
|
||||||
const api = require('./api');
|
|
||||||
const cookie = require('./cookie')('bbctrl-');
|
|
||||||
const Sock = require('./sock');
|
|
||||||
const omit = require('lodash.omit');
|
|
||||||
|
|
||||||
function is_newer_version(current, latest) {
|
function is_newer_version(current, latest) {
|
||||||
const pattern = /(\d+)\.(\d+)\.(\d+)(.*)/;
|
const pattern = /(\d+)\.(\d+)\.(\d+)(.*)/;
|
||||||
@@ -47,7 +24,8 @@ function is_newer_version(current, latest) {
|
|||||||
const patch = latestParts[3] - currentParts[3];
|
const patch = latestParts[3] - currentParts[3];
|
||||||
|
|
||||||
// If current is a pre-release, and latest is a release
|
// If current is a pre-release, and latest is a release
|
||||||
const betaToRelease = latestParts[4].length === 0 && currentParts[4].length > 0;
|
const betaToRelease =
|
||||||
|
latestParts[4].length === 0 && currentParts[4].length > 0;
|
||||||
|
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case major > 0:
|
case major > 0:
|
||||||
@@ -61,15 +39,17 @@ function is_newer_version(current, latest) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function is_object(o) { return o !== null && typeof o == 'object' }
|
function is_object(o) {
|
||||||
function is_array(o) { return Array.isArray(o) }
|
return o !== null && typeof o == "object";
|
||||||
|
}
|
||||||
function update_array(dst, src) {
|
function is_array(o) {
|
||||||
while (dst.length) dst.pop()
|
return Array.isArray(o);
|
||||||
for (var i = 0; i < src.length; i++)
|
|
||||||
Vue.set(dst, i, src[i]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function update_array(dst, src) {
|
||||||
|
while (dst.length) dst.pop();
|
||||||
|
for (var i = 0; i < src.length; i++) Vue.set(dst, i, src[i]);
|
||||||
|
}
|
||||||
|
|
||||||
function update_object(dst, src, remove) {
|
function update_object(dst, src, remove) {
|
||||||
var props, index, key, value;
|
var props, index, key, value;
|
||||||
@@ -79,8 +59,7 @@ function update_object(dst, src, remove) {
|
|||||||
|
|
||||||
for (index in props) {
|
for (index in props) {
|
||||||
key = props[index];
|
key = props[index];
|
||||||
if (!src.hasOwnProperty(key))
|
if (!src.hasOwnProperty(key)) Vue.delete(dst, key);
|
||||||
Vue.delete(dst, key);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,111 +70,104 @@ function update_object(dst, src, remove) {
|
|||||||
|
|
||||||
if (is_array(value) && dst.hasOwnProperty(key) && is_array(dst[key]))
|
if (is_array(value) && dst.hasOwnProperty(key) && is_array(dst[key]))
|
||||||
update_array(dst[key], value);
|
update_array(dst[key], value);
|
||||||
|
|
||||||
else if (is_object(value) && dst.hasOwnProperty(key) && is_object(dst[key]))
|
else if (is_object(value) && dst.hasOwnProperty(key) && is_object(dst[key]))
|
||||||
update_object(dst[key], value, remove);
|
update_object(dst[key], value, remove);
|
||||||
|
|
||||||
else Vue.set(dst, key, value);
|
else Vue.set(dst, key, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = new Vue({
|
module.exports = new Vue({
|
||||||
el: 'body',
|
el: "body",
|
||||||
|
|
||||||
|
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
status: 'connecting',
|
status: "connecting",
|
||||||
currentView: 'loading',
|
currentView: "loading",
|
||||||
|
display_units: localStorage.getItem("display_units") || "METRIC",
|
||||||
index: -1,
|
index: -1,
|
||||||
modified: false,
|
modified: false,
|
||||||
template: require('../resources/config-template.json'),
|
template: require("../resources/config-template.json"),
|
||||||
config: {
|
config: {
|
||||||
settings: { units: 'METRIC' },
|
settings: { units: "METRIC" },
|
||||||
motors: [{}, {}, {}, {}],
|
motors: [{}, {}, {}, {}],
|
||||||
version: '<loading>',
|
version: "<loading>",
|
||||||
full_version: '<loading>'
|
full_version: "<loading>",
|
||||||
},
|
},
|
||||||
state: {
|
state: {
|
||||||
messages: [],
|
messages: [],
|
||||||
probing_active: false,
|
|
||||||
wait_for_probing_complete: false,
|
|
||||||
show_probe_complete_modal: false,
|
|
||||||
show_probe_failed_modal: false
|
|
||||||
},
|
},
|
||||||
video_size: cookie.get('video-size', 'small'),
|
video_size: cookie.get("video-size", "small"),
|
||||||
crosshair: cookie.get('crosshair', 'false') != 'false',
|
crosshair: cookie.get("crosshair", "false") != "false",
|
||||||
errorTimeout: 30,
|
errorTimeout: 30,
|
||||||
errorTimeoutStart: 0,
|
errorTimeoutStart: 0,
|
||||||
errorShow: false,
|
errorShow: false,
|
||||||
errorMessage: '',
|
errorMessage: "",
|
||||||
confirmUpgrade: false,
|
confirmUpgrade: false,
|
||||||
confirmUpload: false,
|
confirmUpload: false,
|
||||||
firmwareUpgrading: false,
|
firmwareUpgrading: false,
|
||||||
checkedUpgrade: false,
|
checkedUpgrade: false,
|
||||||
firmwareName: '',
|
firmwareName: "",
|
||||||
latestVersion: '',
|
latestVersion: "",
|
||||||
ipAddress: '0.0.0.0',
|
|
||||||
wifiSSID: '',
|
|
||||||
confirmShutdown: false,
|
confirmShutdown: false,
|
||||||
diskSpace: ''
|
};
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
'estop': { template: '#estop-template' },
|
estop: { template: "#estop-template" },
|
||||||
'loading-view': { template: '<h1>Loading...</h1>' },
|
"loading-view": { template: "<h1>Loading...</h1>" },
|
||||||
'control-view': require('./control-view'),
|
"control-view": require("./control-view"),
|
||||||
'settings-view': require('./settings-view'),
|
"settings-view": require("./settings-view"),
|
||||||
'motor-view': require('./motor-view'),
|
"motor-view": require("./motor-view"),
|
||||||
'tool-view': require('./tool-view'),
|
"tool-view": require("./tool-view"),
|
||||||
'io-view': require('./io-view'),
|
"io-view": require("./io-view"),
|
||||||
'admin-general-view': require('./admin-general-view'),
|
"admin-general-view": require("./admin-general-view"),
|
||||||
'admin-network-view': require('./admin-network-view'),
|
"admin-network-view": require("./admin-network-view"),
|
||||||
'help-view': { template: '#help-view-template' },
|
"help-view": { template: "#help-view-template" },
|
||||||
'cheat-sheet-view': {
|
"cheat-sheet-view": {
|
||||||
template: '#cheat-sheet-view-template',
|
template: "#cheat-sheet-view-template",
|
||||||
data: function () { return { showUnimplemented: false } }
|
data: function () {
|
||||||
}
|
return {
|
||||||
|
showUnimplemented: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
display_units: function (value) {
|
||||||
|
localStorage.setItem("display_units", value);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
events: {
|
events: {
|
||||||
'config-changed': function () {
|
"config-changed": function () {
|
||||||
this.modified = true;
|
this.modified = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
'hostname-changed': function (hostname) {
|
|
||||||
this.hostname = hostname
|
|
||||||
},
|
|
||||||
|
|
||||||
send: function (msg) {
|
send: function (msg) {
|
||||||
if (this.status == 'connected') {
|
if (this.status == "connected") {
|
||||||
console.debug('>', msg);
|
|
||||||
this.sock.send(msg);
|
this.sock.send(msg);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
connected: function () {
|
connected: function () {
|
||||||
this.update()
|
this.update();
|
||||||
},
|
},
|
||||||
|
|
||||||
update: function () {
|
update: function () {
|
||||||
this.update()
|
this.update();
|
||||||
},
|
},
|
||||||
|
|
||||||
check: function () {
|
check: async function () {
|
||||||
this.latestVersion = '';
|
try {
|
||||||
|
const response = await fetch("https://raw.githubusercontent.com/OneFinityCNC/onefinity-release/master/latest.txt", {
|
||||||
|
cache: "no-cache"
|
||||||
|
});
|
||||||
|
|
||||||
$.ajax({
|
this.latestVersion = (await response.text()).trim();
|
||||||
type: 'GET',
|
} catch (err) {
|
||||||
url: 'https://raw.githubusercontent.com/OneFinityCNC/onefinity-release/master/latest.txt',
|
this.latestVersion = "";
|
||||||
data: { hid: this.state.hid },
|
}
|
||||||
cache: false
|
|
||||||
|
|
||||||
}).done(function (data) {
|
|
||||||
this.latestVersion = data;
|
|
||||||
this.$broadcast('latest_version', data);
|
|
||||||
}.bind(this))
|
|
||||||
},
|
},
|
||||||
|
|
||||||
upgrade: function () {
|
upgrade: function () {
|
||||||
@@ -221,7 +193,7 @@ module.exports = new Vue({
|
|||||||
// Popup error dialog
|
// Popup error dialog
|
||||||
this.errorShow = true;
|
this.errorShow = true;
|
||||||
this.errorMessage = msg.msg;
|
this.errorMessage = msg.msg;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
@@ -236,49 +208,45 @@ module.exports = new Vue({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return msgs;
|
return msgs;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
ready: function () {
|
ready: function () {
|
||||||
$(window).on('hashchange', this.parse_hash);
|
$(window).on("hashchange", this.parse_hash);
|
||||||
this.connect();
|
this.connect();
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
metric: function () {
|
|
||||||
return this.config.settings.units != 'IMPERIAL'
|
|
||||||
},
|
|
||||||
|
|
||||||
block_error_dialog: function () {
|
block_error_dialog: function () {
|
||||||
this.errorTimeoutStart = Date.now();
|
this.errorTimeoutStart = Date.now();
|
||||||
this.errorShow = false;
|
this.errorShow = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
toggle_video: function (e) {
|
toggle_video: function (e) {
|
||||||
if (this.video_size == 'small') this.video_size = 'large';
|
if (this.video_size == "small") this.video_size = "large";
|
||||||
else if (this.video_size == 'large') this.video_size = 'small';
|
else if (this.video_size == "large") this.video_size = "small";
|
||||||
cookie.set('video-size', this.video_size);
|
cookie.set("video-size", this.video_size);
|
||||||
},
|
},
|
||||||
|
|
||||||
toggle_crosshair: function (e) {
|
toggle_crosshair: function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.crosshair = !this.crosshair;
|
this.crosshair = !this.crosshair;
|
||||||
cookie.set('crosshair', this.crosshair);
|
cookie.set("crosshair", this.crosshair);
|
||||||
},
|
},
|
||||||
|
|
||||||
estop: function () {
|
estop: function () {
|
||||||
if (this.state.xx == 'ESTOPPED') api.put('clear');
|
if (this.state.xx == "ESTOPPED") api.put("clear");
|
||||||
else api.put('estop');
|
else api.put("estop");
|
||||||
},
|
},
|
||||||
|
|
||||||
upgrade_confirmed: async function () {
|
upgrade_confirmed: async function () {
|
||||||
this.confirmUpgrade = false;
|
this.confirmUpgrade = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await api.put('upgrade');
|
await api.put("upgrade");
|
||||||
this.firmwareUpgrading = true;
|
this.firmwareUpgrading = true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
api.alert('Error during upgrade.');
|
api.alert("Error during upgrade.");
|
||||||
console.error("Error during upgrade", err);
|
console.error("Error during upgrade", err);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -287,22 +255,27 @@ module.exports = new Vue({
|
|||||||
this.confirmUpload = false;
|
this.confirmUpload = false;
|
||||||
|
|
||||||
const form = new FormData();
|
const form = new FormData();
|
||||||
form.append('firmware', this.firmware);
|
form.append("firmware", this.firmware);
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: '/api/firmware/update',
|
url: "/api/firmware/update",
|
||||||
type: 'PUT',
|
type: "PUT",
|
||||||
data: form,
|
data: form,
|
||||||
cache: false,
|
cache: false,
|
||||||
contentType: false,
|
contentType: false,
|
||||||
processData: false
|
processData: false,
|
||||||
|
})
|
||||||
}).success(function () {
|
.success(
|
||||||
this.firmwareUpgrading = true;
|
function () {
|
||||||
}.bind(this)).error(function (err) {
|
this.firmwareUpgrading = true;
|
||||||
api.alert('Firmware update failed');
|
}.bind(this)
|
||||||
console.error('Firmware update failed', err);
|
)
|
||||||
}.bind(this))
|
.error(
|
||||||
|
function (err) {
|
||||||
|
api.alert("Firmware update failed");
|
||||||
|
console.error("Firmware update failed", err);
|
||||||
|
}.bind(this)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
show_upgrade: function () {
|
show_upgrade: function () {
|
||||||
@@ -310,97 +283,64 @@ module.exports = new Vue({
|
|||||||
return is_newer_version(this.config.version, this.latestVersion);
|
return is_newer_version(this.config.version, this.latestVersion);
|
||||||
},
|
},
|
||||||
|
|
||||||
update: function () {
|
update: async function () {
|
||||||
api.get('config/load').done(function (config) {
|
const config = await api.get("config/load");
|
||||||
update_object(this.config, config, true);
|
|
||||||
this.parse_hash();
|
|
||||||
|
|
||||||
if (!this.checkedUpgrade) {
|
update_object(this.config, config, true);
|
||||||
this.checkedUpgrade = true;
|
this.parse_hash();
|
||||||
|
|
||||||
var check = this.config.admin['auto-check-upgrade'];
|
if (!this.devModChecked) {
|
||||||
if (typeof check == 'undefined' || check)
|
this.devModChecked = true;
|
||||||
this.$emit('check');
|
if (this.config.devmode) {
|
||||||
|
SvelteComponents.createComponent("Devmode",
|
||||||
|
document.getElementById("svelte-devmode-host")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.check_ip_address();
|
if (!this.checkedUpgrade) {
|
||||||
this.check_ssid();
|
this.checkedUpgrade = true;
|
||||||
}.bind(this))
|
|
||||||
},
|
|
||||||
|
|
||||||
check_ip_address: function () {
|
var check = this.config.admin["auto-check-upgrade"];
|
||||||
$.ajax({
|
if (typeof check == "undefined" || check) this.$emit("check");
|
||||||
type: 'GET',
|
}
|
||||||
url: 'hostinfo.txt',
|
|
||||||
data: { hid: this.state.hid },
|
|
||||||
cache: false
|
|
||||||
|
|
||||||
}).done(function (data) {
|
SvelteComponents.handleConfigUpdate(this.config);
|
||||||
console.debug('>', data);
|
|
||||||
this.ipAddress = 'IP:' + data;
|
|
||||||
this.$broadcast('ipAddress', data);
|
|
||||||
}.bind(this))
|
|
||||||
},
|
|
||||||
|
|
||||||
check_ssid: function () {
|
|
||||||
$.ajax({
|
|
||||||
type: 'GET',
|
|
||||||
url: 'ssidinfo.txt',
|
|
||||||
data: { hid: this.state.hid },
|
|
||||||
cache: false
|
|
||||||
|
|
||||||
}).done(function (data) {
|
|
||||||
console.debug('>', data);
|
|
||||||
this.wifiSSID = 'SSID:' + data;
|
|
||||||
this.$broadcast('wifiSSID', data);
|
|
||||||
}.bind(this))
|
|
||||||
},
|
|
||||||
|
|
||||||
get_ip_address: function () {
|
|
||||||
console.debug('get_ip>', this.ipAddress);
|
|
||||||
return this.ipAddress;
|
|
||||||
},
|
|
||||||
|
|
||||||
get_ssid: function () {
|
|
||||||
console.debug('get_ssid>', this.wifiSSID);
|
|
||||||
return this.wifiSSID;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
shutdown: function () {
|
shutdown: function () {
|
||||||
this.confirmShutdown = false;
|
this.confirmShutdown = false;
|
||||||
api.put('shutdown');
|
api.put("shutdown");
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
reboot: function () {
|
reboot: function () {
|
||||||
this.confirmShutdown = false;
|
this.confirmShutdown = false;
|
||||||
api.put('reboot');
|
api.put("reboot");
|
||||||
},
|
},
|
||||||
|
|
||||||
connect: function () {
|
connect: function () {
|
||||||
this.sock = new Sock(`//${location.host}/sockjs`);
|
this.sock = new Sock(`//${location.host}/sockjs`);
|
||||||
|
|
||||||
this.sock.onmessage = (e) => {
|
this.sock.onmessage = (e) => {
|
||||||
if (typeof e.data != 'object') {
|
if (typeof e.data != "object") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('log' in e.data) {
|
if ("log" in e.data) {
|
||||||
if (e.data.log.msg === "Switch not found") {
|
if (e.data.log.msg !== "Switch not found") {
|
||||||
this.$broadcast('probing_failed');
|
this.$broadcast("log", e.data.log);
|
||||||
} else {
|
|
||||||
this.$broadcast('log', e.data.log);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
delete e.data.log;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for session ID change on controller
|
// Check for session ID change on controller
|
||||||
if ('sid' in e.data) {
|
if ("sid" in e.data) {
|
||||||
if (typeof this.sid == 'undefined') {
|
if (typeof this.sid == "undefined") {
|
||||||
this.sid = e.data.sid;
|
this.sid = e.data.sid;
|
||||||
} else if (this.sid != e.data.sid) {
|
} else if (this.sid != e.data.sid) {
|
||||||
if (typeof this.hostname !== 'undefined' && location.hostname !== 'localhost') {
|
if (
|
||||||
|
typeof this.hostname !== "undefined" &&
|
||||||
|
location.hostname !== "localhost"
|
||||||
|
) {
|
||||||
location.hostname = this.hostname;
|
location.hostname = this.hostname;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -408,49 +348,23 @@ module.exports = new Vue({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set this to true to get console output of changes to the state
|
|
||||||
const debugStateChanges = false;
|
|
||||||
if (debugStateChanges) {
|
|
||||||
const data = omit(e.data, [
|
|
||||||
'vdd',
|
|
||||||
'vin',
|
|
||||||
'vout',
|
|
||||||
'motor',
|
|
||||||
'temp',
|
|
||||||
'heartbeat',
|
|
||||||
'load1',
|
|
||||||
'load2',
|
|
||||||
'rpi_temp'
|
|
||||||
]);
|
|
||||||
if (Object.keys(data).length > 0) {
|
|
||||||
console.log(JSON.stringify(data, null, 4));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
update_object(this.state, e.data, false);
|
update_object(this.state, e.data, false);
|
||||||
|
|
||||||
if (this.state.pw === 0) {
|
SvelteComponents.handleControllerStateUpdate(this.state);
|
||||||
Vue.set(this.state, "saw_probe_connected", true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.state.cycle === 'idle') {
|
delete this.state.log;
|
||||||
if (this.state.wait_for_probing_complete) {
|
|
||||||
Vue.set(this.state, "wait_for_probing_complete", false);
|
|
||||||
this.$broadcast("probing_complete");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$broadcast('update');
|
this.$broadcast("update");
|
||||||
};
|
};
|
||||||
|
|
||||||
this.sock.onopen = () => {
|
this.sock.onopen = () => {
|
||||||
this.status = 'connected';
|
this.status = "connected";
|
||||||
this.$emit(this.status);
|
this.$emit(this.status);
|
||||||
this.$broadcast(this.status);
|
this.$broadcast(this.status);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.sock.onclose = () => {
|
this.sock.onclose = () => {
|
||||||
this.status = 'disconnected';
|
this.status = "disconnected";
|
||||||
this.$emit(this.status);
|
this.$emit(this.status);
|
||||||
this.$broadcast(this.status);
|
this.$broadcast(this.status);
|
||||||
};
|
};
|
||||||
@@ -460,47 +374,51 @@ module.exports = new Vue({
|
|||||||
var hash = location.hash.substr(1);
|
var hash = location.hash.substr(1);
|
||||||
|
|
||||||
if (!hash.trim().length) {
|
if (!hash.trim().length) {
|
||||||
location.hash = 'control';
|
location.hash = "control";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var parts = hash.split(':');
|
var parts = hash.split(":");
|
||||||
|
|
||||||
if (parts.length == 2) this.index = parts[1];
|
if (parts.length == 2) this.index = parts[1];
|
||||||
|
|
||||||
this.currentView = parts[0];
|
this.currentView = parts[0];
|
||||||
},
|
},
|
||||||
|
|
||||||
save: function () {
|
save: async function () {
|
||||||
const selected_tool = this.config.tool['selected-tool'];
|
const selected_tool = this.config.tool["selected-tool"];
|
||||||
const saveModbus = selected_tool !== "pwm" &&
|
const saveModbus =
|
||||||
|
selected_tool !== "pwm" &&
|
||||||
selected_tool !== "laser" &&
|
selected_tool !== "laser" &&
|
||||||
selected_tool !== "router";
|
selected_tool !== "router";
|
||||||
const settings = {
|
const settings = {
|
||||||
['tool']: { ...this.config.tool },
|
["tool"]: { ...this.config.tool },
|
||||||
['pwm-spindle']: { ...this.config['pwm-spindle'] },
|
["pwm-spindle"]: { ...this.config["pwm-spindle"] },
|
||||||
['modbus-spindle']: saveModbus ? { ...this.config['modbus-spindle'] } : undefined
|
["modbus-spindle"]: saveModbus
|
||||||
}
|
? { ...this.config["modbus-spindle"] }
|
||||||
delete settings.tool['tool-type'];
|
: undefined,
|
||||||
|
};
|
||||||
|
delete settings.tool["tool-type"];
|
||||||
|
|
||||||
this.config['selected-tool-settings'][selected_tool] = settings;
|
this.config["selected-tool-settings"][selected_tool] = settings;
|
||||||
|
|
||||||
api.put('config/save', this.config).done(function (data) {
|
try {
|
||||||
|
await api.put("config/save", this.config);
|
||||||
this.modified = false;
|
this.modified = false;
|
||||||
}.bind(this)).fail(function (error) {
|
} catch (error) {
|
||||||
api.alert('Save failed', error);
|
api.alert("Save failed", error);
|
||||||
});
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
close_messages: function (action) {
|
close_messages: function (action) {
|
||||||
if (action == 'stop') api.put('stop');
|
if (action == "stop") api.put("stop");
|
||||||
if (action == 'continue') api.put('unpause');
|
if (action == "continue") api.put("unpause");
|
||||||
|
|
||||||
// Acknowledge messages
|
// Acknowledge messages
|
||||||
if (this.state.messages.length) {
|
if (this.state.messages.length) {
|
||||||
var id = this.state.messages.slice(-1)[0].id
|
var id = this.state.messages.slice(-1)[0].id;
|
||||||
api.put('message/' + id + '/ack');
|
api.put("message/" + id + "/ack");
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -1,107 +1,72 @@
|
|||||||
/******************************************************************************\
|
|
||||||
|
|
||||||
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>
|
|
||||||
|
|
||||||
\******************************************************************************/
|
|
||||||
|
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
|
|
||||||
function is_defined(x) {return typeof x != 'undefined'}
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
props: ['state', 'config'],
|
props: ['state', 'config'],
|
||||||
|
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
x: function () {return this._compute_axis('x')},
|
metric: function () { this.$root.display_units === "METRIC" },
|
||||||
y: function () {return this._compute_axis('y')},
|
x: function () { return this._compute_axis('x') },
|
||||||
z: function () {return this._compute_axis('z')},
|
y: function () { return this._compute_axis('y') },
|
||||||
a: function () {return this._compute_axis('a')},
|
z: function () { return this._compute_axis('z') },
|
||||||
b: function () {return this._compute_axis('b')},
|
a: function () { return this._compute_axis('a') },
|
||||||
c: function () {return this._compute_axis('c')},
|
b: function () { return this._compute_axis('b') },
|
||||||
axes: function () {return this._compute_axes()}
|
c: function () { return this._compute_axis('c') },
|
||||||
|
axes: function () { return this._compute_axes() }
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
_convert_length: function (value) {
|
_convert_length: function (value) {
|
||||||
return this.state.imperial ? value / 25.4 : value;
|
return this.metric
|
||||||
|
? value
|
||||||
|
: value / 25.4;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
_length_str: function (value) {
|
_length_str: function (value) {
|
||||||
return this._convert_length(value).toLocaleString() +
|
return this._convert_length(value).toLocaleString() +
|
||||||
(this.state.imperial ? ' in' : ' mm');
|
(this.metric ? ' mm' : ' in');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
_compute_axis: function (axis) {
|
_compute_axis: function (axis) {
|
||||||
var abs = this.state[axis + 'p'] || 0;
|
var abs = this.state[axis + 'p'] || 0;
|
||||||
var off = this.state['offset_' + axis];
|
var off = this.state['offset_' + axis];
|
||||||
var motor_id = this._get_motor_id(axis);
|
var motor_id = this._get_motor_id(axis);
|
||||||
var motor = motor_id == -1 ? {} : this.config.motors[motor_id];
|
var motor = motor_id == -1 ? {} : this.config.motors[motor_id];
|
||||||
var enabled = typeof motor.enabled != 'undefined' && motor.enabled;
|
var enabled = typeof motor.enabled != 'undefined' && motor.enabled;
|
||||||
var homingMode = motor['homing-mode']
|
var homingMode = motor['homing-mode']
|
||||||
var homed = this.state[motor_id + 'homed'];
|
var homed = this.state[motor_id + 'homed'];
|
||||||
var min = this.state[motor_id + 'tn'];
|
var min = this.state[motor_id + 'tn'];
|
||||||
var max = this.state[motor_id + 'tm'];
|
var max = this.state[motor_id + 'tm'];
|
||||||
var dim = max - min;
|
var dim = max - min;
|
||||||
var pathMin = this.state['path_min_' + axis];
|
var pathMin = this.state['path_min_' + axis];
|
||||||
var pathMax = this.state['path_max_' + axis];
|
var pathMax = this.state['path_max_' + axis];
|
||||||
var pathDim = pathMax - pathMin;
|
var pathDim = pathMax - pathMin;
|
||||||
var under = pathMin + off < min;
|
var under = pathMin + off < min;
|
||||||
var over = max < pathMax + off;
|
var over = max < pathMax + off;
|
||||||
var klass = (homed ? 'homed' : 'unhomed') + ' axis-' + axis;
|
var klass = (homed ? 'homed' : 'unhomed') + ' axis-' + axis;
|
||||||
var state = 'UNHOMED';
|
var state = 'UNHOMED';
|
||||||
var icon = 'question-circle';
|
var icon = 'question-circle';
|
||||||
var fault = this.state[motor_id + 'df'] & 0x1f;
|
var fault = this.state[motor_id + 'df'] & 0x1f;
|
||||||
var shutdown = this.state.power_shutdown;
|
var shutdown = this.state.power_shutdown;
|
||||||
var title;
|
var title;
|
||||||
var ticon = 'question-circle';
|
var ticon = 'question-circle';
|
||||||
var tstate = 'NO FILE';
|
var tstate = 'NO FILE';
|
||||||
var toolmsg;
|
var toolmsg;
|
||||||
var tklass = (homed ? 'homed' : 'unhomed') + ' axis-' + axis;
|
var tklass = (homed ? 'homed' : 'unhomed') + ' axis-' + axis;
|
||||||
|
|
||||||
if (fault || shutdown) {
|
if (fault || shutdown) {
|
||||||
state = shutdown ? 'SHUTDOWN' : 'FAULT';
|
state = shutdown ? 'SHUTDOWN' : 'FAULT';
|
||||||
klass += ' error';
|
klass += ' error';
|
||||||
icon = 'exclamation-circle';
|
icon = 'exclamation-circle';
|
||||||
|
} else if (homed) {
|
||||||
} else if(homed) {
|
|
||||||
state = 'HOMED';
|
state = 'HOMED';
|
||||||
icon = 'check-circle';
|
icon = 'check-circle';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (0 < dim && dim < pathDim) {
|
if (0 < dim && dim < pathDim) {
|
||||||
tstate = 'NO FIT';
|
tstate = 'NO FIT';
|
||||||
tklass += ' error';
|
tklass += ' error';
|
||||||
ticon = 'ban';
|
ticon = 'ban';
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
if (over || under) {
|
if (over || under) {
|
||||||
tstate = over ? 'OVER' : 'UNDER';
|
tstate = over ? 'OVER' : 'UNDER';
|
||||||
tklass += ' warn';
|
tklass += ' warn';
|
||||||
@@ -113,46 +78,44 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case 'UNHOMED': title = 'Click the home button to home axis.'; break;
|
case 'UNHOMED': title = 'Click the home button to home axis.'; break;
|
||||||
case 'HOMED': title = 'Axis successfuly homed.'; break;
|
case 'HOMED': title = 'Axis successfuly homed.'; break;
|
||||||
case 'FAULT':
|
case 'FAULT':
|
||||||
title = 'Motor driver fault. A potentially damaging electrical ' +
|
title = 'Motor driver fault. A potentially damaging electrical ' +
|
||||||
'condition was detected and the motor driver was shutdown. ' +
|
'condition was detected and the motor driver was shutdown. ' +
|
||||||
'Please power down the controller and check your motor cabling. ' +
|
'Please power down the controller and check your motor cabling. ' +
|
||||||
'See the "Motor Faults" table on the "Indicators" tab for more ' +
|
'See the "Motor Faults" table on the "Indicators" tab for more ' +
|
||||||
'information.';
|
'information.';
|
||||||
break;
|
break;
|
||||||
case 'SHUTDOWN':
|
|
||||||
title = 'Motor power fault. All motors in shutdown. ' +
|
|
||||||
'See the "Power Faults" table on the "Indicators" tab for more ' +
|
|
||||||
'information. Reboot controller to reset.';
|
|
||||||
}
|
|
||||||
|
|
||||||
switch(tstate) {
|
|
||||||
|
|
||||||
case 'OVER':
|
|
||||||
toolmsg = 'Caution: The current tool path file would move ' +
|
|
||||||
this._length_str(pathMax + off - max) + ' above axis limit with the current offset.';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'UNDER':
|
case 'SHUTDOWN':
|
||||||
toolmsg = 'Caution: The current tool path file would move ' +
|
title = 'Motor power fault. All motors in shutdown. ' +
|
||||||
this._length_str(min - pathMin - off) + ' below limit with the current offset.';
|
'See the "Power Faults" table on the "Indicators" tab for more ' +
|
||||||
break;
|
'information. Reboot controller to reset.';
|
||||||
|
}
|
||||||
case 'NO FIT':
|
|
||||||
toolmsg = 'Warning: The current tool path dimensions (' +
|
switch (tstate) {
|
||||||
this._length_str(pathDim) + ') exceed axis dimensions (' +
|
case 'OVER':
|
||||||
this._length_str(dim) + ') by ' +
|
toolmsg = 'Caution: The current tool path file would move ' +
|
||||||
this._length_str(pathDim - dim) + '.';
|
this._length_str(pathMax + off - max) + ' above axis limit with the current offset.';
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
case 'UNDER':
|
||||||
toolmsg = 'Tool path ' + axis + ' dimensions OK.';
|
toolmsg = 'Caution: The current tool path file would move ' +
|
||||||
break;
|
this._length_str(min - pathMin - off) + ' below limit with the current offset.';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'NO FIT':
|
||||||
|
toolmsg = 'Warning: The current tool path dimensions (' +
|
||||||
|
this._length_str(pathDim) + ') exceed axis dimensions (' +
|
||||||
|
this._length_str(dim) + ') by ' +
|
||||||
|
this._length_str(pathDim - dim) + '.';
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
toolmsg = 'Tool path ' + axis + ' dimensions OK.';
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pos: abs - off,
|
pos: abs - off,
|
||||||
@@ -179,7 +142,6 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
_get_motor_id: function (axis) {
|
_get_motor_id: function (axis) {
|
||||||
for (var i = 0; i < this.config.motors.length; i++) {
|
for (var i = 0; i < this.config.motors.length; i++) {
|
||||||
var motor = this.config.motors[i];
|
var motor = this.config.motors[i];
|
||||||
@@ -189,7 +151,6 @@ module.exports = {
|
|||||||
return -1;
|
return -1;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
_compute_axes: function () {
|
_compute_axes: function () {
|
||||||
var homed = false;
|
var homed = false;
|
||||||
|
|
||||||
@@ -197,7 +158,7 @@ module.exports = {
|
|||||||
var axis = this[name];
|
var axis = this[name];
|
||||||
|
|
||||||
if (!axis.enabled) continue
|
if (!axis.enabled) continue
|
||||||
if (!axis.homed) {homed = false; break}
|
if (!axis.homed) { homed = false; break }
|
||||||
homed = true;
|
homed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,10 +178,11 @@ module.exports = {
|
|||||||
if (error) klass += ' error';
|
if (error) klass += ' error';
|
||||||
else if (warn) klass += ' warn';
|
else if (warn) klass += ' warn';
|
||||||
|
|
||||||
if(!homed && this.ask_home)
|
if (!homed && this.ask_home) {
|
||||||
{
|
this.ask_home = false;
|
||||||
this.ask_home_msg = true;
|
SvelteComponents.showDialog("HomeMachine", {
|
||||||
this.ask_home = false;
|
home: () => this.home()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,33 +1,6 @@
|
|||||||
/******************************************************************************\
|
|
||||||
|
|
||||||
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>
|
|
||||||
|
|
||||||
\******************************************************************************/
|
|
||||||
|
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
var api = require('./api');
|
var api = require('./api');
|
||||||
var cookie = require('./cookie')('bbctrl-');
|
var cookie = require('./cookie')('bbctrl-');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
@@ -36,7 +9,7 @@ module.exports = {
|
|||||||
|
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
mach_units: 'METRIC',
|
mach_units: this.$root.state.metric ? "METRIC" : "IMPERIAL",
|
||||||
mdi: '',
|
mdi: '',
|
||||||
last_file: undefined,
|
last_file: undefined,
|
||||||
last_file_time: undefined,
|
last_file_time: undefined,
|
||||||
@@ -62,16 +35,26 @@ module.exports = {
|
|||||||
b: false,
|
b: false,
|
||||||
c: false
|
c: false
|
||||||
},
|
},
|
||||||
|
jog_incr_amounts: {
|
||||||
|
"METRIC": {
|
||||||
|
fine: 0.1,
|
||||||
|
small: 1.0,
|
||||||
|
medium: 10,
|
||||||
|
large: 100,
|
||||||
|
},
|
||||||
|
"IMPERIAL": {
|
||||||
|
fine: 0.005,
|
||||||
|
small: 0.05,
|
||||||
|
medium: 0.5,
|
||||||
|
large: 5,
|
||||||
|
}
|
||||||
|
},
|
||||||
axis_position: 0,
|
axis_position: 0,
|
||||||
|
jog_incr: localStorage.getItem("jog_incr") || 'small',
|
||||||
jog_step: cookie.get_bool('jog-step'),
|
jog_step: cookie.get_bool('jog-step'),
|
||||||
jog_adjust: parseInt(cookie.get('jog-adjust', 2)),
|
jog_adjust: parseInt(cookie.get('jog-adjust', 2)),
|
||||||
deleteGCode: false,
|
deleteGCode: false,
|
||||||
tab: 'auto',
|
tab: 'auto',
|
||||||
jog_incr: 1.0,
|
|
||||||
tool_diameter: 6.35,
|
|
||||||
tool_diameter_for_prompt: 6.35,
|
|
||||||
show_probe_test_modal: false,
|
|
||||||
show_tool_diameter_modal: false,
|
|
||||||
toolpath_msg: {
|
toolpath_msg: {
|
||||||
x: false,
|
x: false,
|
||||||
y: false,
|
y: false,
|
||||||
@@ -81,7 +64,6 @@ module.exports = {
|
|||||||
c: false
|
c: false
|
||||||
},
|
},
|
||||||
ask_home: true,
|
ask_home: true,
|
||||||
ask_home_msg: false,
|
|
||||||
ask_zero_xy_msg: false,
|
ask_zero_xy_msg: false,
|
||||||
ask_zero_z_msg: false,
|
ask_zero_z_msg: false,
|
||||||
showGcodeMessage: false
|
showGcodeMessage: false
|
||||||
@@ -94,33 +76,24 @@ module.exports = {
|
|||||||
'gcode-viewer': require('./gcode-viewer')
|
'gcode-viewer': require('./gcode-viewer')
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
'state.imperial': {
|
jog_incr: function (value) {
|
||||||
handler: function (imperial) {
|
localStorage.setItem("jog_incr", value);
|
||||||
this.mach_units = imperial ? 'IMPERIAL' : 'METRIC';
|
|
||||||
},
|
|
||||||
immediate: true
|
|
||||||
},
|
|
||||||
|
|
||||||
'state.bitDiameter': {
|
|
||||||
handler: function (bitDiameter) {
|
|
||||||
this.tool_diameter = bitDiameter;
|
|
||||||
},
|
|
||||||
immediate: true
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'state.metric': {
|
||||||
mach_units: function (units) {
|
handler: function (metric) {
|
||||||
if ((units == 'METRIC') != this.metric)
|
this.mach_units = metric
|
||||||
this.send(units == 'METRIC' ? 'G21' : 'G20');
|
? 'METRIC'
|
||||||
|
: 'IMPERIAL';
|
||||||
this.units_changed();
|
},
|
||||||
|
immediate: true
|
||||||
},
|
},
|
||||||
|
|
||||||
'state.line': function () {
|
'state.line': function () {
|
||||||
if (this.mach_state != 'HOMING')
|
if (this.mach_state != 'HOMING') {
|
||||||
this.$broadcast('gcode-line', this.state.line);
|
this.$broadcast('gcode-line', this.state.line);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
'state.selected_time': function () {
|
'state.selected_time': function () {
|
||||||
@@ -129,54 +102,73 @@ module.exports = {
|
|||||||
|
|
||||||
jog_step: function () {
|
jog_step: function () {
|
||||||
cookie.set_bool('jog-step', this.jog_step);
|
cookie.set_bool('jog-step', this.jog_step);
|
||||||
},
|
},
|
||||||
|
|
||||||
jog_adjust: function () {
|
jog_adjust: function () {
|
||||||
cookie.set('jog-adjust', this.jog_adjust);
|
cookie.set('jog-adjust', this.jog_adjust);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
metric: function () {
|
display_units: {
|
||||||
return !this.state.imperial;
|
cache: false,
|
||||||
|
get: function () {
|
||||||
|
return this.$root.display_units;
|
||||||
|
},
|
||||||
|
set: function (value) {
|
||||||
|
this.$root.display_units = value;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
metric: function () {
|
||||||
|
return this.display_units === "METRIC";
|
||||||
|
},
|
||||||
|
|
||||||
mach_state: function () {
|
mach_state: function () {
|
||||||
var cycle = this.state.cycle;
|
var cycle = this.state.cycle;
|
||||||
var state = this.state.xx;
|
var state = this.state.xx;
|
||||||
|
|
||||||
if (typeof cycle != 'undefined' && state != 'ESTOPPED' &&
|
if (typeof cycle != 'undefined' && state != 'ESTOPPED' &&
|
||||||
(cycle == 'jogging' || cycle == 'homing'))
|
(cycle == 'jogging' || cycle == 'homing')) {
|
||||||
return cycle.toUpperCase();
|
return cycle.toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
return state || ''
|
return state || ''
|
||||||
},
|
},
|
||||||
|
|
||||||
|
pause_reason: function () {
|
||||||
pause_reason: function () {return this.state.pr},
|
return this.state.pr
|
||||||
|
},
|
||||||
|
|
||||||
is_running: function () {
|
is_running: function () {
|
||||||
return this.mach_state == 'RUNNING' || this.mach_state == 'HOMING';
|
return this.mach_state == 'RUNNING' || this.mach_state == 'HOMING';
|
||||||
},
|
},
|
||||||
|
|
||||||
|
is_stopping: function () {
|
||||||
|
return this.mach_state == 'STOPPING'
|
||||||
|
},
|
||||||
|
|
||||||
is_stopping: function () {return this.mach_state == 'STOPPING'},
|
is_holding: function () {
|
||||||
is_holding: function () {return this.mach_state == 'HOLDING'},
|
return this.mach_state == 'HOLDING'
|
||||||
is_ready: function () {return this.mach_state == 'READY'},
|
},
|
||||||
is_idle: function () {return this.state.cycle == 'idle'},
|
|
||||||
|
|
||||||
|
is_ready: function () {
|
||||||
|
return this.mach_state == 'READY'
|
||||||
|
},
|
||||||
|
|
||||||
|
is_idle: function () {
|
||||||
|
return this.state.cycle == 'idle'
|
||||||
|
},
|
||||||
|
|
||||||
is_paused: function () {
|
is_paused: function () {
|
||||||
return this.is_holding &&
|
return this.is_holding &&
|
||||||
(this.pause_reason == 'User pause' ||
|
(this.pause_reason == 'User pause' ||
|
||||||
this.pause_reason == 'Program pause')
|
this.pause_reason == 'Program pause')
|
||||||
},
|
},
|
||||||
|
|
||||||
|
can_mdi: function () {
|
||||||
can_mdi: function () {return this.is_idle || this.state.cycle == 'mdi'},
|
return this.is_idle || this.state.cycle == 'mdi'
|
||||||
|
},
|
||||||
|
|
||||||
can_set_axis: function () {
|
can_set_axis: function () {
|
||||||
return this.is_idle
|
return this.is_idle
|
||||||
@@ -184,293 +176,96 @@ module.exports = {
|
|||||||
return this.is_idle || this.is_paused
|
return this.is_idle || this.is_paused
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
message: function () {
|
message: function () {
|
||||||
if (this.mach_state == 'ESTOPPED') return this.state.er;
|
if (this.mach_state == 'ESTOPPED') {
|
||||||
if (this.mach_state == 'HOLDING') return this.state.pr;
|
return this.state.er;
|
||||||
if (this.state.messages.length)
|
}
|
||||||
|
|
||||||
|
if (this.mach_state == 'HOLDING') {
|
||||||
|
return this.state.pr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state.messages.length) {
|
||||||
return this.state.messages.slice(-1)[0].text;
|
return this.state.messages.slice(-1)[0].text;
|
||||||
|
}
|
||||||
|
|
||||||
return '';
|
return '';
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
highlight_state: function () {
|
highlight_state: function () {
|
||||||
return this.mach_state == 'ESTOPPED' || this.mach_state == 'HOLDING';
|
return this.mach_state == 'ESTOPPED' || this.mach_state == 'HOLDING';
|
||||||
},
|
},
|
||||||
|
|
||||||
|
plan_time: function () {
|
||||||
plan_time: function () {return this.state.plan_time},
|
return this.state.plan_time
|
||||||
|
},
|
||||||
|
|
||||||
plan_time_remaining: function () {
|
plan_time_remaining: function () {
|
||||||
if (!(this.is_stopping || this.is_running || this.is_holding)) return 0;
|
if (!(this.is_stopping || this.is_running || this.is_holding)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
return this.toolpath.time - this.plan_time
|
return this.toolpath.time - this.plan_time
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
eta: function () {
|
eta: function () {
|
||||||
if (this.mach_state != 'RUNNING') return '';
|
if (this.mach_state != 'RUNNING') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
var remaining = this.plan_time_remaining;
|
var remaining = this.plan_time_remaining;
|
||||||
var d = new Date();
|
var d = new Date();
|
||||||
d.setSeconds(d.getSeconds() + remaining);
|
d.setSeconds(d.getSeconds() + remaining);
|
||||||
return d.toLocaleString();
|
return d.toLocaleString();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
progress: function () {
|
progress: function () {
|
||||||
if (!this.toolpath.time || this.is_ready) return 0;
|
if (!this.toolpath.time || this.is_ready) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
var p = this.plan_time / this.toolpath.time;
|
var p = this.plan_time / this.toolpath.time;
|
||||||
return p < 1 ? p : 1;
|
return p < 1 ? p : 1;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
events: {
|
events: {
|
||||||
jog: function (axis, power) {
|
jog: function (axis, power) {
|
||||||
var data = {ts: new Date().getTime()};
|
var data = { ts: new Date().getTime() };
|
||||||
data[axis] = power;
|
data[axis] = power;
|
||||||
api.put('jog', data);
|
api.put('jog', data);
|
||||||
},
|
},
|
||||||
|
|
||||||
back2zero: function(axis0,axis1) {
|
back2zero: function (axis0, axis1) {
|
||||||
this.send("G0"+axis0+"0"+axis1+"0");
|
this.send(`G0 ${axis0}0 ${axis1}0`);
|
||||||
},
|
},
|
||||||
|
|
||||||
step: function (axis, value) {
|
step: function (axis, value) {
|
||||||
this.send('M70\nG91\nG0' + axis + value + '\nM72');
|
this.send(`
|
||||||
|
M70
|
||||||
|
G91
|
||||||
|
G0 ${axis}${value}
|
||||||
|
M72
|
||||||
|
`);
|
||||||
},
|
},
|
||||||
|
|
||||||
probing_failed: function() {
|
|
||||||
Vue.set(this.state, "probing_active", false);
|
|
||||||
Vue.set(this.state, "wait_for_probing_complete", false);
|
|
||||||
Vue.set(this.state, "show_probe_complete_modal", false);
|
|
||||||
Vue.set(this.state, "goto_xy_zero_after_probe", false);
|
|
||||||
|
|
||||||
Vue.set(this.state, "show_probe_failed_modal", true);
|
|
||||||
},
|
|
||||||
|
|
||||||
probing_complete: function() {
|
|
||||||
Vue.set(this.state, "probing_active", false);
|
|
||||||
|
|
||||||
if (this.config.settings['probing-prompts']) {
|
|
||||||
Vue.set(this.state, "show_probe_complete_modal", true);
|
|
||||||
} else {
|
|
||||||
this.$emit("finalize_probe");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
finalize_probe: function() {
|
|
||||||
Vue.set(this.state, "show_probe_complete_modal", false);
|
|
||||||
|
|
||||||
if (this.state.goto_xy_zero_after_probe) {
|
|
||||||
this.goto_zero(1, 1, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
Vue.set(this.state, "goto_xy_zero_after_probe", false);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
ready: function () {
|
ready: function () {
|
||||||
this.load()
|
this.load();
|
||||||
|
|
||||||
|
SvelteComponents.registerControllerMethods({
|
||||||
|
stop: (...args) => this.stop(...args),
|
||||||
|
send: (...args) => this.send(...args),
|
||||||
|
goto_zero: (...args) => this.goto_zero(...args)
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
units_changed : function() {
|
goto_zero(zero_x, zero_y, zero_z, zero_a) {
|
||||||
if(this.mach_units == 'METRIC') {
|
const xcmd = zero_x ? "X0" : "";
|
||||||
document.getElementById("jog_button_fine").innerHTML = "0.1";
|
const ycmd = zero_y ? "Y0" : "";
|
||||||
document.getElementById("jog_button_small").innerHTML = "1.0";
|
const zcmd = zero_z ? "Z0" : "";
|
||||||
document.getElementById("jog_button_medium").innerHTML = "10";
|
const acmd = zero_a ? "A0" : "";
|
||||||
document.getElementById("jog_button_large").innerHTML = "100";
|
|
||||||
} else {
|
|
||||||
document.getElementById("jog_button_fine").innerHTML = "0.005";
|
|
||||||
document.getElementById("jog_button_small").innerHTML = "0.05";
|
|
||||||
document.getElementById("jog_button_medium").innerHTML = "0.5";
|
|
||||||
document.getElementById("jog_button_large").innerHTML = "5";
|
|
||||||
}
|
|
||||||
|
|
||||||
this.set_jog_incr('small');
|
|
||||||
},
|
|
||||||
|
|
||||||
start_probe_test: function(on_finish) {
|
|
||||||
if (!this.config.settings['probing-prompts']) {
|
|
||||||
on_finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.show_probe_test_modal = true;
|
|
||||||
Vue.set(this.state, "saw_probe_connected", false);
|
|
||||||
Vue.set(this.state, "on_probe_finish", on_finish);
|
|
||||||
},
|
|
||||||
|
|
||||||
finish_probe_test: function() {
|
|
||||||
this.show_probe_test_modal = false;
|
|
||||||
Vue.set(this.state, "saw_probe_connected", false);
|
|
||||||
|
|
||||||
const on_finish = this.state.on_probe_finish;
|
|
||||||
Vue.set(this.state, "on_probe_finish", undefined);
|
|
||||||
|
|
||||||
on_finish();
|
|
||||||
},
|
|
||||||
|
|
||||||
hide_probe_failed_modal: function() {
|
|
||||||
Vue.set(this.state, "show_probe_failed_modal", false);
|
|
||||||
},
|
|
||||||
|
|
||||||
prep_and_show_tool_diameter_modal() {
|
|
||||||
this.tool_diameter_for_prompt = (this.mach_units == 'METRIC')
|
|
||||||
? this.tool_diameter
|
|
||||||
: this.tool_diameter / 25.4;
|
|
||||||
|
|
||||||
this.tool_diameter_for_prompt = this.tool_diameter_for_prompt.toFixed(3).replace(/0+$/, "");
|
|
||||||
|
|
||||||
this.show_tool_diameter_modal = true;
|
|
||||||
},
|
|
||||||
|
|
||||||
set_tool_diameter() {
|
|
||||||
this.tool_diameter = parseFloat(this.tool_diameter_for_prompt);
|
|
||||||
|
|
||||||
if (!isFinite(this.tool_diameter)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.show_tool_diameter_modal = false;
|
|
||||||
|
|
||||||
if (this.mach_units !== "METRIC") {
|
|
||||||
this.tool_diameter *= 25.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.probe_xyz();
|
|
||||||
},
|
|
||||||
|
|
||||||
probe(zOnly = false) {
|
|
||||||
const xdim = this.config.probe["probe-xdim"];
|
|
||||||
const ydim = this.config.probe["probe-ydim"];
|
|
||||||
const zdim = this.config.probe["probe-zdim"];
|
|
||||||
const slowSeek = this.config.probe["probe-slow-seek"];
|
|
||||||
const fastSeek = this.config.probe["probe-fast-seek"];
|
|
||||||
|
|
||||||
const zlift = 1;
|
|
||||||
const xoffset = xdim + (this.tool_diameter / 2.0);
|
|
||||||
const yoffset = ydim + (this.tool_diameter / 2.0);
|
|
||||||
const zoffset = zdim;
|
|
||||||
|
|
||||||
const metric = this.mach_units == "METRIC";
|
|
||||||
const mm = n => (metric ? n : n / 25.4).toFixed(5);
|
|
||||||
const speed = s => `F${mm(s)}`;
|
|
||||||
|
|
||||||
// After probing Z, we want to drop the bit down:
|
|
||||||
// Ideally, 12.7mm/0.5in
|
|
||||||
// And we don't want to be more than 75% down on the probe block
|
|
||||||
// Also, add zlift to compensate for the fact that we lift after probing Z
|
|
||||||
const plunge = Math.min(12.7, zoffset * 0.75) + zlift;
|
|
||||||
|
|
||||||
Vue.set(this.state, "probing_active", true);
|
|
||||||
Vue.set(this.state, "goto_xy_zero_after_probe", !zOnly);
|
|
||||||
|
|
||||||
if (zOnly) {
|
|
||||||
this.send(`
|
|
||||||
${metric ? "G21" : "G20"}
|
|
||||||
G92 Z0
|
|
||||||
|
|
||||||
G38.2 Z ${mm(-25.4)} ${speed(fastSeek)}
|
|
||||||
G91 G1 Z ${mm(1)}
|
|
||||||
G38.2 Z ${mm(-2)} ${speed(slowSeek)}
|
|
||||||
G92 Z ${mm(zoffset)}
|
|
||||||
|
|
||||||
G91 G0 Z ${mm(3)}
|
|
||||||
|
|
||||||
M2
|
|
||||||
`);
|
|
||||||
} else {
|
|
||||||
this.send(`
|
|
||||||
${metric ? "G21" : "G20"}
|
|
||||||
G92 X0 Y0 Z0
|
|
||||||
|
|
||||||
G38.2 Z ${mm(-25.4)} ${speed(fastSeek)}
|
|
||||||
G91 G1 Z ${mm(1)}
|
|
||||||
G38.2 Z ${mm(-2)} ${speed(slowSeek)}
|
|
||||||
G92 Z ${mm(zoffset)}
|
|
||||||
|
|
||||||
G91 G0 Z ${mm(zlift)}
|
|
||||||
G91 G0 X ${mm(20)}
|
|
||||||
G91 G0 Z ${mm(-plunge)}
|
|
||||||
G38.2 X ${mm(-20)} ${speed(fastSeek)}
|
|
||||||
G91 G1 X ${mm(1)}
|
|
||||||
G38.2 X ${mm(-2)} ${speed(slowSeek)}
|
|
||||||
G92 X ${mm(xoffset)}
|
|
||||||
|
|
||||||
G91 G0 X ${mm(1)}
|
|
||||||
G91 G0 Y ${mm(20)}
|
|
||||||
G91 G0 X ${mm(-20)}
|
|
||||||
G38.2 Y ${mm(-20)} ${speed(fastSeek)}
|
|
||||||
G91 G1 Y ${mm(1)}
|
|
||||||
G38.2 Y ${mm(-2)} ${speed(slowSeek)}
|
|
||||||
G92 Y ${mm(yoffset)}
|
|
||||||
|
|
||||||
G91 G0 Y ${mm(3)}
|
|
||||||
G91 G0 Z ${mm(25.4)}
|
|
||||||
|
|
||||||
M2
|
|
||||||
`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait 1 second to let the probing sequence begin,
|
|
||||||
// then wait for probing to be complete
|
|
||||||
setTimeout(() => Vue.set(this.state, "wait_for_probing_complete", true), 1000);
|
|
||||||
},
|
|
||||||
|
|
||||||
probe_xyz() {
|
|
||||||
this.probe(false);
|
|
||||||
},
|
|
||||||
|
|
||||||
probe_z() {
|
|
||||||
this.probe(true);
|
|
||||||
},
|
|
||||||
|
|
||||||
set_jog_incr: function(newValue) {
|
|
||||||
document.getElementById("jog_button_fine").style.fontWeight = 'normal';
|
|
||||||
document.getElementById("jog_button_small").style.fontWeight = 'normal';
|
|
||||||
document.getElementById("jog_button_medium").style.fontWeight = 'normal';
|
|
||||||
document.getElementById("jog_button_large").style.fontWeight = 'normal';
|
|
||||||
|
|
||||||
if (newValue == 'fine') {
|
|
||||||
document.getElementById("jog_button_fine").style.fontWeight = 'bold';
|
|
||||||
if(this.mach_units == 'METRIC')
|
|
||||||
this.jog_incr = 0.1;
|
|
||||||
else
|
|
||||||
this.jog_incr = 0.005;
|
|
||||||
} else if (newValue == 'small') {
|
|
||||||
document.getElementById("jog_button_small").style.fontWeight = 'bold';
|
|
||||||
if(this.mach_units == 'METRIC')
|
|
||||||
this.jog_incr = 1.0;
|
|
||||||
else
|
|
||||||
this.jog_incr = 0.05;
|
|
||||||
} else if (newValue == 'medium') {
|
|
||||||
document.getElementById("jog_button_medium").style.fontWeight = 'bold';
|
|
||||||
if(this.mach_units == 'METRIC')
|
|
||||||
this.jog_incr = 10;
|
|
||||||
else
|
|
||||||
this.jog_incr = 0.5;
|
|
||||||
} else if (newValue == 'large') {
|
|
||||||
document.getElementById("jog_button_large").style.fontWeight = 'bold';
|
|
||||||
|
|
||||||
this.jog_incr = (this.mach_units == 'METRIC')
|
|
||||||
? 100
|
|
||||||
: 5;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
goto_zero(zero_x,zero_y,zero_z,zero_a) {
|
|
||||||
var xcmd = "";
|
|
||||||
var ycmd = "";
|
|
||||||
var zcmd = "";
|
|
||||||
var acmd = "";
|
|
||||||
if(zero_x) xcmd = "X0";
|
|
||||||
if(zero_y) ycmd = "Y0";
|
|
||||||
if(zero_z) zcmd = "Z0";
|
|
||||||
if(zero_a) acmd = "A0";
|
|
||||||
|
|
||||||
this.ask_zero_xy_msg = false;
|
this.ask_zero_xy_msg = false;
|
||||||
this.ask_zero_z_msg = false;
|
this.ask_zero_z_msg = false;
|
||||||
@@ -478,13 +273,26 @@ module.exports = {
|
|||||||
this.send('G90\nG0' + xcmd + ycmd + zcmd + acmd + '\n');
|
this.send('G90\nG0' + xcmd + ycmd + zcmd + acmd + '\n');
|
||||||
},
|
},
|
||||||
|
|
||||||
jog_fn: function (x_jog,y_jog,z_jog,a_jog) {
|
getJogIncrStyle(value) {
|
||||||
var xcmd = "X" + x_jog * this.jog_incr;
|
const weight = `font-weight:${this.jog_incr === value ? 'bold' : 'normal'}`;
|
||||||
var ycmd = "Y" + y_jog * this.jog_incr;
|
const color = this.jog_incr === value ? "color:#0078e7" : "";
|
||||||
var zcmd = "Z" + z_jog * this.jog_incr;
|
|
||||||
var acmd = "A" + a_jog * this.jog_incr;
|
|
||||||
|
|
||||||
this.send('G91\nG0' + xcmd + ycmd + zcmd + acmd + '\n');
|
return [weight, color].join(';');
|
||||||
|
},
|
||||||
|
|
||||||
|
jog_fn: function (x_jog, y_jog, z_jog, a_jog) {
|
||||||
|
const amount = this.jog_incr_amounts[this.display_units][this.jog_incr];
|
||||||
|
|
||||||
|
var xcmd = "X" + x_jog * amount;
|
||||||
|
var ycmd = "Y" + y_jog * amount;
|
||||||
|
var zcmd = "Z" + z_jog * amount;
|
||||||
|
var acmd = "A" + a_jog * amount;
|
||||||
|
|
||||||
|
this.send(`
|
||||||
|
G91
|
||||||
|
${this.metric ? "G21" : "G20"}
|
||||||
|
G0 ${xcmd}${ycmd}${zcmd}${acmd}
|
||||||
|
`);
|
||||||
},
|
},
|
||||||
|
|
||||||
send: function (msg) {
|
send: function (msg) {
|
||||||
@@ -494,7 +302,10 @@ module.exports = {
|
|||||||
load: function () {
|
load: function () {
|
||||||
var file_time = this.state.selected_time;
|
var file_time = this.state.selected_time;
|
||||||
var file = this.state.selected;
|
var file = this.state.selected;
|
||||||
if (this.last_file == file && this.last_file_time == file_time) return;
|
if (this.last_file == file && this.last_file_time == file_time) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.last_file = file;
|
this.last_file = file;
|
||||||
this.last_file_time = file_time;
|
this.last_file_time = file_time;
|
||||||
|
|
||||||
@@ -504,12 +315,12 @@ module.exports = {
|
|||||||
this.load_toolpath(file, file_time);
|
this.load_toolpath(file, file_time);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
load_toolpath: async function (file, file_time) {
|
load_toolpath: async function (file, file_time) {
|
||||||
this.toolpath = {};
|
this.toolpath = {};
|
||||||
|
|
||||||
if (!file) return;
|
if (!file || this.last_file_time != file_time) {
|
||||||
if (this.last_file_time != file_time) return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.showGcodeMessage = true;
|
this.showGcodeMessage = true;
|
||||||
|
|
||||||
@@ -535,30 +346,30 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
submit_mdi: function () {
|
submit_mdi: function () {
|
||||||
this.send(this.mdi);
|
this.send(this.mdi);
|
||||||
if (!this.history.length || this.history[0] != this.mdi)
|
|
||||||
|
if (!this.history.length || this.history[0] != this.mdi) {
|
||||||
this.history.unshift(this.mdi);
|
this.history.unshift(this.mdi);
|
||||||
|
}
|
||||||
|
|
||||||
this.mdi = '';
|
this.mdi = '';
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
mdi_start_pause: function () {
|
mdi_start_pause: function () {
|
||||||
if (this.state.xx == 'RUNNING') this.pause();
|
if (this.state.xx == 'RUNNING') {
|
||||||
|
this.pause();
|
||||||
else if (this.state.xx == 'STOPPING' || this.state.xx == 'HOLDING')
|
} else if (this.state.xx == 'STOPPING' || this.state.xx == 'HOLDING') {
|
||||||
this.unpause();
|
this.unpause();
|
||||||
|
} else {
|
||||||
else this.submit_mdi();
|
this.submit_mdi();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
load_history: function (index) {
|
load_history: function (index) {
|
||||||
this.mdi = this.history[index];
|
this.mdi = this.history[index];
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
open: function (e) {
|
open: function (e) {
|
||||||
// If we don't reset the form the browser may cache file if name is same
|
// If we don't reset the form the browser may cache file if name is same
|
||||||
// even if contents have changed
|
// even if contents have changed
|
||||||
@@ -566,7 +377,6 @@ module.exports = {
|
|||||||
$('.gcode-file-input input').click();
|
$('.gcode-file-input input').click();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
upload: async function (e) {
|
upload: async function (e) {
|
||||||
const files = e.target.files || e.dataTransfer.files;
|
const files = e.target.files || e.dataTransfer.files;
|
||||||
if (!files.length) {
|
if (!files.length) {
|
||||||
@@ -602,7 +412,6 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
delete_current: function () {
|
delete_current: function () {
|
||||||
if (this.state.selected) {
|
if (this.state.selected) {
|
||||||
api.delete('file/' + this.state.selected);
|
api.delete('file/' + this.state.selected);
|
||||||
@@ -611,103 +420,120 @@ module.exports = {
|
|||||||
this.deleteGCode = false;
|
this.deleteGCode = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
delete_all: function () {
|
delete_all: function () {
|
||||||
api.delete('file');
|
api.delete('file');
|
||||||
this.deleteGCode = false;
|
this.deleteGCode = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
home: function (axis) {
|
home: function (axis) {
|
||||||
|
|
||||||
this.ask_home = false;
|
this.ask_home = false;
|
||||||
this.ask_home_msg = false;
|
|
||||||
|
|
||||||
if (typeof axis == 'undefined') api.put('home');
|
|
||||||
|
|
||||||
else {
|
if (typeof axis == 'undefined') {
|
||||||
if (this[axis].homingMode != 'manual') api.put('home/' + axis);
|
api.put('home');
|
||||||
else this.manual_home[axis] = true;
|
} else if (this[axis].homingMode != 'manual') {
|
||||||
|
api.put('home/' + axis);
|
||||||
|
} else {
|
||||||
|
this.manual_home[axis] = true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
set_home: function (axis, position) {
|
set_home: function (axis, position) {
|
||||||
this.manual_home[axis] = false;
|
this.manual_home[axis] = false;
|
||||||
api.put('home/' + axis + '/set', {position: parseFloat(position)});
|
api.put('home/' + axis + '/set', { position: parseFloat(position) });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
unhome: function (axis) {
|
unhome: function (axis) {
|
||||||
this.position_msg[axis] = false;
|
this.position_msg[axis] = false;
|
||||||
api.put('home/' + axis + '/clear');
|
api.put('home/' + axis + '/clear');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
show_set_position: function (axis) {
|
show_set_position: function (axis) {
|
||||||
this.axis_position = 0;
|
this.axis_position = 0;
|
||||||
this.position_msg[axis] = true;
|
this.position_msg[axis] = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
show_toolpath_msg : function(axis) {
|
show_toolpath_msg: function (axis) {
|
||||||
this.toolpath_msg[axis] = true;
|
this.toolpath_msg[axis] = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
set_position: function (axis, position) {
|
set_position: function (axis, position) {
|
||||||
this.position_msg[axis] = false;
|
this.position_msg[axis] = false;
|
||||||
api.put('position/' + axis, {'position': parseFloat(position)});
|
api.put('position/' + axis, { 'position': parseFloat(position) });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
zero_all: function () {
|
zero_all: function () {
|
||||||
for (var axis of 'xyzabc')
|
for (var axis of 'xyzabc') {
|
||||||
if (this[axis].enabled) this.zero(axis);
|
if (this[axis].enabled) {
|
||||||
|
this.zero(axis);
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
zero: function (axis) {
|
zero: function (axis) {
|
||||||
if (typeof axis == 'undefined') this.zero_all();
|
if (typeof axis == 'undefined') {
|
||||||
else this.set_position(axis, 0);
|
this.zero_all();
|
||||||
|
} else {
|
||||||
|
this.set_position(axis, 0);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
start_pause: function () {
|
start_pause: function () {
|
||||||
if (this.state.xx == 'RUNNING') this.pause();
|
if (this.state.xx == 'RUNNING') {
|
||||||
|
this.pause();
|
||||||
else if (this.state.xx == 'STOPPING' || this.state.xx == 'HOLDING')
|
} else if (this.state.xx == 'STOPPING' || this.state.xx == 'HOLDING') {
|
||||||
this.unpause();
|
this.unpause();
|
||||||
|
} else {
|
||||||
else this.start();
|
this.start();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
start: function () {
|
||||||
|
api.put('start')
|
||||||
|
},
|
||||||
|
|
||||||
start: function () {api.put('start')},
|
pause: function () {
|
||||||
pause: function () {api.put('pause')},
|
api.put('pause')
|
||||||
unpause: function () {api.put('unpause')},
|
},
|
||||||
optional_pause: function () {api.put('pause/optional')},
|
|
||||||
stop: function () {api.put('stop')},
|
|
||||||
step: function () {api.put('step')},
|
|
||||||
|
|
||||||
|
unpause: function () {
|
||||||
|
api.put('unpause')
|
||||||
|
},
|
||||||
|
|
||||||
override_feed: function () {api.put('override/feed/' + this.feed_override)},
|
optional_pause: function () {
|
||||||
|
api.put('pause/optional')
|
||||||
|
},
|
||||||
|
|
||||||
|
stop: function () {
|
||||||
|
api.put('stop')
|
||||||
|
},
|
||||||
|
|
||||||
|
step: function () {
|
||||||
|
api.put('step')
|
||||||
|
},
|
||||||
|
|
||||||
|
override_feed: function () {
|
||||||
|
api.put('override/feed/' + this.feed_override)
|
||||||
|
},
|
||||||
|
|
||||||
override_speed: function () {
|
override_speed: function () {
|
||||||
api.put('override/speed/' + this.speed_override)
|
api.put('override/speed/' + this.speed_override)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
current: function (axis, value) {
|
current: function (axis, value) {
|
||||||
var x = value / 32.0;
|
var x = value / 32.0;
|
||||||
if (this.state[axis + 'pl'] == x) return;
|
if (this.state[axis + 'pl'] == x) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var data = {};
|
var data = {};
|
||||||
data[axis + 'pl'] = x;
|
data[axis + 'pl'] = x;
|
||||||
this.send(JSON.stringify(data));
|
this.send(JSON.stringify(data));
|
||||||
|
},
|
||||||
|
|
||||||
|
showProbeDialog: function(probeType) {
|
||||||
|
SvelteComponents.showDialog("Probe", { probeType });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
mixins: [require('./axis-vars')]
|
mixins: [require('./axis-vars')]
|
||||||
}
|
}
|
||||||
|
|||||||
114
src/js/main.js
114
src/js/main.js
@@ -1,33 +1,5 @@
|
|||||||
/******************************************************************************\
|
|
||||||
|
|
||||||
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>
|
|
||||||
|
|
||||||
\******************************************************************************/
|
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
|
||||||
function cookie_get(name) {
|
function cookie_get(name) {
|
||||||
var decodedCookie = decodeURIComponent(document.cookie);
|
var decodedCookie = decodeURIComponent(document.cookie);
|
||||||
var ca = decodedCookie.split(';');
|
var ca = decodedCookie.split(';');
|
||||||
@@ -35,12 +7,16 @@ function cookie_get(name) {
|
|||||||
|
|
||||||
for (var i = 0; i < ca.length; i++) {
|
for (var i = 0; i < ca.length; i++) {
|
||||||
var c = ca[i];
|
var c = ca[i];
|
||||||
while (c.charAt(0) == ' ') c = c.substring(1);
|
while (c.charAt(0) == ' ') {
|
||||||
if (!c.indexOf(name)) return c.substring(name.length, c.length);
|
c = c.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!c.indexOf(name)) {
|
||||||
|
return c.substring(name.length, c.length);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function cookie_set(name, value, days) {
|
function cookie_set(name, value, days) {
|
||||||
var d = new Date();
|
var d = new Date();
|
||||||
d.setTime(d.getTime() + days * 24 * 60 * 60 * 1000);
|
d.setTime(d.getTime() + days * 24 * 60 * 60 * 1000);
|
||||||
@@ -48,29 +24,26 @@ function cookie_set(name, value, days) {
|
|||||||
document.cookie = name + '=' + value + ';' + expires + ';path=/';
|
document.cookie = name + '=' + value + ';' + expires + ';path=/';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var uuid_chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_+';
|
||||||
var uuid_chars =
|
|
||||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_+';
|
|
||||||
|
|
||||||
|
|
||||||
function uuid(length) {
|
function uuid(length) {
|
||||||
if (typeof length == 'undefined') length = 52;
|
if (typeof length == 'undefined') {
|
||||||
|
length = 52;
|
||||||
|
}
|
||||||
|
|
||||||
var s = '';
|
var s = '';
|
||||||
for (var i = 0; i < length; i++)
|
for (var i = 0; i < length; i++) {
|
||||||
s += uuid_chars[Math.floor(Math.random() * uuid_chars.length)];
|
s += uuid_chars[Math.floor(Math.random() * uuid_chars.length)];
|
||||||
|
}
|
||||||
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$(function() {
|
$(function () {
|
||||||
if (typeof cookie_get('client-id') == 'undefined')
|
if (typeof cookie_get('client-id') == 'undefined') {
|
||||||
cookie_set('client-id', uuid(), 10000);
|
cookie_set('client-id', uuid(), 10000);
|
||||||
|
}
|
||||||
// Vue debugging
|
|
||||||
Vue.config.debug = true;
|
|
||||||
//Vue.util.warn = function (msg) {console.debug('[Vue warn]: ' + msg)}
|
|
||||||
|
|
||||||
// Register global components
|
// Register global components
|
||||||
Vue.component('templated-input', require('./templated-input'));
|
Vue.component('templated-input', require('./templated-input'));
|
||||||
@@ -81,38 +54,64 @@ $(function() {
|
|||||||
Vue.component('unit-value', require('./unit-value'));
|
Vue.component('unit-value', require('./unit-value'));
|
||||||
|
|
||||||
Vue.filter('number', function (value) {
|
Vue.filter('number', function (value) {
|
||||||
if (isNaN(value)) return 'NaN';
|
if (isNaN(value)) {
|
||||||
|
return 'NaN';
|
||||||
|
}
|
||||||
|
|
||||||
return value.toLocaleString();
|
return value.toLocaleString();
|
||||||
});
|
});
|
||||||
|
|
||||||
Vue.filter('percent', function (value, precision) {
|
Vue.filter('percent', function (value, precision) {
|
||||||
if (typeof value == 'undefined') return '';
|
if (typeof value == 'undefined') {
|
||||||
if (typeof precision == 'undefined') precision = 2;
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof precision == 'undefined') {
|
||||||
|
precision = 2;
|
||||||
|
}
|
||||||
|
|
||||||
return (value * 100.0).toFixed(precision) + '%';
|
return (value * 100.0).toFixed(precision) + '%';
|
||||||
});
|
});
|
||||||
|
|
||||||
Vue.filter('non_zero_percent', function (value, precision) {
|
Vue.filter('non_zero_percent', function (value, precision) {
|
||||||
if (!value) return '';
|
if (!value) {
|
||||||
if (typeof precision == 'undefined') precision = 2;
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof precision == 'undefined') {
|
||||||
|
precision = 2;
|
||||||
|
}
|
||||||
|
|
||||||
return (value * 100.0).toFixed(precision) + '%';
|
return (value * 100.0).toFixed(precision) + '%';
|
||||||
});
|
});
|
||||||
|
|
||||||
Vue.filter('fixed', function (value, precision) {
|
Vue.filter('fixed', function (value, precision) {
|
||||||
if (typeof value == 'undefined') return '0';
|
if (typeof value == 'undefined') {
|
||||||
|
return '0';
|
||||||
|
}
|
||||||
|
|
||||||
return parseFloat(value).toFixed(precision)
|
return parseFloat(value).toFixed(precision)
|
||||||
});
|
});
|
||||||
|
|
||||||
Vue.filter('upper', function (value) {
|
Vue.filter('upper', function (value) {
|
||||||
if (typeof value == 'undefined') return '';
|
if (typeof value == 'undefined') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
return value.toUpperCase()
|
return value.toUpperCase()
|
||||||
});
|
});
|
||||||
|
|
||||||
Vue.filter('time', function (value, precision) {
|
Vue.filter('time', function (value, precision) {
|
||||||
if (isNaN(value)) return '';
|
if (isNaN(value)) {
|
||||||
if (isNaN(precision)) precision = 0;
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNaN(precision)) {
|
||||||
|
precision = 0;
|
||||||
|
}
|
||||||
|
|
||||||
var MIN = 60;
|
var MIN = 60;
|
||||||
var HR = MIN * 60;
|
var HR = MIN * 60;
|
||||||
var DAY = HR * 24;
|
var DAY = HR * 24;
|
||||||
var parts = [];
|
var parts = [];
|
||||||
|
|
||||||
@@ -129,14 +128,17 @@ $(function() {
|
|||||||
if (MIN <= value) {
|
if (MIN <= value) {
|
||||||
parts.push(Math.floor(value / MIN));
|
parts.push(Math.floor(value / MIN));
|
||||||
value %= MIN;
|
value %= MIN;
|
||||||
|
} else {
|
||||||
} else parts.push(0);
|
parts.push(0);
|
||||||
|
}
|
||||||
|
|
||||||
parts.push(value);
|
parts.push(value);
|
||||||
|
|
||||||
for (var i = 0; i < parts.length; i++) {
|
for (var i = 0; i < parts.length; i++) {
|
||||||
parts[i] = parts[i].toFixed(i == parts.length - 1 ? precision : 0);
|
parts[i] = parts[i].toFixed(i == parts.length - 1 ? precision : 0);
|
||||||
if (i && parts[i] < 10) parts[i] = '0' + parts[i];
|
if (i && parts[i] < 10) {
|
||||||
|
parts[i] = '0' + parts[i];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return parts.join(':');
|
return parts.join(':');
|
||||||
|
|||||||
@@ -1,124 +1,105 @@
|
|||||||
/******************************************************************************\
|
|
||||||
|
|
||||||
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>
|
|
||||||
|
|
||||||
\******************************************************************************/
|
|
||||||
|
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
template: '#motor-view-template',
|
template: '#motor-view-template',
|
||||||
props: ['index', 'config', 'template', 'state'],
|
props: ['index', 'config', 'template', 'state'],
|
||||||
|
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
metric: function () {return this.$root.metric()},
|
metric: function () {
|
||||||
|
return this.$root.display_units === "METRIC";
|
||||||
|
},
|
||||||
|
|
||||||
is_slave: function () {
|
is_slave: function () {
|
||||||
for (var i = 0; i < this.index; i++)
|
for (var i = 0; i < this.index; i++) {
|
||||||
if (this.motor.axis == this.config.motors[i].axis)
|
if (this.motor.axis == this.config.motors[i].axis) {
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
motor: function () {
|
||||||
motor: function () {return this.config.motors[this.index]},
|
return this.config.motors[this.index]
|
||||||
|
},
|
||||||
|
|
||||||
invalidMaxVelocity: function () {
|
invalidMaxVelocity: function () {
|
||||||
return this.maxMaxVelocity < this.motor['max-velocity'];
|
return this.maxMaxVelocity < this.motor['max-velocity'];
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
maxMaxVelocity: function () {
|
maxMaxVelocity: function () {
|
||||||
return 1 * (15 * this.umPerStep / this.motor['microsteps']).toFixed(3);
|
return 1 * (15 * this.umPerStep / this.motor['microsteps']).toFixed(3);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
ustepPerSec: function () {
|
ustepPerSec: function () {
|
||||||
return this.rpm * this.stepsPerRev * this.motor['microsteps'] / 60;
|
return this.rpm * this.stepsPerRev * this.motor['microsteps'] / 60;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
rpm: function () {
|
rpm: function () {
|
||||||
return 1000 * this.motor['max-velocity'] / this.motor['travel-per-rev'];
|
return 1000 * this.motor['max-velocity'] / this.motor['travel-per-rev'];
|
||||||
},
|
},
|
||||||
|
|
||||||
|
gForce: function () {
|
||||||
|
return this.motor['max-accel'] * 0.0283254504
|
||||||
|
},
|
||||||
|
|
||||||
gForce: function () {return this.motor['max-accel'] * 0.0283254504},
|
gForcePerMin: function () {
|
||||||
gForcePerMin: function () {return this.motor['max-jerk'] * 0.0283254504},
|
return this.motor['max-jerk'] * 0.0283254504
|
||||||
stepsPerRev: function () {return 360 / this.motor['step-angle']},
|
},
|
||||||
|
|
||||||
|
stepsPerRev: function () {
|
||||||
|
return 360 / this.motor['step-angle']
|
||||||
|
},
|
||||||
|
|
||||||
umPerStep: function () {
|
umPerStep: function () {
|
||||||
return this.motor['travel-per-rev'] * this.motor['step-angle'] / 0.36
|
return this.motor['travel-per-rev'] * this.motor['step-angle'] / 0.36
|
||||||
},
|
},
|
||||||
|
|
||||||
|
milPerStep: function () {
|
||||||
|
return this.umPerStep / 25.4
|
||||||
|
},
|
||||||
|
|
||||||
milPerStep: function () {return this.umPerStep / 25.4},
|
invalidStallVelocity: function () {
|
||||||
|
if (!this.motor['homing-mode'].startsWith('stall-')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
invalidStallVelocity: function() {
|
|
||||||
if(!this.motor['homing-mode'].startsWith('stall-')) return false;
|
|
||||||
return this.maxStallVelocity < this.motor['search-velocity'];
|
return this.maxStallVelocity < this.motor['search-velocity'];
|
||||||
},
|
},
|
||||||
|
|
||||||
stallRPM: function() {
|
stallRPM: function () {
|
||||||
var v = this.motor['search-velocity'];
|
var v = this.motor['search-velocity'];
|
||||||
return 1000 * v / this.motor['travel-per-rev'];
|
return 1000 * v / this.motor['travel-per-rev'];
|
||||||
},
|
},
|
||||||
|
|
||||||
maxStallVelocity: function() {
|
maxStallVelocity: function () {
|
||||||
var maxRate = 900000/this.motor['stall-sample-time'];
|
var maxRate = 900000 / this.motor['stall-sample-time'];
|
||||||
var ustep = this.motor['stall-microstep'];
|
var ustep = this.motor['stall-microstep'];
|
||||||
var angle = this.motor['step-angle'];
|
var angle = this.motor['step-angle'];
|
||||||
var travel = this.motor['travel-per-rev'];
|
var travel = this.motor['travel-per-rev'];
|
||||||
var maxStall = maxRate * 60/ 360 /1000 * angle/ ustep * travel;
|
var maxStall = maxRate * 60 / 360 / 1000 * angle / ustep * travel;
|
||||||
|
|
||||||
return 1 * maxStall.toFixed(3);
|
return 1 * maxStall.toFixed(3);
|
||||||
},
|
},
|
||||||
|
|
||||||
stallUStepPerSec: function() {
|
stallUStepPerSec: function () {
|
||||||
var ustep = this.motor['stall-microstep'];
|
var ustep = this.motor['stall-microstep'];
|
||||||
return this.stallRPM * this.stepsPerRev * ustep / 60;
|
return this.stallRPM * this.stepsPerRev * ustep / 60;
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
events: {
|
events: {
|
||||||
'input-changed': function() {
|
'input-changed': function () {
|
||||||
Vue.nextTick(function () {
|
Vue.nextTick(function () {
|
||||||
// Limit max-velocity
|
// Limit max-velocity
|
||||||
if (this.invalidMaxVelocity)
|
if (this.invalidMaxVelocity) {
|
||||||
this.$set('motor["max-velocity"]', this.maxMaxVelocity);
|
this.$set('motor["max-velocity"]', this.maxMaxVelocity);
|
||||||
|
}
|
||||||
|
|
||||||
//Limit stall-velocity
|
//Limit stall-velocity
|
||||||
if(this.invalidStallVelocity)
|
if (this.invalidStallVelocity) {
|
||||||
this.$set('motor["search-velocity"]', this.maxStallVelocity);
|
this.$set('motor["search-velocity"]', this.maxStallVelocity);
|
||||||
|
}
|
||||||
|
|
||||||
this.$dispatch('config-changed');
|
this.$dispatch('config-changed');
|
||||||
}.bind(this))
|
}.bind(this))
|
||||||
@@ -128,8 +109,11 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
show: function(name, templ) {
|
show: function (name, templ) {
|
||||||
if(templ.hmodes == undefined) return true;
|
if (templ.hmodes == undefined) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return templ.hmodes.indexOf(this.motor['homing-mode']) != -1;
|
return templ.hmodes.indexOf(this.motor['homing-mode']) != -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +1,23 @@
|
|||||||
/******************************************************************************\
|
|
||||||
|
|
||||||
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>
|
|
||||||
|
|
||||||
\******************************************************************************/
|
|
||||||
|
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
template: '#settings-view-template',
|
template: '#settings-view-template',
|
||||||
props: ['config', 'template'],
|
props: ['config', 'template'],
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
display_units: {
|
||||||
|
cache: false,
|
||||||
|
get: function () {
|
||||||
|
return this.$root.display_units;
|
||||||
|
},
|
||||||
|
set: function (value) {
|
||||||
|
this.$root.display_units = value;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
events: {
|
events: {
|
||||||
'input-changed': function() {
|
'input-changed': function () {
|
||||||
this.$dispatch('config-changed');
|
this.$dispatch('config-changed');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,49 +1,24 @@
|
|||||||
/******************************************************************************\
|
|
||||||
|
|
||||||
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>
|
|
||||||
|
|
||||||
\******************************************************************************/
|
|
||||||
|
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
replace: true,
|
replace: true,
|
||||||
template: '#templated-input-template',
|
template: '#templated-input-template',
|
||||||
props: ['name', 'model', 'template'],
|
props: ['name', 'model', 'template'],
|
||||||
|
|
||||||
|
data: function () {
|
||||||
data: function () {return {view: ''}},
|
return { view: '' }
|
||||||
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
metric: function () {return this.$root.metric()},
|
metric: function () {
|
||||||
|
return this.$root.display_units === "METRIC";
|
||||||
|
},
|
||||||
|
|
||||||
_view: function () {
|
_view: function () {
|
||||||
if (this.template.scale) {
|
if (this.template.scale) {
|
||||||
if (this.metric) return 1 * this.model.toFixed(3);
|
if (this.metric) {
|
||||||
|
return 1 * this.model.toFixed(3);
|
||||||
|
}
|
||||||
|
|
||||||
return 1 * (this.model / this.template.scale).toFixed(4);
|
return 1 * (this.model / this.template.scale).toFixed(4);
|
||||||
}
|
}
|
||||||
@@ -51,39 +26,44 @@ module.exports = {
|
|||||||
return this.model;
|
return this.model;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
units: function () {
|
units: function () {
|
||||||
return (this.metric || !this.template.iunit) ?
|
return (this.metric || !this.template.iunit)
|
||||||
this.template.unit : this.template.iunit;
|
? this.template.unit
|
||||||
|
: this.template.iunit;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
title: function () {
|
title: function () {
|
||||||
var s = 'Default ' + this.template.default + ' ' +
|
var s = `Default :${this.template.default} ${(this.template.unit || '')}`;
|
||||||
(this.template.unit || '');
|
|
||||||
if (typeof this.template.help != 'undefined')
|
if (typeof this.template.help != 'undefined') {
|
||||||
s = this.template.help + '\n' + s;
|
s = this.template.help + '\n' + s;
|
||||||
|
}
|
||||||
|
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
_view: function () {this.view = this._view},
|
_view: function () {
|
||||||
|
this.view = this._view
|
||||||
|
},
|
||||||
|
|
||||||
view: function () {
|
view: function () {
|
||||||
if (this.template.scale && !this.metric)
|
if (this.template.scale && !this.metric) {
|
||||||
this.model = this.view * this.template.scale;
|
this.model = this.view * this.template.scale;
|
||||||
else this.model = this.view;
|
} else {
|
||||||
|
this.model = this.view;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
ready: function () {
|
||||||
ready: function () {this.view = this._view},
|
this.view = this._view
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
change: function () {this.$dispatch('input-changed')}
|
change: function () {
|
||||||
|
this.$dispatch('input-changed')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,58 +1,47 @@
|
|||||||
/******************************************************************************\
|
|
||||||
|
|
||||||
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>
|
|
||||||
|
|
||||||
\******************************************************************************/
|
|
||||||
|
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
replace: true,
|
replace: true,
|
||||||
template: '{{text}}<span class="unit">{{metric ? unit : iunit}}</span>',
|
template: '{{text}}<span class="unit">{{metric ? unit : iunit}}</span>',
|
||||||
props: ['value', 'precision', 'unit', 'iunit', 'scale'],
|
props: ['value', 'precision', 'unit', 'iunit', 'scale'],
|
||||||
|
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
metric: function () {return !this.$root.state.imperial},
|
metric: {
|
||||||
|
cache: false,
|
||||||
|
get: function () {
|
||||||
|
return this.$root.display_units === "METRIC";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
text: function () {
|
text: function () {
|
||||||
var value = this.value;
|
var value = this.value;
|
||||||
if (typeof value == 'undefined') return '';
|
if (typeof value == 'undefined') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.metric) value /= this.scale;
|
if (!this.metric) {
|
||||||
|
value /= this.scale;
|
||||||
|
}
|
||||||
|
|
||||||
return (1 * value.toFixed(this.precision)).toLocaleString();
|
return (1 * value.toFixed(this.precision)).toLocaleString();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
ready: function () {
|
ready: function () {
|
||||||
if (typeof this.precision == 'undefined') this.precision = 0;
|
if (typeof this.precision == 'undefined') {
|
||||||
if (typeof this.unit == 'undefined') this.unit = 'mm';
|
this.precision = 0;
|
||||||
if (typeof this.iunit == 'undefined') this.iunit = 'in';
|
}
|
||||||
if (typeof this.scale == 'undefined') this.scale = 25.4;
|
|
||||||
|
if (typeof this.unit == 'undefined') {
|
||||||
|
this.unit = 'mm';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof this.iunit == 'undefined') {
|
||||||
|
this.iunit = 'in';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof this.scale == 'undefined') {
|
||||||
|
this.scale = 25.4;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +1,9 @@
|
|||||||
//-/////////////////////////////////////////////////////////////////////////////
|
|
||||||
//- //
|
|
||||||
//- 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> //
|
|
||||||
//- //
|
|
||||||
//-/////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
doctype html
|
doctype html
|
||||||
html(lang="en")
|
html(lang="en")
|
||||||
head
|
head
|
||||||
meta(charset="utf-8")
|
meta(charset="utf-8")
|
||||||
meta(name="viewport", content="width=device-width, initial-scale=1.0")
|
meta(name="viewport", content="width=device-width, initial-scale=1.0")
|
||||||
|
link(rel="preload" href="/fonts/material-symbols-outlined.woff2" as="font" type="font/woff2" crossorigin)
|
||||||
|
|
||||||
title Onefinity CNC - Web interface
|
title Onefinity CNC - Web interface
|
||||||
|
|
||||||
@@ -39,12 +13,19 @@ html(lang="en")
|
|||||||
style: include ../static/css/font-awesome.min.css
|
style: include ../static/css/font-awesome.min.css
|
||||||
style: include ../static/css/Audiowide.css
|
style: include ../static/css/Audiowide.css
|
||||||
style: include ../static/css/clusterize.css
|
style: include ../static/css/clusterize.css
|
||||||
|
style: include ../svelte-components/node_modules/svelte-material-ui/bare.css
|
||||||
|
style: include ../../build/http/svelte-components/smui.css
|
||||||
|
style: include ../../build/http/svelte-components/style.css
|
||||||
|
style: include ../../build/http/svelte-components/material-symbols-outlined.css
|
||||||
style: include:stylus ../stylus/style.styl
|
style: include:stylus ../stylus/style.styl
|
||||||
|
|
||||||
|
|
||||||
body(v-cloak)
|
body(v-cloak)
|
||||||
|
#svelte-dialog-host
|
||||||
|
#svelte-devmode-host
|
||||||
|
|
||||||
#overlay(v-if="status != 'connected'")
|
#overlay(v-if="status != 'connected'")
|
||||||
span {{status}}
|
span {{status}}
|
||||||
|
|
||||||
#layout
|
#layout
|
||||||
a#menuLink.menu-link(href="#menu"): span
|
a#menuLink.menu-link(href="#menu"): span
|
||||||
|
|
||||||
@@ -100,36 +81,34 @@ html(lang="en")
|
|||||||
|
|
||||||
#main
|
#main
|
||||||
.header
|
.header
|
||||||
.header-content
|
.brand
|
||||||
.banner
|
img(src="/images/onefinity_logo.png")
|
||||||
img(src="/images/onefinity_logo.png")
|
.version
|
||||||
.title
|
| v{{config.full_version}}
|
||||||
.fa.fa-thermometer-full(class="error",
|
a.upgrade-link(v-if="show_upgrade()", href="#admin-general")
|
||||||
v-if="80 <= state.rpi_temp",
|
| Upgrade to v{{latestVersion}}
|
||||||
title="Raspberry Pi temperature too high.")
|
.upgrade-attention(v-if="show_upgrade()")
|
||||||
.subtitle
|
| !
|
||||||
| CNC Controller #[b {{state.demo ? 'Demo ' : ''}}]
|
|
||||||
| v{{config.full_version}}
|
|
||||||
a.upgrade-version(v-if="show_upgrade()", href="#admin-general")
|
|
||||||
| Upgrade to v{{latestVersion}}
|
|
||||||
.fa.fa-check(v-if="!show_upgrade() && latestVersion",
|
|
||||||
title="Firmware up to date" style="font-size: inherit")
|
|
||||||
.p {{get_ip_address()}} {{get_ssid()}}
|
|
||||||
|
|
||||||
.estop(:class="{active: state.es}")
|
.pi-temp-warning
|
||||||
estop(@click="estop")
|
.fa.fa-thermometer-full(class="error",
|
||||||
|
v-if="80 <= state.rpi_temp",
|
||||||
|
title="Raspberry Pi temperature too high.")
|
||||||
|
|
||||||
.video(title="Plug camera into USB.\n" +
|
.whitespace
|
||||||
"Left click to toggle video size.\n" +
|
|
||||||
"Right click to toggle crosshair.", @click="toggle_video",
|
|
||||||
@contextmenu="toggle_crosshair", :class="video_size")
|
|
||||||
.crosshair(v-if="crosshair")
|
|
||||||
.vertical
|
|
||||||
.horizontal
|
|
||||||
.box
|
|
||||||
img(src="/api/video")
|
|
||||||
|
|
||||||
.clear
|
.video(title="Plug camera into USB.\n" +
|
||||||
|
"Left click to toggle video size.\n" +
|
||||||
|
"Right click to toggle crosshair.", @click="toggle_video",
|
||||||
|
@contextmenu="toggle_crosshair", :class="video_size")
|
||||||
|
.crosshair(v-if="crosshair")
|
||||||
|
.vertical
|
||||||
|
.horizontal
|
||||||
|
.box
|
||||||
|
img(src="/api/video")
|
||||||
|
|
||||||
|
.estop(:class="{active: state.es}")
|
||||||
|
estop(@click="estop")
|
||||||
|
|
||||||
.content(class="{{currentView}}-view")
|
.content(class="{{currentView}}-view")
|
||||||
component(:is="currentView + '-view'", :index="index",
|
component(:is="currentView + '-view'", :index="index",
|
||||||
@@ -211,4 +190,5 @@ html(lang="en")
|
|||||||
script: include ../static/js/clusterize.min.js
|
script: include ../static/js/clusterize.min.js
|
||||||
script: include ../static/js/three.min.js
|
script: include ../static/js/three.min.js
|
||||||
script: include:browserify ../js/main.js
|
script: include:browserify ../js/main.js
|
||||||
|
script: include ../../build/http/svelte-components/index.js
|
||||||
script: include ../static/js/ui.js
|
script: include ../static/js/ui.js
|
||||||
|
|||||||
@@ -1,130 +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> //
|
|
||||||
//- //
|
|
||||||
//-/////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
script#admin-network-view-template(type="text/x-template")
|
script#admin-network-view-template(type="text/x-template")
|
||||||
#admin-network
|
#admin-network
|
||||||
h2 Hostname
|
#svelte-root
|
||||||
.pure-form.pure-form-aligned
|
|
||||||
.pure-control-group
|
|
||||||
label(for="hostname") Hostname
|
|
||||||
input(name="hostname", v-model="hostname", @keyup.enter="set_hostname")
|
|
||||||
button.pure-button.pure-button-primary(@click="set_hostname") Set
|
|
||||||
|
|
||||||
message(:show.sync="hostnameSet")
|
|
||||||
h3(slot="header") Hostname Set
|
|
||||||
div(slot="body")
|
|
||||||
p Hostname was successfuly set to #[strong {{hostname}}].
|
|
||||||
p Rebooting to apply changes.
|
|
||||||
p Redirecting to new hostname in {{redirectTimeout}} seconds.
|
|
||||||
div(slot="footer")
|
|
||||||
|
|
||||||
h2 Remote SSH User
|
|
||||||
.pure-form.pure-form-aligned
|
|
||||||
.pure-control-group
|
|
||||||
label(for="username") Username
|
|
||||||
input(name="username", v-model="username", @keyup.enter="set_username")
|
|
||||||
button.pure-button.pure-button-primary(@click="set_username") Set
|
|
||||||
|
|
||||||
.pure-form.pure-form-aligned
|
|
||||||
.pure-control-group
|
|
||||||
label(for="current") Current Password
|
|
||||||
input(name="current", v-model="current", type="password")
|
|
||||||
.pure-control-group
|
|
||||||
label(for="pass1") New Password
|
|
||||||
input(name="pass1", v-model="password", type="password")
|
|
||||||
.pure-control-group
|
|
||||||
label(for="pass2") New Password
|
|
||||||
input(name="pass2", v-model="password2", type="password")
|
|
||||||
button.pure-button.pure-button-primary(@click="set_password") Set
|
|
||||||
|
|
||||||
message(:show.sync="passwordSet")
|
|
||||||
h3(slot="header") Password Set
|
|
||||||
p(slot="body")
|
|
||||||
|
|
||||||
message(:show.sync="usernameSet")
|
|
||||||
h3(slot="header") Username Set
|
|
||||||
p(slot="body")
|
|
||||||
|
|
||||||
h2 Wifi Setup
|
|
||||||
.pure-form.pure-form-aligned
|
|
||||||
.pure-control-group
|
|
||||||
label(for="wifi_mode") Mode
|
|
||||||
select(name="wifi_mode", v-model="wifi_mode",
|
|
||||||
title="Select client or access point mode")
|
|
||||||
option(value="disabled") Disabled
|
|
||||||
option(value="client") Client
|
|
||||||
option(value="ap") Access Point
|
|
||||||
button.pure-button.pure-button-primary(@click="wifiConfirm = true",
|
|
||||||
v-if="wifi_mode == 'disabled'") Set
|
|
||||||
|
|
||||||
.pure-control-group(v-if="wifi_mode == 'ap'")
|
|
||||||
label(for="wifi_ch") Channel
|
|
||||||
select(name="wifi_ch", v-model="wifi_ch")
|
|
||||||
each ch in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
|
|
||||||
option(value=ch)= ch
|
|
||||||
|
|
||||||
.pure-control-group(v-if="wifi_mode != 'disabled'")
|
|
||||||
label(for="ssid") Network (SSID)
|
|
||||||
input(name="ssid", v-model="wifi_ssid")
|
|
||||||
|
|
||||||
.pure-control-group(v-if="wifi_mode != 'disabled'")
|
|
||||||
label(for="wifi_pass") Password
|
|
||||||
input(name="wifi_pass", v-model="wifi_pass", type="password")
|
|
||||||
button.pure-button.pure-button-primary(@click="wifiConfirm = true") Set
|
|
||||||
|
|
||||||
p(v-if="wifi_mode != 'disabled'").
|
|
||||||
WARNING: WiFi may be unreliable in an electrically noisy environment
|
|
||||||
such as a machine shop.
|
|
||||||
|
|
||||||
message.wifi-confirm(:show.sync="wifiConfirm")
|
|
||||||
h3(slot="header") Configure Wifi and reboot?
|
|
||||||
div(slot="body")
|
|
||||||
p
|
|
||||||
| After configuring the Wifi settings the controller will
|
|
||||||
| automatically reboot.
|
|
||||||
table
|
|
||||||
tr
|
|
||||||
th Mode
|
|
||||||
td {{wifi_mode}}
|
|
||||||
tr(v-if="wifi_mode == 'ap'")
|
|
||||||
th Channel
|
|
||||||
td {{wifi_ch}}
|
|
||||||
tr(v-if="wifi_mode != 'disabled'")
|
|
||||||
th SSID
|
|
||||||
td {{wifi_ssid}}
|
|
||||||
tr(v-if="wifi_mode != 'disabled'")
|
|
||||||
th Auth
|
|
||||||
td {{wifi_pass ? 'WPA2' : 'Open'}}
|
|
||||||
|
|
||||||
div(slot="footer")
|
|
||||||
button.pure-button(@click="wifiConfirm = false") Cancel
|
|
||||||
button.pure-button.button-success(@click="config_wifi") OK
|
|
||||||
|
|
||||||
message(:show.sync="rebooting")
|
|
||||||
h3(slot="header") Rebooting
|
|
||||||
p(slot="body") Please wait...
|
|
||||||
div(slot="footer")
|
|
||||||
|
|||||||
@@ -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> //
|
|
||||||
//- //
|
|
||||||
//-/////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
script#control-view-template(type="text/x-template")
|
script#control-view-template(type="text/x-template")
|
||||||
#control
|
#control
|
||||||
message(:show.sync="showGcodeMessage")
|
message(:show.sync="showGcodeMessage")
|
||||||
@@ -36,20 +9,7 @@ script#control-view-template(type="text/x-template")
|
|||||||
|
|
||||||
div(slot="footer")
|
div(slot="footer")
|
||||||
label Simulating {{(toolpath_progress || 0) | percent}}
|
label Simulating {{(toolpath_progress || 0) | percent}}
|
||||||
|
|
||||||
message(:show.sync=`ask_home_msg`)
|
|
||||||
h3(slot="header") Home Machine
|
|
||||||
|
|
||||||
div(slot="body")
|
|
||||||
p Home the machine?
|
|
||||||
|
|
||||||
div(slot="footer")
|
|
||||||
button.pure-button(@click="home()")
|
|
||||||
| OK
|
|
||||||
|
|
||||||
button.pure-button(@click='ask_home_msg = false; ask_home = false')
|
|
||||||
| Cancel
|
|
||||||
|
|
||||||
message(:show.sync=`ask_zero_xy_msg`)
|
message(:show.sync=`ask_zero_xy_msg`)
|
||||||
h3(slot="header") XY Origin
|
h3(slot="header") XY Origin
|
||||||
|
|
||||||
@@ -76,70 +36,6 @@ script#control-view-template(type="text/x-template")
|
|||||||
button.pure-button(@click='ask_zero_z_msg = false')
|
button.pure-button(@click='ask_zero_z_msg = false')
|
||||||
| Cancel
|
| Cancel
|
||||||
|
|
||||||
message(:show.sync=`show_probe_test_modal`)
|
|
||||||
|
|
||||||
h3(slot="header") Test probe connection
|
|
||||||
|
|
||||||
div(slot="body")
|
|
||||||
.pure-form
|
|
||||||
p Attach the probe magnet to the collet.
|
|
||||||
p Touch the probe block to the bit.
|
|
||||||
|
|
||||||
div(slot="footer")
|
|
||||||
button.pure-button(@click=`show_probe_test_modal = false`)
|
|
||||||
| Cancel
|
|
||||||
|
|
||||||
button.pure-button.button-success(
|
|
||||||
:disabled=`!state.saw_probe_connected`
|
|
||||||
@click=`finish_probe_test()`) Continue
|
|
||||||
|
|
||||||
message(:show.sync=`show_tool_diameter_modal`)
|
|
||||||
h3(slot="header") Enter probe tool information
|
|
||||||
|
|
||||||
div(slot="body")
|
|
||||||
.pure-form
|
|
||||||
.pure-control-group
|
|
||||||
label="{{metric ? 'Diameter (mm)' : 'Diameter (inches)'}}"
|
|
||||||
input(v-model="tool_diameter_for_prompt", size="8")
|
|
||||||
p
|
|
||||||
|
|
||||||
div(slot="footer")
|
|
||||||
button.pure-button(@click=`show_tool_diameter_modal = false`)
|
|
||||||
| Cancel
|
|
||||||
|
|
||||||
button.pure-button.button-success(
|
|
||||||
@click=`set_tool_diameter`)
|
|
||||||
| Set
|
|
||||||
|
|
||||||
message(:show.sync=`state.show_probe_complete_modal`)
|
|
||||||
h3(slot="header") Probing complete!
|
|
||||||
|
|
||||||
div(slot="body")
|
|
||||||
.pure-form
|
|
||||||
p Don't forget to put away the probe!
|
|
||||||
div(v-if="state.goto_xy_zero_after_probe")
|
|
||||||
p
|
|
||||||
| The machine will now move
|
|
||||||
br
|
|
||||||
| to the X-Y zero point.
|
|
||||||
p Watch your hands!
|
|
||||||
|
|
||||||
div(slot="footer")
|
|
||||||
button.pure-button.button-success(@click=`$emit("finalize_probe")`)
|
|
||||||
| Done
|
|
||||||
|
|
||||||
message(:show.sync=`state.show_probe_failed_modal`)
|
|
||||||
h3(slot="header") Probing failed!
|
|
||||||
|
|
||||||
div(slot="body")
|
|
||||||
.pure-form
|
|
||||||
p Could not find the probe block during probing!
|
|
||||||
p Make sure the tip of the bit is about 1/4" (~6mm) above the probe block, and try again.
|
|
||||||
|
|
||||||
div(slot="footer")
|
|
||||||
button.pure-button.button-success(@click=`hide_probe_failed_modal()`)
|
|
||||||
| OK
|
|
||||||
|
|
||||||
table(style="table-layout: fixed; width: 100%;")
|
table(style="table-layout: fixed; width: 100%;")
|
||||||
tr(style="height: fit-content;")
|
tr(style="height: fit-content;")
|
||||||
td(style="white-space: nowrap; width: 410px;", rowspan="2")
|
td(style="white-space: nowrap; width: 410px;", rowspan="2")
|
||||||
@@ -151,55 +47,60 @@ script#control-view-template(type="text/x-template")
|
|||||||
col(style="width:100px")
|
col(style="width:100px")
|
||||||
tr
|
tr
|
||||||
td(style="height:100px",align="center")
|
td(style="height:100px",align="center")
|
||||||
button(style="height:100px;width:100px",@click="jog_fn(-1,1,0,0)")
|
button(@click="jog_fn(-1,1,0,0)")
|
||||||
.fa.fa-arrow-right(style="transform: rotate(-135deg);")
|
.fa.fa-arrow-right(style="transform: rotate(-135deg);")
|
||||||
td(style="height:100px",align="center")
|
td(style="height:100px",align="center")
|
||||||
button(style="height:100px;width:100px",@click="jog_fn(0,1,0,0)") Y+
|
button(@click="jog_fn(0,1,0,0)") Y+
|
||||||
td(style="height:100px",align="center")
|
td(style="height:100px",align="center")
|
||||||
button(style="height:100px;width:100px",@click="jog_fn(1,1,0,0)")
|
button(@click="jog_fn(1,1,0,0)")
|
||||||
.fa.fa-arrow-right(style="transform: rotate(-45deg);")
|
.fa.fa-arrow-right(style="transform: rotate(-45deg);")
|
||||||
td(style="height:100px",align="center")
|
td(style="height:100px",align="center")
|
||||||
button(style="height:100px;width:100px",,@click="jog_fn(0,0,1,0)") Z+
|
button(,@click="jog_fn(0,0,1,0)") Z+
|
||||||
tr
|
tr
|
||||||
td(style="height:100px",align="center")
|
td(style="height:100px",align="center")
|
||||||
button(style="height:100px;width:100px",@click="jog_fn(-1,0,0,0)") X-
|
button(@click="jog_fn(-1,0,0,0)") X-
|
||||||
td(style="height:100px",align="center")
|
td(style="height:100px",align="center")
|
||||||
button(style="height:100px;width:100px",@click="ask_zero_xy_msg = true")
|
button(@click="ask_zero_xy_msg = true")
|
||||||
.fa.fa-bullseye(style="font-size: 172%")
|
.fa.fa-bullseye(style="font-size: 172%")
|
||||||
td(style="height:100px",align="center")
|
td(style="height:100px",align="center")
|
||||||
button(style="height:100px;width:100px",@click="jog_fn(1,0,0,0)") X+
|
button(@click="jog_fn(1,0,0,0)") X+
|
||||||
td(style="height:100px",align="center")
|
td(style="height:100px",align="center")
|
||||||
button(style="height:100px;width:100px",@click='ask_zero_z_msg = true') Z0
|
button(@click='ask_zero_z_msg = true') Z0
|
||||||
tr
|
tr
|
||||||
td(style="height:100px",align="center")
|
td(style="height:100px",align="center")
|
||||||
button(style="height:100px;width:100px",@click="jog_fn(-1,-1,0,0)")
|
button(@click="jog_fn(-1,-1,0,0)")
|
||||||
.fa.fa-arrow-right(style="transform: rotate(135deg);")
|
.fa.fa-arrow-right(style="transform: rotate(135deg);")
|
||||||
td(style="height:100px",align="center")
|
td(style="height:100px",align="center")
|
||||||
button(style="height:100px;width:100px",@click="jog_fn(0,-1,0,0)") Y-
|
button(@click="jog_fn(0,-1,0,0)") Y-
|
||||||
td(style="height:100px",align="center")
|
td(style="height:100px",align="center")
|
||||||
button(style="height:100px;width:100px",@click="jog_fn(1,-1,0,0)")
|
button(@click="jog_fn(1,-1,0,0)")
|
||||||
.fa.fa-arrow-right(style="transform: rotate(45deg);")
|
.fa.fa-arrow-right(style="transform: rotate(45deg);")
|
||||||
td(style="height:100px",align="center")
|
td(style="height:100px",align="center")
|
||||||
button(style="height:100px;width:100px",@click="jog_fn(0,0,-1,0)") Z-
|
button(@click="jog_fn(0,0,-1,0)") Z-
|
||||||
tr
|
tr
|
||||||
td(style="height:100px",align="center")
|
td(style="height:100px",align="center")
|
||||||
button#jog_button_fine(style="height:100px;width:100px", @click=`set_jog_incr('fine')`) 0.1
|
button(:style="getJogIncrStyle('fine')", @click="jog_incr = 'fine'")
|
||||||
|
span {{jog_incr_amounts[display_units].fine}}#[span.jog-units {{metric ? 'mm' : 'in'}}]
|
||||||
td(style="height:100px",align="center")
|
td(style="height:100px",align="center")
|
||||||
button#jog_button_small(style="height:100px;width:100px", @click=`set_jog_incr('small')`) 1.0
|
button(:style="getJogIncrStyle('small')", @click="jog_incr = 'small'")
|
||||||
|
span {{jog_incr_amounts[display_units].small}}#[span.jog-units {{metric ? 'mm' : 'in'}}]
|
||||||
td(style="height:100px",align="center")
|
td(style="height:100px",align="center")
|
||||||
button#jog_button_medium(style="height:100px;width:100px", @click=`set_jog_incr('medium')`) 10
|
button(:style="getJogIncrStyle('medium')", @click="jog_incr = 'medium'")
|
||||||
|
span {{jog_incr_amounts[display_units].medium}}#[span.jog-units {{metric ? 'mm' : 'in'}}]
|
||||||
td(style="height:100px",align="center")
|
td(style="height:100px",align="center")
|
||||||
button#jog_button_large(style="height:100px;width:100px", @click=`set_jog_incr('large')`) 100
|
button(:style="getJogIncrStyle('large')", @click="jog_incr = 'large'")
|
||||||
|
span {{jog_incr_amounts[display_units].large}}#[span.jog-units {{metric ? 'mm' : 'in'}}]
|
||||||
tr
|
tr
|
||||||
td(style="height:100px", align="center", colspan="2")
|
td(style="height:100px", align="center", colspan="2")
|
||||||
button(:class="state['pw'] ? '' : 'load-on'",
|
button(:class="state['pw'] ? '' : 'load-on'",
|
||||||
style="height:100px;width:200px",
|
style="height:100px;width:200px",
|
||||||
@click=`start_probe_test(prep_and_show_tool_diameter_modal)`)
|
@click="showProbeDialog('xyz')")
|
||||||
| Probe XYZ
|
| Probe XYZ
|
||||||
|
|
||||||
td(style="height:100px", align="center", colspan="2")
|
td(style="height:100px", align="center", colspan="2")
|
||||||
button(:class="state['pw'] ? '' : 'load-on'",
|
button(:class="state['pw'] ? '' : 'load-on'",
|
||||||
style="height:100px;width:200px",
|
style="height:100px;width:200px",
|
||||||
@click=`start_probe_test(probe_z)`)
|
@click="showProbeDialog('z')")
|
||||||
| Probe Z
|
| Probe Z
|
||||||
|
|
||||||
td(style="vertical-align: top;")
|
td(style="vertical-align: top;")
|
||||||
@@ -211,8 +112,6 @@ script#control-view-template(type="text/x-template")
|
|||||||
th.offset Offset
|
th.offset Offset
|
||||||
th.state State
|
th.state State
|
||||||
th.tstate Toolpath
|
th.tstate Toolpath
|
||||||
//th.tstate Min
|
|
||||||
//th.tstate Max
|
|
||||||
th.actions
|
th.actions
|
||||||
button.pure-button(disabled, style="height:60px;width:60px;display:none;")
|
button.pure-button(disabled, style="height:60px;width:60px;display:none;")
|
||||||
|
|
||||||
@@ -237,8 +136,6 @@ script#control-view-template(type="text/x-template")
|
|||||||
td.tstate(:class=`${axis}.tklass`, :title=`${axis}.toolmsg`, @click=`show_toolpath_msg('${axis}')`)
|
td.tstate(:class=`${axis}.tklass`, :title=`${axis}.toolmsg`, @click=`show_toolpath_msg('${axis}')`)
|
||||||
.fa(:class=`'fa-' + ${axis}.ticon`)
|
.fa(:class=`'fa-' + ${axis}.ticon`)
|
||||||
| {{#{axis}.tstate}}
|
| {{#{axis}.tstate}}
|
||||||
//td.tstate: unit-value(:value=`${axis}.pathMin`, precision=4)
|
|
||||||
//td.tstate: unit-value(:value=`${axis}.pathMax`, precision=4)
|
|
||||||
|
|
||||||
message(:show.sync=`toolpath_msg['${axis}']`)
|
message(:show.sync=`toolpath_msg['${axis}']`)
|
||||||
h3(slot="header") Tool path info {{'#{axis}' | upper}} axis
|
h3(slot="header") Tool path info {{'#{axis}' | upper}} axis
|
||||||
@@ -323,10 +220,10 @@ script#control-view-template(type="text/x-template")
|
|||||||
td.message(:class="{attention: highlight_state}")
|
td.message(:class="{attention: highlight_state}")
|
||||||
| {{message.replace(/^#/, '')}}
|
| {{message.replace(/^#/, '')}}
|
||||||
|
|
||||||
tr(title="Active machine units")
|
tr
|
||||||
th Units
|
th Units
|
||||||
td.mach_units
|
td.units
|
||||||
select(v-model="mach_units", :disabled="!is_idle")
|
select(v-model="display_units")
|
||||||
option(value="METRIC") METRIC
|
option(value="METRIC") METRIC
|
||||||
option(value="IMPERIAL") IMPERIAL
|
option(value="IMPERIAL") IMPERIAL
|
||||||
|
|
||||||
@@ -484,6 +381,9 @@ script#control-view-template(type="text/x-template")
|
|||||||
|
|
||||||
input(v-model="mdi", :disabled="!can_mdi", @keyup.enter="submit_mdi")
|
input(v-model="mdi", :disabled="!can_mdi", @keyup.enter="submit_mdi")
|
||||||
|
|
||||||
|
div
|
||||||
|
em The machine is currently operating in #[strong {{mach_units}}] units. Use G20/G21 to switch units.
|
||||||
|
|
||||||
.history(:class="{placeholder: !history}")
|
.history(:class="{placeholder: !history}")
|
||||||
span(v-if="!history.length") MDI history displays here.
|
span(v-if="!history.length") MDI history displays here.
|
||||||
ul
|
ul
|
||||||
@@ -497,9 +397,6 @@ script#control-view-template(type="text/x-template")
|
|||||||
section#content4.tab-content
|
section#content4.tab-content
|
||||||
indicators(:state="state", :template="template")
|
indicators(:state="state", :template="template")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.override(title="Feed rate override.")
|
.override(title="Feed rate override.")
|
||||||
label Feed
|
label Feed
|
||||||
input(type="range", min="0", max="2", step="0.01",
|
input(type="range", min="0", max="2", step="0.01",
|
||||||
|
|||||||
@@ -1,25 +1,3 @@
|
|||||||
//- 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> //
|
|
||||||
//- //
|
|
||||||
//-/////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
script#settings-view-template(type="text/x-template")
|
script#settings-view-template(type="text/x-template")
|
||||||
#settings
|
#settings
|
||||||
h1 Settings
|
h1 Settings
|
||||||
@@ -27,20 +5,12 @@ script#settings-view-template(type="text/x-template")
|
|||||||
.pure-form.pure-form-aligned
|
.pure-form.pure-form-aligned
|
||||||
fieldset
|
fieldset
|
||||||
h2 Units
|
h2 Units
|
||||||
templated-input(name="units", :model.sync="config.settings.units",
|
.pure-control-group
|
||||||
:template="template.settings.units")
|
label(for="units") units
|
||||||
|
select(name="units", v-model="display_units")
|
||||||
p
|
option(value="METRIC") METRIC
|
||||||
| Note, #[tt units] sets both the machine default units and the
|
option(value="IMPERIAL") IMPERIAL
|
||||||
| units used in motor configuration. GCode #[tt program-start],
|
|
||||||
| set below, may also change the default machine units.
|
|
||||||
|
|
||||||
fieldset
|
|
||||||
h2 Probing safety prompts
|
|
||||||
templated-input(name="probing-prompts",
|
|
||||||
:model.sync="config.settings['probing-prompts']",
|
|
||||||
:template="template.settings['probing-prompts']")
|
|
||||||
|
|
||||||
fieldset
|
fieldset
|
||||||
h2 Probe Dimensions
|
h2 Probe Dimensions
|
||||||
templated-input(v-for="templ in template.probe", :name="$key",
|
templated-input(v-for="templ in template.probe", :name="$key",
|
||||||
|
|||||||
@@ -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> //
|
|
||||||
//- //
|
|
||||||
//-/////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
script#templated-input-template(type="text/x-template")
|
script#templated-input-template(type="text/x-template")
|
||||||
.pure-control-group(class="tmpl-input-{{name}}", :title="title")
|
.pure-control-group(class="tmpl-input-{{name}}", :title="title")
|
||||||
label(:for="name") {{name}}
|
label(:for="name") {{name}}
|
||||||
|
|||||||
@@ -1,32 +1,4 @@
|
|||||||
################################################################################
|
|
||||||
# #
|
|
||||||
# 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 os
|
||||||
import time
|
|
||||||
import bbctrl
|
import bbctrl
|
||||||
|
|
||||||
|
|
||||||
@@ -35,13 +7,16 @@ class Ctrl(object):
|
|||||||
self.args = args
|
self.args = args
|
||||||
self.ioloop = bbctrl.IOLoop(ioloop)
|
self.ioloop = bbctrl.IOLoop(ioloop)
|
||||||
self.id = id
|
self.id = id
|
||||||
self.timeout = None # Used in demo mode
|
self.timeout = None # Used in demo mode
|
||||||
|
|
||||||
if id and not os.path.exists(id): os.mkdir(id)
|
if id and not os.path.exists(id):
|
||||||
|
os.mkdir(id)
|
||||||
|
|
||||||
# Start log
|
# Start log
|
||||||
if args.demo: log_path = self.get_path(filename = 'bbctrl.log')
|
if args.demo:
|
||||||
else: log_path = args.log
|
log_path = self.get_path(filename='bbctrl.log')
|
||||||
|
else:
|
||||||
|
log_path = args.log
|
||||||
self.log = bbctrl.log.Log(args, self.ioloop, log_path)
|
self.log = bbctrl.log.Log(args, self.ioloop, log_path)
|
||||||
|
|
||||||
self.state = bbctrl.State(self)
|
self.state = bbctrl.State(self)
|
||||||
@@ -50,14 +25,17 @@ class Ctrl(object):
|
|||||||
self.log.get('Ctrl').info('Starting %s' % self.id)
|
self.log.get('Ctrl').info('Starting %s' % self.id)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if args.demo: self.avr = bbctrl.AVREmu(self)
|
if args.demo:
|
||||||
else: self.avr = bbctrl.AVR(self)
|
self.avr = bbctrl.AVREmu(self)
|
||||||
|
else:
|
||||||
|
self.avr = bbctrl.AVR(self)
|
||||||
|
|
||||||
self.i2c = bbctrl.I2C(args.i2c_port, args.demo)
|
self.i2c = bbctrl.I2C(args.i2c_port, args.demo)
|
||||||
self.lcd = bbctrl.LCD(self)
|
self.lcd = bbctrl.LCD(self)
|
||||||
self.mach = bbctrl.Mach(self, self.avr)
|
self.mach = bbctrl.Mach(self, self.avr)
|
||||||
self.preplanner = bbctrl.Preplanner(self)
|
self.preplanner = bbctrl.Preplanner(self)
|
||||||
if not args.demo: self.jog = bbctrl.Jog(self)
|
if not args.demo:
|
||||||
|
self.jog = bbctrl.Jog(self)
|
||||||
self.pwr = bbctrl.Pwr(self)
|
self.pwr = bbctrl.Pwr(self)
|
||||||
|
|
||||||
self.mach.connect()
|
self.mach.connect()
|
||||||
@@ -67,48 +45,41 @@ class Ctrl(object):
|
|||||||
|
|
||||||
os.environ['GCODE_SCRIPT_PATH'] = self.get_upload()
|
os.environ['GCODE_SCRIPT_PATH'] = self.get_upload()
|
||||||
|
|
||||||
except Exception: self.log.get('Ctrl').exception('Internal error: Control initialization failed')
|
except Exception:
|
||||||
|
self.log.get('Ctrl').exception(
|
||||||
|
'Internal error: Control initialization failed')
|
||||||
|
|
||||||
def __del__(self): print('Ctrl deleted')
|
def __del__(self): print('Ctrl deleted')
|
||||||
|
|
||||||
|
|
||||||
def clear_timeout(self):
|
def clear_timeout(self):
|
||||||
if self.timeout is not None: self.ioloop.remove_timeout(self.timeout)
|
if self.timeout is not None:
|
||||||
|
self.ioloop.remove_timeout(self.timeout)
|
||||||
self.timeout = None
|
self.timeout = None
|
||||||
|
|
||||||
|
|
||||||
def set_timeout(self, cb, *args, **kwargs):
|
def set_timeout(self, cb, *args, **kwargs):
|
||||||
self.clear_timeout()
|
self.clear_timeout()
|
||||||
t = self.args.client_timeout
|
t = self.args.client_timeout
|
||||||
self.timeout = self.ioloop.call_later(t, cb, *args, **kwargs)
|
self.timeout = self.ioloop.call_later(t, cb, *args, **kwargs)
|
||||||
|
|
||||||
|
def get_path(self, dir=None, filename=None):
|
||||||
def get_path(self, dir = None, filename = None):
|
|
||||||
path = './' + self.id if self.id else '.'
|
path = './' + self.id if self.id else '.'
|
||||||
path = path if dir is None else (path + '/' + dir)
|
path = path if dir is None else (path + '/' + dir)
|
||||||
return path if filename is None else (path + '/' + filename)
|
return path if filename is None else (path + '/' + filename)
|
||||||
|
|
||||||
|
def get_upload(self, filename=None):
|
||||||
def get_upload(self, filename = None):
|
|
||||||
return self.get_path('upload', filename)
|
return self.get_path('upload', filename)
|
||||||
|
|
||||||
|
def get_plan(self, filename=None):
|
||||||
def get_plan(self, filename = None):
|
|
||||||
return self.get_path('plans', filename)
|
return self.get_path('plans', filename)
|
||||||
|
|
||||||
|
|
||||||
def configure(self):
|
def configure(self):
|
||||||
# Indirectly configures state via calls to config() and the AVR
|
# Indirectly configures state via calls to config() and the AVR
|
||||||
self.config.reload()
|
self.config.reload()
|
||||||
self.state.init()
|
|
||||||
|
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
# This is used to synchronize the start of the preplanner
|
# This is used to synchronize the start of the preplanner
|
||||||
self.preplanner.start()
|
self.preplanner.start()
|
||||||
|
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
self.log.get('Ctrl').info('Closing %s' % self.id)
|
self.log.get('Ctrl').info('Closing %s' % self.id)
|
||||||
self.ioloop.close()
|
self.ioloop.close()
|
||||||
|
|||||||
@@ -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 bbctrl
|
import bbctrl
|
||||||
from bbctrl.Comm import Comm
|
from bbctrl.Comm import Comm
|
||||||
import bbctrl.Cmd as Cmd
|
import bbctrl.Cmd as Cmd
|
||||||
@@ -74,6 +47,7 @@ and check your motor cabling. See the "Motor Faults" table on the "Indicators" \
|
|||||||
for more information.\
|
for more information.\
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
def overrides(interface_class):
|
def overrides(interface_class):
|
||||||
def overrider(method):
|
def overrider(method):
|
||||||
if not method.__name__ in dir(interface_class):
|
if not method.__name__ in dir(interface_class):
|
||||||
@@ -102,7 +76,6 @@ class Mach(Comm):
|
|||||||
|
|
||||||
super().reboot()
|
super().reboot()
|
||||||
|
|
||||||
|
|
||||||
def _get_state(self): return self.ctrl.state.get('xx', '')
|
def _get_state(self): return self.ctrl.state.get('xx', '')
|
||||||
def _is_estopped(self): return self._get_state() == 'ESTOPPED'
|
def _is_estopped(self): return self._get_state() == 'ESTOPPED'
|
||||||
def _is_holding(self): return self._get_state() == 'HOLDING'
|
def _is_holding(self): return self._get_state() == 'HOLDING'
|
||||||
@@ -110,19 +83,18 @@ class Mach(Comm):
|
|||||||
def _get_pause_reason(self): return self.ctrl.state.get('pr', '')
|
def _get_pause_reason(self): return self.ctrl.state.get('pr', '')
|
||||||
def _get_cycle(self): return self.ctrl.state.get('cycle', 'idle')
|
def _get_cycle(self): return self.ctrl.state.get('cycle', 'idle')
|
||||||
|
|
||||||
|
|
||||||
def _is_paused(self):
|
def _is_paused(self):
|
||||||
if not self._is_holding() or self.unpausing: return False
|
if not self._is_holding() or self.unpausing:
|
||||||
|
return False
|
||||||
return self._get_pause_reason() in (
|
return self._get_pause_reason() in (
|
||||||
'User pause', 'Program pause', 'Optional pause')
|
'User pause', 'Program pause', 'Optional pause')
|
||||||
|
|
||||||
|
|
||||||
def _set_cycle(self, cycle): self.ctrl.state.set('cycle', cycle)
|
def _set_cycle(self, cycle): self.ctrl.state.set('cycle', cycle)
|
||||||
|
|
||||||
|
|
||||||
def _begin_cycle(self, cycle):
|
def _begin_cycle(self, cycle):
|
||||||
current = self._get_cycle()
|
current = self._get_cycle()
|
||||||
if current == cycle: return # No change
|
if current == cycle:
|
||||||
|
return # No change
|
||||||
|
|
||||||
if current != 'idle':
|
if current != 'idle':
|
||||||
raise Exception('Cannot enter %s cycle while in %s cycle' %
|
raise Exception('Cannot enter %s cycle while in %s cycle' %
|
||||||
@@ -132,14 +104,12 @@ class Mach(Comm):
|
|||||||
# if current == 'idle' or (cycle == 'jogging' and self._is_paused()):
|
# if current == 'idle' or (cycle == 'jogging' and self._is_paused()):
|
||||||
self._set_cycle(cycle)
|
self._set_cycle(cycle)
|
||||||
|
|
||||||
|
|
||||||
def process_log(self, log):
|
def process_log(self, log):
|
||||||
# When a probe has failed, we have to e-stop or things
|
# When a probe has failed, we have to e-stop or things
|
||||||
# end up in a bad state, where positions and offsets are incorrect
|
# end up in a bad state, where positions and offsets are incorrect
|
||||||
if log['msg'] == 'Switch not found':
|
if log['msg'] == 'Switch not found':
|
||||||
self.estop()
|
self.estop()
|
||||||
|
|
||||||
|
|
||||||
def _update(self, update):
|
def _update(self, update):
|
||||||
# Detect motor faults
|
# Detect motor faults
|
||||||
for motor in range(4):
|
for motor in range(4):
|
||||||
@@ -153,12 +123,12 @@ class Mach(Comm):
|
|||||||
|
|
||||||
# Handle EStop
|
# Handle EStop
|
||||||
if state_changed and state == 'ESTOPPED':
|
if state_changed and state == 'ESTOPPED':
|
||||||
self.planner.reset(stop = False)
|
self.planner.reset(stop=False)
|
||||||
|
|
||||||
# Exit cycle if state changed to READY
|
# Exit cycle if state changed to READY
|
||||||
if (state_changed and self._get_cycle() != 'idle' and
|
if (state_changed and self._get_cycle() != 'idle' and
|
||||||
self._is_ready() and not self.planner.is_busy() and
|
self._is_ready() and not self.planner.is_busy() and
|
||||||
not super().is_active()):
|
not super().is_active()):
|
||||||
self.planner.position_change()
|
self.planner.position_change()
|
||||||
self._set_cycle('idle')
|
self._set_cycle('idle')
|
||||||
|
|
||||||
@@ -187,7 +157,6 @@ class Mach(Comm):
|
|||||||
(pr == 'Optional pause' and not op))):
|
(pr == 'Optional pause' and not op))):
|
||||||
self._unpause()
|
self._unpause()
|
||||||
|
|
||||||
|
|
||||||
def _unpause(self):
|
def _unpause(self):
|
||||||
pause_reason = self._get_pause_reason()
|
pause_reason = self._get_pause_reason()
|
||||||
self.mlog.info('Unpause: ' + pause_reason)
|
self.mlog.info('Unpause: ' + pause_reason)
|
||||||
@@ -196,36 +165,31 @@ class Mach(Comm):
|
|||||||
self.planner.stop()
|
self.planner.stop()
|
||||||
self.ctrl.state.set('line', 0)
|
self.ctrl.state.set('line', 0)
|
||||||
|
|
||||||
else: self.planner.restart()
|
else:
|
||||||
|
self.planner.restart()
|
||||||
|
|
||||||
super().i2c_command(Cmd.UNPAUSE)
|
super().i2c_command(Cmd.UNPAUSE)
|
||||||
self.unpausing = True
|
self.unpausing = True
|
||||||
|
|
||||||
|
|
||||||
def _i2c_block(self, block):
|
def _i2c_block(self, block):
|
||||||
super().i2c_command(block[0], block = block[1:])
|
super().i2c_command(block[0], block=block[1:])
|
||||||
|
|
||||||
|
|
||||||
def _i2c_set(self, name, value): self._i2c_block(Cmd.set(name, value))
|
def _i2c_set(self, name, value): self._i2c_block(Cmd.set(name, value))
|
||||||
|
|
||||||
|
|
||||||
@overrides(Comm)
|
@overrides(Comm)
|
||||||
def comm_next(self):
|
def comm_next(self):
|
||||||
if self.planner.is_running() and not self._is_holding():
|
if self.planner.is_running() and not self._is_holding():
|
||||||
return self.planner.next()
|
return self.planner.next()
|
||||||
|
|
||||||
|
|
||||||
@overrides(Comm)
|
@overrides(Comm)
|
||||||
def comm_error(self):
|
def comm_error(self):
|
||||||
self.planner.reset()
|
self.planner.reset()
|
||||||
|
|
||||||
|
|
||||||
@overrides(Comm)
|
@overrides(Comm)
|
||||||
def connect(self):
|
def connect(self):
|
||||||
self.planner.reset()
|
self.planner.reset()
|
||||||
super().connect()
|
super().connect()
|
||||||
|
|
||||||
|
|
||||||
def _query_var(self, cmd):
|
def _query_var(self, cmd):
|
||||||
equal = cmd.find('=')
|
equal = cmd.find('=')
|
||||||
if equal == -1:
|
if equal == -1:
|
||||||
@@ -234,21 +198,26 @@ class Mach(Comm):
|
|||||||
else:
|
else:
|
||||||
name, value = cmd[1:equal], cmd[equal + 1:]
|
name, value = cmd[1:equal], cmd[equal + 1:]
|
||||||
|
|
||||||
if value.lower() == 'true': value = True
|
if value.lower() == 'true':
|
||||||
elif value.lower() == 'false': value = False
|
value = True
|
||||||
|
elif value.lower() == 'false':
|
||||||
|
value = False
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
value = float(value)
|
value = float(value)
|
||||||
except: pass
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
self.ctrl.state.config(name, value)
|
self.ctrl.state.config(name, value)
|
||||||
|
|
||||||
|
def mdi(self, cmd, with_limits=True):
|
||||||
def mdi(self, cmd, with_limits = True):
|
|
||||||
try:
|
try:
|
||||||
if not len(cmd): return
|
if not len(cmd):
|
||||||
if cmd[0] == '$': self._query_var(cmd)
|
return
|
||||||
elif cmd[0] == '\\': super().queue_command(cmd[1:])
|
if cmd[0] == '$':
|
||||||
|
self._query_var(cmd)
|
||||||
|
elif cmd[0] == '\\':
|
||||||
|
super().queue_command(cmd[1:])
|
||||||
else:
|
else:
|
||||||
self._begin_cycle('mdi')
|
self._begin_cycle('mdi')
|
||||||
self.planner.mdi(cmd, with_limits)
|
self.planner.mdi(cmd, with_limits)
|
||||||
@@ -260,18 +229,18 @@ class Mach(Comm):
|
|||||||
def set(self, code, value):
|
def set(self, code, value):
|
||||||
super().queue_command('${}={}'.format(code, value))
|
super().queue_command('${}={}'.format(code, value))
|
||||||
|
|
||||||
|
|
||||||
def jog(self, axes):
|
def jog(self, axes):
|
||||||
self._begin_cycle('jogging')
|
self._begin_cycle('jogging')
|
||||||
self.planner.position_change()
|
self.planner.position_change()
|
||||||
super().queue_command(Cmd.jog(axes))
|
super().queue_command(Cmd.jog(axes))
|
||||||
|
|
||||||
|
def home(self, axis, position=None):
|
||||||
def home(self, axis, position = None):
|
|
||||||
state = self.ctrl.state
|
state = self.ctrl.state
|
||||||
|
|
||||||
if axis is None: axes = 'zxyabc' # TODO This should be configurable
|
if axis is None:
|
||||||
else: axes = '%c' % axis
|
axes = 'zxyabc' # TODO This should be configurable
|
||||||
|
else:
|
||||||
|
axes = '%c' % axis
|
||||||
|
|
||||||
for axis in axes:
|
for axis in axes:
|
||||||
enabled = state.is_axis_enabled(axis)
|
enabled = state.is_axis_enabled(axis)
|
||||||
@@ -291,7 +260,8 @@ class Mach(Comm):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
if mode == 'manual':
|
if mode == 'manual':
|
||||||
if position is None: raise Exception('Position not set')
|
if position is None:
|
||||||
|
raise Exception('Position not set')
|
||||||
self.mdi('G28.3 %c%f' % (axis, position))
|
self.mdi('G28.3 %c%f' % (axis, position))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -299,56 +269,63 @@ class Mach(Comm):
|
|||||||
self.mlog.info('Homing %s axis' % axis)
|
self.mlog.info('Homing %s axis' % axis)
|
||||||
self._begin_cycle('homing')
|
self._begin_cycle('homing')
|
||||||
|
|
||||||
if mode.startswith('stall-'): procedure = stall_homing_procedure
|
if mode.startswith('stall-'):
|
||||||
else: procedure = axis_homing_procedure
|
procedure = stall_homing_procedure
|
||||||
|
else:
|
||||||
|
procedure = axis_homing_procedure
|
||||||
|
|
||||||
gcode = procedure % {'axis': axis}
|
gcode = procedure % {'axis': axis}
|
||||||
|
|
||||||
self.planner.mdi(gcode, False)
|
self.planner.mdi(gcode, False)
|
||||||
super().resume()
|
super().resume()
|
||||||
|
|
||||||
|
|
||||||
def unhome(self, axis): self.mdi('G28.2 %c0' % axis)
|
def unhome(self, axis): self.mdi('G28.2 %c0' % axis)
|
||||||
def estop(self): super().estop()
|
def estop(self): super().estop()
|
||||||
|
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
if self._is_estopped():
|
if self._is_estopped():
|
||||||
self.planner.reset()
|
self.planner.reset()
|
||||||
super().clear()
|
super().clear()
|
||||||
|
|
||||||
|
def fake_probe_contact(self):
|
||||||
|
self._i2c_set('pt', 2)
|
||||||
|
self.ctrl.state.set('pw', 0)
|
||||||
|
self.timer = self.ctrl.ioloop.call_later(0.5, self.clear_fake_probe_contact)
|
||||||
|
|
||||||
|
def clear_fake_probe_contact(self):
|
||||||
|
self._i2c_set('pt', 1)
|
||||||
|
self.ctrl.state.set('pw', 1)
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
filename = self.ctrl.state.get('selected', '')
|
filename = self.ctrl.state.get('selected', '')
|
||||||
if not filename: return
|
if not filename:
|
||||||
|
return
|
||||||
self._begin_cycle('running')
|
self._begin_cycle('running')
|
||||||
self.planner.load(filename)
|
self.planner.load(filename)
|
||||||
super().resume()
|
super().resume()
|
||||||
|
|
||||||
|
|
||||||
def step(self):
|
def step(self):
|
||||||
raise Exception('NYI') # TODO
|
raise Exception('NYI') # TODO
|
||||||
if self._get_cycle() != 'running': self.start()
|
if self._get_cycle() != 'running':
|
||||||
else: super().i2c_command(Cmd.UNPAUSE)
|
self.start()
|
||||||
|
else:
|
||||||
|
super().i2c_command(Cmd.UNPAUSE)
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
if self._get_state() != 'jogging': self.stopping = True
|
if self._get_state() != 'jogging':
|
||||||
|
self.stopping = True
|
||||||
super().i2c_command(Cmd.STOP)
|
super().i2c_command(Cmd.STOP)
|
||||||
|
|
||||||
def pause(self): super().pause()
|
|
||||||
|
|
||||||
|
def pause(self): super().pause()
|
||||||
|
|
||||||
def unpause(self):
|
def unpause(self):
|
||||||
if self._is_paused():
|
if self._is_paused():
|
||||||
self.ctrl.state.set('optional_pause', False)
|
self.ctrl.state.set('optional_pause', False)
|
||||||
self._unpause()
|
self._unpause()
|
||||||
|
|
||||||
|
def optional_pause(self, enable=True):
|
||||||
def optional_pause(self, enable = True):
|
|
||||||
self.ctrl.state.set('optional_pause', enable)
|
self.ctrl.state.set('optional_pause', enable)
|
||||||
|
|
||||||
|
|
||||||
def set_position(self, axis, position):
|
def set_position(self, axis, position):
|
||||||
axis = axis.lower()
|
axis = axis.lower()
|
||||||
state = self.ctrl.state
|
state = self.ctrl.state
|
||||||
@@ -367,17 +344,13 @@ class Mach(Comm):
|
|||||||
state.set(axis + 'p', target)
|
state.set(axis + 'p', target)
|
||||||
super().queue_command(Cmd.set_axis(axis, target))
|
super().queue_command(Cmd.set_axis(axis, target))
|
||||||
|
|
||||||
|
|
||||||
def override_feed(self, override):
|
def override_feed(self, override):
|
||||||
self._i2c_set('fo', int(1000 * override))
|
self._i2c_set('fo', int(1000 * override))
|
||||||
|
|
||||||
|
|
||||||
def override_speed(self, override):
|
def override_speed(self, override):
|
||||||
self._i2c_set('so', int(1000 * override))
|
self._i2c_set('so', int(1000 * override))
|
||||||
|
|
||||||
|
|
||||||
def modbus_read(self, addr): self._i2c_block(Cmd.modbus_read(addr))
|
def modbus_read(self, addr): self._i2c_block(Cmd.modbus_read(addr))
|
||||||
|
|
||||||
|
|
||||||
def modbus_write(self, addr, value):
|
def modbus_write(self, addr, value):
|
||||||
self._i2c_block(Cmd.modbus_write(addr, value))
|
self._i2c_block(Cmd.modbus_write(addr, value))
|
||||||
|
|||||||
@@ -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 traceback
|
import traceback
|
||||||
import copy
|
import copy
|
||||||
import uuid
|
import uuid
|
||||||
@@ -33,11 +6,12 @@ import bbctrl
|
|||||||
from watchdog.observers import Observer
|
from watchdog.observers import Observer
|
||||||
from watchdog.events import FileSystemEventHandler
|
from watchdog.events import FileSystemEventHandler
|
||||||
|
|
||||||
|
|
||||||
class UploadChangeHandler(FileSystemEventHandler):
|
class UploadChangeHandler(FileSystemEventHandler):
|
||||||
def __init__(self, state):
|
def __init__(self, state):
|
||||||
self.state = state
|
self.state = state
|
||||||
|
|
||||||
def on_any_event(self, event):
|
def on_any_event(self, event):
|
||||||
self.state.load_files()
|
self.state.load_files()
|
||||||
|
|
||||||
|
|
||||||
@@ -70,52 +44,36 @@ class State(object):
|
|||||||
# the planner will scale returned values when in imperial mode.
|
# the planner will scale returned values when in imperial mode.
|
||||||
for i in range(4):
|
for i in range(4):
|
||||||
self.set_callback(str(i) + 'home_position',
|
self.set_callback(str(i) + 'home_position',
|
||||||
lambda name, i = i: self.motor_home_position(i))
|
lambda name, i=i: self.motor_home_position(i))
|
||||||
self.set_callback(str(i) + 'home_travel',
|
self.set_callback(str(i) + 'home_travel',
|
||||||
lambda name, i = i: self.motor_home_travel(i))
|
lambda name, i=i: self.motor_home_travel(i))
|
||||||
self.set_callback(str(i) + 'latch_backoff',
|
self.set_callback(str(i) + 'latch_backoff',
|
||||||
lambda name, i = i: self.motor_latch_backoff(i))
|
lambda name, i=i: self.motor_latch_backoff(i))
|
||||||
self.set_callback(str(i) + 'zero_backoff',
|
self.set_callback(str(i) + 'zero_backoff',
|
||||||
lambda name, i = i: self.motor_zero_backoff(i))
|
lambda name, i=i: self.motor_zero_backoff(i))
|
||||||
self.set_callback(str(i) + 'search_velocity',
|
self.set_callback(str(i) + 'search_velocity',
|
||||||
lambda name, i = i: self.motor_search_velocity(i))
|
lambda name, i=i: self.motor_search_velocity(i))
|
||||||
self.set_callback(str(i) + 'latch_velocity',
|
self.set_callback(str(i) + 'latch_velocity',
|
||||||
lambda name, i = i: self.motor_latch_velocity(i))
|
lambda name, i=i: self.motor_latch_velocity(i))
|
||||||
|
|
||||||
#self.set_callback('metric', lambda name: 1 if self.is_metric() else 0)
|
|
||||||
#self.set_callback('imperial', lambda name: 0 if self.is_metric() else 1)
|
|
||||||
|
|
||||||
self.reset()
|
self.reset()
|
||||||
self.load_files()
|
self.load_files()
|
||||||
|
|
||||||
observer = Observer()
|
observer = Observer()
|
||||||
observer.schedule(UploadChangeHandler(self), self.ctrl.get_upload(), recursive=True)
|
observer.schedule(UploadChangeHandler(
|
||||||
|
self), self.ctrl.get_upload(), recursive=True)
|
||||||
observer.start()
|
observer.start()
|
||||||
|
|
||||||
|
|
||||||
def init(self):
|
|
||||||
# Init machine units
|
|
||||||
metric = self.ctrl.config.get('units', 'METRIC').upper() == 'METRIC'
|
|
||||||
self.log.info('INIT Metric %d' % metric)
|
|
||||||
if not 'metric' in self.vars: self.set('metric', metric)
|
|
||||||
if not 'imperial' in self.vars: self.set('imperial', not metric)
|
|
||||||
|
|
||||||
# Bit diameter for probing
|
|
||||||
diameter = self.ctrl.config.get('probe-diameter', 6.35)
|
|
||||||
self.log.info('INIT Diameter %f' % diameter)
|
|
||||||
self.set('bitDiameter',diameter)
|
|
||||||
|
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
# Unhome all motors
|
# Unhome all motors
|
||||||
for i in range(4): self.set('%dhomed' % i, False)
|
for i in range(4):
|
||||||
|
self.set('%dhomed' % i, False)
|
||||||
|
|
||||||
# Zero offsets and positions
|
# Zero offsets and positions
|
||||||
for axis in 'xyzabc':
|
for axis in 'xyzabc':
|
||||||
self.set(axis + 'p', 0)
|
self.set(axis + 'p', 0)
|
||||||
self.set('offset_' + axis, 0)
|
self.set('offset_' + axis, 0)
|
||||||
|
|
||||||
|
|
||||||
def load_files(self):
|
def load_files(self):
|
||||||
files = []
|
files = []
|
||||||
|
|
||||||
@@ -133,15 +91,15 @@ class State(object):
|
|||||||
files.sort()
|
files.sort()
|
||||||
self.set('files', files)
|
self.set('files', files)
|
||||||
|
|
||||||
if len(files): self.select_file(files[0])
|
if len(files):
|
||||||
else: self.select_file('')
|
self.select_file(files[0])
|
||||||
|
else:
|
||||||
|
self.select_file('')
|
||||||
|
|
||||||
def clear_files(self):
|
def clear_files(self):
|
||||||
self.select_file('')
|
self.select_file('')
|
||||||
self.set('files', [])
|
self.set('files', [])
|
||||||
|
|
||||||
|
|
||||||
def add_file(self, filename):
|
def add_file(self, filename):
|
||||||
files = copy.deepcopy(self.get('files'))
|
files = copy.deepcopy(self.get('files'))
|
||||||
if not filename in files:
|
if not filename in files:
|
||||||
@@ -151,7 +109,6 @@ class State(object):
|
|||||||
|
|
||||||
self.select_file(filename)
|
self.select_file(filename)
|
||||||
|
|
||||||
|
|
||||||
def remove_file(self, filename):
|
def remove_file(self, filename):
|
||||||
files = copy.deepcopy(self.get('files'))
|
files = copy.deepcopy(self.get('files'))
|
||||||
if filename in files:
|
if filename in files:
|
||||||
@@ -159,16 +116,16 @@ class State(object):
|
|||||||
self.set('files', files)
|
self.set('files', files)
|
||||||
|
|
||||||
if self.get('selected', filename) == filename:
|
if self.get('selected', filename) == filename:
|
||||||
if len(files): self.select_file(files[0])
|
if len(files):
|
||||||
else: self.select_file('')
|
self.select_file(files[0])
|
||||||
|
else:
|
||||||
|
self.select_file('')
|
||||||
|
|
||||||
def select_file(self, filename):
|
def select_file(self, filename):
|
||||||
self.set('selected', filename)
|
self.set('selected', filename)
|
||||||
time = os.path.getmtime(self.ctrl.get_upload(filename))
|
time = os.path.getmtime(self.ctrl.get_upload(filename))
|
||||||
self.set('selected_time', time)
|
self.set('selected_time', time)
|
||||||
|
|
||||||
|
|
||||||
def set_bounds(self, bounds):
|
def set_bounds(self, bounds):
|
||||||
for axis in 'xyzabc':
|
for axis in 'xyzabc':
|
||||||
for name in ('min', 'max'):
|
for name in ('min', 'max'):
|
||||||
@@ -176,24 +133,22 @@ class State(object):
|
|||||||
value = bounds[name][axis] if axis in bounds[name] else 0
|
value = bounds[name][axis] if axis in bounds[name] else 0
|
||||||
self.set(var, value)
|
self.set(var, value)
|
||||||
|
|
||||||
|
|
||||||
def ack_message(self, id):
|
def ack_message(self, id):
|
||||||
self.log.info('Message %d acknowledged' % id)
|
self.log.info('Message %d acknowledged' % id)
|
||||||
msgs = self.vars['messages']
|
msgs = self.vars['messages']
|
||||||
msgs = list(filter(lambda m: id < m['id'], msgs))
|
msgs = list(filter(lambda m: id < m['id'], msgs))
|
||||||
self.set('messages', msgs)
|
self.set('messages', msgs)
|
||||||
|
|
||||||
|
|
||||||
def add_message(self, text):
|
def add_message(self, text):
|
||||||
msg = dict(text = text, id = self.message_id)
|
msg = dict(text=text, id=self.message_id)
|
||||||
self.message_id += 1
|
self.message_id += 1
|
||||||
msgs = self.vars['messages']
|
msgs = self.vars['messages']
|
||||||
msgs = msgs + [msg] # It's important we make a new list here
|
msgs = msgs + [msg] # It's important we make a new list here
|
||||||
self.set('messages', msgs)
|
self.set('messages', msgs)
|
||||||
|
|
||||||
|
|
||||||
def _notify(self):
|
def _notify(self):
|
||||||
if not self.changes: return
|
if not self.changes:
|
||||||
|
return
|
||||||
|
|
||||||
for listener in self.listeners:
|
for listener in self.listeners:
|
||||||
try:
|
try:
|
||||||
@@ -201,25 +156,23 @@ class State(object):
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.log.warning('Updating state listener: %s',
|
self.log.warning('Updating state listener: %s',
|
||||||
traceback.format_exc())
|
traceback.format_exc())
|
||||||
|
|
||||||
self.changes = {}
|
self.changes = {}
|
||||||
self.timeout = None
|
self.timeout = None
|
||||||
|
|
||||||
|
|
||||||
def resolve(self, name):
|
def resolve(self, name):
|
||||||
# Resolve axis prefixes to motor numbers
|
# Resolve axis prefixes to motor numbers
|
||||||
if 2 < len(name) and name[1] == '_' and name[0] in 'xyzabc':
|
if 2 < len(name) and name[1] == '_' and name[0] in 'xyzabc':
|
||||||
motor = self.find_motor(name[0])
|
motor = self.find_motor(name[0])
|
||||||
if motor is not None: return str(motor) + name[2:]
|
if motor is not None:
|
||||||
|
return str(motor) + name[2:]
|
||||||
|
|
||||||
return name
|
return name
|
||||||
|
|
||||||
|
|
||||||
def has(self, name): return self.resolve(name) in self.vars
|
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_callback(self, name, cb): self.callbacks[self.resolve(name)] = cb
|
||||||
|
|
||||||
|
|
||||||
def set(self, name, value):
|
def set(self, name, value):
|
||||||
name = self.resolve(name)
|
name = self.resolve(name)
|
||||||
|
|
||||||
@@ -231,23 +184,22 @@ class State(object):
|
|||||||
if self.timeout is None:
|
if self.timeout is None:
|
||||||
self.timeout = self.ctrl.ioloop.call_later(0.25, self._notify)
|
self.timeout = self.ctrl.ioloop.call_later(0.25, self._notify)
|
||||||
|
|
||||||
|
|
||||||
def update(self, update):
|
def update(self, update):
|
||||||
for name, value in update.items():
|
for name, value in update.items():
|
||||||
self.set(name, value)
|
self.set(name, value)
|
||||||
|
|
||||||
|
def get(self, name, default=None):
|
||||||
def get(self, name, default = None):
|
|
||||||
name = self.resolve(name)
|
name = self.resolve(name)
|
||||||
|
|
||||||
if name in self.vars: return self.vars[name]
|
if name in self.vars:
|
||||||
if name in self.callbacks: return self.callbacks[name](name)
|
return self.vars[name]
|
||||||
|
if name in self.callbacks:
|
||||||
|
return self.callbacks[name](name)
|
||||||
if default is None:
|
if default is None:
|
||||||
self.log.warning('State variable "%s" not found' % name)
|
self.log.warning('State variable "%s" not found' % name)
|
||||||
|
|
||||||
return default
|
return default
|
||||||
|
|
||||||
|
|
||||||
def snapshot(self):
|
def snapshot(self):
|
||||||
vars = copy.deepcopy(self.vars)
|
vars = copy.deepcopy(self.vars)
|
||||||
|
|
||||||
@@ -270,21 +222,19 @@ class State(object):
|
|||||||
|
|
||||||
return vars
|
return vars
|
||||||
|
|
||||||
|
|
||||||
def config(self, code, value):
|
def config(self, code, value):
|
||||||
# Set machine variables via mach, others directly
|
# Set machine variables via mach, others directly
|
||||||
if code in self.machine_var_set: self.ctrl.mach.set(code, value)
|
if code in self.machine_var_set:
|
||||||
else: self.set(code, value)
|
self.ctrl.mach.set(code, value)
|
||||||
|
else:
|
||||||
|
self.set(code, value)
|
||||||
|
|
||||||
def add_listener(self, listener):
|
def add_listener(self, listener):
|
||||||
self.listeners.append(listener)
|
self.listeners.append(listener)
|
||||||
listener(self.vars)
|
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):
|
def set_machine_vars(self, vars):
|
||||||
# Record all machine vars, indexed or otherwise
|
# Record all machine vars, indexed or otherwise
|
||||||
self.machine_var_set = set()
|
self.machine_var_set = set()
|
||||||
@@ -292,8 +242,8 @@ class State(object):
|
|||||||
if 'index' in spec:
|
if 'index' in spec:
|
||||||
for index in spec['index']:
|
for index in spec['index']:
|
||||||
self.machine_var_set.add(index + code)
|
self.machine_var_set.add(index + code)
|
||||||
else: self.machine_var_set.add(code)
|
else:
|
||||||
|
self.machine_var_set.add(code)
|
||||||
|
|
||||||
def get_position(self):
|
def get_position(self):
|
||||||
position = {}
|
position = {}
|
||||||
@@ -304,8 +254,7 @@ class State(object):
|
|||||||
|
|
||||||
return position
|
return position
|
||||||
|
|
||||||
|
def get_axis_vector(self, name, scale=1):
|
||||||
def get_axis_vector(self, name, scale = 1):
|
|
||||||
v = {}
|
v = {}
|
||||||
|
|
||||||
for axis in 'xyzabc':
|
for axis in 'xyzabc':
|
||||||
@@ -313,11 +262,11 @@ class State(object):
|
|||||||
|
|
||||||
if motor is not None and self.motor_enabled(motor):
|
if motor is not None and self.motor_enabled(motor):
|
||||||
value = self.get(str(motor) + name, None)
|
value = self.get(str(motor) + name, None)
|
||||||
if value is not None: v[axis] = value * scale
|
if value is not None:
|
||||||
|
v[axis] = value * scale
|
||||||
|
|
||||||
return v
|
return v
|
||||||
|
|
||||||
|
|
||||||
def get_soft_limit_vector(self, var, default):
|
def get_soft_limit_vector(self, var, default):
|
||||||
limit = self.get_axis_vector(var, 1)
|
limit = self.get_axis_vector(var, 1)
|
||||||
|
|
||||||
@@ -327,23 +276,20 @@ class State(object):
|
|||||||
|
|
||||||
return limit
|
return limit
|
||||||
|
|
||||||
|
|
||||||
def find_motor(self, axis):
|
def find_motor(self, axis):
|
||||||
for motor in range(4):
|
for motor in range(4):
|
||||||
if not ('%dan' % motor) in self.vars: continue
|
if not ('%dan' % motor) in self.vars:
|
||||||
|
continue
|
||||||
motor_axis = 'xyzabc'[self.vars['%dan' % motor]]
|
motor_axis = 'xyzabc'[self.vars['%dan' % motor]]
|
||||||
if motor_axis == axis.lower() and self.vars.get('%dme' % motor, 0):
|
if motor_axis == axis.lower() and self.vars.get('%dme' % motor, 0):
|
||||||
return motor
|
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):
|
def is_axis_enabled(self, axis):
|
||||||
motor = self.find_motor(axis)
|
motor = self.find_motor(axis)
|
||||||
return motor is not None and self.motor_enabled(motor)
|
return motor is not None and self.motor_enabled(motor)
|
||||||
|
|
||||||
|
|
||||||
def get_enabled_axes(self):
|
def get_enabled_axes(self):
|
||||||
axes = []
|
axes = []
|
||||||
|
|
||||||
@@ -353,26 +299,25 @@ class State(object):
|
|||||||
|
|
||||||
return axes
|
return axes
|
||||||
|
|
||||||
|
|
||||||
def is_motor_faulted(self, motor):
|
def is_motor_faulted(self, motor):
|
||||||
return self.get('%ddf' % motor, 0) & 0x1f
|
return self.get('%ddf' % motor, 0) & 0x1f
|
||||||
|
|
||||||
|
|
||||||
def is_axis_faulted(self, axis):
|
def is_axis_faulted(self, axis):
|
||||||
motor = self.find_motor(axis)
|
motor = self.find_motor(axis)
|
||||||
return motor is not None and self.is_motor_faulted(motor)
|
return motor is not None and self.is_motor_faulted(motor)
|
||||||
|
|
||||||
|
|
||||||
def axis_homing_mode(self, axis):
|
def axis_homing_mode(self, axis):
|
||||||
motor = self.find_motor(axis)
|
motor = self.find_motor(axis)
|
||||||
if motor is None: return 'disabled'
|
if motor is None:
|
||||||
|
return 'disabled'
|
||||||
return self.motor_homing_mode(motor)
|
return self.motor_homing_mode(motor)
|
||||||
|
|
||||||
|
|
||||||
def axis_home_fail_reason(self, axis):
|
def axis_home_fail_reason(self, axis):
|
||||||
motor = self.find_motor(axis)
|
motor = self.find_motor(axis)
|
||||||
if motor is None: return 'Not mapped to motor'
|
if motor is None:
|
||||||
if not self.motor_enabled(motor): return 'Motor disabled'
|
return 'Not mapped to motor'
|
||||||
|
if not self.motor_enabled(motor):
|
||||||
|
return 'Motor disabled'
|
||||||
|
|
||||||
mode = self.motor_homing_mode(motor)
|
mode = self.motor_homing_mode(motor)
|
||||||
|
|
||||||
@@ -389,35 +334,39 @@ class State(object):
|
|||||||
return 'max-soft-limit must be at least 1mm greater ' \
|
return 'max-soft-limit must be at least 1mm greater ' \
|
||||||
'than min-soft-limit'
|
'than min-soft-limit'
|
||||||
|
|
||||||
|
|
||||||
def motor_enabled(self, motor):
|
def motor_enabled(self, motor):
|
||||||
return bool(int(self.vars.get('%dme' % motor, 0)))
|
return bool(int(self.vars.get('%dme' % motor, 0)))
|
||||||
|
|
||||||
|
|
||||||
def motor_homing_mode(self, motor):
|
def motor_homing_mode(self, motor):
|
||||||
mode = str(self.vars.get('%dho' % motor, 0))
|
mode = str(self.vars.get('%dho' % motor, 0))
|
||||||
if mode == '0': return 'manual'
|
if mode == '0':
|
||||||
if mode == '1': return 'switch-min'
|
return 'manual'
|
||||||
if mode == '2': return 'switch-max'
|
if mode == '1':
|
||||||
if mode == '3': return 'stall-min'
|
return 'switch-min'
|
||||||
if mode == '4': return 'stall-max'
|
if mode == '2':
|
||||||
|
return 'switch-max'
|
||||||
|
if mode == '3':
|
||||||
|
return 'stall-min'
|
||||||
|
if mode == '4':
|
||||||
|
return 'stall-max'
|
||||||
raise Exception('Unrecognized homing mode "%s"' % mode)
|
raise Exception('Unrecognized homing mode "%s"' % mode)
|
||||||
|
|
||||||
|
|
||||||
def motor_home_direction(self, motor):
|
def motor_home_direction(self, motor):
|
||||||
mode = self.motor_homing_mode(motor)
|
mode = self.motor_homing_mode(motor)
|
||||||
if mode.endswith('-min'): return -1
|
if mode.endswith('-min'):
|
||||||
if mode.endswith('-max'): return 1
|
return -1
|
||||||
return 0 # Disabled
|
if mode.endswith('-max'):
|
||||||
|
return 1
|
||||||
|
return 0 # Disabled
|
||||||
|
|
||||||
def motor_home_position(self, motor):
|
def motor_home_position(self, motor):
|
||||||
mode = self.motor_homing_mode(motor)
|
mode = self.motor_homing_mode(motor)
|
||||||
# Return soft limit positions
|
# Return soft limit positions
|
||||||
if mode.endswith('-min'): return self.vars['%dtn' % motor]
|
if mode.endswith('-min'):
|
||||||
if mode.endswith('-max'): return self.vars['%dtm' % motor]
|
return self.vars['%dtn' % motor]
|
||||||
return 0 # Disabled
|
if mode.endswith('-max'):
|
||||||
|
return self.vars['%dtm' % motor]
|
||||||
|
return 0 # Disabled
|
||||||
|
|
||||||
def motor_home_travel(self, motor):
|
def motor_home_travel(self, motor):
|
||||||
tmin = self.get(str(motor) + 'tm', 0)
|
tmin = self.get(str(motor) + 'tm', 0)
|
||||||
@@ -427,27 +376,22 @@ class State(object):
|
|||||||
# (travel_max - travel_min) * 1.5 * home_dir
|
# (travel_max - travel_min) * 1.5 * home_dir
|
||||||
return (tmin - tmax) * 1.5 * hdir
|
return (tmin - tmax) * 1.5 * hdir
|
||||||
|
|
||||||
|
|
||||||
def motor_latch_backoff(self, motor):
|
def motor_latch_backoff(self, motor):
|
||||||
lb = self.get(str(motor) + 'lb', 0)
|
lb = self.get(str(motor) + 'lb', 0)
|
||||||
hdir = self.motor_home_direction(motor)
|
hdir = self.motor_home_direction(motor)
|
||||||
return -(lb * hdir) # -latch_backoff * home_dir
|
return -(lb * hdir) # -latch_backoff * home_dir
|
||||||
|
|
||||||
|
|
||||||
def motor_zero_backoff(self, motor):
|
def motor_zero_backoff(self, motor):
|
||||||
zb = self.get(str(motor) + 'zb', 0)
|
zb = self.get(str(motor) + 'zb', 0)
|
||||||
hdir = self.motor_home_direction(motor)
|
hdir = self.motor_home_direction(motor)
|
||||||
return -(zb * hdir) # -zero_backoff * home_dir
|
return -(zb * hdir) # -zero_backoff * home_dir
|
||||||
|
|
||||||
|
|
||||||
def motor_search_velocity(self, motor):
|
def motor_search_velocity(self, motor):
|
||||||
return 1000 * self.get(str(motor) + 'sv', 0)
|
return 1000 * self.get(str(motor) + 'sv', 0)
|
||||||
|
|
||||||
|
|
||||||
def motor_latch_velocity(self, motor):
|
def motor_latch_velocity(self, motor):
|
||||||
return 1000 * self.get(str(motor) + 'lv', 0)
|
return 1000 * self.get(str(motor) + 'lv', 0)
|
||||||
|
|
||||||
|
|
||||||
def get_axis_switch(self, axis, side):
|
def get_axis_switch(self, axis, side):
|
||||||
axis = axis.lower()
|
axis = axis.lower()
|
||||||
|
|
||||||
@@ -461,14 +405,17 @@ class State(object):
|
|||||||
|
|
||||||
# This must match the switch ID enum in avr/src/switch.h
|
# This must match the switch ID enum in avr/src/switch.h
|
||||||
hmode = self.motor_homing_mode(motor)
|
hmode = self.motor_homing_mode(motor)
|
||||||
if hmode.startswith('stall-'): return motor + 10
|
if hmode.startswith('stall-'):
|
||||||
|
return motor + 10
|
||||||
return 2 * motor + 2 + (0 if side.lower() == 'min' else 1)
|
return 2 * motor + 2 + (0 if side.lower() == 'min' else 1)
|
||||||
|
|
||||||
|
|
||||||
def get_switch_id(self, switch):
|
def get_switch_id(self, switch):
|
||||||
# TODO Support other input switches in CAMotics gcode/machine/PortType.h
|
# TODO Support other input switches in CAMotics gcode/machine/PortType.h
|
||||||
switch = switch.lower()
|
switch = switch.lower()
|
||||||
if switch == 'probe': return 1
|
if switch == 'probe':
|
||||||
if switch[1:] == '-min': return self.get_axis_switch(switch[0], 'min')
|
return 1
|
||||||
if switch[1:] == '-max': return self.get_axis_switch(switch[0], 'max')
|
if switch[1:] == '-min':
|
||||||
|
return self.get_axis_switch(switch[0], 'min')
|
||||||
|
if switch[1:] == '-max':
|
||||||
|
return self.get_axis_switch(switch[0], 'max')
|
||||||
raise Exception('Unsupported switch "%s"' % switch)
|
raise Exception('Unsupported switch "%s"' % switch)
|
||||||
|
|||||||
@@ -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 os
|
||||||
import json
|
import json
|
||||||
import tornado
|
import tornado
|
||||||
@@ -36,50 +9,26 @@ from tornado.web import HTTPError
|
|||||||
from tornado import gen
|
from tornado import gen
|
||||||
|
|
||||||
import bbctrl
|
import bbctrl
|
||||||
|
import iw_parse
|
||||||
|
|
||||||
|
|
||||||
def call_get_output(cmd):
|
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')
|
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
|
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):
|
class RebootHandler(bbctrl.APIHandler):
|
||||||
def put_ok(self):
|
def put_ok(self):
|
||||||
self.get_ctrl().lcd.goodbye('Rebooting...')
|
self.get_ctrl().lcd.goodbye('Rebooting...')
|
||||||
subprocess.Popen('reboot')
|
subprocess.Popen('reboot')
|
||||||
|
|
||||||
|
|
||||||
class ShutdownHandler(bbctrl.APIHandler):
|
class ShutdownHandler(bbctrl.APIHandler):
|
||||||
def put_ok(self):
|
def put_ok(self):
|
||||||
subprocess.Popen(['shutdown','-h','now'])
|
subprocess.Popen(['shutdown', '-h', 'now'])
|
||||||
|
|
||||||
|
|
||||||
class LogHandler(bbctrl.RequestHandler):
|
class LogHandler(bbctrl.RequestHandler):
|
||||||
@@ -87,7 +36,6 @@ class LogHandler(bbctrl.RequestHandler):
|
|||||||
with open(self.get_ctrl().log.get_path(), 'r') as f:
|
with open(self.get_ctrl().log.get_path(), 'r') as f:
|
||||||
self.write(f.read())
|
self.write(f.read())
|
||||||
|
|
||||||
|
|
||||||
def set_default_headers(self):
|
def set_default_headers(self):
|
||||||
fmt = socket.gethostname() + '-%Y%m%d.log'
|
fmt = socket.gethostname() + '-%Y%m%d.log'
|
||||||
filename = datetime.date.today().strftime(fmt)
|
filename = datetime.date.today().strftime(fmt)
|
||||||
@@ -102,14 +50,16 @@ class MessageAckHandler(bbctrl.APIHandler):
|
|||||||
|
|
||||||
class BugReportHandler(bbctrl.RequestHandler):
|
class BugReportHandler(bbctrl.RequestHandler):
|
||||||
def get(self):
|
def get(self):
|
||||||
import tarfile, io
|
import tarfile
|
||||||
|
import io
|
||||||
|
|
||||||
buf = io.BytesIO()
|
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 os.path.isfile(path):
|
||||||
if arcname is None: arcname = path
|
if arcname is None:
|
||||||
|
arcname = path
|
||||||
tar.add(path, self.basename + '/' + arcname)
|
tar.add(path, self.basename + '/' + arcname)
|
||||||
|
|
||||||
def check_add_basename(path):
|
def check_add_basename(path):
|
||||||
@@ -128,7 +78,6 @@ class BugReportHandler(bbctrl.RequestHandler):
|
|||||||
|
|
||||||
self.write(buf.getvalue())
|
self.write(buf.getvalue())
|
||||||
|
|
||||||
|
|
||||||
def set_default_headers(self):
|
def set_default_headers(self):
|
||||||
fmt = socket.gethostname() + '-%Y%m%d-%H%M%S'
|
fmt = socket.gethostname() + '-%Y%m%d-%H%M%S'
|
||||||
self.basename = datetime.datetime.now().strftime(fmt)
|
self.basename = datetime.datetime.now().strftime(fmt)
|
||||||
@@ -138,8 +87,6 @@ class BugReportHandler(bbctrl.RequestHandler):
|
|||||||
|
|
||||||
|
|
||||||
class HostnameHandler(bbctrl.APIHandler):
|
class HostnameHandler(bbctrl.APIHandler):
|
||||||
def get(self): self.write_json(socket.gethostname())
|
|
||||||
|
|
||||||
def put(self):
|
def put(self):
|
||||||
if self.get_ctrl().args.demo:
|
if self.get_ctrl().args.demo:
|
||||||
raise HTTPError(400, 'Cannot set hostname in demo mode')
|
raise HTTPError(400, 'Cannot set hostname in demo mode')
|
||||||
@@ -153,77 +100,59 @@ class HostnameHandler(bbctrl.APIHandler):
|
|||||||
raise HTTPError(400, 'Failed to set hostname')
|
raise HTTPError(400, 'Failed to set hostname')
|
||||||
|
|
||||||
|
|
||||||
class WifiHandler(bbctrl.APIHandler):
|
class NetworkHandler(bbctrl.APIHandler):
|
||||||
def get(self):
|
def get(self):
|
||||||
data = {'ssid': '', 'channel': 0}
|
|
||||||
try:
|
try:
|
||||||
data = json.loads(call_get_output(['config-wifi', '-j']))
|
ipAddresses = call_get_output(['hostname', '-I']).split()
|
||||||
except: pass
|
except:
|
||||||
self.write_json(data)
|
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):
|
def put(self):
|
||||||
if self.get_ctrl().args.demo:
|
if self.get_ctrl().args.demo:
|
||||||
raise HTTPError(400, 'Cannot configure WiFi in demo mode')
|
raise HTTPError(400, 'Cannot configure WiFi in demo mode')
|
||||||
|
|
||||||
if 'mode' in self.json:
|
if not 'wifi' in self.json:
|
||||||
cmd = ['config-wifi', '-r']
|
raise HTTPError(400, 'Payload is missing wifi config information')
|
||||||
mode = self.json['mode']
|
|
||||||
|
|
||||||
if mode == 'disabled': cmd += ['-d']
|
wifi = self.json['wifi']
|
||||||
elif 'ssid' in self.json:
|
|
||||||
cmd += ['-s', self.json['ssid']]
|
|
||||||
|
|
||||||
if mode == 'ap':
|
cmd = ['config-wifi', '-r']
|
||||||
cmd += ['-a']
|
|
||||||
if 'channel' in self.json:
|
|
||||||
cmd += ['-c', self.json['channel']]
|
|
||||||
|
|
||||||
if 'pass' in self.json:
|
if not wifi['enabled']:
|
||||||
cmd += ['-p', self.json['pass']]
|
cmd += ['-d']
|
||||||
|
else:
|
||||||
|
if 'ssid' in wifi:
|
||||||
|
cmd += ['-s', wifi['ssid']]
|
||||||
|
|
||||||
if subprocess.call(cmd) == 0:
|
if 'password' in wifi:
|
||||||
self.write_json('ok')
|
cmd += ['-p', wifi['password']]
|
||||||
return
|
|
||||||
|
if subprocess.call(cmd) == 0:
|
||||||
|
self.write_json('ok')
|
||||||
|
return
|
||||||
|
|
||||||
raise HTTPError(400, 'Failed to configure wifi')
|
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):
|
class ConfigLoadHandler(bbctrl.APIHandler):
|
||||||
def get(self):
|
def get(self):
|
||||||
self.write_json(self.get_ctrl().config.load())
|
self.write_json(self.get_ctrl().config.load())
|
||||||
@@ -238,7 +167,7 @@ class ConfigDownloadHandler(bbctrl.APIHandler):
|
|||||||
'attachment; filename="%s"' % filename)
|
'attachment; filename="%s"' % filename)
|
||||||
|
|
||||||
def get(self):
|
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):
|
class ConfigSaveHandler(bbctrl.APIHandler):
|
||||||
@@ -252,14 +181,14 @@ class ConfigResetHandler(bbctrl.APIHandler):
|
|||||||
class FirmwareUpdateHandler(bbctrl.APIHandler):
|
class FirmwareUpdateHandler(bbctrl.APIHandler):
|
||||||
def prepare(self): pass
|
def prepare(self): pass
|
||||||
|
|
||||||
|
|
||||||
def put_ok(self):
|
def put_ok(self):
|
||||||
if not 'firmware' in self.request.files:
|
if not 'firmware' in self.request.files:
|
||||||
raise HTTPError(401, 'Missing "firmware"')
|
raise HTTPError(401, 'Missing "firmware"')
|
||||||
|
|
||||||
firmware = self.request.files['firmware'][0]
|
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:
|
with open('firmware/update.tar.bz2', 'wb') as f:
|
||||||
f.write(firmware['body'])
|
f.write(firmware['body'])
|
||||||
@@ -284,20 +213,23 @@ class PathHandler(bbctrl.APIHandler):
|
|||||||
future = preplanner.get_plan(filename)
|
future = preplanner.get_plan(filename)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
delta = datetime.timedelta(seconds = 1)
|
delta = datetime.timedelta(seconds=1)
|
||||||
data = yield gen.with_timeout(delta, future)
|
data = yield gen.with_timeout(delta, future)
|
||||||
|
|
||||||
except gen.TimeoutError:
|
except gen.TimeoutError:
|
||||||
progress = preplanner.get_plan_progress(filename)
|
progress = preplanner.get_plan_progress(filename)
|
||||||
self.write_json(dict(progress = progress))
|
self.write_json(dict(progress=progress))
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if data is None: return
|
if data is None:
|
||||||
|
return
|
||||||
meta, positions, speeds = data
|
meta, positions, speeds = data
|
||||||
|
|
||||||
if dataType == '/positions': data = positions
|
if dataType == '/positions':
|
||||||
elif dataType == '/speeds': data = speeds
|
data = positions
|
||||||
|
elif dataType == '/speeds':
|
||||||
|
data = speeds
|
||||||
else:
|
else:
|
||||||
self.get_ctrl().state.set_bounds(meta['bounds'])
|
self.get_ctrl().state.set_bounds(meta['bounds'])
|
||||||
self.write_json(meta)
|
self.write_json(meta)
|
||||||
@@ -316,12 +248,14 @@ class PathHandler(bbctrl.APIHandler):
|
|||||||
self.write(chunk)
|
self.write(chunk)
|
||||||
yield self.flush()
|
yield self.flush()
|
||||||
|
|
||||||
except tornado.iostream.StreamClosedError as e: pass
|
except tornado.iostream.StreamClosedError as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class HomeHandler(bbctrl.APIHandler):
|
class HomeHandler(bbctrl.APIHandler):
|
||||||
def put_ok(self, axis, action, *args):
|
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 action == '/set':
|
||||||
if not 'position' in self.json:
|
if not 'position' in self.json:
|
||||||
@@ -329,8 +263,18 @@ class HomeHandler(bbctrl.APIHandler):
|
|||||||
|
|
||||||
self.get_ctrl().mach.home(axis, self.json['position'])
|
self.get_ctrl().mach.home(axis, self.json['position'])
|
||||||
|
|
||||||
elif action == '/clear': self.get_ctrl().mach.unhome(axis)
|
elif action == '/clear':
|
||||||
else: self.get_ctrl().mach.home(axis)
|
self.get_ctrl().mach.unhome(axis)
|
||||||
|
else:
|
||||||
|
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):
|
class StartHandler(bbctrl.APIHandler):
|
||||||
@@ -386,7 +330,7 @@ class ModbusReadHandler(bbctrl.APIHandler):
|
|||||||
class ModbusWriteHandler(bbctrl.APIHandler):
|
class ModbusWriteHandler(bbctrl.APIHandler):
|
||||||
def put_ok(self):
|
def put_ok(self):
|
||||||
self.get_ctrl().mach.modbus_write(int(self.json['address']),
|
self.get_ctrl().mach.modbus_write(int(self.json['address']),
|
||||||
int(self.json['value']))
|
int(self.json['value']))
|
||||||
|
|
||||||
|
|
||||||
class JogHandler(bbctrl.APIHandler):
|
class JogHandler(bbctrl.APIHandler):
|
||||||
@@ -402,7 +346,8 @@ class JogHandler(bbctrl.APIHandler):
|
|||||||
last = self.app.last_jog.get(id, 0)
|
last = self.app.last_jog.get(id, 0)
|
||||||
self.app.last_jog[id] = ts
|
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)
|
self.get_ctrl().mach.jog(self.json)
|
||||||
|
|
||||||
@@ -413,17 +358,14 @@ class ClientConnection(object):
|
|||||||
self.app = app
|
self.app = app
|
||||||
self.count = 0
|
self.count = 0
|
||||||
|
|
||||||
|
|
||||||
def heartbeat(self):
|
def heartbeat(self):
|
||||||
self.timer = self.app.ioloop.call_later(3, self.heartbeat)
|
self.timer = self.app.ioloop.call_later(3, self.heartbeat)
|
||||||
self.send({'heartbeat': self.count})
|
self.send({'heartbeat': self.count})
|
||||||
self.count += 1
|
self.count += 1
|
||||||
|
|
||||||
|
|
||||||
def send(self, msg): raise HTTPError(400, 'Not implemented')
|
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 = self.app.get_ctrl(id)
|
||||||
|
|
||||||
self.ctrl.state.add_listener(self.send)
|
self.ctrl.state.add_listener(self.send)
|
||||||
@@ -432,7 +374,6 @@ class ClientConnection(object):
|
|||||||
self.heartbeat()
|
self.heartbeat()
|
||||||
self.app.opened(self.ctrl)
|
self.app.opened(self.ctrl)
|
||||||
|
|
||||||
|
|
||||||
def on_close(self):
|
def on_close(self):
|
||||||
self.app.ioloop.remove_timeout(self.timer)
|
self.app.ioloop.remove_timeout(self.timer)
|
||||||
self.ctrl.state.remove_listener(self.send)
|
self.ctrl.state.remove_listener(self.send)
|
||||||
@@ -440,7 +381,6 @@ class ClientConnection(object):
|
|||||||
self.is_open = False
|
self.is_open = False
|
||||||
self.app.closed(self.ctrl)
|
self.app.closed(self.ctrl)
|
||||||
|
|
||||||
|
|
||||||
def on_message(self, data):
|
def on_message(self, data):
|
||||||
self.ctrl.mach.mdi(data)
|
self.ctrl.mach.mdi(data)
|
||||||
|
|
||||||
@@ -465,23 +405,24 @@ class SockJSConnection(ClientConnection, sockjs.tornado.SockJSConnection):
|
|||||||
ClientConnection.__init__(self, session.server.app)
|
ClientConnection.__init__(self, session.server.app)
|
||||||
sockjs.tornado.SockJSConnection.__init__(self, session)
|
sockjs.tornado.SockJSConnection.__init__(self, session)
|
||||||
|
|
||||||
|
|
||||||
def send(self, msg):
|
def send(self, msg):
|
||||||
try:
|
try:
|
||||||
sockjs.tornado.SockJSConnection.send(self, msg)
|
sockjs.tornado.SockJSConnection.send(self, msg)
|
||||||
except:
|
except:
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
|
|
||||||
def on_open(self, info):
|
def on_open(self, info):
|
||||||
cookie = info.get_cookie('client-id')
|
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:
|
else:
|
||||||
id = cookie.value
|
id = cookie.value
|
||||||
|
|
||||||
ip = info.ip
|
ip = info.ip
|
||||||
if 'X-Real-IP' in info.headers: ip = info.headers['X-Real-IP']
|
if 'X-Real-IP' in info.headers:
|
||||||
self.app.get_ctrl(id).log.get('Web').info('Connection from %s' % ip)
|
ip = info.headers['X-Real-IP']
|
||||||
|
self.app.get_ctrl(id).log.get(
|
||||||
|
'Web').info('Connection from %s' % ip)
|
||||||
super().on_open(id)
|
super().on_open(id)
|
||||||
|
|
||||||
|
|
||||||
@@ -499,10 +440,13 @@ class Web(tornado.web.Application):
|
|||||||
|
|
||||||
# Init camera
|
# Init camera
|
||||||
if not args.disable_camera:
|
if not args.disable_camera:
|
||||||
if self.args.demo: log = bbctrl.log.Log(args, ioloop, 'camera.log')
|
if self.args.demo:
|
||||||
else: log = self.get_ctrl().log
|
log = bbctrl.log.Log(args, ioloop, 'camera.log')
|
||||||
|
else:
|
||||||
|
log = self.get_ctrl().log
|
||||||
self.camera = bbctrl.Camera(ioloop, args, log)
|
self.camera = bbctrl.Camera(ioloop, args, log)
|
||||||
else: self.camera = None
|
else:
|
||||||
|
self.camera = None
|
||||||
|
|
||||||
# Init controller
|
# Init controller
|
||||||
if not self.args.demo:
|
if not self.args.demo:
|
||||||
@@ -517,9 +461,7 @@ class Web(tornado.web.Application):
|
|||||||
(r'/api/reboot', RebootHandler),
|
(r'/api/reboot', RebootHandler),
|
||||||
(r'/api/shutdown', ShutdownHandler),
|
(r'/api/shutdown', ShutdownHandler),
|
||||||
(r'/api/hostname', HostnameHandler),
|
(r'/api/hostname', HostnameHandler),
|
||||||
(r'/api/wifi', WifiHandler),
|
(r'/api/network', NetworkHandler),
|
||||||
(r'/api/remote/username', UsernameHandler),
|
|
||||||
(r'/api/remote/password', PasswordHandler),
|
|
||||||
(r'/api/config/load', ConfigLoadHandler),
|
(r'/api/config/load', ConfigLoadHandler),
|
||||||
(r'/api/config/download', ConfigDownloadHandler),
|
(r'/api/config/download', ConfigDownloadHandler),
|
||||||
(r'/api/config/save', ConfigSaveHandler),
|
(r'/api/config/save', ConfigSaveHandler),
|
||||||
@@ -529,6 +471,7 @@ class Web(tornado.web.Application):
|
|||||||
(r'/api/file(/[^/]+)?', bbctrl.FileHandler),
|
(r'/api/file(/[^/]+)?', bbctrl.FileHandler),
|
||||||
(r'/api/path/([^/]+)((/positions)|(/speeds))?', PathHandler),
|
(r'/api/path/([^/]+)((/positions)|(/speeds))?', PathHandler),
|
||||||
(r'/api/home(/[xyzabcXYZABC]((/set)|(/clear))?)?', HomeHandler),
|
(r'/api/home(/[xyzabcXYZABC]((/set)|(/clear))?)?', HomeHandler),
|
||||||
|
(r'/api/devmode((/probe))?', DevmodeHandler),
|
||||||
(r'/api/start', StartHandler),
|
(r'/api/start', StartHandler),
|
||||||
(r'/api/estop', EStopHandler),
|
(r'/api/estop', EStopHandler),
|
||||||
(r'/api/clear', ClearHandler),
|
(r'/api/clear', ClearHandler),
|
||||||
@@ -547,7 +490,7 @@ class Web(tornado.web.Application):
|
|||||||
(r'/(.*)', StaticFileHandler,
|
(r'/(.*)', StaticFileHandler,
|
||||||
{'path': bbctrl.get_resource('http/'),
|
{'path': bbctrl.get_resource('http/'),
|
||||||
'default_filename': 'index.html'}),
|
'default_filename': 'index.html'}),
|
||||||
]
|
]
|
||||||
|
|
||||||
router = sockjs.tornado.SockJSRouter(SockJSConnection, '/sockjs')
|
router = sockjs.tornado.SockJSRouter(SockJSConnection, '/sockjs')
|
||||||
router.app = self
|
router.app = self
|
||||||
@@ -555,7 +498,7 @@ class Web(tornado.web.Application):
|
|||||||
tornado.web.Application.__init__(self, router.urls + handlers)
|
tornado.web.Application.__init__(self, router.urls + handlers)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.listen(args.port, address = args.addr)
|
self.listen(args.port, address=args.addr)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise Exception('Failed to bind %s:%d: %s' % (
|
raise Exception('Failed to bind %s:%d: %s' % (
|
||||||
@@ -563,33 +506,32 @@ class Web(tornado.web.Application):
|
|||||||
|
|
||||||
print('Listening on http://%s:%d/' % (args.addr, args.port))
|
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):
|
def closed(self, ctrl):
|
||||||
# Time out clients in demo mode
|
# 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):
|
def _reap_ctrl(self, ctrl):
|
||||||
ctrl.close()
|
ctrl.close()
|
||||||
del self.ctrls[ctrl.id]
|
del self.ctrls[ctrl.id]
|
||||||
|
|
||||||
|
def get_ctrl(self, id=None):
|
||||||
def get_ctrl(self, id = None):
|
if not id or not self.args.demo:
|
||||||
if not id or not self.args.demo: id = ''
|
id = ''
|
||||||
|
|
||||||
if not id in self.ctrls:
|
if not id in self.ctrls:
|
||||||
ctrl = bbctrl.Ctrl(self.args, self.ioloop, id)
|
ctrl = bbctrl.Ctrl(self.args, self.ioloop, id)
|
||||||
self.ctrls[id] = ctrl
|
self.ctrls[id] = ctrl
|
||||||
|
|
||||||
else: ctrl = self.ctrls[id]
|
else:
|
||||||
|
ctrl = self.ctrls[id]
|
||||||
|
|
||||||
return ctrl
|
return ctrl
|
||||||
|
|
||||||
|
|
||||||
# Override default logger
|
# Override default logger
|
||||||
|
|
||||||
def log_request(self, handler):
|
def log_request(self, handler):
|
||||||
log = self.get_ctrl(handler.get_cookie('client-id')).log.get('Web')
|
log = self.get_ctrl(handler.get_cookie('client-id')).log.get('Web')
|
||||||
log.info("%d %s", handler.get_status(), handler._request_summary())
|
log.info("%d %s", handler.get_status(), handler._request_summary())
|
||||||
|
|||||||
80
src/py/iw_parse/README.md
Normal file
80
src/py/iw_parse/README.md
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
iw_parse
|
||||||
|
========
|
||||||
|
|
||||||
|
Parse the output of iwlist scan to get the name, address, quality, channel, and encryption type of all networks broadcasting within your Wireless NIC's reach.
|
||||||
|
|
||||||
|
Dependencies
|
||||||
|
------------
|
||||||
|
|
||||||
|
* [pip](http://www.pip-installer.org/en/latest/installing.html "pip installation guide") - If you don't have pip installed, followed the link.
|
||||||
|
|
||||||
|
|
||||||
|
Installation
|
||||||
|
------------
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install iw_parse
|
||||||
|
```
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
|
||||||
|
```bash
|
||||||
|
iwlist <INTERFACE_NAME> scan | iw_parse
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace `<INTERFACE_NAME>` with the system name for your wireless NIC. It is usually something like `wlan0`. The command `iwconfig` will list all of your network interfaces.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
iwlist wlan0 scan | iw_parse
|
||||||
|
```
|
||||||
|
|
||||||
|
The result should look something like:
|
||||||
|
|
||||||
|
```
|
||||||
|
Name Address Quality Channel Encryption
|
||||||
|
wireless1 20:AA:4B:34:2C:F5 100 % 11 WEP
|
||||||
|
wireless2 00:26:F2:1E:FC:03 84 % 1 WPA v.1
|
||||||
|
wireless3 00:1D:D3:6A:3C:60 66 % 6 WEP
|
||||||
|
wireless4 20:10:7A:E5:02:98 64 % 1 WEP
|
||||||
|
wireless5 CC:A4:62:B7:D2:B0 54 % 8 WPA v.1
|
||||||
|
wireless6 30:46:9A:53:3C:76 47 % 11 WPA v.1
|
||||||
|
wireless7 A0:21:B7:5F:84:B0 44 % 11 WEP
|
||||||
|
wireless8 04:A1:51:18:E8:E0 41 % 6 WPA v.1
|
||||||
|
```
|
||||||
|
|
||||||
|
Example from Python shell:
|
||||||
|
|
||||||
|
```python
|
||||||
|
>>> import iw_parse
|
||||||
|
>>> networks = iw_parse.get_interfaces(interface='wlan0')
|
||||||
|
>>> print networks
|
||||||
|
[{'Address': 'F8:1E:DF:F9:B0:0B',
|
||||||
|
'Channel': '3',
|
||||||
|
'Encryption': 'WEP',
|
||||||
|
'Name': 'Francis',
|
||||||
|
'Bit Rates': '144 Mb/s',
|
||||||
|
'Signal Level': '42',
|
||||||
|
'Name': 'Francis',
|
||||||
|
'Quality': '100'},
|
||||||
|
{'Address': '86:1B:5E:33:17:D4',
|
||||||
|
'Channel': '6',
|
||||||
|
'Encryption': 'Open',
|
||||||
|
'Bit Rates': '54 Mb/s',
|
||||||
|
'Signal Level': '72',
|
||||||
|
'Name': 'optimumwifi',
|
||||||
|
'Quality': '100'},
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
Acknowledgements
|
||||||
|
----------------
|
||||||
|
|
||||||
|
* The vast majority of iw_parse was written by Hugo Chargois.
|
||||||
|
|
||||||
|
License
|
||||||
|
-------
|
||||||
|
|
||||||
|
iw_parse is free--as in BSD. Hack your heart out, hackers.
|
||||||
1
src/py/iw_parse/__init__.py
Normal file
1
src/py/iw_parse/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .iw_parse import *
|
||||||
25
src/py/iw_parse/iw_parse
Normal file
25
src/py/iw_parse/iw_parse
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#! /usr/bin/env python
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from iw_parse import get_parsed_cells, print_cells
|
||||||
|
|
||||||
|
def main():
|
||||||
|
""" Pretty prints the output of iwlist scan into a table. """
|
||||||
|
parsed_cells = get_parsed_cells(sys.stdin)
|
||||||
|
|
||||||
|
# You can choose which columns to display here, and most importantly
|
||||||
|
# in what order. Of course, they must exist as keys in the dict rules.
|
||||||
|
columns = [
|
||||||
|
"Name",
|
||||||
|
"Address",
|
||||||
|
"Quality",
|
||||||
|
"Channel",
|
||||||
|
"Signal Level",
|
||||||
|
"Encryption"
|
||||||
|
]
|
||||||
|
|
||||||
|
print_cells(parsed_cells, columns)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
337
src/py/iw_parse/iw_parse.py
Normal file
337
src/py/iw_parse/iw_parse.py
Normal file
@@ -0,0 +1,337 @@
|
|||||||
|
#! /usr/bin/env python
|
||||||
|
|
||||||
|
# Hugo Chargois - 17 jan. 2010 - v.0.1
|
||||||
|
# Parses the output of iwlist scan into a table
|
||||||
|
|
||||||
|
# You can add or change the functions to parse the properties
|
||||||
|
# of each AP (cell) below. They take one argument, the bunch of text
|
||||||
|
# describing one cell in iwlist scan and return a property of that cell.
|
||||||
|
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
VERSION_RGX = re.compile("version\s+\d+", re.IGNORECASE)
|
||||||
|
|
||||||
|
def get_name(cell):
|
||||||
|
""" Gets the name / essid of a network / cell.
|
||||||
|
@param string cell
|
||||||
|
A network / cell from iwlist scan.
|
||||||
|
|
||||||
|
@return string
|
||||||
|
The name / essid of the network.
|
||||||
|
"""
|
||||||
|
|
||||||
|
essid = matching_line(cell, "ESSID:")
|
||||||
|
if not essid:
|
||||||
|
return ""
|
||||||
|
return essid[1:-1]
|
||||||
|
|
||||||
|
def get_quality(cell):
|
||||||
|
""" Gets the quality of a network / cell.
|
||||||
|
@param string cell
|
||||||
|
A network / cell from iwlist scan.
|
||||||
|
|
||||||
|
@return string
|
||||||
|
The quality of the network.
|
||||||
|
"""
|
||||||
|
|
||||||
|
quality = matching_line(cell, "Quality=")
|
||||||
|
if quality is None:
|
||||||
|
return ""
|
||||||
|
quality = quality.split()[0].split("/")
|
||||||
|
quality = matching_line(cell, "Quality=").split()[0].split("/")
|
||||||
|
return str(int(round(float(quality[0]) / float(quality[1]) * 100)))
|
||||||
|
|
||||||
|
def get_signal_level(cell):
|
||||||
|
""" Gets the signal level of a network / cell.
|
||||||
|
@param string cell
|
||||||
|
A network / cell from iwlist scan.
|
||||||
|
|
||||||
|
@return string
|
||||||
|
The signal level of the network.
|
||||||
|
"""
|
||||||
|
|
||||||
|
signal = matching_line(cell, "Signal level=")
|
||||||
|
if signal is None:
|
||||||
|
return ""
|
||||||
|
signal = signal.split("=")[1].split("/")
|
||||||
|
if len(signal) == 2:
|
||||||
|
return str(int(round(float(signal[0]) / float(signal[1]) * 100)))
|
||||||
|
elif len(signal) == 1:
|
||||||
|
return signal[0].split(' ')[0]
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def get_noise_level(cell):
|
||||||
|
""" Gets the noise level of a network / cell.
|
||||||
|
@param string cell
|
||||||
|
A network / cell from iwlist scan.
|
||||||
|
|
||||||
|
@return string
|
||||||
|
The noise level of the network.
|
||||||
|
"""
|
||||||
|
|
||||||
|
noise = matching_line(cell, "Noise level=")
|
||||||
|
if noise is None:
|
||||||
|
return ""
|
||||||
|
noise = noise.split("=")[1]
|
||||||
|
return noise.split(' ')[0]
|
||||||
|
|
||||||
|
def get_channel(cell):
|
||||||
|
""" Gets the channel of a network / cell.
|
||||||
|
@param string cell
|
||||||
|
A network / cell from iwlist scan.
|
||||||
|
|
||||||
|
@return string
|
||||||
|
The channel of the network.
|
||||||
|
"""
|
||||||
|
|
||||||
|
channel = matching_line(cell, "Channel:")
|
||||||
|
if channel:
|
||||||
|
return channel
|
||||||
|
frequency = matching_line(cell, "Frequency:")
|
||||||
|
channel = re.sub(r".*\(Channel\s(\d{1,3})\).*", r"\1", frequency)
|
||||||
|
return channel
|
||||||
|
|
||||||
|
def get_frequency(cell):
|
||||||
|
""" Gets the frequency of a network / cell.
|
||||||
|
@param string cell
|
||||||
|
A network / cell from iwlist scan.
|
||||||
|
|
||||||
|
@return string
|
||||||
|
The frequency of the network.
|
||||||
|
"""
|
||||||
|
|
||||||
|
frequency = matching_line(cell, "Frequency:")
|
||||||
|
if frequency is None:
|
||||||
|
return ""
|
||||||
|
return frequency.split()[0]
|
||||||
|
|
||||||
|
def get_encryption(cell, emit_version=False):
|
||||||
|
""" Gets the encryption type of a network / cell.
|
||||||
|
@param string cell
|
||||||
|
A network / cell from iwlist scan.
|
||||||
|
|
||||||
|
@return string
|
||||||
|
The encryption type of the network.
|
||||||
|
"""
|
||||||
|
|
||||||
|
enc = ""
|
||||||
|
if matching_line(cell, "Encryption key:") == "off":
|
||||||
|
enc = "Open"
|
||||||
|
else:
|
||||||
|
for line in cell:
|
||||||
|
matching = match(line,"IE:")
|
||||||
|
if matching == None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
wpa = match(matching,"WPA")
|
||||||
|
if wpa == None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
version_matches = VERSION_RGX.search(wpa)
|
||||||
|
if len(version_matches.regs) == 1:
|
||||||
|
version = version_matches \
|
||||||
|
.group(0) \
|
||||||
|
.lower() \
|
||||||
|
.replace("version", "") \
|
||||||
|
.strip()
|
||||||
|
wpa = wpa.replace(version_matches.group(0), "").strip()
|
||||||
|
if wpa == "":
|
||||||
|
wpa = "WPA"
|
||||||
|
if emit_version:
|
||||||
|
enc = "{0} v.{1}".format(wpa, version)
|
||||||
|
else:
|
||||||
|
enc = wpa
|
||||||
|
if wpa == "WPA2":
|
||||||
|
return enc
|
||||||
|
else:
|
||||||
|
enc = wpa
|
||||||
|
if enc == "":
|
||||||
|
enc = "WEP"
|
||||||
|
return enc
|
||||||
|
|
||||||
|
def get_mode(cell):
|
||||||
|
""" Gets the mode of a network / cell.
|
||||||
|
@param string cell
|
||||||
|
A network / cell from iwlist scan.
|
||||||
|
|
||||||
|
@return string
|
||||||
|
The IEEE 802.11 mode of the network.
|
||||||
|
"""
|
||||||
|
|
||||||
|
mode = matching_line(cell, "Extra:ieee_mode=")
|
||||||
|
if mode is None:
|
||||||
|
return ""
|
||||||
|
return mode
|
||||||
|
|
||||||
|
def get_address(cell):
|
||||||
|
""" Gets the address of a network / cell.
|
||||||
|
@param string cell
|
||||||
|
A network / cell from iwlist scan.
|
||||||
|
|
||||||
|
@return string
|
||||||
|
The address of the network.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return matching_line(cell, "Address: ")
|
||||||
|
|
||||||
|
def get_bit_rates(cell):
|
||||||
|
""" Gets the bit rate of a network / cell.
|
||||||
|
@param string cell
|
||||||
|
A network / cell from iwlist scan.
|
||||||
|
|
||||||
|
@return string
|
||||||
|
The bit rate of the network.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return matching_line(cell, "Bit Rates:")
|
||||||
|
|
||||||
|
# Here you can choose the way of sorting the table. sortby should be a key of
|
||||||
|
# the dictionary rules.
|
||||||
|
|
||||||
|
def sort_cells(cells):
|
||||||
|
sortby = "Quality"
|
||||||
|
reverse = True
|
||||||
|
cells.sort(key=lambda el:el[sortby], reverse=reverse)
|
||||||
|
|
||||||
|
|
||||||
|
# Below here goes the boring stuff. You shouldn't have to edit anything below
|
||||||
|
# this point
|
||||||
|
|
||||||
|
def matching_line(lines, keyword):
|
||||||
|
""" Returns the first matching line in a list of lines.
|
||||||
|
@see match()
|
||||||
|
"""
|
||||||
|
for line in lines:
|
||||||
|
matching = match(line,keyword)
|
||||||
|
if matching != None:
|
||||||
|
return matching
|
||||||
|
return None
|
||||||
|
|
||||||
|
def match(line, keyword):
|
||||||
|
""" If the first part of line (modulo blanks) matches keyword,
|
||||||
|
returns the end of that line. Otherwise checks if keyword is
|
||||||
|
anywhere in the line and returns that section, else returns None"""
|
||||||
|
|
||||||
|
line = line.lstrip()
|
||||||
|
length = len(keyword)
|
||||||
|
if line[:length] == keyword:
|
||||||
|
return line[length:]
|
||||||
|
else:
|
||||||
|
if keyword in line:
|
||||||
|
return line[line.index(keyword):]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def parse_cell(cell, rules):
|
||||||
|
""" Applies the rules to the bunch of text describing a cell.
|
||||||
|
@param string cell
|
||||||
|
A network / cell from iwlist scan.
|
||||||
|
@param dictionary rules
|
||||||
|
A dictionary of parse rules.
|
||||||
|
|
||||||
|
@return dictionary
|
||||||
|
parsed networks. """
|
||||||
|
|
||||||
|
parsed_cell = {}
|
||||||
|
for key in rules:
|
||||||
|
rule = rules[key]
|
||||||
|
parsed_cell.update({key: rule(cell)})
|
||||||
|
return parsed_cell
|
||||||
|
|
||||||
|
def print_table(table):
|
||||||
|
# Functional black magic.
|
||||||
|
widths = list(map(max, map(lambda l: map(len, l), zip(*table))))
|
||||||
|
|
||||||
|
justified_table = []
|
||||||
|
for line in table:
|
||||||
|
justified_line = []
|
||||||
|
for i, el in enumerate(line):
|
||||||
|
justified_line.append(el.ljust(widths[i] + 2))
|
||||||
|
justified_table.append(justified_line)
|
||||||
|
|
||||||
|
for line in justified_table:
|
||||||
|
print("\t".join(line))
|
||||||
|
|
||||||
|
def print_cells(cells, columns):
|
||||||
|
table = [columns]
|
||||||
|
for cell in cells:
|
||||||
|
cell_properties = []
|
||||||
|
for column in columns:
|
||||||
|
if column == 'Quality':
|
||||||
|
# make print nicer
|
||||||
|
cell[column] = cell[column].rjust(3) + " %"
|
||||||
|
cell_properties.append(cell[column])
|
||||||
|
table.append(cell_properties)
|
||||||
|
print_table(table)
|
||||||
|
|
||||||
|
def get_parsed_cells(iw_data, rules=None):
|
||||||
|
""" Parses iwlist output into a list of networks.
|
||||||
|
@param list iw_data
|
||||||
|
Output from iwlist scan.
|
||||||
|
A list of strings.
|
||||||
|
|
||||||
|
@return list
|
||||||
|
properties: Name, Address, Quality, Channel, Frequency, Encryption, Signal Level, Noise Level, Bit Rates, Mode.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Here's a dictionary of rules that will be applied to the description
|
||||||
|
# of each cell. The key will be the name of the column in the table.
|
||||||
|
# The value is a function defined above.
|
||||||
|
rules = rules or {
|
||||||
|
"Name": get_name,
|
||||||
|
"Quality": get_quality,
|
||||||
|
"Channel": get_channel,
|
||||||
|
"Frequency": get_frequency,
|
||||||
|
"Encryption": get_encryption,
|
||||||
|
"Address": get_address,
|
||||||
|
"Signal Level": get_signal_level,
|
||||||
|
"Noise Level": get_noise_level,
|
||||||
|
"Bit Rates": get_bit_rates,
|
||||||
|
"Mode": get_mode,
|
||||||
|
}
|
||||||
|
|
||||||
|
cells = [[]]
|
||||||
|
parsed_cells = []
|
||||||
|
|
||||||
|
for line in iw_data:
|
||||||
|
cell_line = match(line, "Cell ")
|
||||||
|
if cell_line != None:
|
||||||
|
cells.append([])
|
||||||
|
line = cell_line[-27:]
|
||||||
|
cells[-1].append(line.rstrip())
|
||||||
|
|
||||||
|
cells = cells[1:]
|
||||||
|
|
||||||
|
for cell in cells:
|
||||||
|
parsed_cells.append(parse_cell(cell, rules))
|
||||||
|
|
||||||
|
sort_cells(parsed_cells)
|
||||||
|
return parsed_cells
|
||||||
|
|
||||||
|
def call_iwlist(interface='wlan0'):
|
||||||
|
""" Get iwlist output via subprocess
|
||||||
|
@param string interface
|
||||||
|
interface to scan
|
||||||
|
default is wlan0
|
||||||
|
|
||||||
|
@return string
|
||||||
|
properties: iwlist output
|
||||||
|
"""
|
||||||
|
return subprocess.check_output(['iwlist', interface, 'scanning'])
|
||||||
|
|
||||||
|
def get_interfaces(interface="wlan0"):
|
||||||
|
""" Get parsed iwlist output
|
||||||
|
@param string interface
|
||||||
|
interface to scan
|
||||||
|
default is wlan0
|
||||||
|
|
||||||
|
@param list columns
|
||||||
|
default data attributes to return
|
||||||
|
|
||||||
|
@return dict
|
||||||
|
properties: dictionary of iwlist attributes
|
||||||
|
"""
|
||||||
|
return get_parsed_cells(call_iwlist(interface).split('\n'))
|
||||||
|
|
||||||
10
src/py/iw_parse/license.txt
Normal file
10
src/py/iw_parse/license.txt
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
Copyright (c) 2013, Cuzzo Yahn
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||||
|
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
@@ -22,11 +22,6 @@
|
|||||||
"max": 100000000,
|
"max": 100000000,
|
||||||
"unit": "mm/min²",
|
"unit": "mm/min²",
|
||||||
"default": 200000
|
"default": 200000
|
||||||
},
|
|
||||||
"probing-prompts": {
|
|
||||||
"help": "Enable or disable safety prompts during and after probing",
|
|
||||||
"type": "bool",
|
|
||||||
"default": true
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
BIN
src/resources/fonts/material-symbols-outlined.woff2
Normal file
BIN
src/resources/fonts/material-symbols-outlined.woff2
Normal file
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 250 KiB |
@@ -29,25 +29,61 @@ tt
|
|||||||
clear right
|
clear right
|
||||||
|
|
||||||
.header
|
.header
|
||||||
height 140px
|
padding-left 60px
|
||||||
padding 0
|
display flex
|
||||||
|
|
||||||
.header-content
|
.brand
|
||||||
max-width 90%
|
display flex
|
||||||
margin auto
|
flex-direction column
|
||||||
text-align left
|
align-self center
|
||||||
|
white-space nowrap
|
||||||
|
|
||||||
.estop
|
img
|
||||||
float right
|
width 300px
|
||||||
margin 5px
|
|
||||||
|
.version
|
||||||
|
font-size 18pt
|
||||||
|
color #777
|
||||||
|
display flex
|
||||||
|
|
||||||
|
.upgrade-link
|
||||||
|
margin-left 20px
|
||||||
|
font-size 16pt
|
||||||
|
align-self center
|
||||||
|
color blue
|
||||||
|
|
||||||
|
.upgrade-attention
|
||||||
|
background-color red
|
||||||
|
width 20px
|
||||||
|
height 20px
|
||||||
|
display inline-block
|
||||||
|
border-radius 50%
|
||||||
|
color white
|
||||||
|
font-size 13pt
|
||||||
|
font-weight 1000
|
||||||
|
margin-left 5px
|
||||||
|
align-self center
|
||||||
|
|
||||||
|
.pi-temp-warning
|
||||||
|
align-self center
|
||||||
|
font-size 30pt
|
||||||
|
font-family Audiowide
|
||||||
|
display inline
|
||||||
|
margin 0 30px
|
||||||
|
|
||||||
|
.left
|
||||||
|
color #444
|
||||||
|
.right
|
||||||
|
color #e5aa3d
|
||||||
|
|
||||||
|
.whitespace
|
||||||
|
flex-grow 1
|
||||||
|
|
||||||
.video
|
.video
|
||||||
position relative
|
position relative
|
||||||
float right
|
|
||||||
width 174px
|
width 174px
|
||||||
height 130px
|
height 130px
|
||||||
margin 2px 5px
|
border 2px solid transparent
|
||||||
border 2px solid #fff
|
|
||||||
border-radius 5px
|
border-radius 5px
|
||||||
|
|
||||||
&:hover
|
&:hover
|
||||||
@@ -87,29 +123,9 @@ tt
|
|||||||
width 100%
|
width 100%
|
||||||
height 100%
|
height 100%
|
||||||
|
|
||||||
.banner
|
.estop
|
||||||
float left
|
align-self center
|
||||||
padding-top 40px
|
margin 0 30px
|
||||||
white-space nowrap
|
|
||||||
|
|
||||||
img
|
|
||||||
vertical-align top
|
|
||||||
|
|
||||||
.title
|
|
||||||
font-size 30pt
|
|
||||||
font-family Audiowide
|
|
||||||
display inline
|
|
||||||
margin-right 0.5em
|
|
||||||
|
|
||||||
.left
|
|
||||||
color #444
|
|
||||||
.right
|
|
||||||
color #e5aa3d
|
|
||||||
|
|
||||||
.subtitle
|
|
||||||
font-size 18pt
|
|
||||||
font-weight 100
|
|
||||||
color #aaa
|
|
||||||
|
|
||||||
.error
|
.error
|
||||||
background red
|
background red
|
||||||
@@ -214,8 +230,6 @@ span.unit
|
|||||||
.pure-control-group
|
.pure-control-group
|
||||||
label.units
|
label.units
|
||||||
width 6em
|
width 6em
|
||||||
|
|
||||||
label.units
|
|
||||||
text-align left
|
text-align left
|
||||||
|
|
||||||
textarea
|
textarea
|
||||||
@@ -230,6 +244,7 @@ span.unit
|
|||||||
padding 0.7em 1em
|
padding 0.7em 1em
|
||||||
border-radius 3px
|
border-radius 3px
|
||||||
display inline-block
|
display inline-block
|
||||||
|
|
||||||
|
|
||||||
@keyframes blink
|
@keyframes blink
|
||||||
50%
|
50%
|
||||||
@@ -267,6 +282,12 @@ span.unit
|
|||||||
// The jogging buttons, etc.
|
// The jogging buttons, etc.
|
||||||
.control-buttons button
|
.control-buttons button
|
||||||
font-size 150%
|
font-size 150%
|
||||||
|
width 100px
|
||||||
|
height 100px
|
||||||
|
|
||||||
|
.jog-units
|
||||||
|
font-size initial
|
||||||
|
margin-left 5px
|
||||||
|
|
||||||
&:first-child
|
&:first-child
|
||||||
margin 0.5em 0
|
margin 0.5em 0
|
||||||
@@ -421,7 +442,7 @@ span.unit
|
|||||||
min-width 8em
|
min-width 8em
|
||||||
width 100%
|
width 100%
|
||||||
|
|
||||||
.mach_units
|
.units
|
||||||
padding 0
|
padding 0
|
||||||
|
|
||||||
select
|
select
|
||||||
@@ -848,18 +869,6 @@ tt.save
|
|||||||
text-decoration none
|
text-decoration none
|
||||||
|
|
||||||
|
|
||||||
.upgrade-version
|
|
||||||
display inline-block
|
|
||||||
border-radius 4px
|
|
||||||
padding 2px
|
|
||||||
margin-left 0.5em
|
|
||||||
color #555
|
|
||||||
background-color #e5aa3d
|
|
||||||
text-decoration none
|
|
||||||
|
|
||||||
&:hover
|
|
||||||
color #fff
|
|
||||||
|
|
||||||
.modal-mask
|
.modal-mask
|
||||||
position fixed
|
position fixed
|
||||||
z-index 9998
|
z-index 9998
|
||||||
|
|||||||
24
src/svelte-components/.gitignore
vendored
Normal file
24
src/svelte-components/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
48
src/svelte-components/README.md
Normal file
48
src/svelte-components/README.md
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# Svelte + TS + Vite
|
||||||
|
|
||||||
|
This template should help get you started developing with Svelte and TypeScript in Vite.
|
||||||
|
|
||||||
|
## Recommended IDE Setup
|
||||||
|
|
||||||
|
[VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode).
|
||||||
|
|
||||||
|
## Need an official Svelte framework?
|
||||||
|
|
||||||
|
Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also powered by Vite. Deploy anywhere with its serverless-first approach and adapt to various platforms, with out of the box support for TypeScript, SCSS, and Less, and easily-added support for mdsvex, GraphQL, PostCSS, Tailwind CSS, and more.
|
||||||
|
|
||||||
|
## Technical considerations
|
||||||
|
|
||||||
|
**Why use this over SvelteKit?**
|
||||||
|
|
||||||
|
- It brings its own routing solution which might not be preferable for some users.
|
||||||
|
- It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app.
|
||||||
|
`vite dev` and `vite build` wouldn't work in a SvelteKit environment, for example.
|
||||||
|
|
||||||
|
This template contains as little as possible to get started with Vite + TypeScript + Svelte, while taking into account the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte project.
|
||||||
|
|
||||||
|
Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been structured similarly to SvelteKit so that it is easy to migrate.
|
||||||
|
|
||||||
|
**Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?**
|
||||||
|
|
||||||
|
Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash references keeps the default TypeScript setting of accepting type information from the entire workspace, while also adding `svelte` and `vite/client` type information.
|
||||||
|
|
||||||
|
**Why include `.vscode/extensions.json`?**
|
||||||
|
|
||||||
|
Other templates indirectly recommend extensions via the README, but this file allows VS Code to prompt the user to install the recommended extension upon opening the project.
|
||||||
|
|
||||||
|
**Why enable `allowJs` in the TS template?**
|
||||||
|
|
||||||
|
While `allowJs: false` would indeed prevent the use of `.js` files in the project, it does not prevent the use of JavaScript syntax in `.svelte` files. In addition, it would force `checkJs: false`, bringing the worst of both worlds: not being able to guarantee the entire codebase is TypeScript, and also having worse typechecking for the existing JavaScript. In addition, there are valid use cases in which a mixed codebase may be relevant.
|
||||||
|
|
||||||
|
**Why is HMR not preserving my local component state?**
|
||||||
|
|
||||||
|
HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/rixo/svelte-hmr#svelte-hmr).
|
||||||
|
|
||||||
|
If you have state that's important to retain within a component, consider creating an external store which would not be replaced by HMR.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// store.ts
|
||||||
|
// An extremely simple external store
|
||||||
|
import { writable } from 'svelte/store'
|
||||||
|
export default writable(0)
|
||||||
|
```
|
||||||
13
src/svelte-components/index.html
Normal file
13
src/svelte-components/index.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Svelte + TS + Vite App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
9507
src/svelte-components/package-lock.json
generated
Normal file
9507
src/svelte-components/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
28
src/svelte-components/package.json
Normal file
28
src/svelte-components/package.json
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"name": "svelte-components",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"postbuild": "smui-theme compile dist/smui.css -i src/theme",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"check": "svelte-check --tsconfig ./tsconfig.json"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@sveltejs/kit": "^1.0.0-next.357",
|
||||||
|
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.49",
|
||||||
|
"@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",
|
||||||
|
"svelte-check": "^2.8.0",
|
||||||
|
"svelte-material-ui": "^6.0.0-beta.16",
|
||||||
|
"svelte-preprocess": "^4.10.7",
|
||||||
|
"tslib": "^2.4.0",
|
||||||
|
"typescript": "^4.7.4",
|
||||||
|
"vite": "^2.9.13"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
src/svelte-components/public/favicon.ico
Normal file
BIN
src/svelte-components/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
28
src/svelte-components/public/material-symbols-outlined.css
Normal file
28
src/svelte-components/public/material-symbols-outlined.css
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/* To browse the icons in material-symbols, see https://marella.me/material-symbols/demo/ */
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "Material Symbols Outlined";
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 100 700;
|
||||||
|
font-display: block;
|
||||||
|
src: url("./fonts/material-symbols-outlined.woff2") format("woff2");
|
||||||
|
}
|
||||||
|
.material-symbols-outlined {
|
||||||
|
font-family: "Material Symbols Outlined";
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 1;
|
||||||
|
letter-spacing: normal;
|
||||||
|
text-transform: none;
|
||||||
|
display: inline-block;
|
||||||
|
white-space: nowrap;
|
||||||
|
word-wrap: normal;
|
||||||
|
direction: ltr;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
font-feature-settings: "liga";
|
||||||
|
|
||||||
|
font-variation-settings: "FILL" 1, "wght" 400, "GRAD" 0, "opsz" 48;
|
||||||
|
}
|
||||||
202
src/svelte-components/src/components/AdminNetworkView.svelte
Normal file
202
src/svelte-components/src/components/AdminNetworkView.svelte
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import WifiConnectionDialog from "$dialogs/WifiConnectionDialog.svelte";
|
||||||
|
import ChangeHostnameDialog from "$dialogs/ChangeHostnameDialog.svelte";
|
||||||
|
import Button, { Label } from "@smui/button";
|
||||||
|
import List, { Item, Graphic, Text, Meta } from "@smui/list";
|
||||||
|
import Card from "@smui/card";
|
||||||
|
import { networkInfo, type WifiNetwork } from "$lib/NetworkInfo";
|
||||||
|
|
||||||
|
let changeHostnameDialog = {
|
||||||
|
open: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let wifiConnectionDialog = {
|
||||||
|
open: false,
|
||||||
|
network: {} as WifiNetwork,
|
||||||
|
};
|
||||||
|
|
||||||
|
function getWifiStrengthIcon(network: WifiNetwork) {
|
||||||
|
const strength = Math.ceil((Number(network.Quality) / 100) * 4);
|
||||||
|
|
||||||
|
switch (strength) {
|
||||||
|
case 1:
|
||||||
|
return "";
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
return "wifi_1_bar";
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
return "wifi_2_bar";
|
||||||
|
|
||||||
|
case 4:
|
||||||
|
return "wifi";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onChangeHostname() {
|
||||||
|
changeHostnameDialog = {
|
||||||
|
open: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function onNetworkSelected(network: WifiNetwork) {
|
||||||
|
wifiConnectionDialog = {
|
||||||
|
open: true,
|
||||||
|
network,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<WifiConnectionDialog {...wifiConnectionDialog} />
|
||||||
|
<ChangeHostnameDialog {...changeHostnameDialog} />
|
||||||
|
|
||||||
|
<div class="admin-network-view">
|
||||||
|
<h1>Network Info</h1>
|
||||||
|
|
||||||
|
<div class="pure-form pure-form-aligned">
|
||||||
|
<div class="pure-control-group">
|
||||||
|
<label for="hostname">Hostname</label>
|
||||||
|
<Card id="hostname" variant="outlined">
|
||||||
|
<Text id="hostname">
|
||||||
|
{$networkInfo.hostname}
|
||||||
|
</Text>
|
||||||
|
</Card>
|
||||||
|
<Button on:click={onChangeHostname} touch variant="raised">
|
||||||
|
<Label>Change</Label>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pure-form pure-form-aligned">
|
||||||
|
<div class="pure-control-group">
|
||||||
|
<label for="ip-addresses">IP Addresses</label>
|
||||||
|
<Card id="ip-addresses" variant="outlined">
|
||||||
|
{#each $networkInfo.ipAddresses as ipAddress}
|
||||||
|
<div>
|
||||||
|
<Text id="hostname">
|
||||||
|
{ipAddress}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pure-form pure-form-aligned">
|
||||||
|
<div class="pure-control-group">
|
||||||
|
<label for="wifi">Wi-Fi</label>
|
||||||
|
<div class="wifi-networks">
|
||||||
|
<Card id="wifi" variant="outlined">
|
||||||
|
<List>
|
||||||
|
{#if $networkInfo.wifi.networks.length === 0}
|
||||||
|
<Item class="wifi-network">
|
||||||
|
<Text>Scanning...</Text>
|
||||||
|
</Item>
|
||||||
|
{:else}
|
||||||
|
{#each $networkInfo.wifi.networks as network}
|
||||||
|
<Item
|
||||||
|
class="wifi-network"
|
||||||
|
on:SMUI:action={() => onNetworkSelected(network)}
|
||||||
|
>
|
||||||
|
<Graphic
|
||||||
|
class="strength {$networkInfo.wifi.ssid === network.Name
|
||||||
|
? 'active'
|
||||||
|
: ''}"
|
||||||
|
>
|
||||||
|
<span class="material-symbols-outlined background"
|
||||||
|
>wifi</span
|
||||||
|
>
|
||||||
|
<span class="material-symbols-outlined">
|
||||||
|
{getWifiStrengthIcon(network)}
|
||||||
|
</span>
|
||||||
|
</Graphic>
|
||||||
|
<Text style="margin-right: 20px;">{network.Name}</Text>
|
||||||
|
{#if network.Encryption !== "Open"}
|
||||||
|
<Meta>
|
||||||
|
<span class="material-symbols-outlined lock">lock</span>
|
||||||
|
</Meta>
|
||||||
|
{/if}
|
||||||
|
</Item>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
</List>
|
||||||
|
</Card>
|
||||||
|
<em style="display: block;">
|
||||||
|
Click on a Wi-Fi network to connect or disconnect.
|
||||||
|
</em>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
$primary: #0078e7;
|
||||||
|
$very-dark: #555;
|
||||||
|
$text: #777;
|
||||||
|
$grey: #bbb;
|
||||||
|
$light: #ddd;
|
||||||
|
|
||||||
|
:global {
|
||||||
|
.admin-network-view {
|
||||||
|
.pure-form-aligned .pure-control-group label {
|
||||||
|
vertical-align: top;
|
||||||
|
font-size: 15pt;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mdc-card {
|
||||||
|
width: 400px;
|
||||||
|
min-height: 38px;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
margin-right: 20px;
|
||||||
|
padding: 5px 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wifi-networks {
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
.mdc-card {
|
||||||
|
padding: 0;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wifi-network {
|
||||||
|
.lock {
|
||||||
|
font-size: 20px;
|
||||||
|
vertical-align: text-bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
.strength {
|
||||||
|
border-radius: 50%;
|
||||||
|
padding: 3px;
|
||||||
|
background-color: $light;
|
||||||
|
color: $very-dark;
|
||||||
|
margin-right: 10px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: $primary;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
position: absolute;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
|
||||||
|
&.background {
|
||||||
|
opacity: 0.25;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
23
src/svelte-components/src/components/Devmode.svelte
Normal file
23
src/svelte-components/src/components/Devmode.svelte
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import * as api from "$lib/api";
|
||||||
|
</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>
|
||||||
93
src/svelte-components/src/components/DimensionInput.svelte
Normal file
93
src/svelte-components/src/components/DimensionInput.svelte
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import TextField from "@smui/textfield";
|
||||||
|
import Select, { Option } from "@smui/select";
|
||||||
|
import type { MenuComponentDev } from "@smui/menu";
|
||||||
|
import Menu from "@smui/menu";
|
||||||
|
import List, { Item, Text } from "@smui/list";
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
import { set_input_value } from "svelte/internal";
|
||||||
|
|
||||||
|
let menu: MenuComponentDev;
|
||||||
|
|
||||||
|
type Option = {
|
||||||
|
value: number;
|
||||||
|
metric: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export let label: string;
|
||||||
|
export let value: number;
|
||||||
|
export let metric: boolean;
|
||||||
|
export let options: Option[];
|
||||||
|
|
||||||
|
let textValue = "";
|
||||||
|
|
||||||
|
$: if (textValue) {
|
||||||
|
value = Number.parseFloat(textValue) || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
textValue = value?.toString() || "";
|
||||||
|
});
|
||||||
|
|
||||||
|
function onOptionSelected(option: Option) {
|
||||||
|
textValue = option.value.toString();
|
||||||
|
metric = option.metric;
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterKeys(event) {
|
||||||
|
const input = event.target;
|
||||||
|
|
||||||
|
if (input.value.match(/[^0-9.]/)) {
|
||||||
|
input.value = input.value.replace(/[^0-9.]/g, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.value.match(/\.[^.]*\./)) {
|
||||||
|
input.value = input.value.replace(/(\.[^.]*)\./g, "$1");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="value-and-unit">
|
||||||
|
<TextField
|
||||||
|
{label}
|
||||||
|
on:keypress={filterKeys}
|
||||||
|
on:keyup={filterKeys}
|
||||||
|
bind:value={textValue}
|
||||||
|
on:click={() => menu.setOpen(true)}
|
||||||
|
/>
|
||||||
|
<Select bind:value={metric}>
|
||||||
|
<Option value={true}>mm</Option>
|
||||||
|
<Option value={false}>in</Option>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<Menu bind:this={menu} anchorCorner="BOTTOM_LEFT">
|
||||||
|
<List>
|
||||||
|
{#each options as option}
|
||||||
|
<Item on:SMUI:action={() => onOptionSelected(option)}>
|
||||||
|
<Text>
|
||||||
|
{option.value}
|
||||||
|
{option.metric ? "mm" : "in"}
|
||||||
|
</Text>
|
||||||
|
</Item>
|
||||||
|
{/each}
|
||||||
|
</List>
|
||||||
|
</Menu>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.value-and-unit {
|
||||||
|
display: flex;
|
||||||
|
column-gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global {
|
||||||
|
.mdc-select {
|
||||||
|
max-width: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.smui-select--standard .mdc-select__dropdown-icon {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Dialog, { Title, Content, Actions } from "@smui/dialog";
|
||||||
|
import Button, { Label } from "@smui/button";
|
||||||
|
import TextField from "@smui/textfield";
|
||||||
|
import MessageDialog from "$dialogs/MessageDialog.svelte";
|
||||||
|
import * as api from "$lib/api";
|
||||||
|
|
||||||
|
// https://man7.org/linux/man-pages/man7/hostname.7.html
|
||||||
|
//
|
||||||
|
// Each element of the hostname must be from 1 to 63 characters long
|
||||||
|
// and the entire hostname, including the dots, can be at most 253
|
||||||
|
// characters long. Valid characters for hostnames are ASCII(7)
|
||||||
|
// letters from a to z, the digits from 0 to 9, and the hyphen (-).
|
||||||
|
// A hostname may not start with a hyphen.
|
||||||
|
|
||||||
|
const pattern = /[a-zA-Z0-9][a-zA-Z0-9-]{0,62}/;
|
||||||
|
|
||||||
|
export let open = false;
|
||||||
|
|
||||||
|
let rebooting = false;
|
||||||
|
let redirectTimeout = 45;
|
||||||
|
let hostname = "";
|
||||||
|
|
||||||
|
$: setTimeout(() => {
|
||||||
|
hostname = (hostname.match(pattern) || [""])[0].toLowerCase();
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
$: if (open) {
|
||||||
|
hostname = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onConfirm() {
|
||||||
|
rebooting = true;
|
||||||
|
await api.PUT("hostname", { hostname });
|
||||||
|
await api.PUT("reboot");
|
||||||
|
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
if (0 < redirectTimeout) {
|
||||||
|
redirectTimeout -= 1;
|
||||||
|
} else {
|
||||||
|
clearInterval(interval);
|
||||||
|
location.hostname = getRedirectTarget();
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRedirectTarget() {
|
||||||
|
if (location.hostname.endsWith(".local")) {
|
||||||
|
return `${hostname}.local`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (location.hostname.endsWith(".lan")) {
|
||||||
|
return `${hostname}.lan`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hostname;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<MessageDialog open={rebooting} title="Rebooting">
|
||||||
|
Rebooting to apply the hostname change...
|
||||||
|
</MessageDialog>
|
||||||
|
|
||||||
|
<Dialog
|
||||||
|
bind:open
|
||||||
|
scrimClickAction=""
|
||||||
|
aria-labelledby="simple-title"
|
||||||
|
aria-describedby="simple-content"
|
||||||
|
>
|
||||||
|
<Title id="simple-title">Change Hostname</Title>
|
||||||
|
|
||||||
|
<Content id="simple-content">
|
||||||
|
<TextField
|
||||||
|
bind:value={hostname}
|
||||||
|
label="New Hostname"
|
||||||
|
spellcheck="false"
|
||||||
|
variant="filled"
|
||||||
|
style="width: 100%;"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<em>Clicking Confirm will reboot the controller to apply the change.</em>
|
||||||
|
</p>
|
||||||
|
</Content>
|
||||||
|
|
||||||
|
<Actions>
|
||||||
|
<Button>
|
||||||
|
<Label>Cancel</Label>
|
||||||
|
</Button>
|
||||||
|
<Button defaultAction on:click={onConfirm} disabled={hostname.length === 0}>
|
||||||
|
<Label>Confirm</Label>
|
||||||
|
</Button>
|
||||||
|
</Actions>
|
||||||
|
</Dialog>
|
||||||
38
src/svelte-components/src/dialogs/DialogHost.svelte
Normal file
38
src/svelte-components/src/dialogs/DialogHost.svelte
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<script lang="ts" context="module">
|
||||||
|
import HomeMachineDialog from "$dialogs/HomeMachineDialog.svelte";
|
||||||
|
import ProbeDialog from "$dialogs/ProbeDialog.svelte";
|
||||||
|
import {
|
||||||
|
HomeMachineProps,
|
||||||
|
ProbeProps,
|
||||||
|
type HomeMachinePropsType,
|
||||||
|
type ProbePropsType,
|
||||||
|
} from "$dialogs/DialogProps";
|
||||||
|
|
||||||
|
export function showDialog(
|
||||||
|
dialog: "HomeMachine",
|
||||||
|
props: Omit<HomeMachinePropsType, "open">
|
||||||
|
);
|
||||||
|
|
||||||
|
export function showDialog(
|
||||||
|
dialog: "Probe",
|
||||||
|
props: Omit<ProbePropsType, "open">
|
||||||
|
);
|
||||||
|
|
||||||
|
export function showDialog(dialog: string, props: any) {
|
||||||
|
switch (dialog) {
|
||||||
|
case "HomeMachine":
|
||||||
|
HomeMachineProps.set({ ...props, open: true });
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "Probe":
|
||||||
|
ProbeProps.set({ ...props, open: true });
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown dialog '${dialog}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<HomeMachineDialog {...$HomeMachineProps} />
|
||||||
|
<ProbeDialog {...$ProbeProps} />
|
||||||
13
src/svelte-components/src/dialogs/DialogProps.ts
Normal file
13
src/svelte-components/src/dialogs/DialogProps.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { writable } from "svelte/store";
|
||||||
|
|
||||||
|
export const HomeMachineProps = writable<HomeMachinePropsType>();
|
||||||
|
export type HomeMachinePropsType = {
|
||||||
|
open: boolean,
|
||||||
|
home: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ProbeProps = writable<ProbePropsType>();
|
||||||
|
export type ProbePropsType = {
|
||||||
|
open: boolean,
|
||||||
|
probeType: "xyz" | "z"
|
||||||
|
};
|
||||||
27
src/svelte-components/src/dialogs/HomeMachineDialog.svelte
Normal file
27
src/svelte-components/src/dialogs/HomeMachineDialog.svelte
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Dialog, { Title, Content, Actions, InitialFocus } from "@smui/dialog";
|
||||||
|
import Button, { Label } from "@smui/button";
|
||||||
|
|
||||||
|
export let open;
|
||||||
|
export let home: () => any;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Dialog
|
||||||
|
bind:open
|
||||||
|
aria-labelledby="simple-title"
|
||||||
|
aria-describedby="simple-content"
|
||||||
|
>
|
||||||
|
<Title id="simple-title">Home Machine</Title>
|
||||||
|
|
||||||
|
<Content id="simple-content">Home the machine?</Content>
|
||||||
|
|
||||||
|
<Actions>
|
||||||
|
<Button>
|
||||||
|
<Label>Cancel</Label>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button defaultAction use={[InitialFocus]} on:click={home}>
|
||||||
|
<Label>OK</Label>
|
||||||
|
</Button>
|
||||||
|
</Actions>
|
||||||
|
</Dialog>
|
||||||
19
src/svelte-components/src/dialogs/MessageDialog.svelte
Normal file
19
src/svelte-components/src/dialogs/MessageDialog.svelte
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Dialog, { Title, Content } from "@smui/dialog";
|
||||||
|
|
||||||
|
export let open: boolean;
|
||||||
|
export let title: string;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Dialog
|
||||||
|
bind:open
|
||||||
|
scrimClickAction=""
|
||||||
|
escapeKeyAction=""
|
||||||
|
aria-labelledby="simple-title"
|
||||||
|
aria-describedby="simple-content"
|
||||||
|
>
|
||||||
|
<Title id="simple-title">{title}</Title>
|
||||||
|
<Content id="simple-content">
|
||||||
|
<slot />
|
||||||
|
</Content>
|
||||||
|
</Dialog>
|
||||||
479
src/svelte-components/src/dialogs/ProbeDialog.svelte
Normal file
479
src/svelte-components/src/dialogs/ProbeDialog.svelte
Normal file
@@ -0,0 +1,479 @@
|
|||||||
|
<script type="ts" context="module">
|
||||||
|
import { get, writable, type Writable } from "svelte/store";
|
||||||
|
|
||||||
|
type Step =
|
||||||
|
| "None"
|
||||||
|
| "CheckProbe"
|
||||||
|
| "BitDimensions"
|
||||||
|
| "PlaceProbeBlock"
|
||||||
|
| "Probe"
|
||||||
|
| "Done";
|
||||||
|
|
||||||
|
const stepLabels: Record<Step, string> = {
|
||||||
|
None: "",
|
||||||
|
CheckProbe: "Check probe",
|
||||||
|
BitDimensions: "Bit dimensions",
|
||||||
|
PlaceProbeBlock: "Place probe block",
|
||||||
|
Probe: "Probe",
|
||||||
|
Done: "Done",
|
||||||
|
};
|
||||||
|
|
||||||
|
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, metric: false },
|
||||||
|
{ value: 10, metric: true },
|
||||||
|
{ value: 0.25, metric: false },
|
||||||
|
{ value: 6, metric: true },
|
||||||
|
{ value: 0.125, metric: false },
|
||||||
|
{ value: 3, metric: true },
|
||||||
|
];
|
||||||
|
|
||||||
|
const cutterLengthOptions = [
|
||||||
|
{ value: 1, metric: false },
|
||||||
|
{ value: 20, metric: true },
|
||||||
|
{ value: 0.5, metric: false },
|
||||||
|
{ value: 10, metric: true },
|
||||||
|
{ value: 0.25, metric: false },
|
||||||
|
{ value: 6, metric: true },
|
||||||
|
];
|
||||||
|
|
||||||
|
export let open;
|
||||||
|
export let probeType: "xyz" | "z";
|
||||||
|
let currentStep: Step = "None";
|
||||||
|
let cutterDiameter: number;
|
||||||
|
let cutterLength: number;
|
||||||
|
let showCancelButton = true;
|
||||||
|
let steps: Array<Step> = [];
|
||||||
|
let nextButton = {
|
||||||
|
label: "Next",
|
||||||
|
disabled: false,
|
||||||
|
allowClose: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
$: metric = $Config.settings?.units === "METRIC";
|
||||||
|
|
||||||
|
$: if (open) {
|
||||||
|
cutterDiameter =
|
||||||
|
Number.parseFloat(localStorage.getItem("cutterDiameter")) || null;
|
||||||
|
cutterLength =
|
||||||
|
Number.parseFloat(localStorage.getItem("cutterLength")) || null;
|
||||||
|
|
||||||
|
// Svelte appears not to like it when you invoke
|
||||||
|
// an async function from a reactive statement, so we
|
||||||
|
// use requestAnimationFrame to call 'begin' at a later moment.
|
||||||
|
requestAnimationFrame(begin);
|
||||||
|
}
|
||||||
|
|
||||||
|
$: if (cutterDiameter && cutterLength) {
|
||||||
|
updateButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeSkippedSteps(steps: Step[]): Step[] {
|
||||||
|
return steps.filter((x) => x);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function begin() {
|
||||||
|
try {
|
||||||
|
$probingActive = true;
|
||||||
|
assertValidProbeType();
|
||||||
|
|
||||||
|
steps = removeSkippedSteps([
|
||||||
|
"CheckProbe",
|
||||||
|
probeType === "xyz" ? "BitDimensions" : undefined,
|
||||||
|
"PlaceProbeBlock",
|
||||||
|
"Probe",
|
||||||
|
"Done",
|
||||||
|
]);
|
||||||
|
|
||||||
|
await stepCompleted("CheckProbe", probeContacted);
|
||||||
|
|
||||||
|
if (probeType === "xyz") {
|
||||||
|
await stepCompleted("BitDimensions", userAcknowledged);
|
||||||
|
localStorage.setItem("cutterDiameter", cutterDiameter);
|
||||||
|
localStorage.setItem("cutterLength", cutterLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
await stepCompleted("PlaceProbeBlock", userAcknowledged);
|
||||||
|
await stepCompleted("Probe", probingComplete, probingFailed);
|
||||||
|
await stepCompleted("Done", userAcknowledged);
|
||||||
|
|
||||||
|
if (probeType === "xyz") {
|
||||||
|
ControllerMethods.goto_zero(1, 1, 0, 0);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err.message !== "cancelled") {
|
||||||
|
console.error("Error during probing:", err);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
$probingActive = false;
|
||||||
|
currentStep = "None";
|
||||||
|
|
||||||
|
if ($probingStarted) {
|
||||||
|
ControllerMethods.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
clearFlags();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertValidProbeType() {
|
||||||
|
switch (probeType) {
|
||||||
|
case "xyz":
|
||||||
|
case "z":
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`Invalid probe type: ${probeType}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function stepCompleted(
|
||||||
|
nextStep: Step,
|
||||||
|
...writables: Array<Writable<any>>
|
||||||
|
) {
|
||||||
|
currentStep = nextStep;
|
||||||
|
|
||||||
|
clearFlags();
|
||||||
|
updateButtons();
|
||||||
|
|
||||||
|
if (currentStep === "Probe") {
|
||||||
|
executeProbe();
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.race([
|
||||||
|
...writables.map((writable) => waitForChange(writable)),
|
||||||
|
waitForChange(cancelled),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($cancelled) {
|
||||||
|
throw new Error("cancelled");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearFlags(foo: string = "") {
|
||||||
|
$cancelled = false;
|
||||||
|
$probeContacted = false;
|
||||||
|
$probingStarted = false;
|
||||||
|
$probingFailed = false;
|
||||||
|
$probingComplete = false;
|
||||||
|
$userAcknowledged = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateButtons() {
|
||||||
|
showCancelButton = true;
|
||||||
|
|
||||||
|
nextButton = {
|
||||||
|
label: "Next",
|
||||||
|
disabled: false,
|
||||||
|
allowClose: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (currentStep) {
|
||||||
|
case "CheckProbe":
|
||||||
|
case "Probe":
|
||||||
|
nextButton.disabled = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "BitDimensions":
|
||||||
|
nextButton.disabled = !(
|
||||||
|
cutterDiameter !== null &&
|
||||||
|
cutterDiameter !== 0 &&
|
||||||
|
isFinite(cutterDiameter) &&
|
||||||
|
cutterLength !== null &&
|
||||||
|
cutterLength !== 0 &&
|
||||||
|
isFinite(cutterLength)
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "Done":
|
||||||
|
showCancelButton = false;
|
||||||
|
nextButton = {
|
||||||
|
disabled: false,
|
||||||
|
label: "Done",
|
||||||
|
allowClose: true,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function executeProbe() {
|
||||||
|
const probeBlockWidth = $Config.probe["probe-xdim"];
|
||||||
|
const probeBlockLength = $Config.probe["probe-ydim"];
|
||||||
|
const probeBlockHeight = $Config.probe["probe-zdim"];
|
||||||
|
const slowSeek = $Config.probe["probe-slow-seek"];
|
||||||
|
const fastSeek = $Config.probe["probe-fast-seek"];
|
||||||
|
|
||||||
|
const zLift = 1;
|
||||||
|
const xOffset = probeBlockWidth + cutterDiameter / 2.0;
|
||||||
|
const yOffset = probeBlockLength + cutterDiameter / 2.0;
|
||||||
|
const zOffset = probeBlockHeight;
|
||||||
|
|
||||||
|
if (probeType === "z") {
|
||||||
|
ControllerMethods.send(`
|
||||||
|
G21
|
||||||
|
G92 Z0
|
||||||
|
|
||||||
|
G38.2 Z -25.4 F${fastSeek}
|
||||||
|
G91 G1 Z 1
|
||||||
|
G38.2 Z -2 F${slowSeek}
|
||||||
|
G92 Z ${zOffset}
|
||||||
|
|
||||||
|
G91 G0 Z 3
|
||||||
|
|
||||||
|
M2
|
||||||
|
`);
|
||||||
|
} else {
|
||||||
|
// After probing Z, we want to drop the bit down:
|
||||||
|
// Ideally, 12.7mm/0.5in
|
||||||
|
// And we don't want to be more than 90% down on the probe block
|
||||||
|
// Also, add zlift to compensate for the fact that we lift after probing Z
|
||||||
|
const plunge = Math.min(cutterLength, zOffset * 0.9) + zLift;
|
||||||
|
|
||||||
|
ControllerMethods.send(`
|
||||||
|
G21
|
||||||
|
G92 X0 Y0 Z0
|
||||||
|
|
||||||
|
G38.2 Z -25 F${fastSeek}
|
||||||
|
G91 G1 Z 1
|
||||||
|
G38.2 Z -2 F${slowSeek}
|
||||||
|
G92 Z ${zOffset}
|
||||||
|
|
||||||
|
G91 G0 Z ${zLift}
|
||||||
|
G91 G0 X 20
|
||||||
|
G91 G0 Z ${-plunge}
|
||||||
|
G38.2 X -20 F${fastSeek}
|
||||||
|
G91 G1 X 1
|
||||||
|
G38.2 X -2 F${slowSeek}
|
||||||
|
G92 X ${xOffset}
|
||||||
|
|
||||||
|
G91 G0 X 1
|
||||||
|
G91 G0 Y 20
|
||||||
|
G91 G0 X -20
|
||||||
|
G38.2 Y -20 F${fastSeek}
|
||||||
|
G91 G1 Y 1
|
||||||
|
G38.2 Y -2 F${slowSeek}
|
||||||
|
G92 Y ${yOffset}
|
||||||
|
|
||||||
|
G91 G0 Y 3
|
||||||
|
G91 G0 Z 25
|
||||||
|
|
||||||
|
M2
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Dialog
|
||||||
|
bind:open
|
||||||
|
class="probe-dialog"
|
||||||
|
scrimClickAction=""
|
||||||
|
aria-labelledby="simple-title"
|
||||||
|
aria-describedby="simple-content"
|
||||||
|
surface$style="width: 700px; height: 400px; max-width: calc(100vw - 32px); overflow: visible;"
|
||||||
|
>
|
||||||
|
<Title id="simple-title">Probing {probeType?.toUpperCase()}</Title>
|
||||||
|
|
||||||
|
<Content id="simple-content" style="overflow: visible;">
|
||||||
|
<div class="steps">
|
||||||
|
<p><b>Step {steps.indexOf(currentStep) + 1} of {steps.length}</b></p>
|
||||||
|
<ul>
|
||||||
|
{#each steps as step}
|
||||||
|
<li class:active={currentStep === step}>{stepLabels[step]}</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="current-step">
|
||||||
|
<p>
|
||||||
|
{#if currentStep === "CheckProbe"}
|
||||||
|
Attach the probe magnet to the collet, then touch the probe block to
|
||||||
|
the bit.
|
||||||
|
{:else if currentStep === "BitDimensions"}
|
||||||
|
<DimensionInput
|
||||||
|
label="Cutter diameter"
|
||||||
|
options={cutterDiameterOptions}
|
||||||
|
bind:value={cutterDiameter}
|
||||||
|
{metric}
|
||||||
|
/>
|
||||||
|
<DimensionInput
|
||||||
|
label="Cutter length"
|
||||||
|
options={cutterLengthOptions}
|
||||||
|
bind:value={cutterLength}
|
||||||
|
{metric}
|
||||||
|
/>
|
||||||
|
{:else if currentStep === "PlaceProbeBlock"}
|
||||||
|
{#if probeType === "xyz"}
|
||||||
|
Place the probe block face up, on the lower-left corner of your
|
||||||
|
workpiece.
|
||||||
|
{:else}
|
||||||
|
Place the probe block face down, with the bit above the recess.
|
||||||
|
{/if}
|
||||||
|
{:else if currentStep === "Probe"}
|
||||||
|
Probing in progress...
|
||||||
|
{:else if currentStep === "Done"}
|
||||||
|
{#if $probingFailed}
|
||||||
|
Could not find the probe block during probing!
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Make sure the tip of the bit is less than {metric
|
||||||
|
? "25mm"
|
||||||
|
: "1 in"} above the probe block, and try again.
|
||||||
|
</p>
|
||||||
|
{:else}
|
||||||
|
Don't forget to put away the probe!
|
||||||
|
|
||||||
|
{#if probeType === "xyz"}
|
||||||
|
<p>The machine will now move to the XY origin.</p>
|
||||||
|
|
||||||
|
<p>Watch your hands!</p>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Content>
|
||||||
|
|
||||||
|
<Actions>
|
||||||
|
{#if showCancelButton}
|
||||||
|
<Button on:click={() => ($cancelled = true)}>
|
||||||
|
<Label>Cancel</Label>
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
|
<Button
|
||||||
|
defaultAction
|
||||||
|
data-mdc-dialog-action={nextButton.allowClose ? "close" : ""}
|
||||||
|
disabled={nextButton.disabled}
|
||||||
|
on:click={() => ($userAcknowledged = true)}
|
||||||
|
>
|
||||||
|
<Label>
|
||||||
|
{nextButton.label}
|
||||||
|
</Label>
|
||||||
|
</Button>
|
||||||
|
</Actions>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
$primary: #0078e7;
|
||||||
|
$very-dark: #555;
|
||||||
|
$text: #777;
|
||||||
|
$grey: #bbb;
|
||||||
|
$light: #ddd;
|
||||||
|
|
||||||
|
:global {
|
||||||
|
.mdc-dialog__content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-step {
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
|
.mdc-text-field {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.steps {
|
||||||
|
margin-right: 50px;
|
||||||
|
|
||||||
|
ul {
|
||||||
|
margin: 0 auto;
|
||||||
|
list-style-type: none;
|
||||||
|
counter-reset: steps;
|
||||||
|
margin: 0;
|
||||||
|
font-family: sans-serif;
|
||||||
|
padding-inline-start: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul li {
|
||||||
|
padding: 0 0 12px 30px;
|
||||||
|
position: relative;
|
||||||
|
margin: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
color: $text;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
position: absolute;
|
||||||
|
top: 3px;
|
||||||
|
left: 0.5px;
|
||||||
|
content: "";
|
||||||
|
border: 2px solid $text;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: inline-block;
|
||||||
|
height: 11px;
|
||||||
|
width: 11px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 12px;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
position: absolute;
|
||||||
|
left: 7px;
|
||||||
|
top: 22px;
|
||||||
|
bottom: 0;
|
||||||
|
content: "";
|
||||||
|
width: 0;
|
||||||
|
border-left: 2px solid $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-of-type:before {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
color: $primary;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
border: 3px solid $primary;
|
||||||
|
top: 2.5px;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Dialog, { Title, Content, Actions, InitialFocus } from "@smui/dialog";
|
||||||
|
import Button, { Label } from "@smui/button";
|
||||||
|
import TextField from "@smui/textfield";
|
||||||
|
import Icon from "@smui/textfield/icon";
|
||||||
|
import HelperText from "@smui/textfield/helper-text";
|
||||||
|
import MessageDialog from "$dialogs/MessageDialog.svelte";
|
||||||
|
import type { WifiNetwork } from "$lib/NetworkInfo";
|
||||||
|
import * as api from "$lib/api";
|
||||||
|
|
||||||
|
export let open = false;
|
||||||
|
export let network: WifiNetwork;
|
||||||
|
|
||||||
|
let rebooting = false;
|
||||||
|
let password = "";
|
||||||
|
let showPassword = false;
|
||||||
|
|
||||||
|
$: needPassword = !network?.active && network?.Encryption !== "Open";
|
||||||
|
$: connectOrDisconnect = network?.active ? "Disconnect" : "Connect";
|
||||||
|
$: connectToOrDisconnectFrom = network?.active
|
||||||
|
? "Disconnect from"
|
||||||
|
: "Connect to";
|
||||||
|
|
||||||
|
$: if (open) {
|
||||||
|
password = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onConfirm() {
|
||||||
|
rebooting = true;
|
||||||
|
|
||||||
|
await api.PUT("network", {
|
||||||
|
wifi: {
|
||||||
|
enabled: !network.active,
|
||||||
|
ssid: network.Name,
|
||||||
|
password,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<MessageDialog open={rebooting} title="Rebooting">
|
||||||
|
Rebooting to apply Wifi changes...
|
||||||
|
</MessageDialog>
|
||||||
|
|
||||||
|
<Dialog
|
||||||
|
bind:open
|
||||||
|
scrimClickAction=""
|
||||||
|
aria-labelledby="simple-title"
|
||||||
|
aria-describedby="simple-content"
|
||||||
|
>
|
||||||
|
<Title id="simple-title">{connectToOrDisconnectFrom} {network.Name}</Title>
|
||||||
|
|
||||||
|
<Content id="simple-content">
|
||||||
|
{#if needPassword}
|
||||||
|
<TextField
|
||||||
|
bind:value={password}
|
||||||
|
label="Password"
|
||||||
|
spellcheck="false"
|
||||||
|
variant="filled"
|
||||||
|
type={showPassword ? "text" : "password"}
|
||||||
|
style="width: 100%;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
slot="trailingIcon"
|
||||||
|
on:click={() => (showPassword = !showPassword)}
|
||||||
|
>
|
||||||
|
<Icon class="material-symbols-outlined">
|
||||||
|
{showPassword ? "password" : "abc"}
|
||||||
|
</Icon>
|
||||||
|
</div>
|
||||||
|
<HelperText persistent slot="helper">
|
||||||
|
Wifi passwords must be 8 to 128 characters
|
||||||
|
</HelperText>
|
||||||
|
</TextField>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<em>
|
||||||
|
Clicking {connectOrDisconnect} will reboot the controller to apply the changes.
|
||||||
|
</em>
|
||||||
|
</p>
|
||||||
|
</Content>
|
||||||
|
|
||||||
|
<Actions>
|
||||||
|
<Button>
|
||||||
|
<Label>Cancel</Label>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
defaultAction
|
||||||
|
use={[InitialFocus]}
|
||||||
|
on:click={onConfirm}
|
||||||
|
disabled={needPassword && (password.length < 8 || password.length > 128)}
|
||||||
|
>
|
||||||
|
<Label>{connectOrDisconnect}</Label>
|
||||||
|
</Button>
|
||||||
|
</Actions>
|
||||||
|
</Dialog>
|
||||||
7
src/svelte-components/src/lib/ConfigStore.ts
Normal file
7
src/svelte-components/src/lib/ConfigStore.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { writable } from "svelte/store";
|
||||||
|
|
||||||
|
export const Config = writable<Record<string, any>>({});
|
||||||
|
|
||||||
|
export function handleConfigUpdate(config: Record<string, any>) {
|
||||||
|
Config.set(config);
|
||||||
|
}
|
||||||
93
src/svelte-components/src/lib/NetworkInfo.ts
Normal file
93
src/svelte-components/src/lib/NetworkInfo.ts
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import { readable } from "svelte/store";
|
||||||
|
import * as api from "$lib/api";
|
||||||
|
|
||||||
|
export type WifiNetwork = {
|
||||||
|
Quality: string;
|
||||||
|
Channel: string;
|
||||||
|
Frequency: string;
|
||||||
|
Mode: string;
|
||||||
|
"Bit Rates": string;
|
||||||
|
Name: string;
|
||||||
|
Address: string;
|
||||||
|
Encryption: string;
|
||||||
|
"Signal Level": string;
|
||||||
|
"Noise Level": string;
|
||||||
|
lastSeen: number;
|
||||||
|
active: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type NetworkInfo = {
|
||||||
|
ipAddresses: Array<string>;
|
||||||
|
hostname: string;
|
||||||
|
wifi: {
|
||||||
|
ssid: string;
|
||||||
|
networks: Array<WifiNetwork>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const empty: NetworkInfo = {
|
||||||
|
ipAddresses: [],
|
||||||
|
hostname: "",
|
||||||
|
wifi: {
|
||||||
|
ssid: "",
|
||||||
|
networks: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const networkInfo = readable<NetworkInfo>(empty, (set) => {
|
||||||
|
getNetworkInfo();
|
||||||
|
const networkInfoIntervalId = setInterval(getNetworkInfo, 5000);
|
||||||
|
|
||||||
|
async function getNetworkInfo() {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearInterval(networkInfoIntervalId);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export function init() {
|
||||||
|
return networkInfo.subscribe(() => ({}));
|
||||||
|
}
|
||||||
11
src/svelte-components/src/lib/RegisterControllerMethods.ts
Normal file
11
src/svelte-components/src/lib/RegisterControllerMethods.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
type ControllerMethods = {
|
||||||
|
stop: () => void;
|
||||||
|
send: (gcode: string) => void;
|
||||||
|
goto_zero: (x: number, y: number, z: number, a: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export let ControllerMethods: ControllerMethods;
|
||||||
|
|
||||||
|
export function registerControllerMethods(methods: ControllerMethods) {
|
||||||
|
ControllerMethods = methods;
|
||||||
|
}
|
||||||
18
src/svelte-components/src/lib/StoreHelpers.ts
Normal file
18
src/svelte-components/src/lib/StoreHelpers.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { get, type Writable } from "svelte/store";
|
||||||
|
|
||||||
|
export function listenForChange<T>(writable: Writable<T>, cb: (value: T) => void) {
|
||||||
|
const priorValue = get(writable);
|
||||||
|
|
||||||
|
const unsubscribe = writable.subscribe((value) => {
|
||||||
|
if (value !== priorValue) {
|
||||||
|
unsubscribe();
|
||||||
|
cb(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function waitForChange<T>(writable: Writable<T>): Promise<T> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
listenForChange(writable, (value) => resolve(value));
|
||||||
|
});
|
||||||
|
}
|
||||||
41
src/svelte-components/src/lib/api.ts
Normal file
41
src/svelte-components/src/lib/api.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
type HttpMethod = "GET" | "PUT" | "POST" | "DELETE";
|
||||||
|
|
||||||
|
async function doFetch(method: HttpMethod, url: string, data: any, config: RequestInit) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/${url}`, {
|
||||||
|
...config,
|
||||||
|
method,
|
||||||
|
cache: "no-cache",
|
||||||
|
body: (typeof data === 'object')
|
||||||
|
? JSON.stringify(data)
|
||||||
|
: undefined,
|
||||||
|
headers: (typeof data === 'object')
|
||||||
|
? {
|
||||||
|
"Content-Type": 'application/json; charset=utf-8'
|
||||||
|
}
|
||||||
|
: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.debug('API Error: ' + url + ': ' + error);
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function GET(url: string, config: RequestInit = {}) {
|
||||||
|
return doFetch('GET', url, undefined, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function PUT(url: string, data: any = undefined, config: RequestInit = {}) {
|
||||||
|
return doFetch('PUT', url, data, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function POST(url: string, data: any = undefined, config: RequestInit = {}) {
|
||||||
|
return doFetch('POST', url, data, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function DELETE(url: string, config = {}) {
|
||||||
|
return doFetch('DELETE', url, undefined, config);
|
||||||
|
}
|
||||||
33
src/svelte-components/src/main.ts
Normal file
33
src/svelte-components/src/main.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import 'polyfill-object.fromentries';
|
||||||
|
|
||||||
|
import AdminNetworkView from '$components/AdminNetworkView.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 { registerControllerMethods } from "$lib/RegisterControllerMethods";
|
||||||
|
|
||||||
|
export function createComponent(component: string, target: HTMLElement, props: Record<string, any>) {
|
||||||
|
switch (component) {
|
||||||
|
case "AdminNetworkView":
|
||||||
|
return new AdminNetworkView({ 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,
|
||||||
|
};
|
||||||
42
src/svelte-components/src/theme/_smui-theme.scss
Normal file
42
src/svelte-components/src/theme/_smui-theme.scss
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
@use "sass:color";
|
||||||
|
|
||||||
|
@use "@material/theme/color-palette";
|
||||||
|
|
||||||
|
// Svelte Colors!
|
||||||
|
@use "@material/theme/index" as theme with (
|
||||||
|
$primary: #0078e7,
|
||||||
|
$secondary: #676778,
|
||||||
|
$surface: #fff,
|
||||||
|
$background: #fff,
|
||||||
|
$error: color-palette.$red-900,
|
||||||
|
$on-surface: #777
|
||||||
|
);
|
||||||
|
|
||||||
|
@use "@material/elevation/mdc-elevation";
|
||||||
|
@use "@material/list";
|
||||||
|
|
||||||
|
@include list.deprecated-core-styles;
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--mdc-theme-text-primary-on-background: #777;
|
||||||
|
|
||||||
|
.mdc-dialog .mdc-dialog__content {
|
||||||
|
color: #777;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mdc-dialog .mdc-dialog__title {
|
||||||
|
color: #777;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
input::-webkit-outer-spin-button,
|
||||||
|
input::-webkit-inner-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Firefox */
|
||||||
|
input[type="number"] {
|
||||||
|
-moz-appearance: textfield;
|
||||||
|
}
|
||||||
|
}
|
||||||
12
src/svelte-components/src/theme/dark/_smui-theme.scss
Normal file
12
src/svelte-components/src/theme/dark/_smui-theme.scss
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
@use 'sass:color';
|
||||||
|
|
||||||
|
@use '@material/theme/color-palette';
|
||||||
|
|
||||||
|
// Svelte Colors! (Dark Theme)
|
||||||
|
@use '@material/theme/index' as theme with (
|
||||||
|
$primary: #ff3e00,
|
||||||
|
$secondary: color.scale(#676778, $whiteness: -10%),
|
||||||
|
$surface: color.adjust(color-palette.$grey-900, $blue: +4),
|
||||||
|
$background: #000,
|
||||||
|
$error: color-palette.$red-700
|
||||||
|
);
|
||||||
2
src/svelte-components/src/vite-env.d.ts
vendored
Normal file
2
src/svelte-components/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/// <reference types="svelte" />
|
||||||
|
/// <reference types="vite/client" />
|
||||||
7
src/svelte-components/svelte.config.js
Normal file
7
src/svelte-components/svelte.config.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import sveltePreprocess from 'svelte-preprocess'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
// Consult https://github.com/sveltejs/svelte-preprocess
|
||||||
|
// for more information about preprocessors
|
||||||
|
preprocess: sveltePreprocess()
|
||||||
|
}
|
||||||
27
src/svelte-components/tsconfig.json
Normal file
27
src/svelte-components/tsconfig.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"extends": "@tsconfig/svelte/tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "esnext",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"$lib/*": ["src/lib/*"],
|
||||||
|
"$components/*": ["src/components/*"],
|
||||||
|
"$dialogs/*": ["src/dialogs/*"]
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Typecheck JS in `.svelte` and `.js` files by default.
|
||||||
|
* Disable checkJs if you'd like to use dynamic types in JS.
|
||||||
|
* Note that setting allowJs false does not prevent the use
|
||||||
|
* of JS in `.svelte` files.
|
||||||
|
*/
|
||||||
|
"allowJs": true,
|
||||||
|
"checkJs": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"moduleSuffixes": [".svelte", ""]
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte"],
|
||||||
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
|
}
|
||||||
8
src/svelte-components/tsconfig.node.json
Normal file
8
src/svelte-components/tsconfig.node.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node"
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
26
src/svelte-components/vite.config.ts
Normal file
26
src/svelte-components/vite.config.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import { svelte } from '@sveltejs/vite-plugin-svelte'
|
||||||
|
import { resolve } from 'path';
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
svelte()
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
$lib: resolve('./src/lib'),
|
||||||
|
$dialogs: resolve('./src/dialogs'),
|
||||||
|
$components: resolve('./src/components')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
target: "chrome60",
|
||||||
|
lib: {
|
||||||
|
entry: resolve(__dirname, 'src/main.ts'),
|
||||||
|
name: 'SvelteComponents',
|
||||||
|
formats: ['iife'],
|
||||||
|
fileName: () => "index.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user