From 3bb1994eae8a6ed43a6d89f4c34b0b9bf0530b66 Mon Sep 17 00:00:00 2001 From: Joseph Coffland Date: Sun, 31 Dec 2017 11:43:56 -0800 Subject: [PATCH] New planner sending line moves --- src/avr/.gitignore | 2 + src/avr/Makefile | 15 +- src/avr/src/action.c | 136 ---- src/avr/src/axis.h | 1 - src/avr/src/base64.c | 151 ++++ src/avr/src/{action.h => base64.h} | 34 +- src/avr/src/command.def | 27 +- src/avr/src/command.h | 27 +- src/avr/src/command.json.in | 12 + src/avr/src/commands.c | 116 +++ src/avr/src/config.h | 7 +- src/avr/src/cpp_magic.h | 1 - src/avr/src/estop.c | 14 + src/avr/src/exec.c | 261 +------ src/avr/src/exec.h | 19 +- src/avr/src/hardware.c | 1 - src/avr/src/hardware.h | 2 - src/avr/src/huanyang.c | 48 +- src/avr/src/huanyang.h | 10 +- src/avr/src/i2c.c | 9 +- src/avr/src/i2c.h | 18 +- src/avr/src/jog.c | 45 +- src/avr/src/lcd.c | 1 - src/avr/src/lcd.h | 1 - src/avr/src/line.c | 240 ++++++ src/avr/src/main.c | 8 +- src/avr/src/messages.def | 39 +- src/avr/src/messages.json.in | 7 + src/avr/src/motor.c | 7 +- src/avr/src/queue.c | 103 --- src/avr/src/ringbuf.def | 98 ++- src/avr/src/rtc.c | 3 +- src/avr/src/rtc.h | 1 - src/avr/src/spindle.c | 7 +- src/avr/src/spindle.h | 1 - src/avr/src/state.c | 79 +- src/avr/src/state.h | 15 - src/avr/src/status.c | 23 +- src/avr/src/status.h | 2 +- src/avr/src/stepper.c | 9 +- src/avr/src/stepper.h | 2 - src/avr/src/switch.c | 1 - src/avr/src/switch.h | 1 - src/avr/src/type.c | 188 +++++ src/avr/src/{queue.h => type.def} | 43 +- src/avr/src/type.h | 67 ++ src/avr/src/usart.c | 14 +- src/avr/src/usart.h | 1 + src/avr/src/util.c | 26 +- src/avr/src/util.h | 5 +- src/avr/src/vars.c | 396 +++++----- src/avr/src/vars.def | 161 ++-- src/avr/src/vars.h | 3 +- src/jade/templates/control-view.jade | 7 +- src/js/control-view.js | 6 +- src/pwr/Makefile | 2 + src/py/bbctrl/AVR.py | 66 +- src/py/bbctrl/Cmd.py | 56 ++ src/py/bbctrl/Jog.py | 5 +- src/py/bbctrl/Planner.py | 76 ++ src/py/bbctrl/__init__.py | 2 + src/resources/js/smoothie.js | 1025 ++++++++++++++++++++++++++ 62 files changed, 2577 insertions(+), 1176 deletions(-) delete mode 100644 src/avr/src/action.c create mode 100644 src/avr/src/base64.c rename src/avr/src/{action.h => base64.h} (70%) create mode 100644 src/avr/src/command.json.in create mode 100644 src/avr/src/commands.c create mode 100644 src/avr/src/line.c create mode 100644 src/avr/src/messages.json.in delete mode 100644 src/avr/src/queue.c create mode 100644 src/avr/src/type.c rename src/avr/src/{queue.h => type.def} (67%) create mode 100644 src/avr/src/type.h create mode 100644 src/py/bbctrl/Cmd.py create mode 100644 src/py/bbctrl/Planner.py create mode 100644 src/resources/js/smoothie.js diff --git a/src/avr/.gitignore b/src/avr/.gitignore index bc78328..abe2d5d 100755 --- a/src/avr/.gitignore +++ b/src/avr/.gitignore @@ -10,3 +10,5 @@ build /*.elf /*.lss /*.map + +*.o diff --git a/src/avr/Makefile b/src/avr/Makefile index 892c91e..27ca410 100644 --- a/src/avr/Makefile +++ b/src/avr/Makefile @@ -2,7 +2,7 @@ PROJECT = bbctrl-avr-firmware MCU = atxmega192a3u CLOCK = 32000000 -VERSION = 0.4.0 +VERSION = 0.5.0 TARGET = $(PROJECT).elf @@ -30,8 +30,10 @@ EEFLAGS += --set-section-flags=.eeprom="alloc,load" EEFLAGS += --change-section-lma .eeprom=0 --no-change-warnings # Programming flags +ifndef (PROGRAMMER) PROGRAMMER = avrispmkII #PROGRAMMER = jtag3pdi +endif PDEV = usb AVRDUDE_OPTS = -c $(PROGRAMMER) -p $(MCU) -P $(PDEV) @@ -44,16 +46,17 @@ FUSE5=0xeb # SRC SRC = $(wildcard src/*.c) $(wildcard src/plan/*.c) OBJ = $(patsubst src/%.c,build/%.o,$(SRC)) +JSON = vars command messages +JSON := $(patsubst %,build/%.json,$(JSON)) # Build -all: $(PROJECT).hex build/vars.json size +all: $(PROJECT).hex $(JSON) size -build/vars.json: src/vars.def +# JSON +build/%.json: src/%.json.in src/%.def + cpp -Isrc $< | sed "/^#.*$$/d;s/'\(.\)'/\"\1\"/g" > $@ # Compile -build/%.json: src/%.json.in - cpp -Isrc $< | sed '/^#.*$$/d' > $@ - build/%.o: src/%.c @mkdir -p $(shell dirname $@) $(CC) $(INCLUDES) $(CFLAGS) -c -o $@ $< diff --git a/src/avr/src/action.c b/src/avr/src/action.c deleted file mode 100644 index 4cc1222..0000000 --- a/src/avr/src/action.c +++ /dev/null @@ -1,136 +0,0 @@ -/******************************************************************************\ - - This file is part of the Buildbotics firmware. - - Copyright (c) 2015 - 2017 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" - -\******************************************************************************/ - -#include "action.h" - -#include "queue.h" - -#include -#include -#include - - -static stat_t _queue(const char **block) { - queue_push(**block++); - return STAT_OK; -} - - -static stat_t _queue_int(const char **block) { - char *end = 0; - - int32_t value = strtol(*block + 1, &end, 0); - if (end == *block + 1) return STAT_INVALID_ARGUMENTS; - - queue_push_int(**block, value); - *block = end; - - return STAT_OK; -} - - -static stat_t _queue_bool(const char **block) { - if (4 < strlen(*block) && tolower((*block)[1]) == 't' && - tolower((*block)[2]) == 'r' && tolower((*block)[3]) == 'u' && - tolower((*block)[4]) == 'e' && !isalpha((*block)[5])) { - queue_push_bool(**block, true); - *block += 5; - return STAT_OK; - } - - char *end = 0; - int32_t value = strtol(*block + 1, &end, 0); - - queue_push_bool(**block, value); - *block = end; - - return STAT_OK; -} - - -static stat_t _queue_float(const char **block) { - char *end = 0; - - float value = strtod(*block + 1, &end); - if (end == *block + 1) return STAT_INVALID_ARGUMENTS; - - queue_push_float(**block, value); - *block = end; - - return STAT_OK; -} - - -static stat_t _queue_pair(const char **block) { - char *end = 0; - - int32_t left = strtol(*block + 1, &end, 0); - if (end == *block + 1) return STAT_INVALID_ARGUMENTS; - *block = end; - - int32_t right = strtol(*block, &end, 0); - if (*block == end) return STAT_INVALID_ARGUMENTS; - - queue_push_pair(**block, left, right); - *block = end; - - return STAT_OK; -} - - -stat_t action_parse(const char *block) { - stat_t status = STAT_OK; - - while (*block && status == STAT_OK) { - if (isspace(*block)) {block++; continue;} - - status = STAT_INVALID_COMMAND; - - switch (*block) { - case ACTION_SCURVE: status = _queue_int(&block); break; - case ACTION_DATA: status = _queue_float(&block); break; - case ACTION_VELOCITY: status = _queue_float(&block); break; - case ACTION_X: status = _queue_float(&block); break; - case ACTION_Y: status = _queue_float(&block); break; - case ACTION_Z: status = _queue_float(&block); break; - case ACTION_A: status = _queue_float(&block); break; - case ACTION_B: status = _queue_float(&block); break; - case ACTION_C: status = _queue_float(&block); break; - case ACTION_SEEK: status = _queue_pair(&block); break; - case ACTION_OUTPUT: status = _queue_pair(&block); break; - case ACTION_DWELL: status = _queue_float(&block); break; - case ACTION_PAUSE: status = _queue_bool(&block); break; - case ACTION_TOOL: status = _queue_int(&block); break; - case ACTION_SPEED: status = _queue_float(&block); break; - case ACTION_JOG: status = _queue(&block); break; - case ACTION_LINE_NUM: status = _queue_int(&block); break; - case ACTION_SET_HOME: status = _queue_pair(&block); break; - } - } - - return status; -} diff --git a/src/avr/src/axis.h b/src/avr/src/axis.h index 79305b0..810ea2d 100644 --- a/src/avr/src/axis.h +++ b/src/avr/src/axis.h @@ -3,7 +3,6 @@ This file is part of the Buildbotics firmware. Copyright (c) 2015 - 2017 Buildbotics LLC - Copyright (c) 2010 - 2015 Alden S. Hart, Jr. All rights reserved. This file ("the software") is free software: you can redistribute it diff --git a/src/avr/src/base64.c b/src/avr/src/base64.c new file mode 100644 index 0000000..545e670 --- /dev/null +++ b/src/avr/src/base64.c @@ -0,0 +1,151 @@ +/******************************************************************************\ + + This file is part of the Buildbotics firmware. + + Copyright (c) 2015 - 2017 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" + +\******************************************************************************/ + +#include "base64.h" + +#include "util.h" + +#include + + +static const char *_b64_encode = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + +static const int8_t _b64_decode[] = { // 43-122 + 62, -1, -1, -1, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 +}; + + +static int8_t _decode(char c) { + return (c < 43 || 122 < c) ? -1 : _b64_decode[c - 43]; +} + + +static char _encode(uint8_t b) {return _b64_encode[b & 63];} + + +static void _skip_space(const char **s, const char *end) { + while (*s < end && isspace(**s)) (*s)++; +} + + +static char _next(const char **s, const char *end) { + char c = *(*s)++; + _skip_space(s, end); + return c; +} + + +unsigned b64_encoded_length(unsigned len, bool pad) { + unsigned elen = len / 3 * 4; + + switch (len % 3) { + case 1: elen += pad ? 4 : 2; break; + case 2: elen += pad ? 4 : 3; break; + } + + return elen; +} + + +void b64_encode(const uint8_t *in, unsigned len, char *out, bool pad) { + const uint8_t *end = in + len; + int padding = 0; + uint8_t a, b, c; + + while (in < end) { + a = *in++; + + if (in < end) { + b = *in++; + + if (in < end) c = *in++; + else {c = 0; padding = 1;} + + } else {c = b = 0; padding = 2;} + + *out++ = _encode(63 & (a >> 2)); + *out++ = _encode(63 & (a << 4 | b >> 4)); + + if (pad && padding == 2) *out++ = '='; + else *out++ = _encode(63 & (b << 2 | c >> 6)); + + if (pad && padding) *out++ = '='; + else *out++ = _encode(63 & c); + } +} + + +bool b64_decode(const char *in, unsigned len, uint8_t *out) { + const char *end = in + len; + + _skip_space(&in, end); + + while (in < end) { + int8_t w = _decode(_next(&in, end)); + int8_t x = in < end ? _decode(_next(&in, end)) : -2; + int8_t y = in < end ? _decode(_next(&in, end)) : -2; + int8_t z = in < end ? _decode(_next(&in, end)) : -2; + + if (w == -2 || x == -2 || w == -1 || x == -1 || y == -1 || z == -1) + return false; + + *out++ = (uint8_t)(w << 2 | x >> 4); + if (y != -2) { + *out++ = (uint8_t)(x << 4 | y >> 2); + if (z != -2) *out++ = (uint8_t)(y << 6 | z); + } + } + + return true; +} + + +bool b64_decode_float(const char *s, float *f) { + union { + float f; + uint8_t b[4]; + } u; + + if (!b64_decode(s, 6, u.b)) return false; + +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +#define XORSWAP(a, b) a ^= (b ^ (b ^= a) + XORSWAP(u.b[0], u.b[3]); + XORSWAP(u.b[1], u.b[2]); +#endif + + *f = u.f; + + return true; +} diff --git a/src/avr/src/action.h b/src/avr/src/base64.h similarity index 70% rename from src/avr/src/action.h rename to src/avr/src/base64.h index d8678fa..feaaa7b 100644 --- a/src/avr/src/action.h +++ b/src/avr/src/base64.h @@ -27,33 +27,11 @@ #pragma once -#include "status.h" +#include +#include -typedef enum { - ACTION_SCURVE = 's', - ACTION_DATA = 'd', - ACTION_VELOCITY = 'v', - - ACTION_X = 'X', - ACTION_Y = 'Y', - ACTION_Z = 'Z', - ACTION_A = 'A', - ACTION_B = 'B', - ACTION_C = 'C', - - ACTION_SEEK = 'K', - ACTION_OUTPUT = 'O', - - ACTION_DWELL = 'D', - ACTION_PAUSE = 'P', - ACTION_TOOL = 'T', - ACTION_SPEED = 'S', - ACTION_JOG = 'J', - ACTION_LINE_NUM = 'N', - - ACTION_SET_HOME = 'H', -} action_t; - - -stat_t action_parse(const char *block); +unsigned b64_encoded_length(unsigned len, bool pad); +void b64_encode(const uint8_t *in, unsigned len, char *out, bool pad); +bool b64_decode(const char *in, unsigned len, uint8_t *out); +bool b64_decode_float(const char *s, float *f); diff --git a/src/avr/src/command.def b/src/avr/src/command.def index 130fa10..b4ecdb9 100644 --- a/src/avr/src/command.def +++ b/src/avr/src/command.def @@ -25,11 +25,22 @@ \******************************************************************************/ -// Name Min, Max args, Help -CMD(help, 0, 1, "Print this help screen") -CMD(report, 1, 2, "[var] . Enable or disable var reporting") -CMD(reboot, 0, 0, "Reboot the controller") -CMD(jog, 1, 4, "Jog") -CMD(mreset, 0, 1, "Reset motor") -CMD(messages, 0, 0, "Dump all possible status messages") -CMD(resume, 0, 0, "Resume processing after a flush") + +CMD('$', var, 0, "Set or get variable") +CMD('#', sync_var, 1, "Set variable synchronous") +CMD('s', seek, 1, "Seek") +CMD('l', line, 1, "[targetVel][maxJerk][axes][times]") +CMD('d', dwell, 1, "[seconds]") +CMD('o', out, 1, "Output") +CMD('p', opt_pause, 1, "Set an optional pause") +CMD('P', pause, 0, "[optional]") +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") +CMD('R', reboot, 0, "Reboot the controller") +CMD('c', resume, 0, "Continue processing after a flush") +CMD('E', estop, 0, "Emergency stop") +CMD('C', clear, 0, "Clear estop") +CMD('S', step, 0, "Advance one step") +CMD('F', flush, 0, "Flush command queue") +CMD('D', dump, 0, "Report all variables") diff --git a/src/avr/src/command.h b/src/avr/src/command.h index 08dd468..7d7c098 100644 --- a/src/avr/src/command.h +++ b/src/avr/src/command.h @@ -27,26 +27,25 @@ #pragma once +#include "status.h" -#include #include -#define MAX_ARGS 16 - -typedef uint8_t (*command_cb_t)(int argc, char *argv[]); - -typedef struct { - const char *name; - command_cb_t cb; - uint8_t min_args; - uint8_t max_args; - const char *help; +// Commands +typedef enum { +#define CMD(CODE, NAME, ...) COMMAND_##NAME = CODE, +#include "command.def" +#undef CMD } command_t; void command_init(); -int command_find(const char *name); -int command_exec(int argc, char *argv[]); -void command_callback(); +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); +void command_callback(); +bool command_exec(); diff --git a/src/avr/src/command.json.in b/src/avr/src/command.json.in new file mode 100644 index 0000000..dbb2263 --- /dev/null +++ b/src/avr/src/command.json.in @@ -0,0 +1,12 @@ +#include "cpp_magic.h" +{ +#define CMD(CODE, NAME, SYNC, HELP) \ + #NAME: { \ + "code": CODE, \ + "sync": IF_ELSE(SYNC)(true, false), \ + "help": HELP \ + }, +#include "command.def" +#undef CMD + "_": {} +} diff --git a/src/avr/src/commands.c b/src/avr/src/commands.c new file mode 100644 index 0000000..9ff56d9 --- /dev/null +++ b/src/avr/src/commands.c @@ -0,0 +1,116 @@ +/******************************************************************************\ + + This file is part of the Buildbotics firmware. + + Copyright (c) 2015 - 2017 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" + +\******************************************************************************/ + +#include "config.h" +#include "rtc.h" +#include "stepper.h" +#include "command.h" +#include "vars.h" +#include "base64.h" +#include "hardware.h" +#include "report.h" +#include "state.h" + +#include +#include + + +// TODO +stat_t command_seek(char *cmd) {return STAT_OK;} +unsigned command_seek_size() {return 0;} +void command_seek_exec(void *data) {} + + +stat_t command_dwell(char *cmd) { + float seconds; + if (!b64_decode_float(cmd + 1, &seconds)) return STAT_BAD_FLOAT; + command_push(*cmd, &seconds); + return STAT_OK; +} + + +unsigned command_dwell_size() {return sizeof(float);} +void command_dwell_exec(float *seconds) {st_prep_dwell(*seconds);} + + +// TODO +stat_t command_out(char *cmd) {return STAT_OK;} +unsigned command_out_size() {return 0;} +void command_out_exec(void *data) {} + + +stat_t command_pause(char *cmd) { + if (cmd[1] == '1') state_request_optional_pause(); + else state_request_hold(); + return STAT_OK; +} + + +stat_t command_help(char *cmd) { + puts_P(PSTR("\nLine editing:\n" + " ENTER Submit current command line.\n" + " BS Backspace, delete last character.\n" + " CTRL-X Cancel current line entry.")); + + puts_P(PSTR("\nCommands:")); + command_print_help(); + + puts_P(PSTR("\nVariables:")); + vars_print_help(); + + return STAT_OK; +} + + +stat_t command_reboot(char *cmd) { + hw_request_hard_reset(); + return STAT_OK; +} + + +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; +} diff --git a/src/avr/src/config.h b/src/avr/src/config.h index 02069ca..8a490fe 100644 --- a/src/avr/src/config.h +++ b/src/avr/src/config.h @@ -186,10 +186,11 @@ enum { #define SERIAL_PORT USARTC0 #define SERIAL_DRE_vect USARTC0_DRE_vect #define SERIAL_RXC_vect USARTC0_RXC_vect +#define SERIAL_CTS_THRESH 4 // Input -#define INPUT_BUFFER_LEN 255 // text buffer size (255 max) +#define INPUT_BUFFER_LEN 128 // text buffer size (255 max) // I2C @@ -210,6 +211,6 @@ enum { #define CURRENT_SENSE_REF 2.75 // volts #define MAX_CURRENT 10 // amps #define JERK_MULTIPLIER 1000000.0 -#define QUEUE_SIZE 256 -#define EXEC_FILL_TARGET 64 +#define SYNC_QUEUE_SIZE 4096 +#define EXEC_FILL_TARGET 8 #define EXEC_DELAY 250 // ms diff --git a/src/avr/src/cpp_magic.h b/src/avr/src/cpp_magic.h index c7abc59..f5f2918 100644 --- a/src/avr/src/cpp_magic.h +++ b/src/avr/src/cpp_magic.h @@ -507,4 +507,3 @@ (DEFER2(_EMAP2_INNER)()(ARG1, ARG2, ##__VA_ARGS__)) #define _EMAP2_INNER() EMAP2_INNER - diff --git a/src/avr/src/estop.c b/src/avr/src/estop.c index 50cb37a..a73dd1a 100644 --- a/src/avr/src/estop.c +++ b/src/avr/src/estop.c @@ -122,6 +122,7 @@ void estop_clear() { } +// Var callbacks bool get_estop() { return estop_triggered(); } @@ -137,3 +138,16 @@ void set_estop(bool value) { PGM_P get_estop_reason() { return status_to_pgmstr(_get_reason()); } + + +// Command callbacks +stat_t command_estop(char *cmd) { + estop_trigger(STAT_ESTOP_USER); + return STAT_OK; +} + + +stat_t command_clear(char *cmd) { + estop_clear(); + return STAT_OK; +} diff --git a/src/avr/src/exec.c b/src/avr/src/exec.c index 99c1d6c..59798e8 100644 --- a/src/avr/src/exec.c +++ b/src/avr/src/exec.c @@ -26,40 +26,20 @@ \******************************************************************************/ #include "exec.h" -#include "queue.h" #include "jog.h" #include "stepper.h" #include "axis.h" -#include "coolant.h" #include "spindle.h" -#include "rtc.h" #include "util.h" +#include "command.h" #include "config.h" #include #include -typedef struct { - bool new; - - float position[AXES]; - float target[AXES]; - float unit[AXES]; - - uint16_t steps; - uint16_t step; - float delta; - - float dist; - float vel; - float jerk; -} segment_t; - - static struct { - bool busy; - bool new; + exec_cb_t cb; float position[AXES]; float velocity; @@ -72,17 +52,11 @@ static struct { float feed_override; float spindle_override; - int scurve; - float time; float leftover_time; bool seek_error; bool seek_open; int seek_switch; - - uint32_t last_empty; - - segment_t seg; } ex; @@ -95,35 +69,26 @@ void exec_init() { // TODO implement feedhold // TODO implement move stepping // TODO implement overrides + // TODO implement optional pause } -// Var callbacks -int32_t get_line() {return ex.line;} -uint8_t get_tool() {return ex.tool;} -float get_velocity() {return ex.velocity;} -float get_acceleration() {return ex.accel;} -float get_jerk() {return ex.jerk;} -float get_feed_override() {return ex.feed_override;} -float get_speed_override() {return ex.spindle_override;} -float get_axis_mach_coord(int axis) {return exec_get_axis_position(axis);} - -void set_tool(uint8_t tool) {ex.tool = tool;} -void set_feed_override(float value) {ex.feed_override = value;} -void set_speed_override(float value) {ex.spindle_override = value;} -void set_axis_mach_coord(int axis, float position) { - exec_set_axis_position(axis, position); +void exec_get_position(float p[AXES]) { + memcpy(p, ex.position, sizeof(ex.position)); } -bool exec_is_busy() {return ex.busy;} float exec_get_axis_position(int axis) {return ex.position[axis];} -void exec_set_axis_position(int axis, float p) {ex.position[axis] = p;} void exec_set_velocity(float v) {ex.velocity = v;} +float exec_get_velocity() {return ex.velocity;} +void exec_set_acceleration(float a) {ex.accel = a;} +void exec_set_jerk(float j) {ex.jerk = j;} void exec_set_line(int line) {ex.line = line;} +void exec_set_cb(exec_cb_t cb) {ex.cb = cb;} stat_t exec_move_to_target(float time, const float target[]) { + ASSERT(isfinite(time)); ASSERT(isfinite(target[AXIS_X]) && isfinite(target[AXIS_Y]) && isfinite(target[AXIS_Z]) && isfinite(target[AXIS_A]) && isfinite(target[AXIS_B]) && isfinite(target[AXIS_C])); @@ -150,194 +115,30 @@ stat_t exec_move_to_target(float time, const float target[]) { void exec_reset_encoder_counts() {st_set_position(ex.position);} -static float _compute_distance(double t, double v, double a, double j) { - // v * t + 1/2 * a * t^2 + 1/6 * j * t^3 - return t * (v + t * (0.5 * a + 1.0 / 6.0 * j * t)); +stat_t exec_next() { + if (!ex.cb && !command_exec()) return STAT_NOOP; // Queue empty + if (!ex.cb) return STAT_EAGAIN; // Non-exec command + return ex.cb(); // Exec } -static float _compute_velocity(double t, double a, double j) { - // a * t + 1/2 * j * t^2 - return t * (a + 0.5 * j * t); -} - - -static void _segment_init(float time) { - ASSERT(isfinite(time) && 0 < time && time < 0x10000 * SEGMENT_TIME); - - // Init segment - ex.seg.new = false; - ex.time = time; - ex.seg.step = 0; - ex.seg.steps = ceil(ex.time / SEGMENT_TIME); - ex.seg.delta = time / ex.seg.steps; - if (ex.scurve == 0) ex.seg.dist = 0; - - // Record starting position and compute unit vector - float length = 0; - for (int i = 0; i < AXES; i++) { - ex.seg.position[i] = ex.position[i]; - ex.seg.unit[i] = ex.seg.target[i] - ex.position[i]; - length = ex.seg.unit[i] * ex.seg.unit[i]; - } - length = sqrt(length); - for (int i = 0; i < AXES; i++) ex.seg.unit[i] /= length; - - // Compute axis limited jerk - if (ex.scurve == 0) { - ex.seg.jerk = FLT_MAX; - - for (int i = 0; i < AXES; i++) - if (ex.seg.unit[i]) { - double j = fabs(axis_get_jerk_max(i) / ex.seg.unit[i]); - if (j < ex.seg.jerk) ex.seg.jerk = j; - } - } - - // Jerk - switch (ex.scurve) { - case 0: case 6: ex.jerk = ex.seg.jerk; break; - case 2: case 4: ex.jerk = -ex.seg.jerk; break; - default: ex.jerk = 0; - } - - // Acceleration - switch (ex.scurve) { - case 1: ex.accel = ex.seg.jerk * ex.time; break; - case 3: ex.accel = 0; break; - case 5: ex.accel = -ex.seg.jerk * ex.time; break; - } -} - - -static stat_t _move_distance(float dist, bool end) { - // Compute target position from distance - float target[AXES]; - for (int i = 0; i < AXES; i++) - target[i] = ex.seg.position[i] + ex.seg.unit[i] * dist; - - // Check if we have reached the end of the segment - for (int i = 0; end && i < AXES; i++) - if (0.000001 < fabs(ex.seg.target[i] - target[i])) end = false; - - // Do move - return exec_move_to_target(ex.seg.delta, end ? ex.seg.target : target); -} - - -static stat_t _segment_body() { - // Compute time, distance and velocity offsets - float t = ex.seg.delta * (ex.seg.step + 1); - float d = _compute_distance(t, ex.seg.vel, ex.accel, ex.jerk); - float v = _compute_velocity(t, ex.accel, ex.jerk); - - // Update velocity - exec_set_velocity(ex.seg.vel + v); - - return _move_distance(ex.seg.dist + d, false); -} - - -static void _set_scurve(int scurve) { - ex.scurve = scurve; - ex.seg.new = true; -} - - -static stat_t _segment_end() { - // Update distance and velocity - ex.seg.dist += _compute_distance(ex.time, ex.seg.vel, ex.accel, ex.jerk); - ex.seg.vel += _compute_velocity(ex.time, ex.accel, ex.jerk); - - // Set final segment velocity - exec_set_velocity(ex.seg.vel); - - // Automatically advance S-curve segment - if (++ex.scurve == 7) ex.scurve = 0; - _set_scurve(ex.scurve); - - return _move_distance(ex.seg.dist, true); -} - - -static stat_t _scurve_action(float time) { - if (time <= 0) return STAT_NOOP; // Skip invalid curves - if (ex.seg.new) _segment_init(time); - return ++ex.seg.step == ex.seg.steps ? _segment_end() : _segment_body(); -} - - -static void _set_output() { - int output = queue_head_left(); - bool enable = queue_head_right(); - - switch (output) { - case 0: coolant_set_mist(enable); break; - case 1: coolant_set_flood(enable); break; - } -} - - -static void _seek() { - ex.seek_switch = queue_head_left(); - ex.seek_open = queue_head_right() & (1 << 0); - ex.seek_error = queue_head_right() & (1 << 1); -} - - -static void _set_home() { - axis_set_homed(queue_head_left(), queue_head_right()); -} - - -static void _pause(bool optional) { - // TODO Initial immediate feedhold -} - - -stat_t exec_next_action() { - if (queue_is_empty()) { - ex.busy = false; - ex.last_empty = rtc_get_time(); - return STAT_NOOP; - } +// Variable callbacks +int32_t get_line() {return ex.line;} +uint8_t get_tool() {return ex.tool;} +float get_velocity() {return ex.velocity;} +float get_acceleration() {return ex.accel;} +float get_jerk() {return ex.jerk;} +float get_feed_override() {return ex.feed_override;} +float get_speed_override() {return ex.spindle_override;} +float get_axis_position(int axis) {return ex.position[axis];} - // On restart wait a bit to give queue a chance to fill - if (!ex.busy && queue_get_fill() < EXEC_FILL_TARGET && - !rtc_expired(ex.last_empty + EXEC_DELAY)) return STAT_NOOP; - - ex.busy = true; - stat_t status = STAT_NOOP; - - switch (queue_head()) { - case ACTION_SCURVE: _set_scurve(queue_head_int()); break; - case ACTION_DATA: status = _scurve_action(queue_head_float()); break; // TODO - case ACTION_VELOCITY: ex.seg.vel = queue_head_float(); break; - case ACTION_X: ex.seg.target[AXIS_X] = queue_head_float(); break; - case ACTION_Y: ex.seg.target[AXIS_Y] = queue_head_float(); break; - case ACTION_Z: ex.seg.target[AXIS_Z] = queue_head_float(); break; - case ACTION_A: ex.seg.target[AXIS_A] = queue_head_float(); break; - case ACTION_B: ex.seg.target[AXIS_B] = queue_head_float(); break; - case ACTION_C: ex.seg.target[AXIS_C] = queue_head_float(); break; - case ACTION_SEEK: _seek(); break; - case ACTION_OUTPUT: _set_output(); break; - case ACTION_DWELL: st_prep_dwell(queue_head_float()); status = STAT_OK; break; - case ACTION_PAUSE: _pause(queue_head_bool()); status = STAT_PAUSE; break; - case ACTION_TOOL: set_tool(queue_head_int()); break; - case ACTION_SPEED: spindle_set_speed(queue_head_float()); break; - case ACTION_JOG: status = jog_exec(); break; - case ACTION_LINE_NUM: exec_set_line(queue_head_int()); break; - case ACTION_SET_HOME: _set_home(); break; - default: status = STAT_INTERNAL_ERROR; break; - } +void set_tool(uint8_t tool) {ex.tool = tool;} +void set_feed_override(float value) {ex.feed_override = value;} +void set_speed_override(float value) {ex.spindle_override = value;} +void set_axis_position(int axis, float p) {ex.position[axis] = p;} - if (status != STAT_EAGAIN) queue_pop(); - switch (status) { - case STAT_NOOP: return STAT_EAGAIN; - case STAT_PAUSE: return STAT_NOOP; - case STAT_EAGAIN: return STAT_OK; - default: break; - } - return status; -} +// Command callbacks +stat_t command_opt_pause(char *cmd) {command_push(*cmd, 0); return STAT_OK;} +unsigned command_opt_pause_size() {return 0;} +void command_opt_pause_exec(void *data) {} // TODO pause if requested diff --git a/src/avr/src/exec.h b/src/avr/src/exec.h index 6ae311f..f2832a9 100644 --- a/src/avr/src/exec.h +++ b/src/avr/src/exec.h @@ -28,22 +28,27 @@ #pragma once -#include "pgmspace.h" +#include "config.h" #include "status.h" #include -void exec_init(); +typedef stat_t (*exec_cb_t)(); -bool exec_is_busy(); -float exec_get_axis_position(int axis); // jog.c -void exec_set_axis_position(int axis, float position); +void exec_init(); -void exec_set_velocity(float v); // jog.c +void exec_get_position(float p[AXES]); +float exec_get_axis_position(int axis); +void exec_set_velocity(float v); +float exec_get_velocity(); +void exec_set_acceleration(float a); +void exec_set_jerk(float j); void exec_set_line(int line); +void exec_set_cb(exec_cb_t cb); + stat_t exec_move_to_target(float time, const float target[]); void exec_reset_encoder_counts(); -stat_t exec_next_action(); +stat_t exec_next(); diff --git a/src/avr/src/hardware.c b/src/avr/src/hardware.c index bd3de2b..36b77d3 100644 --- a/src/avr/src/hardware.c +++ b/src/avr/src/hardware.c @@ -3,7 +3,6 @@ This file is part of the Buildbotics firmware. Copyright (c) 2015 - 2017 Buildbotics LLC - Copyright (c) 2010 - 2015 Alden S. Hart, Jr. All rights reserved. This file ("the software") is free software: you can redistribute it diff --git a/src/avr/src/hardware.h b/src/avr/src/hardware.h index 56ea39e..4f1acf6 100644 --- a/src/avr/src/hardware.h +++ b/src/avr/src/hardware.h @@ -3,8 +3,6 @@ This file is part of the Buildbotics firmware. Copyright (c) 2015 - 2017 Buildbotics LLC - Copyright (c) 2010 - 2015 Alden S. Hart, Jr. - Copyright (c) 2012 - 2015 Rob Giseburt All rights reserved. This file ("the software") is free software: you can redistribute it diff --git a/src/avr/src/huanyang.c b/src/avr/src/huanyang.c index 6a22951..39d2278 100644 --- a/src/avr/src/huanyang.c +++ b/src/avr/src/huanyang.c @@ -123,10 +123,10 @@ typedef struct { uint16_t rated_rpm; uint8_t status; -} huanyang_t; +} hy_t; -static huanyang_t ha = {0}; +static hy_t ha = {0}; #define CTRL_STATUS_RESPONSE(R) ((uint16_t)R[4] << 8 | R[5]) @@ -403,7 +403,7 @@ ISR(HUANYANG_RXC_vect) { } -void huanyang_init() { +void hy_init() { PR.PRPD &= ~PR_USART1_bm; // Disable power reduction DIRCLR_PIN(RS485_RO_PIN); // Input @@ -422,11 +422,11 @@ void huanyang_init() { USARTD1.CTRLB = USART_RXEN_bm | USART_TXEN_bm | USART_CLK2X_bm; ha.id = HUANYANG_ID; - huanyang_reset(); + hy_reset(); } -void huanyang_set(float speed) { +void hy_set(float speed) { if (ha.speed != speed) { if (ha.debug) STATUS_DEBUG("huanyang: speed=%0.2f", speed); ha.speed = speed; @@ -435,7 +435,7 @@ void huanyang_set(float speed) { } -void huanyang_reset() { +void hy_reset() { _set_dre_interrupt(false); _set_txc_interrupt(false); _set_rxc_interrupt(false); @@ -464,7 +464,7 @@ void huanyang_reset() { } -void huanyang_rtc_callback() { +void hy_rtc_callback() { if (ha.last && rtc_expired(ha.last + HUANYANG_TIMEOUT)) { if (ha.retry < HUANYANG_RETRIES) _retry_command(); else { @@ -488,28 +488,28 @@ void huanyang_rtc_callback() { sent, received, ha.response_length); } - huanyang_reset(); + hy_reset(); } } } -void huanyang_stop() { - huanyang_set(0); - huanyang_reset(); +void hy_stop() { + hy_set(0); + hy_reset(); } -uint8_t get_huanyang_id() {return ha.id;} -void set_huanyang_id(uint8_t value) {ha.id = value;} -bool get_huanyang_debug() {return ha.debug;} -void set_huanyang_debug(bool value) {ha.debug = value;} -bool get_huanyang_connected() {return ha.connected;} -float get_huanyang_freq() {return ha.actual_freq;} -float get_huanyang_current() {return ha.actual_current;} -uint16_t get_huanyang_rpm() {return ha.actual_rpm;} -uint16_t get_huanyang_temp() {return ha.temperature;} -float get_huanyang_max_freq() {return ha.max_freq;} -float get_huanyang_min_freq() {return ha.min_freq;} -uint16_t get_huanyang_rated_rpm() {return ha.rated_rpm;} -float get_huanyang_status() {return ha.status;} +uint8_t get_hy_id() {return ha.id;} +void set_hy_id(uint8_t value) {ha.id = value;} +bool get_hy_debug() {return ha.debug;} +void set_hy_debug(bool value) {ha.debug = value;} +bool get_hy_connected() {return ha.connected;} +float get_hy_freq() {return ha.actual_freq;} +float get_hy_current() {return ha.actual_current;} +uint16_t get_hy_rpm() {return ha.actual_rpm;} +uint16_t get_hy_temp() {return ha.temperature;} +float get_hy_max_freq() {return ha.max_freq;} +float get_hy_min_freq() {return ha.min_freq;} +uint16_t get_hy_rated_rpm() {return ha.rated_rpm;} +float get_hy_status() {return ha.status;} diff --git a/src/avr/src/huanyang.h b/src/avr/src/huanyang.h index e2e3ec8..3e28471 100644 --- a/src/avr/src/huanyang.h +++ b/src/avr/src/huanyang.h @@ -30,8 +30,8 @@ #include "spindle.h" -void huanyang_init(); -void huanyang_set(float speed); -void huanyang_reset(); -void huanyang_rtc_callback(); -void huanyang_stop(); +void hy_init(); +void hy_set(float speed); +void hy_reset(); +void hy_rtc_callback(); +void hy_stop(); diff --git a/src/avr/src/i2c.c b/src/avr/src/i2c.c index 7ed4510..45fa9d0 100644 --- a/src/avr/src/i2c.c +++ b/src/avr/src/i2c.c @@ -3,7 +3,6 @@ This file is part of the Buildbotics firmware. Copyright (c) 2015 - 2017 Buildbotics LLC - Copyright (c) 2010 - 2013 Alden S. Hart Jr. All rights reserved. This file ("the software") is free software: you can redistribute it @@ -36,7 +35,7 @@ typedef struct { i2c_read_cb_t read_cb; i2c_write_cb_t write_cb; - uint8_t data[I2C_MAX_DATA]; + uint8_t data[I2C_MAX_DATA + 1]; uint8_t length; bool done; bool write; @@ -53,8 +52,10 @@ static void _i2c_reset_command() { static void _i2c_end_command() { - if (i2c.length && !i2c.write && i2c.read_cb) - i2c.read_cb(*i2c.data, i2c.data + 1, i2c.length - 1); + if (i2c.length && !i2c.write && i2c.read_cb) { + i2c.data[i2c.length] = 0; // Null terminate + i2c.read_cb(i2c.data, i2c.length); + } _i2c_reset_command(); } diff --git a/src/avr/src/i2c.h b/src/avr/src/i2c.h index 83ba8c1..373664f 100644 --- a/src/avr/src/i2c.h +++ b/src/avr/src/i2c.h @@ -3,7 +3,6 @@ This file is part of the Buildbotics firmware. Copyright (c) 2015 - 2017 Buildbotics LLC - Copyright (c) 2010 - 2013 Alden S. Hart Jr. All rights reserved. This file ("the software") is free software: you can redistribute it @@ -32,22 +31,7 @@ #include -// Must be kept in sync with AVR.py -typedef enum { - I2C_NULL, - I2C_ESTOP, - I2C_CLEAR, - I2C_PAUSE, - I2C_OPTIONAL_PAUSE, - I2C_RUN, - I2C_STEP, - I2C_FLUSH, - I2C_REPORT, - I2C_REBOOT, -} i2c_cmd_t; - - -typedef void (*i2c_read_cb_t)(i2c_cmd_t cmd, uint8_t *data, uint8_t length); +typedef void (*i2c_read_cb_t)(uint8_t *data, uint8_t length); typedef uint8_t (*i2c_write_cb_t)(uint8_t offset, bool *done); diff --git a/src/avr/src/jog.c b/src/avr/src/jog.c index e8ae33d..4a75ab0 100644 --- a/src/avr/src/jog.c +++ b/src/avr/src/jog.c @@ -31,7 +31,6 @@ #include "util.h" #include "exec.h" #include "state.h" -#include "queue.h" #include "config.h" #include @@ -56,6 +55,7 @@ typedef struct { typedef struct { + bool active; bool writing; bool done; @@ -257,20 +257,17 @@ stat_t jog_exec() { // Check if we are done if (jr.done) { - // Update machine position - //mach_set_position_from_runtime(); - state_set_cycle(CYCLE_MACHINING); // Default cycle - state_pause_queue(false); + exec_set_cb(0); + jr.active = false; return STAT_NOOP; // Done, no move executed } // Compute target from velocity float target[AXES]; + exec_get_position(target); for (int axis = 0; axis < AXES; axis++) { - target[axis] = exec_get_axis_position(axis) + - jr.axes[axis].velocity * SEGMENT_TIME; - + target[axis] += jr.axes[axis].velocity * SEGMENT_TIME; target[axis] = _limit_position(axis, target[axis]); } @@ -279,33 +276,37 @@ stat_t jog_exec() { stat_t status = exec_move_to_target(SEGMENT_TIME, target); if (status != STAT_OK) return status; - return STAT_EAGAIN; + return STAT_OK; } -uint8_t command_jog(int argc, char *argv[]) { - if (state_get_cycle() != CYCLE_JOGGING && - (state_get() != STATE_READY || state_get_cycle() != CYCLE_MACHINING)) - return STAT_NOOP; - float velocity[AXES]; +stat_t command_jog(char *cmd) { + // Ignore jog commands when not already idle + if (!jr.active && state_get() != STATE_READY) return STAT_NOOP; - for (int axis = 0; axis < AXES; axis++) - if (axis < argc - 1) velocity[axis] = atof(argv[axis + 1]); - else velocity[axis] = 0; + // Skip command code + cmd++; + + // Get velocities + float velocity[AXES] = {0,}; + stat_t status = decode_axes(&cmd, velocity); + if (status) return status; + + // Check for end of command + if (*cmd) return STAT_INVALID_ARGUMENTS; // Reset - if (state_get_cycle() != CYCLE_JOGGING) memset(&jr, 0, sizeof(jr)); + if (!jr.active) memset(&jr, 0, sizeof(jr)); jr.writing = true; for (int axis = 0; axis < AXES; axis++) jr.axes[axis].next = velocity[axis]; jr.writing = false; - if (state_get_cycle() != CYCLE_JOGGING) { - state_set_cycle(CYCLE_JOGGING); - state_pause_queue(true); - queue_push(ACTION_JOG); + if (!jr.active) { + jr.active = true; + exec_set_cb(jog_exec); } return STAT_OK; diff --git a/src/avr/src/lcd.c b/src/avr/src/lcd.c index 2050b94..43f57dd 100644 --- a/src/avr/src/lcd.c +++ b/src/avr/src/lcd.c @@ -3,7 +3,6 @@ This file is part of the Buildbotics firmware. Copyright (c) 2015 - 2017 Buildbotics LLC - Copyright (c) 2010 Alex Forencich All rights reserved. This file ("the software") is free software: you can redistribute it diff --git a/src/avr/src/lcd.h b/src/avr/src/lcd.h index 96826f0..7bc2148 100644 --- a/src/avr/src/lcd.h +++ b/src/avr/src/lcd.h @@ -3,7 +3,6 @@ This file is part of the Buildbotics firmware. Copyright (c) 2015 - 2017 Buildbotics LLC - Copyright (c) 2010 Alex Forencich All rights reserved. This file ("the software") is free software: you can redistribute it diff --git a/src/avr/src/line.c b/src/avr/src/line.c new file mode 100644 index 0000000..4566308 --- /dev/null +++ b/src/avr/src/line.c @@ -0,0 +1,240 @@ +/******************************************************************************\ + + This file is part of the Buildbotics firmware. + + Copyright (c) 2015 - 2017 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" + +\******************************************************************************/ + +#include "config.h" +#include "exec.h" +#include "axis.h" +#include "command.h" +#include "util.h" + +#include +#include +#include + + +typedef struct { + float start[AXES]; + float target[AXES]; + float times[7]; + float target_vel; + float max_jerk; + + float unit[AXES]; +} line_t; + + +static struct { + float next_start[AXES]; + + line_t line; + + bool new; + int seg; + uint16_t steps; + uint16_t step; + float delta; + + float dist; + float vel; + float accel; + float jerk; +} l = {}; + + +static float _compute_distance(float t, float v, float a, float j) { + // v * t + 1/2 * a * t^2 + 1/6 * j * t^3 + return t * (v + t * (0.5 * a + 1.0 / 6.0 * j * t)); +} + + +static float _compute_velocity(float t, float a, float j) { + // a * t + 1/2 * j * t^2 + return t * (a + 0.5 * j * t); +} + + +static bool _segment_next() { + while (++l.seg < 7) + if (l.line.times[l.seg]) return (l.new = true); + return false; +} + + +static void _segment_init() { + l.new = false; + + // Jerk + switch (l.seg) { + case 0: case 6: l.jerk = l.line.max_jerk; break; + case 2: case 4: l.jerk = -l.line.max_jerk; break; + default: l.jerk = 0; + } + exec_set_jerk(l.jerk); + + // Acceleration + switch (l.seg) { + case 1: case 2: l.accel = l.line.max_jerk * l.line.times[0]; break; + case 5: case 6: l.accel = -l.line.max_jerk * l.line.times[4]; break; + default: l.accel = 0; + } + exec_set_acceleration(l.accel); + + // Compute interpolation steps + l.step = 0; + float time = l.line.times[l.seg]; + l.steps = round(time / SEGMENT_TIME); + if (!l.steps) {l.steps = 1; l.delta = time;} + else l.delta = time / l.steps; +} + + +static stat_t _line_exec() { + if (l.new) _segment_init(); + + bool lastStep = l.step == l.steps - 1; + + // Compute time, distance and velocity offsets + float t = lastStep ? l.line.times[l.seg] : (l.delta * (l.step + 1)); + float d = l.dist + _compute_distance(t, l.vel, l.accel, l.jerk); + float v = l.vel + _compute_velocity(t, l.accel, l.jerk); + + // Compute target position from distance + float target[AXES]; + for (int i = 0; i < AXES; i++) + target[i] = l.line.start[i] + l.line.unit[i] * d; + + // Update dist and vel for next seg + if (lastStep) { + l.dist = d; + l.vel = v; + + } else l.step++; + + // Advance curve + bool lastCurve = lastStep && !_segment_next(); + + // Do move + stat_t status = + exec_move_to_target(l.delta, lastCurve ? l.line.target : target); + + // Check if we're done + if (lastCurve) { + exec_set_velocity(l.vel = l.line.target_vel); + exec_set_cb(0); + + } else exec_set_velocity(v); + + return status; +} + + +void _print_vector(const char *name, float v[AXES]) { + printf("%s %f %f %f %f\n", name, v[0], v[1], v[2], v[3]); +} + + +stat_t command_line(char *cmd) { + line_t line = {}; + + cmd++; // Skip command code + + // Get start position + copy_vector(line.start, l.next_start); + + // Get target velocity + if (!decode_float(&cmd, &line.target_vel)) return STAT_BAD_FLOAT; + if (line.target_vel < 0) return STAT_INVALID_ARGUMENTS; + + // Get max jerk + if (!decode_float(&cmd, &line.max_jerk)) return STAT_BAD_FLOAT; + if (line.max_jerk < 0) return STAT_INVALID_ARGUMENTS; + + // Get target position + copy_vector(line.target, line.start); + stat_t status = decode_axes(&cmd, line.target); + if (status) return status; + + // Get times + bool has_time = false; + while (*cmd) { + if (*cmd < '0' || '6' < *cmd) break; + int seg = *cmd - '0'; + cmd++; + + float time; + if (!decode_float(&cmd, &time)) return STAT_BAD_FLOAT; + + if (time < 0 || 0x10000 * SEGMENT_TIME <= time) return STAT_BAD_SEG_TIME; + line.times[seg] = time; + if (time) has_time = true; + } + + if (!has_time) return STAT_BAD_SEG_TIME; + + // Check for end of command + if (*cmd) return STAT_INVALID_ARGUMENTS; + + // Set next start position + copy_vector(l.next_start, line.target); + + // Compute direction vector + float length = 0; + for (int i = 0; i < AXES; i++) + if (axis_is_enabled(i)) { + line.unit[i] = line.target[i] - line.start[i]; + length += line.unit[i] * line.unit[i]; + + } else line.unit[i] = 0; + + length = sqrt(length); + for (int i = 0; i < AXES; i++) + if (line.unit[i]) line.unit[i] /= length; + + // Queue + command_push(COMMAND_line, &line); + + return STAT_OK; +} + + +unsigned command_line_size() {return sizeof(line_t);} + + +void command_line_exec(void *data) { + l.line = *(line_t *)data; + + // Init dist & velocity + l.dist = 0; + l.vel = exec_get_velocity(); + + // Find first segment + l.seg = -1; + _segment_next(); + + // Set callback + exec_set_cb(_line_exec); +} diff --git a/src/avr/src/main.c b/src/avr/src/main.c index 642b391..a0504b9 100644 --- a/src/avr/src/main.c +++ b/src/avr/src/main.c @@ -3,8 +3,6 @@ This file is part of the Buildbotics firmware. Copyright (c) 2015 - 2017 Buildbotics LLC - Copyright (c) 2010 - 2015 Alden S. Hart, Jr. - Copyright (c) 2013 - 2015 Robert Giseburt All rights reserved. This file ("the software") is free software: you can redistribute it @@ -65,7 +63,7 @@ int main() { stepper_init(); // steppers motor_init(); // motors switch_init(); // switches - exec_init(); // motion exec + exec_init(); // motion exec vars_init(); // configuration variables estop_init(); // emergency stop handler command_init(); @@ -73,8 +71,8 @@ int main() { sei(); // enable interrupts // Splash - fprintf_P(stdout, PSTR("\n{\"firmware\": \"Buildbotics AVR\", " - "\"version\": \"" VERSION "\"}\n")); + fprintf_P(stdout, PSTR("\n{\"firmware\":\"Buildbotics AVR\"," + "\"version\":\"" VERSION "\"}\n")); // Main loop while (true) { diff --git a/src/avr/src/messages.def b/src/avr/src/messages.def index b002b2c..8e43a17 100644 --- a/src/avr/src/messages.def +++ b/src/avr/src/messages.def @@ -25,22 +25,25 @@ \******************************************************************************/ -STAT_MSG(OK, "OK") -STAT_MSG(EAGAIN, "Run command again") -STAT_MSG(NOOP, "No op") -STAT_MSG(PAUSE, "Pause") -STAT_MSG(INTERNAL_ERROR, "Internal error") -STAT_MSG(ESTOP_USER, "User triggered EStop") -STAT_MSG(ESTOP_SWITCH, "Switch triggered EStop") -STAT_MSG(UNRECOGNIZED_NAME, "Unrecognized command or variable name") -STAT_MSG(INVALID_COMMAND, "Invalid command") -STAT_MSG(INVALID_ARGUMENTS, "Invalid argument(s) to command") -STAT_MSG(TOO_MANY_ARGUMENTS, "Too many arguments to command") -STAT_MSG(TOO_FEW_ARGUMENTS, "Too few arguments to command") +STAT_MSG(OK, "OK") +STAT_MSG(EAGAIN, "Run command again") +STAT_MSG(NOOP, "No op") +STAT_MSG(PAUSE, "Pause") +STAT_MSG(INTERNAL_ERROR, "Internal error") +STAT_MSG(ESTOP_USER, "User triggered EStop") +STAT_MSG(ESTOP_SWITCH, "Switch triggered EStop") +STAT_MSG(UNRECOGNIZED_NAME, "Unrecognized command or variable name") +STAT_MSG(INVALID_COMMAND, "Invalid command") +STAT_MSG(INVALID_ARGUMENTS, "Invalid argument(s) to command") +STAT_MSG(TOO_MANY_ARGUMENTS, "Too many arguments to command") +STAT_MSG(TOO_FEW_ARGUMENTS, "Too few arguments to command") STAT_MSG(COMMAND_NOT_ACCEPTED, "Command not accepted at this time") -STAT_MSG(MACHINE_ALARMED, "Machine alarmed - Command not processed") -STAT_MSG(EXPECTED_MOVE, "A move was expected but none was queued") -STAT_MSG(QUEUE_FULL, "Queue full") - -// End of stats marker -STAT_MSG(MAX, "") +STAT_MSG(MACHINE_ALARMED, "Machine alarmed - Command not processed") +STAT_MSG(EXPECTED_MOVE, "A move was expected but none was queued") +STAT_MSG(QUEUE_FULL, "Queue full") +STAT_MSG(QUEUE_EMPTY, "Queue empty") +STAT_MSG(BAD_FLOAT, "Failed to parse float") +STAT_MSG(INVALID_VARIABLE, "Invalid variable") +STAT_MSG(INVALID_VALUE, "Invalid value") +STAT_MSG(BUFFER_OVERFLOW, "Buffer overflow") +STAT_MSG(BAD_SEG_TIME, "Bad s-curve segment time") diff --git a/src/avr/src/messages.json.in b/src/avr/src/messages.json.in new file mode 100644 index 0000000..d12e38f --- /dev/null +++ b/src/avr/src/messages.json.in @@ -0,0 +1,7 @@ +#include "cpp_magic.h" +[ +#define STAT_MSG(NAME, MSG) [#NAME, MSG], +#include "messages.def" +#undef CMD + [] +] diff --git a/src/avr/src/motor.c b/src/avr/src/motor.c index 60f3e1d..4a8142b 100644 --- a/src/avr/src/motor.c +++ b/src/avr/src/motor.c @@ -283,16 +283,15 @@ void motor_load_move(int motor) { void motor_prep_move(int motor, float time, float target) { + // Validate input + ASSERT(0 <= motor && motor < MOTORS); ASSERT(isfinite(target)); - int32_t position = _position_to_steps(motor, target); motor_t *m = &motors[motor]; - - // Validate input - ASSERT(0 <= motor && motor < MOTORS); ASSERT(!m->prepped); // Travel in half steps + int32_t position = _position_to_steps(motor, target); int24_t half_steps = position - m->position; m->position = position; diff --git a/src/avr/src/queue.c b/src/avr/src/queue.c deleted file mode 100644 index 7a350bc..0000000 --- a/src/avr/src/queue.c +++ /dev/null @@ -1,103 +0,0 @@ -/******************************************************************************\ - - This file is part of the Buildbotics firmware. - - Copyright (c) 2015 - 2017 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" - -\******************************************************************************/ - -#include "queue.h" - -#include - - -typedef struct { - action_t action; - - union { - float f; - int32_t i; - bool b; - struct { - int16_t l; - int16_t r; - } p; - } value; -} buffer_t; - - -#define RING_BUF_NAME q -#define RING_BUF_TYPE buffer_t -#define RING_BUF_SIZE QUEUE_SIZE -#include "ringbuf.def" - - -void queue_init() { - q_init(); -} - - -void queue_flush() {q_init();} -bool queue_is_empty() {return q_empty();} -int queue_get_room() {return q_space();} -int queue_get_fill() {return q_fill();} - - -void queue_push(action_t action) { - buffer_t b = {action}; - q_push(b); -} - - -void queue_push_float(action_t action, float value) { - buffer_t b = {action, {.f = value}}; - q_push(b); -} - - -void queue_push_int(action_t action, int32_t value) { - buffer_t b = {action, {.i = value}}; - q_push(b); -} - - -void queue_push_bool(action_t action, bool value) { - buffer_t b = {action, {.b = value}}; - q_push(b); -} - - -void queue_push_pair(action_t action, int16_t left, int16_t right) { - buffer_t b = {action}; - b.value.p.l = left; - b.value.p.r = right; - q_push(b); -} - - -void queue_pop() {q_pop();} -action_t queue_head() {return q_peek().action;} -float queue_head_float() {return q_peek().value.f;} -int32_t queue_head_int() {return q_peek().value.i;} -bool queue_head_bool() {return q_peek().value.b;} -int16_t queue_head_left() {return q_peek().value.p.l;} -int16_t queue_head_right() {return q_peek().value.p.r;} diff --git a/src/avr/src/ringbuf.def b/src/avr/src/ringbuf.def index 40f57cb..bb08720 100644 --- a/src/avr/src/ringbuf.def +++ b/src/avr/src/ringbuf.def @@ -51,6 +51,7 @@ */ #include +#include #ifndef RING_BUF_NAME #error Must define RING_BUF_NAME @@ -65,7 +66,7 @@ #endif #ifndef RING_BUF_INDEX_TYPE -#define RING_BUF_INDEX_TYPE uint8_t +#define RING_BUF_INDEX_TYPE volatile uint8_t #endif #ifndef RING_BUF_FUNC @@ -83,68 +84,108 @@ #endif #define RING_BUF_STRUCT CONCAT(RING_BUF_NAME, _ring_buf_t) -#define RING_BUF CONCAT(RING_BUF_NAME, _ring_buf) +#define RING_BUF RING_BUF_NAME typedef struct { RING_BUF_TYPE buf[RING_BUF_SIZE]; - volatile RING_BUF_INDEX_TYPE head; - volatile RING_BUF_INDEX_TYPE tail; - volatile RING_BUF_INDEX_TYPE tran_tail; - volatile bool transaction; + RING_BUF_INDEX_TYPE head; + RING_BUF_INDEX_TYPE tail; } RING_BUF_STRUCT; static RING_BUF_STRUCT RING_BUF; RING_BUF_FUNC void CONCAT(RING_BUF_NAME, _init)() { - RING_BUF.head = RING_BUF.tail = RING_BUF.tran_tail = RING_BUF.transaction = 0; + RING_BUF.head = RING_BUF.tail = 0; } -#define RING_BUF_INC CONCAT(RING_BUF_NAME, _inc) -RING_BUF_FUNC RING_BUF_INDEX_TYPE RING_BUF_INC(RING_BUF_INDEX_TYPE x) { - return (x + 1) & RING_BUF_MASK; -} +#define RING_BUF_INC(x) (((x) + 1) & RING_BUF_MASK) + + +#ifdef RING_BUF_ATOMIC_COPY + +#define RING_BUF_ATOMIC_READ_INDEX(INDEX) \ + RING_BUF_FUNC RING_BUF_INDEX_TYPE CONCAT(RING_BUF_NAME, _read_##INDEX)() { \ + RING_BUF_INDEX_TYPE index; \ + RING_BUF_ATOMIC_COPY(index, RING_BUF.INDEX); \ + return index; \ + } + + +#define _RING_BUF_ATOMIC_WRITE_INDEX(INDEX, TARGET) \ + RING_BUF_FUNC void CONCAT(RING_BUF_NAME, _write_##INDEX) \ + (RING_BUF_INDEX_TYPE value) { \ + RING_BUF_ATOMIC_COPY(TARGET, value); \ + } + + +#define RING_BUF_ATOMIC_WRITE_INDEX(INDEX) \ + _RING_BUF_ATOMIC_WRITE_INDEX(INDEX, RING_BUF.INDEX) + +RING_BUF_ATOMIC_READ_INDEX(head); +RING_BUF_ATOMIC_READ_INDEX(tail); +RING_BUF_ATOMIC_WRITE_INDEX(head); +RING_BUF_ATOMIC_WRITE_INDEX(tail); -RING_BUF_FUNC int CONCAT(RING_BUF_NAME, _empty)() { - return RING_BUF.head == RING_BUF.tail; +#define RING_BUF_READ_INDEX(INDEX) CONCAT(RING_BUF_NAME, _read_##INDEX)() +#define RING_BUF_WRITE_INDEX(INDEX, VALUE) \ + CONCAT(RING_BUF_NAME, _write_##INDEX)(VALUE) + + +#else // RING_BUF_ATOMIC_COPY +#define RING_BUF_READ_INDEX(INDEX) RING_BUF.INDEX +#define RING_BUF_WRITE_INDEX(INDEX, VALUE) RING_BUF.INDEX = VALUE +#endif // RING_BUF_ATOMIC_COPY + + +RING_BUF_FUNC bool CONCAT(RING_BUF_NAME, _empty)() { + return RING_BUF_READ_INDEX(head) == RING_BUF_READ_INDEX(tail); } -RING_BUF_FUNC int CONCAT(RING_BUF_NAME, _full)() { - return RING_BUF.head == RING_BUF_INC(RING_BUF.tail); +RING_BUF_FUNC bool CONCAT(RING_BUF_NAME, _full)() { + return RING_BUF_READ_INDEX(head) == RING_BUF_INC(RING_BUF_READ_INDEX(tail)); } -RING_BUF_FUNC int CONCAT(RING_BUF_NAME, _fill)() { - return (RING_BUF.tail - RING_BUF.head) & RING_BUF_MASK; +RING_BUF_FUNC RING_BUF_INDEX_TYPE CONCAT(RING_BUF_NAME, _fill)() { + return + (RING_BUF_READ_INDEX(tail) - RING_BUF_READ_INDEX(head)) & RING_BUF_MASK; } -RING_BUF_FUNC int CONCAT(RING_BUF_NAME, _space)() { - return RING_BUF_SIZE - CONCAT(RING_BUF_NAME, _fill)(); +RING_BUF_FUNC RING_BUF_INDEX_TYPE CONCAT(RING_BUF_NAME, _space)() { + return (RING_BUF_SIZE - 1) - CONCAT(RING_BUF_NAME, _fill)(); } RING_BUF_FUNC RING_BUF_TYPE CONCAT(RING_BUF_NAME, _peek)() { - return RING_BUF.buf[RING_BUF.head]; + return RING_BUF.buf[RING_BUF_READ_INDEX(head)]; } RING_BUF_FUNC RING_BUF_TYPE CONCAT(RING_BUF_NAME, _get)(int offset) { - return RING_BUF.buf[(RING_BUF.head + offset) & RING_BUF_MASK]; + return RING_BUF.buf[(RING_BUF_READ_INDEX(head) + offset) & RING_BUF_MASK]; } RING_BUF_FUNC void CONCAT(RING_BUF_NAME, _pop)() { - RING_BUF.head = RING_BUF_INC(RING_BUF.head); + RING_BUF_WRITE_INDEX(head, RING_BUF_INC(RING_BUF_READ_INDEX(head))); +} + + +RING_BUF_FUNC RING_BUF_TYPE CONCAT(RING_BUF_NAME, _next)() { + RING_BUF_TYPE x = CONCAT(RING_BUF_NAME, _peek)(); + CONCAT(RING_BUF_NAME, _pop)(); + return x; } RING_BUF_FUNC void CONCAT(RING_BUF_NAME, _push)(RING_BUF_TYPE data) { - RING_BUF.buf[RING_BUF.tail] = data; - RING_BUF.tail = RING_BUF_INC(RING_BUF.tail); + RING_BUF.buf[RING_BUF_READ_INDEX(tail)] = data; + RING_BUF_WRITE_INDEX(tail, RING_BUF_INC(RING_BUF_READ_INDEX(tail))); } @@ -158,3 +199,12 @@ RING_BUF_FUNC void CONCAT(RING_BUF_NAME, _push)(RING_BUF_TYPE data) { #undef RING_BUF_TYPE #undef RING_BUF_INDEX_TYPE #undef RING_BUF_FUNC + +#undef RING_BUF_READ_INDEX +#undef RING_BUF_WRITE_INDEX + +#ifdef RING_BUF_ATOMIC_COPY +#undef RING_BUF_ATOMIC_COPY +#undef RING_BUF_ATOMIC_READ_INDEX +#undef RING_BUF_ATOMIC_WRITE_INDEX +#endif // RING_BUF_ATOMIC_COPY diff --git a/src/avr/src/rtc.c b/src/avr/src/rtc.c index f3bc3f3..d37f9e1 100644 --- a/src/avr/src/rtc.c +++ b/src/avr/src/rtc.c @@ -3,7 +3,6 @@ This file is part of the Buildbotics firmware. Copyright (c) 2015 - 2017 Buildbotics LLC - Copyright (c) 2010 - 2013 Alden S. Hart Jr. All rights reserved. This file ("the software") is free software: you can redistribute it @@ -46,7 +45,7 @@ ISR(RTC_OVF_vect) { ticks++; switch_rtc_callback(); - huanyang_rtc_callback(); + hy_rtc_callback(); if (!(ticks & 255)) motor_rtc_callback(); lcd_rtc_callback(); } diff --git a/src/avr/src/rtc.h b/src/avr/src/rtc.h index 2c040bb..c25ebd0 100644 --- a/src/avr/src/rtc.h +++ b/src/avr/src/rtc.h @@ -3,7 +3,6 @@ This file is part of the Buildbotics firmware. Copyright (c) 2015 - 2017 Buildbotics LLC - Copyright (c) 2010 - 2013 Alden S. Hart Jr. All rights reserved. This file ("the software") is free software: you can redistribute it diff --git a/src/avr/src/spindle.c b/src/avr/src/spindle.c index 2349895..46cd355 100644 --- a/src/avr/src/spindle.c +++ b/src/avr/src/spindle.c @@ -3,7 +3,6 @@ This file is part of the Buildbotics firmware. Copyright (c) 2015 - 2017 Buildbotics LLC - Copyright (c) 2010 - 2015 Alden S. Hart, Jr. All rights reserved. This file ("the software") is free software: you can redistribute it @@ -52,7 +51,7 @@ static spindle_t spindle = {0}; void spindle_init() { pwm_spindle_init(); - huanyang_init(); + hy_init(); } @@ -61,7 +60,7 @@ void spindle_set_speed(float speed) { switch (spindle.type) { case SPINDLE_TYPE_PWM: pwm_spindle_set(spindle_get_speed()); break; - case SPINDLE_TYPE_HUANYANG: huanyang_set(spindle_get_speed()); break; + case SPINDLE_TYPE_HUANYANG: hy_set(spindle_get_speed()); break; } } @@ -74,7 +73,7 @@ float spindle_get_speed() { void spindle_stop() { switch (spindle.type) { case SPINDLE_TYPE_PWM: pwm_spindle_stop(); break; - case SPINDLE_TYPE_HUANYANG: huanyang_stop(); break; + case SPINDLE_TYPE_HUANYANG: hy_stop(); break; } } diff --git a/src/avr/src/spindle.h b/src/avr/src/spindle.h index 102753b..2d55a30 100644 --- a/src/avr/src/spindle.h +++ b/src/avr/src/spindle.h @@ -3,7 +3,6 @@ This file is part of the Buildbotics firmware. Copyright (c) 2015 - 2017 Buildbotics LLC - Copyright (c) 2010 - 2015 Alden S. Hart, Jr. All rights reserved. This file ("the software") is free software: you can redistribute it diff --git a/src/avr/src/state.c b/src/avr/src/state.c index 742df89..3d70f5f 100644 --- a/src/avr/src/state.c +++ b/src/avr/src/state.c @@ -3,8 +3,6 @@ This file is part of the Buildbotics firmware. Copyright (c) 2015 - 2017 Buildbotics LLC - Copyright (c) 2013 - 2015 Alden S. Hart, Jr. - Copyright (c) 2013 - 2015 Robert Giseburt All rights reserved. This file ("the software") is free software: you can redistribute it @@ -30,7 +28,7 @@ #include "state.h" #include "exec.h" -#include "queue.h" +#include "command.h" #include "stepper.h" #include "spindle.h" #include "report.h" @@ -40,9 +38,7 @@ static struct { state_t state; - cycle_t cycle; hold_reason_t hold_reason; - bool pause; bool hold_requested; bool flush_requested; @@ -68,18 +64,6 @@ PGM_P state_get_pgmstr(state_t state) { } -PGM_P state_get_cycle_pgmstr(cycle_t cycle) { - switch (cycle) { - case CYCLE_MACHINING: return PSTR("MACHINING"); - case CYCLE_HOMING: return PSTR("HOMING"); - case CYCLE_PROBING: return PSTR("PROBING"); - case CYCLE_JOGGING: return PSTR("JOGGING"); - } - - return PSTR("INVALID"); -} - - PGM_P state_get_hold_reason_pgmstr(hold_reason_t reason) { switch (reason) { case HOLD_REASON_USER_PAUSE: return PSTR("USER"); @@ -94,7 +78,6 @@ PGM_P state_get_hold_reason_pgmstr(hold_reason_t reason) { state_t state_get() {return s.state;} -cycle_t state_get_cycle() {return s.cycle;} static void _set_state(state_t state) { @@ -106,29 +89,6 @@ static void _set_state(state_t state) { } -void state_set_cycle(cycle_t cycle) { - if (s.cycle == cycle) return; // No change - - if (s.state != STATE_READY && cycle != CYCLE_MACHINING) { - STATUS_ERROR(STAT_INTERNAL_ERROR, "Cannot transition to %S while %S", - state_get_cycle_pgmstr(cycle), - state_get_pgmstr(s.state)); - return; - } - - if (s.cycle != CYCLE_MACHINING && cycle != CYCLE_MACHINING) { - STATUS_ERROR(STAT_INTERNAL_ERROR, - "Cannot transition to cycle %S while in %S", - state_get_cycle_pgmstr(cycle), - state_get_cycle_pgmstr(s.cycle)); - return; - } - - s.cycle = cycle; - report_request(); -} - - void state_set_hold_reason(hold_reason_t reason) { if (s.hold_reason == reason) return; // No change s.hold_reason = reason; @@ -142,18 +102,10 @@ bool state_is_resuming() {return s.resume_requested;} bool state_is_quiescent() { return (state_get() == STATE_READY || state_get() == STATE_HOLDING) && - !st_is_busy() && !exec_is_busy(); -} - - -bool state_is_ready() { - return queue_get_room() && !state_is_resuming() && !s.pause; + !st_is_busy() && !command_is_busy(); } -void state_pause_queue(bool x) {s.pause = x;} - - void state_optional_pause() { if (s.optional_pause_requested) { state_set_hold_reason(HOLD_REASON_USER_PAUSE); @@ -176,27 +128,12 @@ void state_running() { } -void state_idle() { - if (state_get() == STATE_RUNNING) _set_state(STATE_READY); -} - - -void state_estop() { - _set_state(STATE_ESTOPPED); - state_pause_queue(false); -} - - +void state_idle() {if (state_get() == STATE_RUNNING) _set_state(STATE_READY);} +void state_estop() {_set_state(STATE_ESTOPPED);} void state_request_hold() {s.hold_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_resume() {if (s.flush_requested) s.resume_requested = true;} void state_request_optional_pause() {s.optional_pause_requested = true;} @@ -237,8 +174,7 @@ void state_callback() { // Only flush queue when idle or holding. if (s.flush_requested && state_is_quiescent()) { - - if (!queue_is_empty()) queue_flush(); + command_flush_queue(); // Stop spindle spindle_stop(); @@ -258,7 +194,7 @@ void state_callback() { if (state_get() == STATE_HOLDING) { // Check if any moves are buffered - if (!queue_is_empty()) _set_state(STATE_RUNNING); + if (command_get_count()) _set_state(STATE_RUNNING); else _set_state(STATE_READY); } } @@ -267,5 +203,4 @@ void state_callback() { // Var callbacks PGM_P get_state() {return state_get_pgmstr(state_get());} -PGM_P get_cycle() {return state_get_cycle_pgmstr(state_get_cycle());} PGM_P get_hold_reason() {return state_get_hold_reason_pgmstr(s.hold_reason);} diff --git a/src/avr/src/state.h b/src/avr/src/state.h index 5d42cc5..dac8776 100644 --- a/src/avr/src/state.h +++ b/src/avr/src/state.h @@ -3,8 +3,6 @@ This file is part of the Buildbotics firmware. Copyright (c) 2015 - 2017 Buildbotics LLC - Copyright (c) 2013 - 2015 Alden S. Hart, Jr. - Copyright (c) 2013 - 2015 Robert Giseburt All rights reserved. This file ("the software") is free software: you can redistribute it @@ -43,14 +41,6 @@ typedef enum { } state_t; -typedef enum { - CYCLE_MACHINING, - CYCLE_HOMING, - CYCLE_PROBING, - CYCLE_JOGGING, -} cycle_t; - - typedef enum { HOLD_REASON_USER_PAUSE, HOLD_REASON_PROGRAM_PAUSE, @@ -61,19 +51,14 @@ typedef enum { PGM_P state_get_pgmstr(state_t state); -PGM_P state_get_cycle_pgmstr(cycle_t cycle); PGM_P state_get_hold_reason_pgmstr(hold_reason_t reason); state_t state_get(); -cycle_t state_get_cycle(); -void state_set_cycle(cycle_t cycle); void state_set_hold_reason(hold_reason_t reason); bool state_is_flushing(); bool state_is_resuming(); bool state_is_quiescent(); -bool state_is_ready(); -void state_pause_queue(bool x); void state_optional_pause(); void state_holding(); diff --git a/src/avr/src/status.c b/src/avr/src/status.c index c791286..8351b5d 100644 --- a/src/avr/src/status.c +++ b/src/avr/src/status.c @@ -73,7 +73,7 @@ stat_t status_message_P(const char *location, status_level_t level, va_list args; // Type - printf_P(PSTR("\n{\"level\":\"%"PRPSTR"\", \"msg\":\""), + printf_P(PSTR("\n{\"level\":\"%"PRPSTR"\",\"msg\":\""), status_level_pgmstr(level)); // Message @@ -82,15 +82,15 @@ stat_t status_message_P(const char *location, status_level_t level, vfprintf_P(stdout, msg, args); va_end(args); - } else printf_P("%" PRPSTR, status_to_pgmstr(code)); + } else printf_P(PSTR("%" PRPSTR), status_to_pgmstr(code)); putchar('"'); // Code - if (code) printf_P(PSTR(", \"code\": %d"), code); + if (code) printf_P(PSTR(",\"code\":%d"), code); // Location - if (location) printf_P(PSTR(", \"where\": \"%"PRPSTR"\""), location); + if (location) printf_P(PSTR(",\"where\":\"%"PRPSTR"\""), location); putchar('}'); putchar('\n'); @@ -99,21 +99,6 @@ stat_t status_message_P(const char *location, status_level_t level, } -void status_help() { - putchar('{'); - - for (int i = 0; i < STAT_MAX; i++) { - if (i) putchar(','); - putchar('\n'); - printf_P(PSTR(" \"%d\": \"%"PRPSTR"\""), i, status_to_pgmstr(i)); - } - - putchar('\n'); - putchar('}'); - putchar('\n'); -} - - /// Alarm state; send an exception report and stop processing input stat_t status_alarm(const char *location, stat_t code, const char *msg) { status_message_P(location, STAT_LEVEL_ERROR, code, msg); diff --git a/src/avr/src/status.h b/src/avr/src/status.h index 5e2b91d..3cb5933 100644 --- a/src/avr/src/status.h +++ b/src/avr/src/status.h @@ -39,6 +39,7 @@ typedef enum { #include "messages.def" #undef STAT_MSG + STAT_MAX, STAT_DO_NOT_EXCEED = 255 // Do not exceed 255 } stat_t; @@ -58,7 +59,6 @@ const char *status_level_pgmstr(status_level_t level); stat_t status_error(stat_t code); stat_t status_message_P(const char *location, status_level_t level, stat_t code, const char *msg, ...); -void status_help(); /// Enter alarm state. returns same status code stat_t status_alarm(const char *location, stat_t status, const char *msg); diff --git a/src/avr/src/stepper.c b/src/avr/src/stepper.c index 660a51f..48d2ec8 100644 --- a/src/avr/src/stepper.c +++ b/src/avr/src/stepper.c @@ -3,8 +3,6 @@ This file is part of the Buildbotics firmware. Copyright (c) 2015 - 2017 Buildbotics LLC - Copyright (c) 2010 - 2015 Alden S. Hart, Jr. - Copyright (c) 2013 - 2015 Robert Giseburt All rights reserved. This file ("the software") is free software: you can redistribute it @@ -109,7 +107,7 @@ bool st_is_busy() {return st.busy;} /// ADC channel 0 triggered by load ISR as a "software" interrupt. ISR(STEP_LOW_LEVEL_ISR) { while (true) { - stat_t status = exec_next_action(); + stat_t status = exec_next(); switch (status) { case STAT_NOOP: st.busy = false; break; // No command executed @@ -204,7 +202,7 @@ ISR(STEP_TIMER_ISR) {_load_move();} void st_prep_line(float time, const float target[]) { - // Trap conditions that would prevent queueing the line + // Trap conditions that would prevent queuing the line ASSERT(!st.move_ready); ASSERT(isfinite(time)); @@ -214,7 +212,8 @@ void st_prep_line(float time, const float target[]) { // Prepare motor moves for (int motor = 0; motor < MOTORS; motor++) - motor_prep_move(motor, time, target[motor_get_axis(motor)]); + //if (motor_is_enabled(motor)) + motor_prep_move(motor, time, target[motor_get_axis(motor)]); st.move_queued = true; // signal prep buffer ready (do this last) } diff --git a/src/avr/src/stepper.h b/src/avr/src/stepper.h index 7b3ea9b..7fd52a0 100644 --- a/src/avr/src/stepper.h +++ b/src/avr/src/stepper.h @@ -3,8 +3,6 @@ This file is part of the Buildbotics firmware. Copyright (c) 2015 - 2017 Buildbotics LLC - Copyright (c) 2010 - 2015 Alden S. Hart, Jr. - Copyright (c) 2012 - 2015 Rob Giseburt All rights reserved. This file ("the software") is free software: you can redistribute it diff --git a/src/avr/src/switch.c b/src/avr/src/switch.c index 489c554..ca676b2 100644 --- a/src/avr/src/switch.c +++ b/src/avr/src/switch.c @@ -3,7 +3,6 @@ This file is part of the Buildbotics firmware. Copyright (c) 2015 - 2017 Buildbotics LLC - Copyright (c) 2010 - 2015 Alden S. Hart, Jr. All rights reserved. This file ("the software") is free software: you can redistribute it diff --git a/src/avr/src/switch.h b/src/avr/src/switch.h index aad8ad1..387dcdf 100644 --- a/src/avr/src/switch.h +++ b/src/avr/src/switch.h @@ -3,7 +3,6 @@ This file is part of the Buildbotics firmware. Copyright (c) 2015 - 2017 Buildbotics LLC - Copyright (c) 2010 - 2015 Alden S. Hart, Jr. All rights reserved. This file ("the software") is free software: you can redistribute it diff --git a/src/avr/src/type.c b/src/avr/src/type.c new file mode 100644 index 0000000..09724ae --- /dev/null +++ b/src/avr/src/type.c @@ -0,0 +1,188 @@ +/******************************************************************************\ + + This file is part of the Buildbotics firmware. + + Copyright (c) 2015 - 2017 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" + +\******************************************************************************/ + +#include "type.h" +#include "base64.h" + +#include +#include +#include +#include +#include + + +#define TYPEDEF(TYPE, DEF) \ + static const char TYPE##_name [] PROGMEM = "<" #TYPE ">"; \ + pstr type_get_##TYPE##_name_pgm() {return TYPE##_name;} + +#include "type.def" +#undef TYPEDEF + + +// String +bool type_eq_str(str a, str b) {return a == b;} +void type_print_str(str s) {printf_P(PSTR("\"%s\""), s);} +float type_str_to_float(str s) {return 0;} +str type_parse_str(const char *s) {return s;} + +// Program string +bool type_eq_pstr(pstr a, pstr b) {return a == b;} +void type_print_pstr(pstr s) {printf_P(PSTR("\"%"PRPSTR"\""), s);} +const char *type_parse_pstr(const char *value) {return value;} +float type_pstr_to_float(pstr s) {return 0;} + + +// Flags +bool type_eq_flags(flags a, flags b) {return a == b;} +float type_flags_to_float(flags x) {return x;} + + +void type_print_flags(flags x) { + extern void print_status_flags(flags x); + print_status_flags(x); +} + +flags type_parse_flags(const char *s) {return 0;} // Not used + + +// Float +bool type_eq_f32(float a, float b) {return a == b || (isnan(a) && isnan(b));} +float type_f32_to_float(float x) {return x;} + + +void type_print_f32(float x) { + if (isnan(x)) printf_P(PSTR("\"nan\"")); + else if (isinf(x)) printf_P(PSTR("\"%cinf\""), x < 0 ? '-' : '+'); + + else { + char buf[20]; + + int len = sprintf_P(buf, PSTR("%.3f"), x); + + // Remove trailing zeros + for (int i = len; 0 < i; i--) { + if (buf[i - 1] == '.') buf[i - 1] = 0; + else if (buf[i - 1] == '0') { + buf[i - 1] = 0; + continue; + } + + break; + } + + printf("%s", buf); + } +} + + +float type_parse_f32(const char *value) { + while (*value && isspace(*value)) value++; + + if (*value == ':') { + value++; + if (strnlen(value, 6) != 6) return NAN; + + float f; + return b64_decode_float(value, &f) ? f : NAN; + } + + return strtod(value, 0); +} + + +// bool +bool type_eq_bool(bool a, bool b) {return a == b;} +float type_bool_to_float(bool x) {return x;} +void type_print_bool(bool x) {printf_P(x ? PSTR("true") : PSTR("false"));} + + +bool type_parse_bool(const char *value) { + return !strcasecmp(value, "true") || type_parse_f32(value); +} + + +// s8 +bool type_eq_s8(s8 a, s8 b) {return a == b;} +float type_s8_to_float(s8 x) {return x;} +void type_print_s8(s8 x) {printf_P(PSTR("%"PRIi8), x);} +s8 type_parse_s8(const char *value) {return strtol(value, 0, 0);} + + +// u8 +bool type_eq_u8(u8 a, u8 b) {return a == b;} +float type_u8_to_float(u8 x) {return x;} +void type_print_u8(u8 x) {printf_P(PSTR("%"PRIu8), x);} +u8 type_parse_u8(const char *value) {return strtol(value, 0, 0);} + + +// u16 +bool type_eq_u16(u16 a, u16 b) {return a == b;} +float type_u16_to_float(u16 x) {return x;} +void type_print_u16(u16 x) {printf_P(PSTR("%"PRIu16), x);} +u16 type_parse_u16(const char *value) {return strtoul(value, 0, 0);} + + +// s32 +bool type_eq_s32(s32 a, s32 b) {return a == b;} +float type_s32_to_float(s32 x) {return x;} +void type_print_s32(s32 x) {printf_P(PSTR("%"PRIi32), x);} +s32 type_parse_s32(const char *value) {return strtol(value, 0, 0);} + + +type_u type_parse(type_t type, const char *s) { + type_u value; + + switch (type) { +#define TYPEDEF(TYPE, ...) \ + case TYPE_##TYPE: value._##TYPE = type_parse_##TYPE(s); break; +#include "type.def" +#undef TYPEDEF + } + + return value; +} + + +void type_print(type_t type, type_u value) { + switch (type) { +#define TYPEDEF(TYPE, ...) \ + case TYPE_##TYPE: type_print_##TYPE(value._##TYPE); break; +#include "type.def" +#undef TYPEDEF + } +} + + +float type_to_float(type_t type, type_u value) { + switch (type) { +#define TYPEDEF(TYPE, ...) \ + case TYPE_##TYPE: return type_##TYPE##_to_float(value._##TYPE); +#include "type.def" +#undef TYPEDEF + } + return 0; +} diff --git a/src/avr/src/queue.h b/src/avr/src/type.def similarity index 67% rename from src/avr/src/queue.h rename to src/avr/src/type.def index f375135..ab5447f 100644 --- a/src/avr/src/queue.h +++ b/src/avr/src/type.def @@ -25,32 +25,17 @@ \******************************************************************************/ -#pragma once - -#include "config.h" -#include "action.h" - -#include -#include - - -// Called by state.c -void queue_flush(); -bool queue_is_empty(); -int queue_get_room(); -int queue_get_fill(); - -void queue_push(action_t action); -void queue_push_float(action_t action, float value); -void queue_push_int(action_t action, int32_t value); -void queue_push_bool(action_t action, bool value); -void queue_push_pair(action_t action, int16_t left, int16_t right); - -void queue_pop(); - -action_t queue_head(); -float queue_head_float(); -int32_t queue_head_int(); -bool queue_head_bool(); -int16_t queue_head_left(); -int16_t queue_head_right(); +#ifdef bool +#undef bool +#endif + +// TYPE DEF +TYPEDEF(flags, uint16_t) +TYPEDEF(str, const char *) +TYPEDEF(pstr, PGM_P) +TYPEDEF(f32, float) +TYPEDEF(u8, uint8_t) +TYPEDEF(s8, int8_t) +TYPEDEF(u16, uint16_t) +TYPEDEF(s32, int32_t) +TYPEDEF(bool, _Bool) diff --git a/src/avr/src/type.h b/src/avr/src/type.h new file mode 100644 index 0000000..d568461 --- /dev/null +++ b/src/avr/src/type.h @@ -0,0 +1,67 @@ +/******************************************************************************\ + + This file is part of the Buildbotics firmware. + + Copyright (c) 2015 - 2017 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" + +\******************************************************************************/ + +#pragma once + +#include "pgmspace.h" + +#include + + +// Define types +#define TYPEDEF(TYPE, DEF) typedef DEF TYPE; +#include "type.def" +#undef TYPEDEF + +typedef enum { +#define TYPEDEF(TYPE, ...) TYPE_##TYPE, +#include "type.def" +#undef TYPEDEF +} type_t; + + +typedef union { +#define TYPEDEF(TYPE, ...) TYPE _##TYPE; +#include "type.def" +#undef TYPEDEF +} type_u; + + +// Define functions +#define TYPEDEF(TYPE, DEF) \ + pstr type_get_##TYPE##_name_pgm(); \ + bool type_eq_##TYPE(TYPE a, TYPE b); \ + TYPE type_parse_##TYPE(const char *s); \ + void type_print_##TYPE(TYPE x); \ + float type_##TYPE##_to_float(TYPE x); +#include "type.def" +#undef TYPEDEF + + +type_u type_parse(type_t type, const char *s); +void type_print(type_t type, type_u value); +float type_to_float(type_t type, type_u value); diff --git a/src/avr/src/usart.c b/src/avr/src/usart.c index 89f14dc..61d3d91 100644 --- a/src/avr/src/usart.c +++ b/src/avr/src/usart.c @@ -55,8 +55,10 @@ static void _set_dre_interrupt(bool enable) { static void _set_rxc_interrupt(bool enable) { if (enable) { + if (SERIAL_CTS_THRESH <= rx_buf_space()) + OUTCLR_PIN(SERIAL_CTS_PIN); // CTS Lo (enable) + SERIAL_PORT.CTRLA |= USART_RXCINTLVL_HI_gc; - if (4 <= rx_buf_space()) OUTCLR_PIN(SERIAL_CTS_PIN); // CTS Lo (enable) } else SERIAL_PORT.CTRLA &= ~USART_RXCINTLVL_HI_gc; } @@ -78,9 +80,9 @@ ISR(SERIAL_RXC_vect) { if (rx_buf_full()) _set_rxc_interrupt(false); // Disable interrupt else { - uint8_t data = SERIAL_PORT.DATA; - rx_buf_push(data); - if (rx_buf_space() < 4) OUTSET_PIN(SERIAL_CTS_PIN); // CTS Hi (disable) + rx_buf_push(SERIAL_PORT.DATA); + if (rx_buf_space() < SERIAL_CTS_THRESH) + OUTSET_PIN(SERIAL_CTS_PIN); // CTS Hi (disable) } } @@ -90,6 +92,7 @@ static int _usart_putchar(char c, FILE *f) { return 0; } + static FILE _stdout = FDEV_SETUP_STREAM(_usart_putchar, 0, _FDEV_SETUP_WRITE); @@ -180,8 +183,7 @@ void usart_puts(const char *s) {while (*s) usart_putc(*s++);} int8_t usart_getc() { while (rx_buf_empty()) continue; - uint8_t data = rx_buf_peek(); - rx_buf_pop(); + uint8_t data = rx_buf_next(); _set_rxc_interrupt(true); // Enable interrupt diff --git a/src/avr/src/usart.h b/src/avr/src/usart.h index 915d362..d806a7d 100644 --- a/src/avr/src/usart.h +++ b/src/avr/src/usart.h @@ -34,6 +34,7 @@ #define USART_TX_RING_BUF_SIZE 256 #define USART_RX_RING_BUF_SIZE 256 + enum { USART_BAUD_9600, USART_BAUD_19200, diff --git a/src/avr/src/util.c b/src/avr/src/util.c index 1a76542..a1acd90 100644 --- a/src/avr/src/util.c +++ b/src/avr/src/util.c @@ -3,7 +3,6 @@ This file is part of the Buildbotics firmware. Copyright (c) 2015 - 2017 Buildbotics LLC - Copyright (c) 2010 - 2015 Alden S. Hart, Jr. All rights reserved. This file ("the software") is free software: you can redistribute it @@ -28,7 +27,11 @@ #include "util.h" +#include "base64.h" + #include +#include +#include /// Fast inverse square root originally from Quake III Arena code. Original @@ -49,3 +52,24 @@ float invsqrt(float x) { return u.f; } + + +bool decode_float(char **s, float *f) { + bool ok = b64_decode_float(*s, f) && isfinite(*f); + *s += 6; + return ok; +} + + +stat_t decode_axes(char **cmd, float axes[AXES]) { + while (**cmd) { + const char *names = "xyzabc"; + const char *match = strchr(names, **cmd); + if (!match) break; + char *s = *cmd + 1; + if (!decode_float(&s, &axes[match - names])) return STAT_BAD_FLOAT; + *cmd = s; + } + + return STAT_OK; +} diff --git a/src/avr/src/util.h b/src/avr/src/util.h index 3ec44b8..b431f8a 100644 --- a/src/avr/src/util.h +++ b/src/avr/src/util.h @@ -3,7 +3,6 @@ This file is part of the Buildbotics firmware. Copyright (c) 2015 - 2017 Buildbotics LLC - Copyright (c) 2010 - 2014 Alden S. Hart, Jr. All rights reserved. This file ("the software") is free software: you can redistribute it @@ -30,6 +29,7 @@ #include "config.h" +#include "status.h" #include #include @@ -65,6 +65,9 @@ inline static bool fp_ZERO(float a) {return fabs(a) < EPSILON;} inline static bool fp_FALSE(float a) {return fp_ZERO(a);} inline static bool fp_TRUE(float a) {return !fp_ZERO(a);} +bool decode_float(char **s, float *f); +stat_t decode_axes(char **cmd, float axes[AXES]); + // Constants #define MM_PER_INCH 25.4 #define INCHES_PER_MM (1 / 25.4) diff --git a/src/avr/src/vars.c b/src/avr/src/vars.c index 8bda422..ae92830 100644 --- a/src/avr/src/vars.c +++ b/src/avr/src/vars.c @@ -27,159 +27,24 @@ #include "vars.h" -#include "cpp_magic.h" +#include "type.h" #include "status.h" #include "hardware.h" #include "config.h" #include "axis.h" -#include "pgmspace.h" +#include "cpp_magic.h" +#include "report.h" +#include "command.h" -#include -#include -#include #include -#include -#include -#include -#include - - -typedef uint16_t flags_t; -typedef const char *string; -typedef const PGM_P pstring; - +#include // Format strings static const char code_fmt[] PROGMEM = "\"%s\":"; static const char indexed_code_fmt[] PROGMEM = "\"%c%s\":"; -// Type names -static const char bool_name [] PROGMEM = ""; -#define TYPE_NAME(TYPE) static const char TYPE##_name [] PROGMEM = "<" #TYPE ">" -MAP(TYPE_NAME, SEMI, flags_t, string, pstring, float, uint8_t, uint16_t, - int32_t); - - -// Eq functions -#define EQ_FUNC(TYPE) \ - inline static bool var_eq_##TYPE(const TYPE a, const TYPE b) {return a == b;} -MAP(EQ_FUNC, SEMI, flags_t, string, pstring, uint8_t, uint16_t, int32_t, char); - - -// String -static void var_print_string(string s) {printf_P(PSTR("\"%s\""), s);} -static float var_string_to_float(string s) {return 0;} - - -// Program string -static void var_print_pstring(pstring s) {printf_P(PSTR("\"%"PRPSTR"\""), s);} -//static const char *var_parse_pstring(const char *value) {return value;} -static float var_pstring_to_float(pstring s) {return 0;} - - -// Flags -static void var_print_flags_t(flags_t x) { - extern void print_status_flags(flags_t x); - print_status_flags(x); -} - -static float var_flags_t_to_float(flags_t x) {return x;} - - -// Float -static bool var_eq_float(float a, float b) { - return a == b || (isnan(a) && isnan(b)); -} - - -static void var_print_float(float x) { - if (isnan(x)) printf_P(PSTR("\"nan\"")); - else if (isinf(x)) printf_P(PSTR("\"%cinf\""), x < 0 ? '-' : '+'); - - else { - char buf[20]; - - int len = sprintf_P(buf, PSTR("%.3f"), x); - - // Remove trailing zeros - for (int i = len; 0 < i; i--) { - if (buf[i - 1] == '.') buf[i - 1] = 0; - else if (buf[i - 1] == '0') { - buf[i - 1] = 0; - continue; - } - - break; - } - - printf("%s", buf); - } -} - - -static float var_parse_float(const char *value) {return strtod(value, 0);} -static float var_float_to_float(float x) {return x;} - - -// Bool -inline static bool var_eq_bool(float a, float b) {return a == b;} -static void var_print_bool(bool x) {printf_P(x ? PSTR("true") : PSTR("false"));} - - -bool var_parse_bool(const char *value) { - return !strcasecmp(value, "true") || var_parse_float(value); -} - -static float var_bool_to_float(bool x) {return x;} - - -// Char -#if 0 -static void var_print_char(char x) {putchar('"'); putchar(x); putchar('"');} -static char var_parse_char(const char *value) {return value[1];} -static float var_char_to_float(char x) {return x;} -#endif - - -// int8 -#if 0 -static void var_print_int8_t(int8_t x) {printf_P(PSTR("%"PRIi8), x);} -static int8_t var_parse_int8_t(const char *value) {return strtol(value, 0, 0);} -static float var_int8_t_to_float(int8_t x) {return x;} -#endif - -// uint8 -static void var_print_uint8_t(uint8_t x) {printf_P(PSTR("%"PRIu8), x);} - - -static uint8_t var_parse_uint8_t(const char *value) { - return strtol(value, 0, 0); -} - -static float var_uint8_t_to_float(uint8_t x) {return x;} - - -// unit16 -static void var_print_uint16_t(uint16_t x) { - printf_P(PSTR("%"PRIu16), x); -} - - -static uint16_t var_parse_uint16_t(const char *value) { - return strtoul(value, 0, 0); -} - - -static float var_uint16_t_to_float(uint16_t x) {return x;} - - -// int32 -static void var_print_int32_t(int32_t x) {printf_P(PSTR("%"PRIi32), x);} -static float var_int32_t_to_float(int32_t x) {return x;} - - -// Ensure no code is used more than once +// Ensure no var code is used more than once enum { #define VAR(NAME, CODE, ...) var_code_##CODE, #include "vars.def" @@ -187,6 +52,7 @@ enum { var_code_count }; + // Var forward declarations #define VAR(NAME, CODE, TYPE, INDEX, SET, ...) \ TYPE get_##NAME(IF(INDEX)(int index)); \ @@ -196,6 +62,40 @@ enum { #include "vars.def" #undef VAR + +// Set callback union +typedef union { +#define TYPEDEF(TYPE, ...) void (*set_##TYPE)(TYPE); +#include "type.def" +#undef TYPEDEF + +#define TYPEDEF(TYPE, ...) void (*set_##TYPE##_index)(int i, TYPE); +#include "type.def" +#undef TYPEDEF +} set_cb_u; + + +// Get callback union +typedef union { +#define TYPEDEF(TYPE, ...) TYPE (*get_##TYPE)(); +#include "type.def" +#undef TYPEDEF + +#define TYPEDEF(TYPE, ...) TYPE (*get_##TYPE##_index)(int i); +#include "type.def" +#undef TYPEDEF +} get_cb_u; + + +typedef struct { + type_t type; + char name[5]; + int8_t index; + get_cb_u get; + set_cb_u set; +} var_info_t; + + // Var names & help #define VAR(NAME, CODE, TYPE, INDEX, SET, REPORT, HELP) \ static const char NAME##_name[] PROGMEM = #NAME; \ @@ -204,6 +104,7 @@ enum { #include "vars.def" #undef VAR + // Last value #define VAR(NAME, CODE, TYPE, INDEX, ...) \ static TYPE NAME##_state IF(INDEX)([INDEX]); @@ -211,6 +112,7 @@ enum { #include "vars.def" #undef VAR + // Report static uint8_t _report_var[(var_code_count >> 3) + 1] = {0,}; @@ -266,7 +168,7 @@ void vars_report(bool full) { TYPE last = (NAME##_state)IF(INDEX)([i]); \ bool report = _get_report_var(var_code_##CODE); \ \ - if ((report && !var_eq_##TYPE(value, last)) || full) { \ + if ((report && !type_eq_##TYPE(value, last)) || full) { \ (NAME##_state)IF(INDEX)([i]) = value; \ \ if (!reported) { \ @@ -278,7 +180,7 @@ void vars_report(bool full) { (IF_ELSE(INDEX)(indexed_code_fmt, code_fmt), \ IF(INDEX)(INDEX##_LABEL[i],) #CODE); \ \ - var_print_##TYPE(value); \ + type_print_##TYPE(value); \ } \ } @@ -291,7 +193,6 @@ void vars_report(bool full) { hw_restore_watchdog(wd_state); } - void vars_report_all(bool enable) { #define VAR(NAME, CODE, TYPE, INDEX, SET, REPORT, ...) \ _set_report_var(var_code_##CODE, enable); @@ -332,22 +233,28 @@ static char *_resolve_name(const char *_name) { } -bool vars_print(const char *_name) { +static bool _find_var(const char *_name, var_info_t *info) { char *name = _resolve_name(_name); if (!name) return false; - int i; -#define VAR(NAME, CODE, TYPE, INDEX, ...) \ + int i = -1; + memset(info, 0, sizeof(var_info_t)); + strcpy(info->name, name); + +#define VAR(NAME, CODE, TYPE, INDEX, SET, ...) \ if (!strcmp(IF_ELSE(INDEX)(name + 1, name), #CODE)) { \ IF(INDEX) \ (i = strchr(INDEX##_LABEL, name[0]) - INDEX##_LABEL; \ if (INDEX <= i) return false); \ \ - printf("{\"%s\":", _name); \ - var_print_##TYPE(get_##NAME(IF(INDEX)(i))); \ - putchar('}'); \ + info->type = TYPE_##TYPE; \ + info->index = i; \ + info->get.IF_ELSE(INDEX)(get_##TYPE##_index, get_##TYPE) = get_##NAME; \ + \ + IF(SET)(info->set.IF_ELSE(INDEX) \ + (set_##TYPE##_index, set_##TYPE) = set_##NAME;) \ \ - return true; \ + return true; \ } #include "vars.def" @@ -357,85 +264,63 @@ bool vars_print(const char *_name) { } -bool vars_set(const char *_name, const char *value) { - char *name = _resolve_name(_name); - if (!name) return false; - - int i; -#define VAR(NAME, CODE, TYPE, INDEX, SET, ...) \ - IF(SET) \ - (if (!strcmp(IF_ELSE(INDEX)(name + 1, name), #CODE)) { \ - IF(INDEX) \ - (i = strchr(INDEX##_LABEL, name[0]) - INDEX##_LABEL; \ - if (INDEX <= i) return false); \ - \ - TYPE x = var_parse_##TYPE(value); \ - set_##NAME(IF(INDEX)(i,) x); \ - \ - return true; \ - }) \ +static type_u _get(type_t type, int8_t index, get_cb_u cb) { + type_u value; -#include "vars.def" -#undef VAR + switch (type) { +#define TYPEDEF(TYPE, ...) \ + case TYPE_##TYPE: \ + if (index == -1) value._##TYPE = cb.get_##TYPE(); \ + value._##TYPE = cb.get_##TYPE##_index(index); \ + break; +#include "type.def" +#undef TYPEDEF + } - return false; + return value; } -float vars_get_number(const char *_name) { - char *name = _resolve_name(_name); - if (!name) return 0; +static void _set(type_t type, int8_t index, set_cb_u cb, type_u value) { + switch (type) { +#define TYPEDEF(TYPE, ...) \ + case TYPE_##TYPE: \ + if (index == -1) cb.set_##TYPE(value._##TYPE); \ + else cb.set_##TYPE##_index(index, value._##TYPE); \ + break; +#include "type.def" +#undef TYPEDEF + } +} - int i; -#define VAR(NAME, CODE, TYPE, INDEX, SET, ...) \ - if (!strcmp(IF_ELSE(INDEX)(name + 1, name), #CODE)) { \ - IF(INDEX) \ - (i = strchr(INDEX##_LABEL, name[0]) - INDEX##_LABEL; \ - if (INDEX <= i) return 0); \ - \ - TYPE x = get_##NAME(IF(INDEX)(i)); \ - return var_##TYPE##_to_float(x); \ - } \ -#include "vars.def" -#undef VAR +bool vars_print(const char *name) { + var_info_t info; + if (!_find_var(name, &info)) return false; + + printf("{\"%s\":", info.name); + type_print(info.type, _get(info.type, info.index, info.get)); + putchar('}'); + putchar('\n'); - return 0; + return true; } -int vars_parser(char *vars) { - if (!*vars || *vars != '{') return STAT_OK; - vars++; - - while (*vars) { - while (isspace(*vars)) vars++; - if (*vars == '}') return STAT_OK; - if (*vars != '"') return STAT_COMMAND_NOT_ACCEPTED; - - // Parse name - vars++; // Skip " - const char *name = vars; - while (*vars && *vars != '"') vars++; - *vars++ = 0; - - while (isspace(*vars)) vars++; - if (*vars != ':') return STAT_COMMAND_NOT_ACCEPTED; - vars++; - while (isspace(*vars)) vars++; - - // Parse value - const char *value = vars; - while (*vars && *vars != ',' && *vars != '}') vars++; - if (*vars) { - char c = *vars; - *vars++ = 0; - vars_set(name, value); - if (c == '}') break; - } - } +bool vars_set(const char *name, const char *value) { + var_info_t info; + if (!_find_var(name, &info)) return false; - return STAT_OK; + _set(info.type, info.index, info.set, type_parse(info.type, value)); + + return true; +} + + +float vars_get_number(const char *name) { + var_info_t info; + if (!_find_var(name, &info)) return 0; + return type_to_float(info.type, _get(info.type, info.index, info.get)); } @@ -446,11 +331,82 @@ void vars_print_help() { // Save and disable watchdog uint8_t wd_state = hw_disable_watchdog(); -#define VAR(NAME, CODE, TYPE, ...) \ - printf_P(fmt, #CODE, NAME##_name, TYPE##_name, NAME##_help); +#define VAR(NAME, CODE, TYPE, ...) \ + printf_P(fmt, #CODE, NAME##_name, type_get_##TYPE##_name_pgm(), NAME##_help); #include "vars.def" #undef VAR // Restore watchdog hw_restore_watchdog(wd_state); } + + +// Command callbacks +stat_t command_var(char *cmd) { + cmd++; // Skip command code + + if (*cmd == '$' && !cmd[1]) { + report_request_full(); + return STAT_OK; + } + + // Get or set variable + char *value = strchr(cmd, '='); + if (value) { + *value++ = 0; + if (vars_set(cmd, value)) return STAT_OK; + + } else if (vars_print(cmd)) return STAT_OK; + + STATUS_ERROR(STAT_UNRECOGNIZED_NAME, "'%s'", cmd); + return STAT_UNRECOGNIZED_NAME; +} + + +typedef struct { + type_t type; + int8_t index; + set_cb_u set; + type_u value; +} var_cmd_t; + + +stat_t command_sync_var(char *cmd) { + // Get value + char *value = strchr(cmd + 1, '='); + if (!value) return STAT_INVALID_COMMAND; + *value++ = 0; + + var_info_t info; + if (!_find_var(cmd + 1, &info)) return STAT_UNRECOGNIZED_NAME; + + var_cmd_t buffer; + + buffer.type = info.type; + buffer.index = info.index; + buffer.set = info.set; + buffer.value = type_parse(info.type, value); + + command_push(*cmd, &buffer); + + return STAT_OK; +} + + +unsigned command_sync_var_size() {return sizeof(var_cmd_t);} + + +void command_sync_var_exec(char *data) { + var_cmd_t *buffer = (var_cmd_t *)data; + _set(buffer->type, buffer->index, buffer->set, buffer->value); +} + + +stat_t command_report(char *cmd) { + bool enable = cmd[1] != '0'; + + if (cmd[2]) vars_report_var(cmd + 2, enable); + else vars_report_all(enable); + + return STAT_OK; +} diff --git a/src/avr/src/vars.def b/src/avr/src/vars.def index c586995..59f9d1c 100644 --- a/src/avr/src/vars.def +++ b/src/avr/src/vars.def @@ -29,104 +29,103 @@ #define MOTORS_LABEL "0123" #define OUTS_LABEL "ed12f" -// VAR(name, code, type, index, settable, report, help) +// VAR(name, code, type, index, settable, report, help) // Motor -VAR(motor_axis, an, uint8_t, MOTORS, 1, 1, "Maps motor to axis") -VAR(step_angle, sa, float, MOTORS, 1, 1, "In degrees per full step") -VAR(travel, tr, float, MOTORS, 1, 1, "Travel in mm per revolution") -VAR(microstep, mi, uint16_t, MOTORS, 1, 1, "Microsteps per full step") -VAR(reverse, rv, uint8_t, MOTORS, 1, 1, "Reverse motor polarity") - -VAR(power_mode, pm, uint8_t, MOTORS, 1, 1, "Motor power mode") -VAR(drive_current, dc, float, MOTORS, 1, 1, "Max motor drive current") -VAR(idle_current, ic, float, MOTORS, 1, 1, "Motor idle current") -VAR(active_current, ac, float, MOTORS, 0, 1, "Motor current now") -VAR(driver_flags, df, uint16_t, MOTORS, 0, 1, "Motor driver status flags") -VAR(status_strings, ds, flags_t, MOTORS, 0, 1, "Motor driver status strings") -VAR(encoder, en, int32_t, MOTORS, 0, 0, "Motor encoder") -VAR(error, ee, int32_t, MOTORS, 0, 0, "Motor position error") - -VAR(motor_fault, fa, bool, 0, 0, 1, "Motor fault status") - -VAR(velocity_max, vm, float, MOTORS, 1, 1, "Maxium velocity in mm/min") -VAR(jerk_max, jm, float, MOTORS, 1, 1, "Maxium jerk in mm/min^3") -VAR(radius, ra, float, MOTORS, 1, 1, "Axis radius or zero") +VAR(motor_axis, an, u8, MOTORS, 1, 1, "Maps motor to axis") +VAR(step_angle, sa, f32, MOTORS, 1, 1, "In degrees per full step") +VAR(travel, tr, f32, MOTORS, 1, 1, "Travel in mm/rev") +VAR(microstep, mi, u16, MOTORS, 1, 1, "Microsteps per full step") +VAR(reverse, rv, u8, MOTORS, 1, 1, "Reverse motor polarity") + +VAR(power_mode, pm, u8, MOTORS, 1, 1, "Motor power mode") +VAR(drive_current, dc, f32, MOTORS, 1, 1, "Max motor drive current") +VAR(idle_current, ic, f32, MOTORS, 1, 1, "Motor idle current") +VAR(active_current, ac, f32, MOTORS, 0, 1, "Motor current now") +VAR(driver_flags, df, u16, MOTORS, 0, 1, "Motor driver flags") +VAR(status_strings, ds, flags, MOTORS, 0, 1, "Motor driver status") +VAR(encoder, en, s32, MOTORS, 0, 0, "Motor encoder") +VAR(error, ee, s32, MOTORS, 0, 0, "Motor position error") + +VAR(motor_fault, fa, bool, 0, 0, 1, "Motor fault status") + +VAR(velocity_max, vm, f32, MOTORS, 1, 1, "Maxium vel in mm/min") +VAR(jerk_max, jm, f32, MOTORS, 1, 1, "Maxium jerk in mm/min^3") +VAR(radius, ra, f32, MOTORS, 1, 1, "Axis radius or zero") // Switches -VAR(travel_min, tn, float, MOTORS, 1, 1, "Minimum soft limit") -VAR(travel_max, tm, float, MOTORS, 1, 1, "Maximum soft limit") -VAR(min_sw_mode, ls, uint8_t, MOTORS, 1, 1, "Minimum switch mode") -VAR(max_sw_mode, xs, uint8_t, MOTORS, 1, 1, "Maximum switch mode") -VAR(estop_mode, et, uint8_t, 0, 1, 1, "Estop switch mode") -VAR(probe_mode, pt, uint8_t, 0, 1, 1, "Probe switch mode") -VAR(min_switch, lw, uint8_t, MOTORS, 0, 1, "Minimum switch state") -VAR(max_switch, xw, uint8_t, MOTORS, 0, 1, "Maximum switch state") -VAR(estop_switch, ew, uint8_t, 0, 0, 1, "Estop switch state") -VAR(probe_switch, pw, uint8_t, 0, 0, 1, "Probe switch state") +VAR(travel_min, tn, f32, MOTORS, 1, 1, "Minimum soft limit") +VAR(travel_max, tm, f32, MOTORS, 1, 1, "Maximum soft limit") +VAR(min_sw_mode, ls, u8, MOTORS, 1, 1, "Minimum switch mode") +VAR(max_sw_mode, xs, u8, MOTORS, 1, 1, "Maximum switch mode") +VAR(estop_mode, et, u8, 0, 1, 1, "Estop switch mode") +VAR(probe_mode, pt, u8, 0, 1, 1, "Probe switch mode") +VAR(min_switch, lw, u8, MOTORS, 0, 1, "Minimum switch state") +VAR(max_switch, xw, u8, MOTORS, 0, 1, "Maximum switch state") +VAR(estop_switch, ew, u8, 0, 0, 1, "Estop switch state") +VAR(probe_switch, pw, u8, 0, 0, 1, "Probe switch state") // Homing -VAR(homing_mode, ho, uint8_t, MOTORS, 1, 1, "Homing type") -VAR(homing_dir, hd, float, MOTORS, 0, 1, "Homing direction") -VAR(home, hp, float, MOTORS, 0, 1, "Home position") -VAR(homed, h, bool, MOTORS, 1, 1, "True if axis is homed") -VAR(search_velocity,sv, float, MOTORS, 1, 1, "Homing search velocity") -VAR(latch_velocity, lv, float, MOTORS, 1, 1, "Homing latch velocity") -VAR(latch_backoff, lb, float, MOTORS, 1, 1, "Homing latch backoff") -VAR(zero_backoff, zb, float, MOTORS, 1, 1, "Homing zero backoff") +VAR(homing_mode, ho, u8, MOTORS, 1, 1, "Homing type") +VAR(homing_dir, hd, f32, MOTORS, 0, 1, "Homing direction") +VAR(home, hp, f32, MOTORS, 0, 1, "Home position") +VAR(homed, h, bool, MOTORS, 1, 1, "True if axis is homed") +VAR(search_velocity, sv, f32, MOTORS, 1, 1, "Homing search velocity") +VAR(latch_velocity, lv, f32, MOTORS, 1, 1, "Homing latch velocity") +VAR(latch_backoff, lb, f32, MOTORS, 1, 1, "Homing latch backoff") +VAR(zero_backoff, zb, f32, MOTORS, 1, 1, "Homing zero backoff") // Axis -VAR(axis_mach_coord, p, float, AXES, 1, 1, "Axis machine coordinate") -VAR(axis_can_home, ch, bool, AXES, 0, 1, "Is axis configured for homing") +VAR(axis_position, p, f32, AXES, 1, 1, "Axis position") +VAR(axis_can_home, ch, bool, AXES, 0, 1, "Axis can home") // Outputs -VAR(output_state, os, uint8_t, OUTS, 0, 1, "Output pin state") -VAR(output_mode, om, uint8_t, OUTS, 1, 1, "Output pin mode") +VAR(output_state, os, u8, OUTS, 0, 1, "Output pin state") +VAR(output_mode, om, u8, OUTS, 1, 1, "Output pin mode") // Spindle -VAR(spindle_type, st, uint8_t, 0, 1, 1, "PWM=0 or HUANYANG=1") -VAR(spin_reversed, sr, bool, 0, 1, 1, "Reverse spin") -VAR(max_spin, sx, float, 0, 1, 1, "Maximum spindle speed") -VAR(min_spin, sm, float, 0, 1, 1, "Minimum spindle speed") -VAR(spin_min_duty, nd, float, 0, 1, 1, "Minimum PWM duty cycle") -VAR(spin_max_duty, md, float, 0, 1, 1, "Maximum PWM duty cycle") -VAR(spin_up, su, float, 0, 1, 1, "Spin up velocity") -VAR(spin_down, sd, float, 0, 1, 1, "Spin down velocity") -VAR(spin_freq, sf, uint16_t, 0, 1, 1, "Spindle PWM frequency") +VAR(spindle_type, st, u8, 0, 1, 1, "PWM=0 or HUANYANG=1") +VAR(spin_reversed, sr, bool, 0, 1, 1, "Reverse spin") +VAR(max_spin, sx, f32, 0, 1, 1, "Maximum spindle speed") +VAR(min_spin, sm, f32, 0, 1, 1, "Minimum spindle speed") +VAR(spin_min_duty, nd, f32, 0, 1, 1, "Minimum PWM duty cycle") +VAR(spin_max_duty, md, f32, 0, 1, 1, "Maximum PWM duty cycle") +VAR(spin_up, su, f32, 0, 1, 1, "Spin up velocity") +VAR(spin_down, sd, f32, 0, 1, 1, "Spin down velocity") +VAR(spin_freq, sf, u16, 0, 1, 1, "Spindle PWM frequency") // PWM spindle -VAR(pwm_invert, pi, bool, 0, 1, 1, "Inverted spindle PWM") +VAR(pwm_invert, pi, bool, 0, 1, 1, "Inverted spindle PWM") // Huanyang spindle -VAR(huanyang_id, hi, uint8_t, 0, 1, 1, "Huanyang ID") -VAR(huanyang_freq, hz, float, 0, 0, 1, "Huanyang actual freq") -VAR(huanyang_current, hc, float, 0, 0, 1, "Huanyang actual current") -VAR(huanyang_rpm, hr, uint16_t, 0, 0, 1, "Huanyang actual RPM") -VAR(huanyang_temp, ht, uint16_t, 0, 0, 1, "Huanyang temperature") -VAR(huanyang_max_freq, hx, float, 0, 0, 1, "Huanyang max freq") -VAR(huanyang_min_freq, hm, float, 0, 0, 1, "Huanyang min freq") -VAR(huanyang_rated_rpm, hq, uint16_t, 0, 0, 1, "Huanyang rated RPM") -VAR(huanyang_status, hs, uint8_t, 0, 0, 1, "Huanyang status flags") -VAR(huanyang_debug, hb, bool, 0, 1, 1, "Huanyang debugging") -VAR(huanyang_connected, he, bool, 0, 0, 1, "Huanyang connected") +VAR(hy_id, hi, u8, 0, 1, 1, "Huanyang ID") +VAR(hy_freq, hz, f32, 0, 0, 1, "Huanyang actual freq") +VAR(hy_current, hc, f32, 0, 0, 1, "Huanyang actual current") +VAR(hy_rpm, hr, u16, 0, 0, 1, "Huanyang actual RPM") +VAR(hy_temp, ht, u16, 0, 0, 1, "Huanyang temperature") +VAR(hy_max_freq, hx, f32, 0, 0, 1, "Huanyang max freq") +VAR(hy_min_freq, hm, f32, 0, 0, 1, "Huanyang min freq") +VAR(hy_rated_rpm, hq, u16, 0, 0, 1, "Huanyang rated RPM") +VAR(hy_status, hs, u8, 0, 0, 1, "Huanyang status flags") +VAR(hy_debug, hb, bool, 0, 1, 1, "Huanyang debugging") +VAR(hy_connected, he, bool, 0, 0, 1, "Huanyang connected") // Machine state -VAR(line, ln, int32_t, 0, 0, 1, "Last line executed") -VAR(speed, s, float, 0, 1, 1, "Current spindle speed") -VAR(tool, t, uint8_t, 0, 1, 1, "Current tool") -VAR(feed_override, fo, float, 0, 1, 1, "Feed rate override") -VAR(speed_override, so, float, 0, 1, 1, "Spindle speed override") -VAR(mist_coolant, mc, bool, 0, 1, 1, "Mist coolant enabled") -VAR(flood_coolant, fc, bool, 0, 1, 1, "Flood coolant enabled") +VAR(line, ln, s32, 0, 0, 1, "Last line executed") +VAR(speed, s, f32, 0, 1, 1, "Current spindle speed") +VAR(tool, t, u8, 0, 1, 1, "Current tool") +VAR(feed_override, fo, f32, 0, 1, 1, "Feed rate override") +VAR(speed_override, so, f32, 0, 1, 1, "Spindle speed override") +VAR(mist_coolant, mc, bool, 0, 1, 1, "Mist coolant enabled") +VAR(flood_coolant, fc, bool, 0, 1, 1, "Flood coolant enabled") // System -VAR(velocity, v, float, 0, 0, 1, "Current velocity") -VAR(acceleration, a, float, 0, 0, 1, "Current acceleration") -VAR(jerk, j, float, 0, 0, 1, "Current jerk") -VAR(hw_id, id, string, 0, 0, 1, "Hardware ID") -VAR(echo, ec, bool, 0, 1, 1, "Enable or disable echo") -VAR(estop, es, bool, 0, 1, 1, "Emergency stop") -VAR(estop_reason, er, pstring, 0, 0, 1, "Emergency stop reason") -VAR(state, x, pstring, 0, 0, 1, "Machine state") -VAR(cycle, c, pstring, 0, 0, 1, "Machine cycle") -VAR(hold_reason, pr, pstring, 0, 0, 1, "Machine pause reason") +VAR(velocity, v, f32, 0, 0, 1, "Current velocity") +VAR(acceleration, a, f32, 0, 0, 1, "Current acceleration") +VAR(jerk, j, f32, 0, 0, 1, "Current jerk") +VAR(hw_id, id, str, 0, 0, 1, "Hardware ID") +VAR(echo, ec, bool, 0, 1, 1, "Enable or disable echo") +VAR(estop, es, bool, 0, 1, 1, "Emergency stop") +VAR(estop_reason, er, pstr, 0, 0, 1, "Emergency stop reason") +VAR(state, x, pstr, 0, 0, 1, "Machine state") +VAR(hold_reason, pr, pstr, 0, 0, 1, "Machine pause reason") diff --git a/src/avr/src/vars.h b/src/avr/src/vars.h index 4ad37b7..abafea4 100644 --- a/src/avr/src/vars.h +++ b/src/avr/src/vars.h @@ -31,6 +31,8 @@ #include + +float var_decode_float(const char *value); bool var_parse_bool(const char *value); void vars_init(); @@ -41,5 +43,4 @@ void vars_report_var(const char *code, bool enable); bool vars_print(const char *name); bool vars_set(const char *name, const char *value); float vars_get_number(const char *name); -int vars_parser(char *vars); void vars_print_help(); diff --git a/src/jade/templates/control-view.jade b/src/jade/templates/control-view.jade index d69fef8..c1ae4a3 100644 --- a/src/jade/templates/control-view.jade +++ b/src/jade/templates/control-view.jade @@ -76,7 +76,7 @@ script#control-view-template(type="text/x-template") td(:class="{attention: highlight_reason()}") {{get_state()}} td tr - th Reason + th Message td.reason(:class="{attention: highlight_reason()}") {{get_reason()}} td tr @@ -134,8 +134,7 @@ script#control-view-template(type="text/x-template") button.pure-button( title="{{state.x == 'RUNNING' ? 'Pause' : 'Start'}} program.", - @click="start_pause", - :disabled="state.c == 'HOMING' || !file") + @click="start_pause", :disabled="!file") .fa(:class="state.x == 'RUNNING' ? 'fa-pause' : 'fa-play'") button.pure-button(title="Stop program.", @click="stop", @@ -143,7 +142,7 @@ script#control-view-template(type="text/x-template") .fa.fa-stop button.pure-button(title="Pause program at next optional stop (M1).", - @click="optional_pause", :disabled="state.c == 'HOMING'") + @click="optional_pause") .fa.fa-stop-circle-o button.pure-button(title="Execute one program step.", @click="step", diff --git a/src/js/control-view.js b/src/js/control-view.js index 3b92f09..96107fc 100644 --- a/src/js/control-view.js +++ b/src/js/control-view.js @@ -81,11 +81,7 @@ module.exports = { methods: { - get_state: function () { - var state = this.state.x || ''; - if (state == 'RUNNING' && this.state.c) state = this.state.c; - return state; - }, + get_state: function () {return this.state.x || ''}, get_reason: function () { diff --git a/src/pwr/Makefile b/src/pwr/Makefile index 69a69d3..b3c9ce2 100644 --- a/src/pwr/Makefile +++ b/src/pwr/Makefile @@ -20,8 +20,10 @@ LDFLAGS += $(COMMON) -Wl,-u,vfprintf -lprintf_flt -lm LIBS += -lm # Programming flags +ifndef (PROGRAMMER) PROGRAMMER = avrispmkII #PROGRAMMER = jtag3pdi +endif PDEV = usb AVRDUDE_OPTS = -c $(PROGRAMMER) -p $(MCU) -P $(PDEV) diff --git a/src/py/bbctrl/AVR.py b/src/py/bbctrl/AVR.py index 9381039..08df973 100644 --- a/src/py/bbctrl/AVR.py +++ b/src/py/bbctrl/AVR.py @@ -6,23 +6,10 @@ import logging from collections import deque import bbctrl - +import bbctrl.Cmd as Cmd log = logging.getLogger('AVR') -# These constants must be kept in sync with i2c.h from the AVR code -I2C_NULL = 0 -I2C_ESTOP = 1 -I2C_CLEAR = 2 -I2C_PAUSE = 3 -I2C_OPTIONAL_PAUSE = 4 -I2C_RUN = 5 -I2C_STEP = 6 -I2C_FLUSH = 7 -I2C_REPORT = 8 -I2C_REBOOT = 9 - - machine_state_vars = ''' xp yp zp ap bp cp u s f t fm pa cs ao pc dm ad fo so mc fc '''.split() @@ -43,6 +30,7 @@ axis_homing_procedure = ''' G28.3 %(axis)s[#<%(axis)s.hp>] ''' + class AVR(): def __init__(self, ctrl): self.ctrl = ctrl @@ -75,7 +63,8 @@ class AVR(): if self.stream is not None: raise Exception('Busy, cannot start new GCode file') - self.stream = bbctrl.GCodeStream(path) + log.info('Running ' + path) + self.stream = bbctrl.Planner(self.ctrl, path) self.set_write(True) @@ -98,8 +87,9 @@ class AVR(): def _i2c_command(self, cmd, byte = None, word = None): - log.info('I2C: %d' % cmd) + log.info('I2C: ' + cmd) retry = 5 + cmd = ord(cmd[0]) while True: try: @@ -131,11 +121,11 @@ class AVR(): self.queue_command('$$') # Refresh all vars, must come after above - def report(self): self._i2c_command(I2C_REPORT) + def report(self): self._i2c_command(Cmd.REPORT) def load_next_command(self, cmd): - log.info('Serial: ' + cmd) + log.info('< ' + cmd) self.command = bytes(cmd.strip() + '\n', 'utf-8') @@ -148,8 +138,11 @@ class AVR(): def serial_handler(self, fd, events): - if self.ctrl.ioloop.READ & events: self.serial_read() - if self.ctrl.ioloop.WRITE & events: self.serial_write() + try: + 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) def serial_write(self): @@ -201,6 +194,8 @@ class AVR(): self.in_buf = self.in_buf[i + 1:] if line: + log.info('> ' + line) + try: msg = json.loads(line) @@ -254,12 +249,7 @@ class AVR(): def _update_lcd(self, msg): - if 'x' in msg or 'c' in msg: - v = self.vars - state = v.get('x', 'INIT') - if 'c' in v and state == 'RUNNING': state = v['c'] - - self.lcd_page.text('%-9s' % state, 0, 0) + if 'x' in msg: self.lcd_page.text('%-9s' % self.vars['x'], 0, 0) # Show enabled axes row = 0 @@ -293,10 +283,10 @@ class AVR(): def jog(self, axes): if self.stream is not None: raise Exception('Busy, cannot jog') - # TODO jogging via I2C + _axes = {} + for i in range(len(axes)): _axes["xyzabc"[i]] = axes[i] - axes = ["{:6.5f}".format(x) for x in axes] - self.queue_command('$jog ' + ' '.join(axes)) + self.queue_command(Cmd.jog(_axes)) def set(self, index, code, value): @@ -321,8 +311,8 @@ class AVR(): self.queue_command(line.strip()) - def estop(self): self._i2c_command(I2C_ESTOP) - def clear(self): self._i2c_command(I2C_CLEAR) + def estop(self): self._i2c_command(Cmd.ESTOP) + def clear(self): self._i2c_command(Cmd.CLEAR) def start(self, path): @@ -330,25 +320,25 @@ class AVR(): if path: self._start_sending_gcode(path) - self._i2c_command(I2C_RUN) + self._i2c_command(Cmd.RUN) def step(self, path): - self._i2c_command(I2C_STEP) + self._i2c_command(Cmd.STEP) if self.stream is None and path and self.vars.get('x', '') == 'READY': self._start_sending_gcode(path) def stop(self): - self._i2c_command(I2C_FLUSH) + self._i2c_command(Cmd.FLUSH) self._stop_sending_gcode() # Resume processing once current queue of GCode commands has flushed - self.queue_command('$resume') + self.queue_command('c') - def pause(self): self._i2c_command(I2C_PAUSE) - def unpause(self): self._i2c_command(I2C_RUN) - def optional_pause(self): self._i2c_command(I2C_OPTIONAL_PAUSE) + def pause(self): self._i2c_command(Cmd.PAUSE, byte = 0) + def unpause(self): self._i2c_command(Cmd.RUN) + def optional_pause(self): self._i2c_command(Cmd.PAUSE, byte = 1) def set_position(self, axis, position): diff --git a/src/py/bbctrl/Cmd.py b/src/py/bbctrl/Cmd.py new file mode 100644 index 0000000..9d05d1e --- /dev/null +++ b/src/py/bbctrl/Cmd.py @@ -0,0 +1,56 @@ +import struct +import base64 +import logging + +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' + + +def encode_float(x): + return base64.b64encode(struct.pack('> 1; + if (value < data[mid][0]) + high = mid; + else + low = mid + 1; + } + return low; + } + }; + + /** + * Initialises a new TimeSeries with optional data options. + * + * Options are of the form (defaults shown): + * + *
+   * {
+   *   resetBounds: true,        // enables/disables automatic scaling of the y-axis
+   *   resetBoundsInterval: 3000 // the period between scaling calculations, in millis
+   * }
+   * 
+ * + * Presentation options for TimeSeries are specified as an argument to SmoothieChart.addTimeSeries. + * + * @constructor + */ + function TimeSeries(options) { + this.options = Util.extend({}, TimeSeries.defaultOptions, options); + this.disabled = false; + this.clear(); + } + + TimeSeries.defaultOptions = { + resetBoundsInterval: 3000, + resetBounds: true + }; + + /** + * Clears all data and state from this TimeSeries object. + */ + TimeSeries.prototype.clear = function() { + this.data = []; + this.maxValue = Number.NaN; // The maximum value ever seen in this TimeSeries. + this.minValue = Number.NaN; // The minimum value ever seen in this TimeSeries. + }; + + /** + * Recalculate the min/max values for this TimeSeries object. + * + * This causes the graph to scale itself in the y-axis. + */ + TimeSeries.prototype.resetBounds = function() { + if (this.data.length) { + // Walk through all data points, finding the min/max value + this.maxValue = this.data[0][1]; + this.minValue = this.data[0][1]; + for (var i = 1; i < this.data.length; i++) { + var value = this.data[i][1]; + if (value > this.maxValue) { + this.maxValue = value; + } + if (value < this.minValue) { + this.minValue = value; + } + } + } else { + // No data exists, so set min/max to NaN + this.maxValue = Number.NaN; + this.minValue = Number.NaN; + } + }; + + /** + * Adds a new data point to the TimeSeries, preserving chronological order. + * + * @param timestamp the position, in time, of this data point + * @param value the value of this data point + * @param sumRepeatedTimeStampValues if timestamp has an exact match in the series, this flag controls + * whether it is replaced, or the values summed (defaults to false.) + */ + TimeSeries.prototype.append = function(timestamp, value, sumRepeatedTimeStampValues) { + // Rewind until we hit an older timestamp + var i = this.data.length - 1; + while (i >= 0 && this.data[i][0] > timestamp) { + i--; + } + + if (i === -1) { + // This new item is the oldest data + this.data.splice(0, 0, [timestamp, value]); + } else if (this.data.length > 0 && this.data[i][0] === timestamp) { + // Update existing values in the array + if (sumRepeatedTimeStampValues) { + // Sum this value into the existing 'bucket' + this.data[i][1] += value; + value = this.data[i][1]; + } else { + // Replace the previous value + this.data[i][1] = value; + } + } else if (i < this.data.length - 1) { + // Splice into the correct position to keep timestamps in order + this.data.splice(i + 1, 0, [timestamp, value]); + } else { + // Add to the end of the array + this.data.push([timestamp, value]); + } + + this.maxValue = isNaN(this.maxValue) ? value : Math.max(this.maxValue, value); + this.minValue = isNaN(this.minValue) ? value : Math.min(this.minValue, value); + }; + + TimeSeries.prototype.dropOldData = function(oldestValidTime, maxDataSetLength) { + // We must always keep one expired data point as we need this to draw the + // line that comes into the chart from the left, but any points prior to that can be removed. + var removeCount = 0; + while (this.data.length - removeCount >= maxDataSetLength && this.data[removeCount + 1][0] < oldestValidTime) { + removeCount++; + } + if (removeCount !== 0) { + this.data.splice(0, removeCount); + } + }; + + /** + * Initialises a new SmoothieChart. + * + * Options are optional, and should be of the form below. Just specify the values you + * need and the rest will be given sensible defaults as shown: + * + *
+   * {
+   *   minValue: undefined,                      // specify to clamp the lower y-axis to a given value
+   *   maxValue: undefined,                      // specify to clamp the upper y-axis to a given value
+   *   maxValueScale: 1,                         // allows proportional padding to be added above the chart. for 10% padding, specify 1.1.
+   *   minValueScale: 1,                         // allows proportional padding to be added below the chart. for 10% padding, specify 1.1.
+   *   yRangeFunction: undefined,                // function({min: , max: }) { return {min: , max: }; }
+   *   scaleSmoothing: 0.125,                    // controls the rate at which y-value zoom animation occurs
+   *   millisPerPixel: 20,                       // sets the speed at which the chart pans by
+   *   enableDpiScaling: true,                   // support rendering at different DPI depending on the device
+   *   yMinFormatter: function(min, precision) { // callback function that formats the min y value label
+   *     return parseFloat(min).toFixed(precision);
+   *   },
+   *   yMaxFormatter: function(max, precision) { // callback function that formats the max y value label
+   *     return parseFloat(max).toFixed(precision);
+   *   },
+   *   maxDataSetLength: 2,
+   *   interpolation: 'bezier'                   // one of 'bezier', 'linear', or 'step'
+   *   timestampFormatter: null,                 // optional function to format time stamps for bottom of chart
+   *                                             // you may use SmoothieChart.timeFormatter, or your own: function(date) { return ''; }
+   *   scrollBackwards: false,                   // reverse the scroll direction of the chart
+   *   horizontalLines: [],                      // [ { value: 0, color: '#ffffff', lineWidth: 1 } ]
+   *   grid:
+   *   {
+   *     fillStyle: '#000000',                   // the background colour of the chart
+   *     lineWidth: 1,                           // the pixel width of grid lines
+   *     strokeStyle: '#777777',                 // colour of grid lines
+   *     millisPerLine: 1000,                    // distance between vertical grid lines
+   *     sharpLines: false,                      // controls whether grid lines are 1px sharp, or softened
+   *     verticalSections: 2,                    // number of vertical sections marked out by horizontal grid lines
+   *     borderVisible: true                     // whether the grid lines trace the border of the chart or not
+   *   },
+   *   labels
+   *   {
+   *     disabled: false,                        // enables/disables labels showing the min/max values
+   *     fillStyle: '#ffffff',                   // colour for text of labels,
+   *     fontSize: 15,
+   *     fontFamily: 'sans-serif',
+   *     precision: 2,
+   *     showIntermediateLabels: false,          // shows intermediate labels between min and max values along y axis
+   *   },
+   *   tooltip: false                            // show tooltip when mouse is over the chart
+   *   tooltipLine: {                            // properties for a vertical line at the cursor position
+   *     lineWidth: 1,
+   *     strokeStyle: '#BBBBBB'
+   *   },
+   *   tooltipFormatter: SmoothieChart.tooltipFormatter, // formatter function for tooltip text
+   *   nonRealtimeData: false,                   // use time of latest data as current time
+   *   displayDataFromPercentile: 1,             // display not latest data, but data from the given percentile
+   *                                             // useful when trying to see old data saved by setting a high value for maxDataSetLength
+   *                                             // should be a value between 0 and 1 
+   *   responsive: false,                        // whether the chart should adapt to the size of the canvas
+   *   limitFPS: 0                         // maximum frame rate the chart will render at, in FPS (zero means no limit)
+   * }
+   * 
+ * + * @constructor + */ + function SmoothieChart(options) { + this.options = Util.extend({}, SmoothieChart.defaultChartOptions, options); + this.seriesSet = []; + this.currentValueRange = 1; + this.currentVisMinValue = 0; + this.lastRenderTimeMillis = 0; + this.lastChartTimestamp = 0; + + this.mousemove = this.mousemove.bind(this); + this.mouseout = this.mouseout.bind(this); + } + + /** Formats the HTML string content of the tooltip. */ + SmoothieChart.tooltipFormatter = function (timestamp, data) { + var timestampFormatter = this.options.timestampFormatter || SmoothieChart.timeFormatter, + lines = [timestampFormatter(new Date(timestamp))]; + + for (var i = 0; i < data.length; ++i) { + lines.push('' + + this.options.yMaxFormatter(data[i].value, this.options.labels.precision) + ''); + } + + return lines.join('
'); + }; + + SmoothieChart.defaultChartOptions = { + millisPerPixel: 20, + enableDpiScaling: true, + yMinFormatter: function(min, precision) { + return parseFloat(min).toFixed(precision); + }, + yMaxFormatter: function(max, precision) { + return parseFloat(max).toFixed(precision); + }, + maxValueScale: 1, + minValueScale: 1, + interpolation: 'bezier', + scaleSmoothing: 0.125, + maxDataSetLength: 2, + scrollBackwards: false, + displayDataFromPercentile: 1, + grid: { + fillStyle: '#000000', + strokeStyle: '#777777', + lineWidth: 1, + sharpLines: false, + millisPerLine: 1000, + verticalSections: 2, + borderVisible: true + }, + labels: { + fillStyle: '#ffffff', + disabled: false, + fontSize: 10, + fontFamily: 'monospace', + precision: 2, + showIntermediateLabels: false, + }, + horizontalLines: [], + tooltip: false, + tooltipLine: { + lineWidth: 1, + strokeStyle: '#BBBBBB' + }, + tooltipFormatter: SmoothieChart.tooltipFormatter, + nonRealtimeData: false, + responsive: false, + limitFPS: 0 + }; + + // Based on http://inspirit.github.com/jsfeat/js/compatibility.js + SmoothieChart.AnimateCompatibility = (function() { + var requestAnimationFrame = function(callback, element) { + var requestAnimationFrame = + window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function(callback) { + return window.setTimeout(function() { + callback(Date.now()); + }, 16); + }; + return requestAnimationFrame.call(window, callback, element); + }, + cancelAnimationFrame = function(id) { + var cancelAnimationFrame = + window.cancelAnimationFrame || + function(id) { + clearTimeout(id); + }; + return cancelAnimationFrame.call(window, id); + }; + + return { + requestAnimationFrame: requestAnimationFrame, + cancelAnimationFrame: cancelAnimationFrame + }; + })(); + + SmoothieChart.defaultSeriesPresentationOptions = { + lineWidth: 1, + strokeStyle: '#ffffff' + }; + + /** + * Adds a TimeSeries to this chart, with optional presentation options. + * + * Presentation options should be of the form (defaults shown): + * + *
+   * {
+   *   lineWidth: 1,
+   *   strokeStyle: '#ffffff',
+   *   fillStyle: undefined
+   * }
+   * 
+ */ + SmoothieChart.prototype.addTimeSeries = function(timeSeries, options) { + this.seriesSet.push({timeSeries: timeSeries, options: Util.extend({}, SmoothieChart.defaultSeriesPresentationOptions, options)}); + if (timeSeries.options.resetBounds && timeSeries.options.resetBoundsInterval > 0) { + timeSeries.resetBoundsTimerId = setInterval( + function() { + timeSeries.resetBounds(); + }, + timeSeries.options.resetBoundsInterval + ); + } + }; + + /** + * Removes the specified TimeSeries from the chart. + */ + SmoothieChart.prototype.removeTimeSeries = function(timeSeries) { + // Find the correct timeseries to remove, and remove it + var numSeries = this.seriesSet.length; + for (var i = 0; i < numSeries; i++) { + if (this.seriesSet[i].timeSeries === timeSeries) { + this.seriesSet.splice(i, 1); + break; + } + } + // If a timer was operating for that timeseries, remove it + if (timeSeries.resetBoundsTimerId) { + // Stop resetting the bounds, if we were + clearInterval(timeSeries.resetBoundsTimerId); + } + }; + + /** + * Gets render options for the specified TimeSeries. + * + * As you may use a single TimeSeries in multiple charts with different formatting in each usage, + * these settings are stored in the chart. + */ + SmoothieChart.prototype.getTimeSeriesOptions = function(timeSeries) { + // Find the correct timeseries to remove, and remove it + var numSeries = this.seriesSet.length; + for (var i = 0; i < numSeries; i++) { + if (this.seriesSet[i].timeSeries === timeSeries) { + return this.seriesSet[i].options; + } + } + }; + + /** + * Brings the specified TimeSeries to the top of the chart. It will be rendered last. + */ + SmoothieChart.prototype.bringToFront = function(timeSeries) { + // Find the correct timeseries to remove, and remove it + var numSeries = this.seriesSet.length; + for (var i = 0; i < numSeries; i++) { + if (this.seriesSet[i].timeSeries === timeSeries) { + var set = this.seriesSet.splice(i, 1); + this.seriesSet.push(set[0]); + break; + } + } + }; + + /** + * Instructs the SmoothieChart to start rendering to the provided canvas, with specified delay. + * + * @param canvas the target canvas element + * @param delayMillis an amount of time to wait before a data point is shown. This can prevent the end of the series + * from appearing on screen, with new values flashing into view, at the expense of some latency. + */ + SmoothieChart.prototype.streamTo = function(canvas, delayMillis) { + this.canvas = canvas; + this.delay = delayMillis; + this.start(); + }; + + SmoothieChart.prototype.getTooltipEl = function () { + // Create the tool tip element lazily + if (!this.tooltipEl) { + this.tooltipEl = document.createElement('div'); + this.tooltipEl.className = 'smoothie-chart-tooltip'; + this.tooltipEl.style.position = 'absolute'; + this.tooltipEl.style.display = 'none'; + document.body.appendChild(this.tooltipEl); + } + return this.tooltipEl; + }; + + SmoothieChart.prototype.updateTooltip = function () { + var el = this.getTooltipEl(); + + if (!this.mouseover || !this.options.tooltip) { + el.style.display = 'none'; + return; + } + + var time = this.lastChartTimestamp; + + // x pixel to time + var t = this.options.scrollBackwards + ? time - this.mouseX * this.options.millisPerPixel + : time - (this.canvas.offsetWidth - this.mouseX) * this.options.millisPerPixel; + + var data = []; + + // For each data set... + for (var d = 0; d < this.seriesSet.length; d++) { + var timeSeries = this.seriesSet[d].timeSeries; + if (timeSeries.disabled) { + continue; + } + + // find datapoint closest to time 't' + var closeIdx = Util.binarySearch(timeSeries.data, t); + if (closeIdx > 0 && closeIdx < timeSeries.data.length) { + data.push({ series: this.seriesSet[d], index: closeIdx, value: timeSeries.data[closeIdx][1] }); + } + } + + if (data.length) { + el.innerHTML = this.options.tooltipFormatter.call(this, t, data); + el.style.display = 'block'; + } else { + el.style.display = 'none'; + } + }; + + SmoothieChart.prototype.mousemove = function (evt) { + this.mouseover = true; + this.mouseX = evt.offsetX; + this.mouseY = evt.offsetY; + this.mousePageX = evt.pageX; + this.mousePageY = evt.pageY; + + var el = this.getTooltipEl(); + el.style.top = Math.round(this.mousePageY) + 'px'; + el.style.left = Math.round(this.mousePageX) + 'px'; + this.updateTooltip(); + }; + + SmoothieChart.prototype.mouseout = function () { + this.mouseover = false; + this.mouseX = this.mouseY = -1; + if (SmoothieChart.tooltipEl) + SmoothieChart.tooltipEl.style.display = 'none'; + }; + + /** + * Make sure the canvas has the optimal resolution for the device's pixel ratio. + */ + SmoothieChart.prototype.resize = function () { + var dpr = !this.options.enableDpiScaling || !window ? 1 : window.devicePixelRatio, + width, height; + if (this.options.responsive) { + // Newer behaviour: Use the canvas's size in the layout, and set the internal + // resolution according to that size and the device pixel ratio (eg: high DPI) + width = this.canvas.offsetWidth; + height = this.canvas.offsetHeight; + + if (width !== this.lastWidth) { + this.lastWidth = width; + this.canvas.setAttribute('width', (Math.floor(width * dpr)).toString()); + } + if (height !== this.lastHeight) { + this.lastHeight = height; + this.canvas.setAttribute('height', (Math.floor(height * dpr)).toString()); + } + } else if (dpr !== 1) { + // Older behaviour: use the canvas's inner dimensions and scale the element's size + // according to that size and the device pixel ratio (eg: high DPI) + width = parseInt(this.canvas.getAttribute('width')); + height = parseInt(this.canvas.getAttribute('height')); + + if (!this.originalWidth || (Math.floor(this.originalWidth * dpr) !== width)) { + this.originalWidth = width; + this.canvas.setAttribute('width', (Math.floor(width * dpr)).toString()); + this.canvas.style.width = width + 'px'; + this.canvas.getContext('2d').scale(dpr, dpr); + } + + if (!this.originalHeight || (Math.floor(this.originalHeight * dpr) !== height)) { + this.originalHeight = height; + this.canvas.setAttribute('height', (Math.floor(height * dpr)).toString()); + this.canvas.style.height = height + 'px'; + this.canvas.getContext('2d').scale(dpr, dpr); + } + } + }; + + /** + * Starts the animation of this chart. + */ + SmoothieChart.prototype.start = function() { + if (this.frame) { + // We're already running, so just return + return; + } + + this.canvas.addEventListener('mousemove', this.mousemove); + this.canvas.addEventListener('mouseout', this.mouseout); + + // Renders a frame, and queues the next frame for later rendering + var animate = function() { + this.frame = SmoothieChart.AnimateCompatibility.requestAnimationFrame(function() { + if(this.options.nonRealtimeData){ + var dateZero = new Date(0); + // find the data point with the latest timestamp + var maxTimeStamp = this.seriesSet.reduce(function(max, series){ + var dataSet = series.timeSeries.data; + var indexToCheck = Math.round(this.options.displayDataFromPercentile * dataSet.length) - 1; + indexToCheck = indexToCheck >= 0 ? indexToCheck : 0; + indexToCheck = indexToCheck <= dataSet.length -1 ? indexToCheck : dataSet.length -1; + if(dataSet && dataSet.length > 0) + { + // timestamp corresponds to element 0 of the data point + var lastDataTimeStamp = dataSet[indexToCheck][0]; + max = max > lastDataTimeStamp ? max : lastDataTimeStamp; + } + return max; + }.bind(this), dateZero); + // use the max timestamp as current time + this.render(this.canvas, maxTimeStamp > dateZero ? maxTimeStamp : null); + } else { + this.render(); + } + animate(); + }.bind(this)); + }.bind(this); + + animate(); + }; + + /** + * Stops the animation of this chart. + */ + SmoothieChart.prototype.stop = function() { + if (this.frame) { + SmoothieChart.AnimateCompatibility.cancelAnimationFrame(this.frame); + delete this.frame; + this.canvas.removeEventListener('mousemove', this.mousemove); + this.canvas.removeEventListener('mouseout', this.mouseout); + } + }; + + SmoothieChart.prototype.updateValueRange = function() { + // Calculate the current scale of the chart, from all time series. + var chartOptions = this.options, + chartMaxValue = Number.NaN, + chartMinValue = Number.NaN; + + for (var d = 0; d < this.seriesSet.length; d++) { + // TODO(ndunn): We could calculate / track these values as they stream in. + var timeSeries = this.seriesSet[d].timeSeries; + if (timeSeries.disabled) { + continue; + } + + if (!isNaN(timeSeries.maxValue)) { + chartMaxValue = !isNaN(chartMaxValue) ? Math.max(chartMaxValue, timeSeries.maxValue) : timeSeries.maxValue; + } + + if (!isNaN(timeSeries.minValue)) { + chartMinValue = !isNaN(chartMinValue) ? Math.min(chartMinValue, timeSeries.minValue) : timeSeries.minValue; + } + } + + // Scale the chartMaxValue to add padding at the top if required + if (chartOptions.maxValue != null) { + chartMaxValue = chartOptions.maxValue; + } else { + chartMaxValue *= chartOptions.maxValueScale; + } + + // Set the minimum if we've specified one + if (chartOptions.minValue != null) { + chartMinValue = chartOptions.minValue; + } else { + chartMinValue -= Math.abs(chartMinValue * chartOptions.minValueScale - chartMinValue); + } + + // If a custom range function is set, call it + if (this.options.yRangeFunction) { + var range = this.options.yRangeFunction({min: chartMinValue, max: chartMaxValue}); + chartMinValue = range.min; + chartMaxValue = range.max; + } + + if (!isNaN(chartMaxValue) && !isNaN(chartMinValue)) { + var targetValueRange = chartMaxValue - chartMinValue; + var valueRangeDiff = (targetValueRange - this.currentValueRange); + var minValueDiff = (chartMinValue - this.currentVisMinValue); + this.isAnimatingScale = Math.abs(valueRangeDiff) > 0.1 || Math.abs(minValueDiff) > 0.1; + this.currentValueRange += chartOptions.scaleSmoothing * valueRangeDiff; + this.currentVisMinValue += chartOptions.scaleSmoothing * minValueDiff; + } + + this.valueRange = { min: chartMinValue, max: chartMaxValue }; + }; + + SmoothieChart.prototype.render = function(canvas, time) { + var nowMillis = Date.now(); + + // Respect any frame rate limit. + if (this.options.limitFPS > 0 && nowMillis - this.lastRenderTimeMillis < (1000/this.options.limitFPS)) + return; + + if (!this.isAnimatingScale) { + // We're not animating. We can use the last render time and the scroll speed to work out whether + // we actually need to paint anything yet. If not, we can return immediately. + + // Render at least every 1/6th of a second. The canvas may be resized, which there is + // no reliable way to detect. + var maxIdleMillis = Math.min(1000/6, this.options.millisPerPixel); + + if (nowMillis - this.lastRenderTimeMillis < maxIdleMillis) { + return; + } + } + + this.resize(); + this.updateTooltip(); + + this.lastRenderTimeMillis = nowMillis; + + canvas = canvas || this.canvas; + time = time || nowMillis - (this.delay || 0); + + // Round time down to pixel granularity, so motion appears smoother. + time -= time % this.options.millisPerPixel; + + this.lastChartTimestamp = time; + + var context = canvas.getContext('2d'), + chartOptions = this.options, + dimensions = { top: 0, left: 0, width: canvas.clientWidth, height: canvas.clientHeight }, + // Calculate the threshold time for the oldest data points. + oldestValidTime = time - (dimensions.width * chartOptions.millisPerPixel), + valueToYPixel = function(value) { + var offset = value - this.currentVisMinValue; + return this.currentValueRange === 0 + ? dimensions.height + : dimensions.height - (Math.round((offset / this.currentValueRange) * dimensions.height)); + }.bind(this), + timeToXPixel = function(t) { + if(chartOptions.scrollBackwards) { + return Math.round((time - t) / chartOptions.millisPerPixel); + } + return Math.round(dimensions.width - ((time - t) / chartOptions.millisPerPixel)); + }; + + this.updateValueRange(); + + context.font = chartOptions.labels.fontSize + 'px ' + chartOptions.labels.fontFamily; + + // Save the state of the canvas context, any transformations applied in this method + // will get removed from the stack at the end of this method when .restore() is called. + context.save(); + + // Move the origin. + context.translate(dimensions.left, dimensions.top); + + // Create a clipped rectangle - anything we draw will be constrained to this rectangle. + // This prevents the occasional pixels from curves near the edges overrunning and creating + // screen cheese (that phrase should need no explanation). + context.beginPath(); + context.rect(0, 0, dimensions.width, dimensions.height); + context.clip(); + + // Clear the working area. + context.save(); + context.fillStyle = chartOptions.grid.fillStyle; + context.clearRect(0, 0, dimensions.width, dimensions.height); + context.fillRect(0, 0, dimensions.width, dimensions.height); + context.restore(); + + // Grid lines... + context.save(); + context.lineWidth = chartOptions.grid.lineWidth; + context.strokeStyle = chartOptions.grid.strokeStyle; + // Vertical (time) dividers. + if (chartOptions.grid.millisPerLine > 0) { + context.beginPath(); + for (var t = time - (time % chartOptions.grid.millisPerLine); + t >= oldestValidTime; + t -= chartOptions.grid.millisPerLine) { + var gx = timeToXPixel(t); + if (chartOptions.grid.sharpLines) { + gx -= 0.5; + } + context.moveTo(gx, 0); + context.lineTo(gx, dimensions.height); + } + context.stroke(); + context.closePath(); + } + + // Horizontal (value) dividers. + for (var v = 1; v < chartOptions.grid.verticalSections; v++) { + var gy = Math.round(v * dimensions.height / chartOptions.grid.verticalSections); + if (chartOptions.grid.sharpLines) { + gy -= 0.5; + } + context.beginPath(); + context.moveTo(0, gy); + context.lineTo(dimensions.width, gy); + context.stroke(); + context.closePath(); + } + // Bounding rectangle. + if (chartOptions.grid.borderVisible) { + context.beginPath(); + context.strokeRect(0, 0, dimensions.width, dimensions.height); + context.closePath(); + } + context.restore(); + + // Draw any horizontal lines... + if (chartOptions.horizontalLines && chartOptions.horizontalLines.length) { + for (var hl = 0; hl < chartOptions.horizontalLines.length; hl++) { + var line = chartOptions.horizontalLines[hl], + hly = Math.round(valueToYPixel(line.value)) - 0.5; + context.strokeStyle = line.color || '#ffffff'; + context.lineWidth = line.lineWidth || 1; + context.beginPath(); + context.moveTo(0, hly); + context.lineTo(dimensions.width, hly); + context.stroke(); + context.closePath(); + } + } + + // For each data set... + for (var d = 0; d < this.seriesSet.length; d++) { + context.save(); + var timeSeries = this.seriesSet[d].timeSeries; + if (timeSeries.disabled) { + continue; + } + + var dataSet = timeSeries.data, + seriesOptions = this.seriesSet[d].options; + + // Delete old data that's moved off the left of the chart. + timeSeries.dropOldData(oldestValidTime, chartOptions.maxDataSetLength); + + // Set style for this dataSet. + context.lineWidth = seriesOptions.lineWidth; + context.strokeStyle = seriesOptions.strokeStyle; + // Draw the line... + context.beginPath(); + // Retain lastX, lastY for calculating the control points of bezier curves. + var firstX = 0, lastX = 0, lastY = 0; + for (var i = 0; i < dataSet.length && dataSet.length !== 1; i++) { + var x = timeToXPixel(dataSet[i][0]), + y = valueToYPixel(dataSet[i][1]); + + if (i === 0) { + firstX = x; + context.moveTo(x, y); + } else { + switch (chartOptions.interpolation) { + case "linear": + case "line": { + context.lineTo(x,y); + break; + } + case "bezier": + default: { + // Great explanation of Bezier curves: http://en.wikipedia.org/wiki/Bezier_curve#Quadratic_curves + // + // Assuming A was the last point in the line plotted and B is the new point, + // we draw a curve with control points P and Q as below. + // + // A---P + // | + // | + // | + // Q---B + // + // Importantly, A and P are at the same y coordinate, as are B and Q. This is + // so adjacent curves appear to flow as one. + // + context.bezierCurveTo( // startPoint (A) is implicit from last iteration of loop + Math.round((lastX + x) / 2), lastY, // controlPoint1 (P) + Math.round((lastX + x)) / 2, y, // controlPoint2 (Q) + x, y); // endPoint (B) + break; + } + case "step": { + context.lineTo(x,lastY); + context.lineTo(x,y); + break; + } + } + } + + lastX = x; lastY = y; + } + + if (dataSet.length > 1) { + if (seriesOptions.fillStyle) { + // Close up the fill region. + context.lineTo(dimensions.width + seriesOptions.lineWidth + 1, lastY); + context.lineTo(dimensions.width + seriesOptions.lineWidth + 1, dimensions.height + seriesOptions.lineWidth + 1); + context.lineTo(firstX, dimensions.height + seriesOptions.lineWidth); + context.fillStyle = seriesOptions.fillStyle; + context.fill(); + } + + if (seriesOptions.strokeStyle && seriesOptions.strokeStyle !== 'none') { + context.stroke(); + } + context.closePath(); + } + context.restore(); + } + + if (chartOptions.tooltip && this.mouseX >= 0) { + // Draw vertical bar to show tooltip position + context.lineWidth = chartOptions.tooltipLine.lineWidth; + context.strokeStyle = chartOptions.tooltipLine.strokeStyle; + context.beginPath(); + context.moveTo(this.mouseX, 0); + context.lineTo(this.mouseX, dimensions.height); + context.closePath(); + context.stroke(); + this.updateTooltip(); + } + + // Draw the axis values on the chart. + if (!chartOptions.labels.disabled && !isNaN(this.valueRange.min) && !isNaN(this.valueRange.max)) { + var maxValueString = chartOptions.yMaxFormatter(this.valueRange.max, chartOptions.labels.precision), + minValueString = chartOptions.yMinFormatter(this.valueRange.min, chartOptions.labels.precision), + maxLabelPos = chartOptions.scrollBackwards ? 0 : dimensions.width - context.measureText(maxValueString).width - 2, + minLabelPos = chartOptions.scrollBackwards ? 0 : dimensions.width - context.measureText(minValueString).width - 2; + context.fillStyle = chartOptions.labels.fillStyle; + context.fillText(maxValueString, maxLabelPos, chartOptions.labels.fontSize); + context.fillText(minValueString, minLabelPos, dimensions.height - 2); + } + + // Display intermediate y axis labels along y-axis to the left of the chart + if ( chartOptions.labels.showIntermediateLabels + && !isNaN(this.valueRange.min) && !isNaN(this.valueRange.max) + && chartOptions.grid.verticalSections > 0) { + // show a label above every vertical section divider + var step = (this.valueRange.max - this.valueRange.min) / chartOptions.grid.verticalSections; + var stepPixels = dimensions.height / chartOptions.grid.verticalSections; + for (var v = 0; v < chartOptions.grid.verticalSections; v++) { + var gy = dimensions.height - Math.round(v * stepPixels); + if (chartOptions.grid.sharpLines) { + gy -= 0.5; + } + var yValue = (this.valueRange.min + (v * step)).toPrecision(chartOptions.labels.precision); + context.fillStyle = chartOptions.labels.fillStyle; + context.fillText(yValue, 0, gy - chartOptions.grid.lineWidth); + } + } + + // Display timestamps along x-axis at the bottom of the chart. + if (chartOptions.timestampFormatter && chartOptions.grid.millisPerLine > 0) { + var textUntilX = chartOptions.scrollBackwards + ? context.measureText(minValueString).width + : dimensions.width - context.measureText(minValueString).width + 4; + for (var t = time - (time % chartOptions.grid.millisPerLine); + t >= oldestValidTime; + t -= chartOptions.grid.millisPerLine) { + var gx = timeToXPixel(t); + // Only draw the timestamp if it won't overlap with the previously drawn one. + if ((!chartOptions.scrollBackwards && gx < textUntilX) || (chartOptions.scrollBackwards && gx > textUntilX)) { + // Formats the timestamp based on user specified formatting function + // SmoothieChart.timeFormatter function above is one such formatting option + var tx = new Date(t), + ts = chartOptions.timestampFormatter(tx), + tsWidth = context.measureText(ts).width; + + textUntilX = chartOptions.scrollBackwards + ? gx + tsWidth + 2 + : gx - tsWidth - 2; + + context.fillStyle = chartOptions.labels.fillStyle; + if(chartOptions.scrollBackwards) { + context.fillText(ts, gx, dimensions.height - 2); + } else { + context.fillText(ts, gx - tsWidth, dimensions.height - 2); + } + } + } + } + + context.restore(); // See .save() above. + }; + + // Sample timestamp formatting function + SmoothieChart.timeFormatter = function(date) { + function pad2(number) { return (number < 10 ? '0' : '') + number } + return pad2(date.getHours()) + ':' + pad2(date.getMinutes()) + ':' + pad2(date.getSeconds()); + }; + + exports.TimeSeries = TimeSeries; + exports.SmoothieChart = SmoothieChart; + +})(typeof exports === 'undefined' ? this : exports); -- 2.27.0