From de665cac4d07f6c02790c3ebb23d9d58bd8ab387 Mon Sep 17 00:00:00 2001 From: David Carley Date: Sun, 10 Jul 2022 02:00:45 -0700 Subject: [PATCH] New probe dialog working --- src/js/app.js | 9 + src/js/control-view.js | 1 + src/pug/index.pug | 1 + src/py/bbctrl/Ctrl.py | 44 ++-- src/py/bbctrl/Mach.py | 133 +++++------ src/py/bbctrl/State.py | 203 +++++++--------- src/py/bbctrl/Web.py | 9 + .../src/components/Devmode.svelte | 23 ++ .../src/dialogs/ProbeDialog.svelte | 222 ++++++++++-------- .../src/lib/RegisterControllerMethods.ts | 1 + src/svelte-components/src/main.ts | 4 + 11 files changed, 320 insertions(+), 330 deletions(-) create mode 100644 src/svelte-components/src/components/Devmode.svelte diff --git a/src/js/app.js b/src/js/app.js index 50cacfc..8e2dd2d 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -289,6 +289,15 @@ module.exports = new Vue({ update_object(this.config, config, true); this.parse_hash(); + if (!this.devModChecked) { + this.devModChecked = true; + if (this.config.devmode) { + SvelteComponents.createComponent("Devmode", + document.getElementById("svelte-devmode-host") + ); + } + } + if (!this.checkedUpgrade) { this.checkedUpgrade = true; diff --git a/src/js/control-view.js b/src/js/control-view.js index 1c54a26..f2b41ac 100644 --- a/src/js/control-view.js +++ b/src/js/control-view.js @@ -254,6 +254,7 @@ module.exports = { this.load(); SvelteComponents.registerControllerMethods({ + stop: (...args) => this.stop(...args), send: (...args) => this.send(...args), goto_zero: (...args) => this.goto_zero(...args) }); diff --git a/src/pug/index.pug b/src/pug/index.pug index 575e904..643b9aa 100644 --- a/src/pug/index.pug +++ b/src/pug/index.pug @@ -21,6 +21,7 @@ html(lang="en") body(v-cloak) #svelte-dialog-host + #svelte-devmode-host #overlay(v-if="status != 'connected'") span {{status}} diff --git a/src/py/bbctrl/Ctrl.py b/src/py/bbctrl/Ctrl.py index a751aff..43fa61f 100644 --- a/src/py/bbctrl/Ctrl.py +++ b/src/py/bbctrl/Ctrl.py @@ -1,18 +1,22 @@ import os import bbctrl + class Ctrl(object): def __init__(self, args, ioloop, id): self.args = args self.ioloop = bbctrl.IOLoop(ioloop) 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 - if args.demo: log_path = self.get_path(filename = 'bbctrl.log') - else: log_path = args.log + if args.demo: + log_path = self.get_path(filename='bbctrl.log') + else: + log_path = args.log self.log = bbctrl.log.Log(args, self.ioloop, log_path) self.state = bbctrl.State(self) @@ -21,14 +25,17 @@ class Ctrl(object): self.log.get('Ctrl').info('Starting %s' % self.id) try: - if args.demo: self.avr = bbctrl.AVREmu(self) - else: self.avr = bbctrl.AVR(self) + if args.demo: + self.avr = bbctrl.AVREmu(self) + else: + self.avr = bbctrl.AVR(self) self.i2c = bbctrl.I2C(args.i2c_port, args.demo) self.lcd = bbctrl.LCD(self) self.mach = bbctrl.Mach(self, self.avr) 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.mach.connect() @@ -38,48 +45,41 @@ class Ctrl(object): 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 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 - def set_timeout(self, cb, *args, **kwargs): self.clear_timeout() t = self.args.client_timeout 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 = path if dir is None else (path + '/' + dir) 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) - - def get_plan(self, filename = None): + def get_plan(self, filename=None): return self.get_path('plans', filename) - def configure(self): # Indirectly configures state via calls to config() and the AVR self.config.reload() - self.state.init() - def ready(self): # This is used to synchronize the start of the preplanner self.preplanner.start() - def close(self): self.log.get('Ctrl').info('Closing %s' % self.id) self.ioloop.close() diff --git a/src/py/bbctrl/Mach.py b/src/py/bbctrl/Mach.py index 677c160..6a4e464 100644 --- a/src/py/bbctrl/Mach.py +++ b/src/py/bbctrl/Mach.py @@ -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 . # -# # -# 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 # -# . # -# # -# For information regarding this software email: # -# "Joseph Coffland" # -# # -################################################################################ - import bbctrl from bbctrl.Comm import Comm 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.\ ''' + def overrides(interface_class): def overrider(method): if not method.__name__ in dir(interface_class): @@ -102,7 +76,6 @@ class Mach(Comm): super().reboot() - def _get_state(self): return self.ctrl.state.get('xx', '') def _is_estopped(self): return self._get_state() == 'ESTOPPED' 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_cycle(self): return self.ctrl.state.get('cycle', 'idle') - 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 ( 'User pause', 'Program pause', 'Optional pause') - def _set_cycle(self, cycle): self.ctrl.state.set('cycle', cycle) - def _begin_cycle(self, cycle): current = self._get_cycle() - if current == cycle: return # No change + if current == cycle: + return # No change if current != 'idle': 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()): self._set_cycle(cycle) - def process_log(self, log): # When a probe has failed, we have to e-stop or things # end up in a bad state, where positions and offsets are incorrect if log['msg'] == 'Switch not found': self.estop() - def _update(self, update): # Detect motor faults for motor in range(4): @@ -153,12 +123,12 @@ class Mach(Comm): # Handle EStop if state_changed and state == 'ESTOPPED': - self.planner.reset(stop = False) + self.planner.reset(stop=False) # Exit cycle if state changed to READY if (state_changed and self._get_cycle() != 'idle' and self._is_ready() and not self.planner.is_busy() and - not super().is_active()): + not super().is_active()): self.planner.position_change() self._set_cycle('idle') @@ -187,7 +157,6 @@ class Mach(Comm): (pr == 'Optional pause' and not op))): self._unpause() - def _unpause(self): pause_reason = self._get_pause_reason() self.mlog.info('Unpause: ' + pause_reason) @@ -196,36 +165,31 @@ class Mach(Comm): self.planner.stop() self.ctrl.state.set('line', 0) - else: self.planner.restart() + else: + self.planner.restart() super().i2c_command(Cmd.UNPAUSE) self.unpausing = True - 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)) - @overrides(Comm) def comm_next(self): if self.planner.is_running() and not self._is_holding(): return self.planner.next() - @overrides(Comm) def comm_error(self): self.planner.reset() - @overrides(Comm) def connect(self): self.planner.reset() super().connect() - def _query_var(self, cmd): equal = cmd.find('=') if equal == -1: @@ -234,21 +198,26 @@ class Mach(Comm): else: name, value = cmd[1:equal], cmd[equal + 1:] - if value.lower() == 'true': value = True - elif value.lower() == 'false': value = False + if value.lower() == 'true': + value = True + elif value.lower() == 'false': + value = False else: try: value = float(value) - except: pass + except: + pass self.ctrl.state.config(name, value) - - def mdi(self, cmd, with_limits = True): + def mdi(self, cmd, with_limits=True): try: - if not len(cmd): return - if cmd[0] == '$': self._query_var(cmd) - elif cmd[0] == '\\': super().queue_command(cmd[1:]) + if not len(cmd): + return + if cmd[0] == '$': + self._query_var(cmd) + elif cmd[0] == '\\': + super().queue_command(cmd[1:]) else: self._begin_cycle('mdi') self.planner.mdi(cmd, with_limits) @@ -260,18 +229,18 @@ class Mach(Comm): def set(self, code, value): super().queue_command('${}={}'.format(code, value)) - def jog(self, axes): self._begin_cycle('jogging') self.planner.position_change() super().queue_command(Cmd.jog(axes)) - - def home(self, axis, position = None): + def home(self, axis, position=None): state = self.ctrl.state - if axis is None: axes = 'zxyabc' # TODO This should be configurable - else: axes = '%c' % axis + if axis is None: + axes = 'zxyabc' # TODO This should be configurable + else: + axes = '%c' % axis for axis in axes: enabled = state.is_axis_enabled(axis) @@ -291,7 +260,8 @@ class Mach(Comm): continue 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)) continue @@ -299,56 +269,63 @@ class Mach(Comm): self.mlog.info('Homing %s axis' % axis) self._begin_cycle('homing') - if mode.startswith('stall-'): procedure = stall_homing_procedure - else: procedure = axis_homing_procedure + if mode.startswith('stall-'): + procedure = stall_homing_procedure + else: + procedure = axis_homing_procedure gcode = procedure % {'axis': axis} self.planner.mdi(gcode, False) super().resume() - def unhome(self, axis): self.mdi('G28.2 %c0' % axis) def estop(self): super().estop() - def clear(self): if self._is_estopped(): self.planner.reset() 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): filename = self.ctrl.state.get('selected', '') - if not filename: return + if not filename: + return self._begin_cycle('running') self.planner.load(filename) super().resume() - def step(self): - raise Exception('NYI') # TODO - if self._get_cycle() != 'running': self.start() - else: super().i2c_command(Cmd.UNPAUSE) - + raise Exception('NYI') # TODO + if self._get_cycle() != 'running': + self.start() + else: + super().i2c_command(Cmd.UNPAUSE) def stop(self): - if self._get_state() != 'jogging': self.stopping = True + if self._get_state() != 'jogging': + self.stopping = True super().i2c_command(Cmd.STOP) - - def pause(self): super().pause() + def pause(self): super().pause() def unpause(self): if self._is_paused(): self.ctrl.state.set('optional_pause', False) self._unpause() - - def optional_pause(self, enable = True): + def optional_pause(self, enable=True): self.ctrl.state.set('optional_pause', enable) - def set_position(self, axis, position): axis = axis.lower() state = self.ctrl.state @@ -367,17 +344,13 @@ class Mach(Comm): state.set(axis + 'p', target) super().queue_command(Cmd.set_axis(axis, target)) - def override_feed(self, override): self._i2c_set('fo', int(1000 * override)) - def override_speed(self, override): self._i2c_set('so', int(1000 * override)) - def modbus_read(self, addr): self._i2c_block(Cmd.modbus_read(addr)) - def modbus_write(self, addr, value): self._i2c_block(Cmd.modbus_write(addr, value)) diff --git a/src/py/bbctrl/State.py b/src/py/bbctrl/State.py index d7c1e67..61b163b 100644 --- a/src/py/bbctrl/State.py +++ b/src/py/bbctrl/State.py @@ -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 . # -# # -# 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 # -# . # -# # -# For information regarding this software email: # -# "Joseph Coffland" # -# # -################################################################################ - import traceback import copy import uuid @@ -33,11 +6,12 @@ import bbctrl from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler + class UploadChangeHandler(FileSystemEventHandler): def __init__(self, state): self.state = state - def on_any_event(self, event): + def on_any_event(self, event): self.state.load_files() @@ -70,44 +44,36 @@ class State(object): # the planner will scale returned values when in imperial mode. for i in range(4): 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', - 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', - 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', - 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', - 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', - lambda name, i = i: self.motor_latch_velocity(i)) + lambda name, i=i: self.motor_latch_velocity(i)) self.reset() self.load_files() observer = Observer() - observer.schedule(UploadChangeHandler(self), self.ctrl.get_upload(), recursive=True) + observer.schedule(UploadChangeHandler( + self), self.ctrl.get_upload(), recursive=True) 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): # 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 for axis in 'xyzabc': self.set(axis + 'p', 0) self.set('offset_' + axis, 0) - def load_files(self): files = [] @@ -125,15 +91,15 @@ class State(object): files.sort() self.set('files', files) - if len(files): self.select_file(files[0]) - else: self.select_file('') - + if len(files): + self.select_file(files[0]) + else: + self.select_file('') def clear_files(self): self.select_file('') self.set('files', []) - def add_file(self, filename): files = copy.deepcopy(self.get('files')) if not filename in files: @@ -143,7 +109,6 @@ class State(object): self.select_file(filename) - def remove_file(self, filename): files = copy.deepcopy(self.get('files')) if filename in files: @@ -151,16 +116,16 @@ class State(object): self.set('files', files) if self.get('selected', filename) == filename: - if len(files): self.select_file(files[0]) - else: self.select_file('') - + if len(files): + self.select_file(files[0]) + else: + self.select_file('') def select_file(self, filename): self.set('selected', filename) time = os.path.getmtime(self.ctrl.get_upload(filename)) self.set('selected_time', time) - def set_bounds(self, bounds): for axis in 'xyzabc': for name in ('min', 'max'): @@ -168,24 +133,22 @@ class State(object): value = bounds[name][axis] if axis in bounds[name] else 0 self.set(var, value) - def ack_message(self, id): self.log.info('Message %d acknowledged' % id) msgs = self.vars['messages'] msgs = list(filter(lambda m: id < m['id'], msgs)) self.set('messages', msgs) - 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 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) - def _notify(self): - if not self.changes: return + if not self.changes: + return for listener in self.listeners: try: @@ -193,25 +156,23 @@ class State(object): except Exception as e: self.log.warning('Updating state listener: %s', - traceback.format_exc()) + traceback.format_exc()) self.changes = {} self.timeout = None - def resolve(self, name): # Resolve axis prefixes to motor numbers if 2 < len(name) and name[1] == '_' and name[0] in 'xyzabc': 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 - def has(self, name): return self.resolve(name) in self.vars def set_callback(self, name, cb): self.callbacks[self.resolve(name)] = cb - def set(self, name, value): name = self.resolve(name) @@ -223,23 +184,22 @@ class State(object): if self.timeout is None: self.timeout = self.ctrl.ioloop.call_later(0.25, self._notify) - def update(self, update): for name, value in update.items(): self.set(name, value) - - def get(self, name, default = None): + def get(self, name, default=None): name = self.resolve(name) - if name in self.vars: return self.vars[name] - if name in self.callbacks: return self.callbacks[name](name) + if name in self.vars: + return self.vars[name] + if name in self.callbacks: + return self.callbacks[name](name) if default is None: self.log.warning('State variable "%s" not found' % name) return default - def snapshot(self): vars = copy.deepcopy(self.vars) @@ -262,21 +222,19 @@ class State(object): return vars - def config(self, code, value): # Set machine variables via mach, others directly - if code in self.machine_var_set: self.ctrl.mach.set(code, value) - else: self.set(code, value) - + if code in self.machine_var_set: + self.ctrl.mach.set(code, value) + else: + self.set(code, value) def add_listener(self, listener): self.listeners.append(listener) listener(self.vars) - def remove_listener(self, listener): self.listeners.remove(listener) - def set_machine_vars(self, vars): # Record all machine vars, indexed or otherwise self.machine_var_set = set() @@ -284,8 +242,8 @@ class State(object): if 'index' in spec: for index in spec['index']: 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): position = {} @@ -296,8 +254,7 @@ class State(object): return position - - def get_axis_vector(self, name, scale = 1): + def get_axis_vector(self, name, scale=1): v = {} for axis in 'xyzabc': @@ -305,11 +262,11 @@ class State(object): if motor is not None and self.motor_enabled(motor): 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 - def get_soft_limit_vector(self, var, default): limit = self.get_axis_vector(var, 1) @@ -319,23 +276,20 @@ class State(object): return limit - def find_motor(self, axis): 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]] if motor_axis == axis.lower() and self.vars.get('%dme' % motor, 0): return motor - def is_axis_homed(self, axis): return self.get('%s_homed' % axis, False) - def is_axis_enabled(self, axis): motor = self.find_motor(axis) return motor is not None and self.motor_enabled(motor) - def get_enabled_axes(self): axes = [] @@ -345,26 +299,25 @@ class State(object): return axes - def is_motor_faulted(self, motor): return self.get('%ddf' % motor, 0) & 0x1f - def is_axis_faulted(self, axis): motor = self.find_motor(axis) return motor is not None and self.is_motor_faulted(motor) - def axis_homing_mode(self, axis): motor = self.find_motor(axis) - if motor is None: return 'disabled' + if motor is None: + return 'disabled' return self.motor_homing_mode(motor) - def axis_home_fail_reason(self, axis): motor = self.find_motor(axis) - if motor is None: return 'Not mapped to motor' - if not self.motor_enabled(motor): return 'Motor disabled' + if motor is None: + return 'Not mapped to motor' + if not self.motor_enabled(motor): + return 'Motor disabled' mode = self.motor_homing_mode(motor) @@ -381,35 +334,39 @@ class State(object): return 'max-soft-limit must be at least 1mm greater ' \ 'than min-soft-limit' - def motor_enabled(self, motor): return bool(int(self.vars.get('%dme' % motor, 0))) - def motor_homing_mode(self, motor): mode = str(self.vars.get('%dho' % motor, 0)) - if mode == '0': return 'manual' - if mode == '1': return 'switch-min' - if mode == '2': return 'switch-max' - if mode == '3': return 'stall-min' - if mode == '4': return 'stall-max' + if mode == '0': + return 'manual' + if mode == '1': + return 'switch-min' + 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) - def motor_home_direction(self, motor): mode = self.motor_homing_mode(motor) - if mode.endswith('-min'): return -1 - if mode.endswith('-max'): return 1 - return 0 # Disabled - + if mode.endswith('-min'): + return -1 + if mode.endswith('-max'): + return 1 + return 0 # Disabled def motor_home_position(self, motor): mode = self.motor_homing_mode(motor) # Return soft limit positions - if mode.endswith('-min'): return self.vars['%dtn' % motor] - if mode.endswith('-max'): return self.vars['%dtm' % motor] - return 0 # Disabled - + if mode.endswith('-min'): + return self.vars['%dtn' % motor] + if mode.endswith('-max'): + return self.vars['%dtm' % motor] + return 0 # Disabled def motor_home_travel(self, motor): tmin = self.get(str(motor) + 'tm', 0) @@ -419,27 +376,22 @@ class State(object): # (travel_max - travel_min) * 1.5 * home_dir return (tmin - tmax) * 1.5 * hdir - def motor_latch_backoff(self, motor): lb = self.get(str(motor) + 'lb', 0) 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): zb = self.get(str(motor) + 'zb', 0) 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): return 1000 * self.get(str(motor) + 'sv', 0) - def motor_latch_velocity(self, motor): return 1000 * self.get(str(motor) + 'lv', 0) - def get_axis_switch(self, axis, side): axis = axis.lower() @@ -453,14 +405,17 @@ class State(object): # This must match the switch ID enum in avr/src/switch.h 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) - def get_switch_id(self, switch): # TODO Support other input switches in CAMotics gcode/machine/PortType.h switch = switch.lower() - if switch == 'probe': return 1 - if switch[1:] == '-min': return self.get_axis_switch(switch[0], 'min') - if switch[1:] == '-max': return self.get_axis_switch(switch[0], 'max') + if switch == 'probe': + return 1 + 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) diff --git a/src/py/bbctrl/Web.py b/src/py/bbctrl/Web.py index cbfbd7c..148fcdd 100644 --- a/src/py/bbctrl/Web.py +++ b/src/py/bbctrl/Web.py @@ -269,6 +269,14 @@ class HomeHandler(bbctrl.APIHandler): self.get_ctrl().mach.home(axis) +class DevmodeHandler(bbctrl.APIHandler): + def put_ok(self, command, *args): + if command == "/probe": + self.get_ctrl().mach.fake_probe_contact() + else: + raise HTTPError(400, 'Not implemented') + + class StartHandler(bbctrl.APIHandler): def put_ok(self): self.get_ctrl().mach.start() @@ -463,6 +471,7 @@ class Web(tornado.web.Application): (r'/api/file(/[^/]+)?', bbctrl.FileHandler), (r'/api/path/([^/]+)((/positions)|(/speeds))?', PathHandler), (r'/api/home(/[xyzabcXYZABC]((/set)|(/clear))?)?', HomeHandler), + (r'/api/devmode((/probe))?', DevmodeHandler), (r'/api/start', StartHandler), (r'/api/estop', EStopHandler), (r'/api/clear', ClearHandler), diff --git a/src/svelte-components/src/components/Devmode.svelte b/src/svelte-components/src/components/Devmode.svelte new file mode 100644 index 0000000..43adbd5 --- /dev/null +++ b/src/svelte-components/src/components/Devmode.svelte @@ -0,0 +1,23 @@ + + +
+ +
+ + diff --git a/src/svelte-components/src/dialogs/ProbeDialog.svelte b/src/svelte-components/src/dialogs/ProbeDialog.svelte index 36775fa..c73613c 100644 --- a/src/svelte-components/src/dialogs/ProbeDialog.svelte +++ b/src/svelte-components/src/dialogs/ProbeDialog.svelte @@ -5,12 +5,10 @@ import { Config } from "$lib/ConfigStore"; import { waitForChange } from "$lib/StoreHelpers"; import { ControllerMethods } from "$lib/RegisterControllerMethods"; - import { tick } from "svelte"; - type Stage = + type Step = | "None" | "TestingProbe" - | "TestingProbeComplete" | "GetToolDiameter" | "Probing" | "ProbingFailed" @@ -55,77 +53,48 @@ @@ -248,27 +260,27 @@ Probe {probeType} - {#if stage === "TestingProbe" || stage === "TestingProbeComplete"} + {#if step === "TestingProbe"}

Attach the probe magnet to the collet.

Touch the probe block to the bit.

- {#if stage === "TestingProbe"} -

Waiting for probe contact...

- {:else} + {#if $probeContacted}

Probe contact detected!

+ {:else} +

Waiting for probe contact...

{/if} - {:else if stage === "GetToolDiameter"} + {:else if step === "GetToolDiameter"} - {:else if stage === "Probing"} + {:else if step === "Probing"}

Probing in progress...

- {:else if stage === "ProbingFailed"} + {:else if step === "ProbingFailed"}

Could not find the probe block during probing!

Make sure the tip of the bit is about 1/4" (6mm) above the probe block, and try again.

- {:else if stage === "ProbingComplete"} + {:else if step === "ProbingComplete"}

Don't forget to put away the probe!

The machine will now move to the XY origin.

Watch your hands!

@@ -276,17 +288,19 @@
- + {#if showCancelButton} + + {/if} diff --git a/src/svelte-components/src/lib/RegisterControllerMethods.ts b/src/svelte-components/src/lib/RegisterControllerMethods.ts index 03917cd..758da85 100644 --- a/src/svelte-components/src/lib/RegisterControllerMethods.ts +++ b/src/svelte-components/src/lib/RegisterControllerMethods.ts @@ -1,4 +1,5 @@ type ControllerMethods = { + stop: () => void; send: (gcode: string) => void; goto_zero: (x: number, y: number, z: number, a: number) => void; } diff --git a/src/svelte-components/src/main.ts b/src/svelte-components/src/main.ts index c51d33d..4d55376 100644 --- a/src/svelte-components/src/main.ts +++ b/src/svelte-components/src/main.ts @@ -2,6 +2,7 @@ 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"; @@ -15,6 +16,9 @@ export function createComponent(component: string, target: HTMLElement, props: R case "DialogHost": return new DialogHost({ target, props }); + case "Devmode": + return new Devmode({target, props}); + default: throw new Error("Unknown component"); }