- Fixed EStop reboot loop.
- Removed AVR unexpected reboot error.
Buildbotics CNC Controller Firmware Change Log
==============================================
+## v0.3.15
+ - Suppress warning missing config.json warning after config reset.
+ - Fixed EStop reboot loop.
+ - Removed AVR unexpected reboot error.
+
## v0.3.14
- Fixed: Config fails silently after web disconnect #112
- Always reload the page after a disconnect.
{
"name": "bbctrl",
- "version": "0.3.14",
+ "version": "0.3.15",
"homepage": "http://buildbotics.com/",
"repository": "https://github.com/buildbotics/bbctrl-firmware",
"license": "GPL-3.0+",
} estop_t;
-static estop_t estop = {0};
+static estop_t estop = {false};
static uint16_t estop_reason_eeprom EEMEM;
void estop_clear() {
+ // It is important that we don't clear the estop if it's not set because
+ // it can cause a reboot loop.
+ if (!estop.triggered) return;
+
// Check if estop switch is set
if (switch_is_active(SW_ESTOP)) {
if (_get_reason() != STAT_ESTOP_SWITCH) _set_reason(STAT_ESTOP_SWITCH);
void hw_request_hard_reset() {hw.hard_reset = true;}
-void hw_hard_reset() {
+static void _hard_reset() {
usart_flush();
cli();
CCP = CCP_IOREG_gc;
/// Controller's rest handler
void hw_reset_handler() {
- if (hw.hard_reset) {
- while (!usart_tx_empty() || !eeprom_is_ready())
- continue;
+ if (!hw.hard_reset) return;
- hw_hard_reset();
- }
+ // Flush serial port and wait for EEPROM writes to finish
+ while (!usart_tx_empty() || !eeprom_is_ready())
+ continue;
+
+ _hard_reset();
}
void hardware_init();
void hw_request_hard_reset();
-void hw_hard_reset();
void hw_reset_handler();
uint8_t hw_disable_watchdog();
cmd++; // Skip command
// Analog or digital
- if (cmd[0] == 'd') input_cmd.digital = true;
- else if (cmd[0] == 'a') input_cmd.digital = false;
+ if (*cmd == 'd') input_cmd.digital = true;
+ else if (*cmd == 'a') input_cmd.digital = false;
else return STAT_INVALID_ARGUMENTS;
cmd++;
// Port index
- if (!isdigit(cmd[1])) return STAT_INVALID_ARGUMENTS;
- input_cmd.port = cmd[1] - '0';
+ if (!isdigit(*cmd)) return STAT_INVALID_ARGUMENTS;
+ input_cmd.port = *cmd - '0';
cmd++;
// Mode
- if (!isdigit(cmd[2])) return STAT_INVALID_ARGUMENTS;
- input_cmd.mode = cmd[2] - '0';
+ if (!isdigit(*cmd)) return STAT_INVALID_ARGUMENTS;
+ input_cmd.mode = *cmd - '0';
if (INPUT_LOW < input_cmd.mode) return STAT_INVALID_ARGUMENTS;
cmd++;
switch_callback_t cb;
bool state;
int8_t debounce;
+ bool initialized;
} switch_t;
// Debounce switch
bool state = IN_PIN(s->pin);
if (state == s->state) s->debounce = 0;
- else if (++s->debounce == SWITCH_DEBOUNCE) {
+ else if ((state && ++s->debounce == SWITCH_DEBOUNCE) ||
+ (!state && --s->debounce == -SWITCH_DEBOUNCE)) {
s->state = state;
s->debounce = 0;
+ s->initialized = true;
if (s->cb) s->cb(i, switch_is_active(i));
}
}
bool switch_is_active(switch_id_t sw) {
if (sw < 0 || num_switches <= sw) return false;
+ if (!switches[sw].initialized) return false;
+
// NOTE, switch inputs are active lo
switch (switches[sw].type) {
case SW_DISABLED: break; // A disabled switch cannot be active
def input(port, mode, timeout):
- type = 'd'
- index = 0
- m = 0
-
- if port == 'digital-in-0': digital, index = 'd', 0
- if port == 'digital-in-1': digital, index = 'd', 1
- if port == 'digital-in-2': digital, index = 'd', 2
- if port == 'digital-in-3': digital, index = 'd', 3
- if port == 'analog-in-0': digital, index = 'a', 0
- if port == 'analog-in-1': digital, index = 'a', 1
- if port == 'analog-in-2': digital, index = 'a', 2
- if port == 'analog-in-3': digital, index = 'a', 3
-
+ type, index, m = 'd', 0, 0
+
+ # Analog/digital & port index
+ if port == 'digital-in-0': type, index = 'd', 0
+ if port == 'digital-in-1': type, index = 'd', 1
+ if port == 'digital-in-2': type, index = 'd', 2
+ if port == 'digital-in-3': type, index = 'd', 3
+ if port == 'analog-in-0': type, index = 'a', 0
+ if port == 'analog-in-1': type, index = 'a', 1
+ if port == 'analog-in-2': type, index = 'a', 2
+ if port == 'analog-in-3': type, index = 'a', 3
+
+ # Mode
if mode == 'immediate': m = 0
if mode == 'rise': m = 1
if mode == 'fall': m = 2
self.queue = deque()
self.in_buf = ''
self.command = None
- self.reboot_expected = True
try:
self.sp = serial.Serial(ctrl.args.serial, ctrl.args.baud,
if 'variables' in msg:
self._update_vars(msg)
- self.reboot_expected = False
elif 'msg' in msg: self._log_msg(msg)
elif 'firmware' in msg:
- if self.reboot_expected: log.info('AVR firmware rebooted')
- else: log.error('Unexpected AVR firmware reboot')
+ log.info('AVR firmware rebooted')
self.connect()
else: self.ctrl.state.update(msg)
def clear(self):
if self.ctrl.state.get('xx', '') == 'ESTOPPED':
self.i2c_command(Cmd.CLEAR)
- self.reboot_expected = True
def pause(self, optional = False):
self.i2c_command(Cmd.PAUSE, byte = data)
- def reboot(self):
- self.queue_command(Cmd.REBOOT)
- self.reboot_expected = True
+ def reboot(self): self.queue_command(Cmd.REBOOT)
def connect(self):
import logging
import pkg_resources
import subprocess
+import copy
import bbctrl
def load(self):
try:
- config = self.load_path('config.json')
+ if os.path.exists('config.json'):
+ config = self.load_path('config.json')
+ else: config = copy.deepcopy(default_config)
try:
self.upgrade(config)
def __encode(self, block):
+ log.info('Cmd:' + json.dumps(block))
+
type = block['type']
if type == 'line':
self.setq.clear()
- def stop(self):
- self.planner.stop()
- self.lastID = 0
- self.setq.clear()
-
-
- def restart(self):
- state = self.ctrl.state
- id = state.get('id')
-
- position = {}
- for axis in 'xyzabc':
- if state.has(axis + 'p'):
- position[axis] = state.get(axis + 'p')
-
- log.info('Planner restart: %d %s' % (id, json.dumps(position)))
- self.planner.restart(id, position)
- if self.planner.is_synchronizing(): self.planner.synchronize(1)
- # TODO if these calls fail we get stuck in a loop
-
-
def mdi(self, cmd):
log.info('MDI:' + cmd)
self.planner.load_string(cmd, self._get_config(True))
self.planner.load(path, self._get_config(False))
+ def stop(self):
+ try:
+ self.planner.stop()
+ self.lastID = 0
+ self.setq.clear()
+
+ except Exception as e:
+ log.exception(e)
+ self.reset()
+
+
+ def restart(self):
+ try:
+ state = self.ctrl.state
+ id = state.get('id')
+
+ position = {}
+ for axis in 'xyzabc':
+ if state.has(axis + 'p'):
+ position[axis] = state.get(axis + 'p')
+
+ log.info('Planner restart: %d %s' % (id, json.dumps(position)))
+ self.planner.restart(id, position)
+ if self.planner.is_synchronizing(): self.planner.synchronize(1)
+
+ except Exception as e:
+ log.exception(e)
+ self.reset()
+
+
def has_move(self): return self.planner.has_more()
if cmd is not None: return cmd
except Exception as e:
- self.reset()
log.exception(e)
+ self.reset()