New probe dialog working
This commit is contained in:
@@ -289,6 +289,15 @@ module.exports = new Vue({
|
|||||||
update_object(this.config, config, true);
|
update_object(this.config, config, true);
|
||||||
this.parse_hash();
|
this.parse_hash();
|
||||||
|
|
||||||
|
if (!this.devModChecked) {
|
||||||
|
this.devModChecked = true;
|
||||||
|
if (this.config.devmode) {
|
||||||
|
SvelteComponents.createComponent("Devmode",
|
||||||
|
document.getElementById("svelte-devmode-host")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.checkedUpgrade) {
|
if (!this.checkedUpgrade) {
|
||||||
this.checkedUpgrade = true;
|
this.checkedUpgrade = true;
|
||||||
|
|
||||||
|
|||||||
@@ -254,6 +254,7 @@ module.exports = {
|
|||||||
this.load();
|
this.load();
|
||||||
|
|
||||||
SvelteComponents.registerControllerMethods({
|
SvelteComponents.registerControllerMethods({
|
||||||
|
stop: (...args) => this.stop(...args),
|
||||||
send: (...args) => this.send(...args),
|
send: (...args) => this.send(...args),
|
||||||
goto_zero: (...args) => this.goto_zero(...args)
|
goto_zero: (...args) => this.goto_zero(...args)
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ html(lang="en")
|
|||||||
|
|
||||||
body(v-cloak)
|
body(v-cloak)
|
||||||
#svelte-dialog-host
|
#svelte-dialog-host
|
||||||
|
#svelte-devmode-host
|
||||||
|
|
||||||
#overlay(v-if="status != 'connected'")
|
#overlay(v-if="status != 'connected'")
|
||||||
span {{status}}
|
span {{status}}
|
||||||
|
|||||||
@@ -1,18 +1,22 @@
|
|||||||
import os
|
import os
|
||||||
import bbctrl
|
import bbctrl
|
||||||
|
|
||||||
|
|
||||||
class Ctrl(object):
|
class Ctrl(object):
|
||||||
def __init__(self, args, ioloop, id):
|
def __init__(self, args, ioloop, id):
|
||||||
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)
|
||||||
@@ -21,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()
|
||||||
@@ -38,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,44 +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.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)
|
|
||||||
|
|
||||||
|
|
||||||
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 = []
|
||||||
|
|
||||||
@@ -125,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:
|
||||||
@@ -143,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:
|
||||||
@@ -151,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'):
|
||||||
@@ -168,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:
|
||||||
@@ -193,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)
|
||||||
|
|
||||||
@@ -223,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)
|
||||||
|
|
||||||
@@ -262,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()
|
||||||
@@ -284,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 = {}
|
||||||
@@ -296,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':
|
||||||
@@ -305,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)
|
||||||
|
|
||||||
@@ -319,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 = []
|
||||||
|
|
||||||
@@ -345,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)
|
||||||
|
|
||||||
@@ -381,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)
|
||||||
@@ -419,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()
|
||||||
|
|
||||||
@@ -453,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)
|
||||||
|
|||||||
@@ -269,6 +269,14 @@ class HomeHandler(bbctrl.APIHandler):
|
|||||||
self.get_ctrl().mach.home(axis)
|
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):
|
||||||
def put_ok(self): self.get_ctrl().mach.start()
|
def put_ok(self): self.get_ctrl().mach.start()
|
||||||
|
|
||||||
@@ -463,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),
|
||||||
|
|||||||
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>
|
||||||
@@ -5,12 +5,10 @@
|
|||||||
import { Config } from "$lib/ConfigStore";
|
import { Config } from "$lib/ConfigStore";
|
||||||
import { waitForChange } from "$lib/StoreHelpers";
|
import { waitForChange } from "$lib/StoreHelpers";
|
||||||
import { ControllerMethods } from "$lib/RegisterControllerMethods";
|
import { ControllerMethods } from "$lib/RegisterControllerMethods";
|
||||||
import { tick } from "svelte";
|
|
||||||
|
|
||||||
type Stage =
|
type Step =
|
||||||
| "None"
|
| "None"
|
||||||
| "TestingProbe"
|
| "TestingProbe"
|
||||||
| "TestingProbeComplete"
|
|
||||||
| "GetToolDiameter"
|
| "GetToolDiameter"
|
||||||
| "Probing"
|
| "Probing"
|
||||||
| "ProbingFailed"
|
| "ProbingFailed"
|
||||||
@@ -55,77 +53,48 @@
|
|||||||
<script type="ts">
|
<script type="ts">
|
||||||
export let open;
|
export let open;
|
||||||
export let probeType: "xyz" | "z";
|
export let probeType: "xyz" | "z";
|
||||||
let stage: Stage = "None";
|
let step: Step = "None";
|
||||||
let toolDiameter;
|
let toolDiameter;
|
||||||
let confirmButton = {
|
let showCancelButton = true;
|
||||||
label: "Continue",
|
let nextButton = {
|
||||||
|
label: "Next",
|
||||||
disabled: false,
|
disabled: false,
|
||||||
allowClose: false,
|
allowClose: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
$: showPrompts = $Config.settings?.["probing-prompts"];
|
$: showPrompts = $Config.settings?.["probing-prompts"];
|
||||||
$: clearFlags(stage);
|
|
||||||
$: updateConfirmButton();
|
|
||||||
|
|
||||||
$: if (open) {
|
$: if (open) {
|
||||||
|
toolDiameter = "";
|
||||||
|
|
||||||
// Svelte appears not to like it when you invoke
|
// Svelte appears not to like it when you invoke
|
||||||
// an async function from a reactive statement
|
// an async function from a reactive statement, so we
|
||||||
|
// use requestAnimationFrame to call begin at a later moment.
|
||||||
requestAnimationFrame(begin);
|
requestAnimationFrame(begin);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateConfirmButton() {
|
|
||||||
confirmButton.label = "Continue";
|
|
||||||
confirmButton.disabled = false;
|
|
||||||
confirmButton.allowClose = false;
|
|
||||||
|
|
||||||
switch (stage) {
|
|
||||||
case "TestingProbe":
|
|
||||||
case "Probing":
|
|
||||||
confirmButton.disabled = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "ProbingComplete":
|
|
||||||
confirmButton.disabled = true;
|
|
||||||
confirmButton.label = "Done";
|
|
||||||
confirmButton.allowClose = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function begin() {
|
async function begin() {
|
||||||
try {
|
try {
|
||||||
$probingActive = true;
|
$probingActive = true;
|
||||||
|
|
||||||
assertValidProbeType();
|
assertValidProbeType();
|
||||||
|
|
||||||
if (showPrompts) {
|
if (showPrompts) {
|
||||||
stage = "TestingProbe";
|
await stepCompleted("TestingProbe", probeContacted);
|
||||||
|
|
||||||
await cancellableSignal(probeContacted);
|
|
||||||
|
|
||||||
stage = "TestingProbeComplete";
|
|
||||||
await cancellableSignal(userAcknowledged);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (probeType === "xyz") {
|
if (probeType === "xyz") {
|
||||||
stage = "GetToolDiameter";
|
await stepCompleted("GetToolDiameter", userAcknowledged);
|
||||||
await cancellableSignal(userAcknowledged);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
stage = "Probing";
|
await stepCompleted("Probing", probingComplete, probingFailed);
|
||||||
executeProbe(probeType, toolDiameter);
|
|
||||||
|
|
||||||
await cancellableSignal(probingComplete, probingFailed);
|
|
||||||
|
|
||||||
if ($probingFailed) {
|
if ($probingFailed) {
|
||||||
stage = "ProbingFailed";
|
await stepCompleted("ProbingFailed", userAcknowledged);
|
||||||
await cancellableSignal(userAcknowledged);
|
|
||||||
}
|
}
|
||||||
} while (!$probingComplete);
|
} while (!$probingComplete);
|
||||||
|
|
||||||
stage = "ProbingComplete";
|
await stepCompleted("ProbingComplete", userAcknowledged);
|
||||||
await cancellableSignal(userAcknowledged);
|
|
||||||
|
|
||||||
if (probeType === "xyz") {
|
if (probeType === "xyz") {
|
||||||
ControllerMethods.goto_zero(1, 1, 0, 0);
|
ControllerMethods.goto_zero(1, 1, 0, 0);
|
||||||
@@ -136,12 +105,40 @@
|
|||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
$probingActive = false;
|
$probingActive = false;
|
||||||
stage = "None";
|
step = "None";
|
||||||
|
|
||||||
|
if ($probingStarted) {
|
||||||
|
ControllerMethods.stop();
|
||||||
|
}
|
||||||
|
|
||||||
clearFlags();
|
clearFlags();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function cancellableSignal<T>(...writables: Array<Writable<T>>) {
|
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>>
|
||||||
|
) {
|
||||||
|
step = nextStep;
|
||||||
|
|
||||||
|
clearFlags();
|
||||||
|
updateButtons();
|
||||||
|
|
||||||
|
if (step === "Probing") {
|
||||||
|
executeProbe();
|
||||||
|
}
|
||||||
|
|
||||||
await Promise.race([
|
await Promise.race([
|
||||||
...writables.map((writable) => waitForChange(writable)),
|
...writables.map((writable) => waitForChange(writable)),
|
||||||
waitForChange(cancelled),
|
waitForChange(cancelled),
|
||||||
@@ -161,18 +158,33 @@
|
|||||||
$userAcknowledged = false;
|
$userAcknowledged = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function assertValidProbeType() {
|
function updateButtons() {
|
||||||
switch (probeType) {
|
showCancelButton = true;
|
||||||
case "xyz":
|
|
||||||
case "z":
|
nextButton = {
|
||||||
|
label: "Next",
|
||||||
|
disabled: false,
|
||||||
|
allowClose: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (step) {
|
||||||
|
case "TestingProbe":
|
||||||
|
case "Probing":
|
||||||
|
nextButton.disabled = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
case "ProbingComplete":
|
||||||
throw new Error(`Invalid probe type: ${probeType}`);
|
showCancelButton = false;
|
||||||
|
nextButton = {
|
||||||
|
disabled: false,
|
||||||
|
label: "Done",
|
||||||
|
allowClose: true,
|
||||||
|
};
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function executeProbe(probeType: "xyz" | "z", toolDiameter: number) {
|
function executeProbe() {
|
||||||
const probeBlockWidth = $Config.probe["probe-xdim"];
|
const probeBlockWidth = $Config.probe["probe-xdim"];
|
||||||
const probeBlockLength = $Config.probe["probe-ydim"];
|
const probeBlockLength = $Config.probe["probe-ydim"];
|
||||||
const probeBlockHeight = $Config.probe["probe-zdim"];
|
const probeBlockHeight = $Config.probe["probe-zdim"];
|
||||||
@@ -186,18 +198,18 @@
|
|||||||
|
|
||||||
if (probeType === "z") {
|
if (probeType === "z") {
|
||||||
ControllerMethods.send(`
|
ControllerMethods.send(`
|
||||||
G21
|
G21
|
||||||
G92 Z0
|
G92 Z0
|
||||||
|
|
||||||
G38.2 Z -25.4 F${fastSeek}
|
G38.2 Z -25.4 F${fastSeek}
|
||||||
G91 G1 Z 1
|
G91 G1 Z 1
|
||||||
G38.2 Z -2 F${slowSeek}
|
G38.2 Z -2 F${slowSeek}
|
||||||
G92 Z ${zOffset}
|
G92 Z ${zOffset}
|
||||||
|
|
||||||
G91 G0 Z 3
|
G91 G0 Z 3
|
||||||
|
|
||||||
M2
|
M2
|
||||||
`);
|
`);
|
||||||
} else {
|
} else {
|
||||||
// After probing Z, we want to drop the bit down:
|
// After probing Z, we want to drop the bit down:
|
||||||
// Ideally, 12.7mm/0.5in
|
// Ideally, 12.7mm/0.5in
|
||||||
@@ -206,35 +218,35 @@
|
|||||||
const plunge = Math.min(12.7, zOffset * 0.75) + zLift;
|
const plunge = Math.min(12.7, zOffset * 0.75) + zLift;
|
||||||
|
|
||||||
ControllerMethods.send(`
|
ControllerMethods.send(`
|
||||||
G21
|
G21
|
||||||
G92 X0 Y0 Z0
|
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}
|
G38.2 Z -25 F${fastSeek}
|
||||||
G91 G0 X 20
|
G91 G1 Z 1
|
||||||
G91 G0 Z ${-plunge}
|
G38.2 Z -2 F${slowSeek}
|
||||||
G38.2 X -20 F${fastSeek}
|
G92 Z ${zOffset}
|
||||||
G91 G1 X 1
|
|
||||||
G38.2 X -2 F${slowSeek}
|
G91 G0 Z ${zLift}
|
||||||
G92 X ${xOffset}
|
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 X 1
|
||||||
G91 G0 Y 20
|
G91 G0 Y 20
|
||||||
G91 G0 X -20
|
G91 G0 X -20
|
||||||
G38.2 Y -20 F${fastSeek}
|
G38.2 Y -20 F${fastSeek}
|
||||||
G91 G1 Y 1
|
G91 G1 Y 1
|
||||||
G38.2 Y -2 F${slowSeek}
|
G38.2 Y -2 F${slowSeek}
|
||||||
G92 Y ${yOffset}
|
G92 Y ${yOffset}
|
||||||
|
|
||||||
G91 G0 Y 3
|
G91 G0 Y 3
|
||||||
G91 G0 Z 25
|
G91 G0 Z 25
|
||||||
|
|
||||||
M2
|
M2
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -248,27 +260,27 @@
|
|||||||
<Title id="simple-title">Probe {probeType}</Title>
|
<Title id="simple-title">Probe {probeType}</Title>
|
||||||
|
|
||||||
<Content id="simple-content">
|
<Content id="simple-content">
|
||||||
{#if stage === "TestingProbe" || stage === "TestingProbeComplete"}
|
{#if step === "TestingProbe"}
|
||||||
<p>Attach the probe magnet to the collet.</p>
|
<p>Attach the probe magnet to the collet.</p>
|
||||||
<p>Touch the probe block to the bit.</p>
|
<p>Touch the probe block to the bit.</p>
|
||||||
|
|
||||||
{#if stage === "TestingProbe"}
|
{#if $probeContacted}
|
||||||
<p>Waiting for probe contact...</p>
|
|
||||||
{:else}
|
|
||||||
<p>Probe contact detected!</p>
|
<p>Probe contact detected!</p>
|
||||||
|
{:else}
|
||||||
|
<p>Waiting for probe contact...</p>
|
||||||
{/if}
|
{/if}
|
||||||
{:else if stage === "GetToolDiameter"}
|
{:else if step === "GetToolDiameter"}
|
||||||
<label for="tool-diameter">Tool Diameter (mm)</label>
|
<label for="tool-diameter">Tool Diameter (mm)</label>
|
||||||
<input id="tool-diameter" bind:value={toolDiameter} />
|
<input id="tool-diameter" bind:value={toolDiameter} />
|
||||||
{:else if stage === "Probing"}
|
{:else if step === "Probing"}
|
||||||
<p>Probing in progress...</p>
|
<p>Probing in progress...</p>
|
||||||
{:else if stage === "ProbingFailed"}
|
{:else if step === "ProbingFailed"}
|
||||||
<p>Could not find the probe block during probing!</p>
|
<p>Could not find the probe block during probing!</p>
|
||||||
<p>
|
<p>
|
||||||
Make sure the tip of the bit is about 1/4" (6mm) above the probe block,
|
Make sure the tip of the bit is about 1/4" (6mm) above the probe block,
|
||||||
and try again.
|
and try again.
|
||||||
</p>
|
</p>
|
||||||
{:else if stage === "ProbingComplete"}
|
{:else if step === "ProbingComplete"}
|
||||||
<p>Don't forget to put away the probe!</p>
|
<p>Don't forget to put away the probe!</p>
|
||||||
<p>The machine will now move to the XY origin.</p>
|
<p>The machine will now move to the XY origin.</p>
|
||||||
<p>Watch your hands!</p>
|
<p>Watch your hands!</p>
|
||||||
@@ -276,17 +288,19 @@
|
|||||||
</Content>
|
</Content>
|
||||||
|
|
||||||
<Actions>
|
<Actions>
|
||||||
<Button on:click={() => ($cancelled = true)}>
|
{#if showCancelButton}
|
||||||
<Label>Cancel</Label>
|
<Button on:click={() => ($cancelled = true)}>
|
||||||
</Button>
|
<Label>Cancel</Label>
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
<Button
|
<Button
|
||||||
defaultAction
|
defaultAction
|
||||||
data-mdc-dialog-action={confirmButton.allowClose ? "close" : ""}
|
data-mdc-dialog-action={nextButton.allowClose ? "close" : ""}
|
||||||
disabled={confirmButton.disabled}
|
disabled={nextButton.disabled}
|
||||||
on:click={() => ($userAcknowledged = true)}
|
on:click={() => ($userAcknowledged = true)}
|
||||||
>
|
>
|
||||||
<Label>
|
<Label>
|
||||||
{confirmButton.label}
|
{nextButton.label}
|
||||||
</Label>
|
</Label>
|
||||||
</Button>
|
</Button>
|
||||||
</Actions>
|
</Actions>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
type ControllerMethods = {
|
type ControllerMethods = {
|
||||||
|
stop: () => void;
|
||||||
send: (gcode: string) => void;
|
send: (gcode: string) => void;
|
||||||
goto_zero: (x: number, y: number, z: number, a: number) => void;
|
goto_zero: (x: number, y: number, z: number, a: number) => void;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'polyfill-object.fromentries';
|
|||||||
|
|
||||||
import AdminNetworkView from '$components/AdminNetworkView.svelte';
|
import AdminNetworkView from '$components/AdminNetworkView.svelte';
|
||||||
import DialogHost, { showDialog } from "$dialogs/DialogHost.svelte";
|
import DialogHost, { showDialog } from "$dialogs/DialogHost.svelte";
|
||||||
|
import Devmode from "$components/Devmode.svelte";
|
||||||
import { handleConfigUpdate } from '$lib/ConfigStore';
|
import { handleConfigUpdate } from '$lib/ConfigStore';
|
||||||
import { init as initNetworkInfo } from '$lib/NetworkInfo';
|
import { init as initNetworkInfo } from '$lib/NetworkInfo';
|
||||||
import { handleControllerStateUpdate } from "$dialogs/ProbeDialog.svelte";
|
import { handleControllerStateUpdate } from "$dialogs/ProbeDialog.svelte";
|
||||||
@@ -15,6 +16,9 @@ export function createComponent(component: string, target: HTMLElement, props: R
|
|||||||
case "DialogHost":
|
case "DialogHost":
|
||||||
return new DialogHost({ target, props });
|
return new DialogHost({ target, props });
|
||||||
|
|
||||||
|
case "Devmode":
|
||||||
|
return new Devmode({target, props});
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new Error("Unknown component");
|
throw new Error("Unknown component");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user