CMD('o', out, 1, "Output")
CMD('p', opt_pause, 1, "Set an optional pause")
CMD('P', pause, 0, "[optional]")
+CMD('U', unpause, 0, "Unpause")
CMD('j', jog, 0, "[axes]")
CMD('h', help, 0, "Print this help screen")
CMD('r', report, 0, "<0|1>[var] Enable or disable var reporting")
#pragma once
+#include "config.h"
#include "status.h"
#include <stdbool.h>
void command_init();
-bool command_is_busy();
bool command_is_active();
unsigned command_get_count();
void command_print_help();
void command_flush_queue();
void command_push(char code, void *data);
bool command_callback();
+void command_set_position(const float position[AXES]);
+void command_get_position(float position[AXES]);
+void command_reset_position();
bool command_exec();
}
-stat_t command_resume(char *cmd) {
- state_request_resume();
- return STAT_OK;
-}
-
-
-stat_t command_step(char *cmd) {
- state_request_step();
- return STAT_OK;
-}
-
-
-stat_t command_flush(char *cmd) {
- state_request_flush();
- return STAT_OK;
-}
-
-
stat_t command_dump(char *cmd) {
report_request_full();
return STAT_OK;
typedef struct {
char id[26];
bool hard_reset; // flag to perform a hard reset
- bool bootloader; // flag to enter the bootloader
} hw_t;
static hw_t hw = {{0}};
hw_hard_reset();
}
-
- if (hw.bootloader) {
- // TODO enable bootloader interrupt vectors and jump to BOOT_SECTION_START
- hw.bootloader = false;
- }
}
-/// Executes a software reset using CCPWrite
-void hw_request_bootloader() {hw.bootloader = true;}
-
-
uint8_t hw_disable_watchdog() {
uint8_t state = WDT.CTRL;
wdt_disable();
void hw_hard_reset();
void hw_reset_handler();
-void hw_request_bootloader();
-
uint8_t hw_disable_watchdog();
void hw_restore_watchdog(uint8_t state);
// Compute next accel
a->accel = scurve_next_accel(SEGMENT_TIME, V, Vt, a->accel, jerk);
- // TODO limit acceleration
+ // Limit acceleration
+ if (axis_get_accel_max(axis) < fabs(a->accel))
+ a->accel = (a->accel < 0 ? -1 : 1) * axis_get_accel_max(axis);
return V + a->accel * SEGMENT_TIME;
}
}
-static stat_t _hold() {
- if (state_get() != STATE_HOLDING) {
- exec_set_cb(0);
- return STAT_AGAIN;
- }
-
- return STAT_NOP;
-}
-
-
static stat_t _pause() {
float t = SEGMENT_TIME - l.current_t;
float v = exec_get_velocity();
exec_set_acceleration(0);
exec_set_jerk(0);
state_holding();
- exec_set_cb(_hold);
- return _hold();
+ exec_set_cb(0);
+
+ return STAT_NOP;
}
// Compute new velocity and acceleration
a = scurve_next_accel(t, v, 0, a, j);
- if (l.line.max_accel < a) a = l.line.max_accel;
+ if (l.line.max_accel < fabs(a)) {
+ a = (a < 0 ? -1 : 1) * l.line.max_accel;
+ j = 0;
+ } else if (0 < a) j = -j;
v += a * t;
// Compute distance that will be traveled
stat_t command_line(char *cmd) {
- static float next_start[AXES];
line_t line = {};
cmd++; // Skip command code
// Get start position
- copy_vector(line.start, next_start);
+ command_get_position(line.start);
// Get target velocity
if (!decode_float(&cmd, &line.target_vel)) return STAT_BAD_FLOAT;
if (*cmd) return STAT_INVALID_ARGUMENTS;
// Set next start position
- copy_vector(next_start, line.target);
+ command_set_position(line.target);
// Compute direction vector
for (int i = 0; i < AXES; i++)
// Fall through
case MOTOR_ALWAYS_POWERED:
- // TODO is ~5ms enough time to enable the motor?
+ // NOTE, we have ~5ms to enable the motor
drv8711_set_state(motor, DRV8711_ACTIVE);
break;
#include "spindle.h"
#include "report.h"
-#include <stdbool.h>
+#include <stdio.h>
static struct {
PGM_P state_get_hold_reason_pgmstr(hold_reason_t reason) {
switch (reason) {
- case HOLD_REASON_USER_PAUSE: return PSTR("USER");
- case HOLD_REASON_PROGRAM_PAUSE: return PSTR("PROGRAM");
- case HOLD_REASON_PROGRAM_END: return PSTR("END");
- case HOLD_REASON_PALLET_CHANGE: return PSTR("PALLET");
- case HOLD_REASON_TOOL_CHANGE: return PSTR("TOOL");
+ case HOLD_REASON_USER_PAUSE: return PSTR("User paused");
+ case HOLD_REASON_PROGRAM_PAUSE: return PSTR("Program paused");
+ case HOLD_REASON_PROGRAM_END: return PSTR("Program end");
+ case HOLD_REASON_PALLET_CHANGE: return PSTR("Pallet change");
+ case HOLD_REASON_TOOL_CHANGE: return PSTR("Tool change");
+ case HOLD_REASON_STEPPING: return PSTR("Stepping");
}
return PSTR("INVALID");
bool state_is_quiescent() {
return (state_get() == STATE_READY || state_get() == STATE_HOLDING) &&
- !st_is_busy() && !command_is_busy();
+ !st_is_busy();
}
-static void _set_plan_steps(bool plan_steps) {} // TODO
-
-
-void state_holding() {
- _set_state(STATE_HOLDING);
- _set_plan_steps(false);
-}
+void state_holding() {_set_state(STATE_HOLDING);}
void state_optional_pause() {
void state_idle() {if (state_get() == STATE_RUNNING) _set_state(STATE_READY);}
void state_estop() {_set_state(STATE_ESTOPPED);}
-void state_request_pause() {s.pause_requested = true;}
-void state_request_start() {s.start_requested = true;}
-void state_request_flush() {s.flush_requested = true;}
-void state_request_resume() {if (s.flush_requested) s.resume_requested = true;}
-void state_request_optional_pause() {s.optional_pause_requested = true;}
-
-
-void state_request_step() {
- _set_plan_steps(true);
- s.start_requested = true;
-}
/*** Pauses, queue flushes and starts are all related. Request functions
if (state_get() == STATE_RUNNING) _set_state(STATE_STOPPING);
}
- // Only flush queue when idle or holding.
+ // Only flush queue when idle or holding
if (s.flush_requested && state_is_quiescent()) {
command_flush_queue();
// Stop spindle
+ // TODO Spindle should not be stopped when pausing
spindle_stop();
// Resume
// Command callbacks
stat_t command_pause(char *cmd) {
- if (cmd[1] == '1') state_request_optional_pause();
- else state_request_pause();
+ if (cmd[1] == '1') s.optional_pause_requested = true;
+ else s.pause_requested = true;
+ return STAT_OK;
+}
+
+
+stat_t command_unpause(char *cmd) {
+ s.start_requested = true;
+ return STAT_OK;
+}
+
+
+stat_t command_resume(char *cmd) {
+ if (s.flush_requested) s.resume_requested = true;
+ return STAT_OK;
+}
+
+
+stat_t command_step(char *cmd) {
+ // TODO
+ return STAT_OK;
+}
+
+
+stat_t command_flush(char *cmd) {
+ s.flush_requested = true;
return STAT_OK;
}
HOLD_REASON_PROGRAM_END,
HOLD_REASON_PALLET_CHANGE,
HOLD_REASON_TOOL_CHANGE,
+ HOLD_REASON_STEPPING,
} hold_reason_t;
void state_idle();
void state_estop();
-void state_request_pause();
-void state_request_start();
-void state_request_flush();
-void state_request_resume();
-void state_request_optional_pause();
-void state_request_step();
-
void state_callback();
if isinstance(value, str): value = '"' + value + '"'
if isinstance(value, bool): value = int(value)
- self.queue_command('${}={}'.format(var, value))
+ self.set('', var, value)
self.queue_command('$$') # Refresh all vars, must come after above
if self.ctrl.ioloop.READ & events: self.serial_read()
if self.ctrl.ioloop.WRITE & events: self.serial_write()
except Exception as e:
- log.error('Serial handler error:', e)
+ log.error('Serial handler error: %s', e)
def serial_write(self):
elif self.stream is not None:
cmd = self.stream.next()
- if cmd is None:
- self.set_write(False)
- self.stream = None
-
+ if cmd is None: self.set_write(False)
else: self.load_next_command(cmd)
# Else stop writing
update.update(msg)
log.debug(line)
+ # Don't overwrite duplicate `msg`
if 'msg' in msg: break
if update:
self.vars.update(update)
+ if self.stream is not None:
+ self.stream.update(update)
+ if not self.stream.is_running():
+ self.stream = None
+
try:
self._update_lcd(update)
def home(self, axis, position = None):
if self.stream is not None: raise Exception('Busy, cannot home')
+ raise Exception('NYI') # TODO
if position is not None:
self.queue_command('G28.3 %c%f' % (axis, position))
def start(self, path):
if self.stream is not None: raise Exception('Busy, cannot start file')
-
- if path:
- self._start_sending_gcode(path)
- self._i2c_command(Cmd.RUN)
+ if path: self._start_sending_gcode(path)
def step(self, path):
self._i2c_command(Cmd.FLUSH)
self._stop_sending_gcode()
# Resume processing once current queue of GCode commands has flushed
- self.queue_command('c')
+ self.queue_command(Cmd.RESUME)
def pause(self): self._i2c_command(Cmd.PAUSE, byte = 0)
- def unpause(self): self._i2c_command(Cmd.RUN)
+
+
+ def unpause(self):
+ if self.vars.get('x', '') != 'HOLDING' or self.stream is None: return
+
+ self._i2c_command(Cmd.FLUSH)
+ self.queue_command(Cmd.RESUME)
+ self.stream.restart()
+ self.set_write(True)
+ self._i2c_command(Cmd.UNPAUSE)
+
+
def optional_pause(self): self._i2c_command(Cmd.PAUSE, byte = 1)
def set_position(self, axis, position):
if self.stream is not None: raise Exception('Busy, cannot set position')
if self._is_axis_homed('%c' % axis):
+ raise Exception('NYI') # TODO
self.queue_command('G92 %c%f' % (axis, position))
else: self.queue_command('$%cp=%f' % (axis, position))
log = logging.getLogger('Cmd')
# TODO, sync this up with AVR code
-REPORT = 'r'
-PAUSE = 'P'
-ESTOP = 'E'
-CLEAR = 'C'
-FLUSH = 'F'
-STEP = 'S'
-RUN = 'p'
+REPORT = 'r'
+PAUSE = 'P'
+UNPAUSE = 'U'
+ESTOP = 'E'
+CLEAR = 'C'
+FLUSH = 'F'
+STEP = 'S'
+RESUME = 'c'
def encode_float(x):
def __init__(self, ctrl, path):
self.ctrl = ctrl
self.path = path
+ self.lastID = -1
+ self.done = False
vars = ctrl.avr.vars
self.reset()
+ def is_running(self): return self.planner.is_running()
+
+
+ def update(self, update):
+ if 'id' in update:
+ id = update['id']
+ if id: self.planner.release(id - 1)
+
+
+ def restart(self):
+ vars = self.ctrl.avr.vars
+ id = vars['id']
+
+ position = {}
+ for axis in 'xyzabc':
+ if (axis + 'p') in vars:
+ position[axis] = vars[axis + 'p']
+
+ self.planner.restart(id, position)
+ self.done = False
+
+
def reset(self):
self.planner = gplan.Planner(self.config)
self.planner.load('upload' + self.path)
+ self.done = False
def encode(self, block):
def next(self):
if self.planner.has_more():
- return self.encode(self.planner.next())
+ cmd = self.planner.next()
+ self.lastID = cmd['id']
+ return self.encode(cmd)
+
+ if not self.done:
+ self.done = True
+
+ # Cause last cmd to flush when complete
+ if 0 <= self.lastID:
+ return '#id=%d' % (self.lastID + 1)