/*.elf
/*.lss
/*.map
+
+*.o
PROJECT = bbctrl-avr-firmware
MCU = atxmega192a3u
CLOCK = 32000000
-VERSION = 0.4.0
+VERSION = 0.5.0
TARGET = $(PROJECT).elf
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)
# 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 $@ $<
+++ /dev/null
-/******************************************************************************\
-
- 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 <http://www.gnu.org/licenses/>.
-
- The software is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with the software. If not, see
- <http://www.gnu.org/licenses/>.
-
- For information regarding this software email:
- "Joseph Coffland" <joseph@buildbotics.com>
-
-\******************************************************************************/
-
-#include "action.h"
-
-#include "queue.h"
-
-#include <stdlib.h>
-#include <ctype.h>
-#include <string.h>
-
-
-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;
-}
+++ /dev/null
-/******************************************************************************\
-
- 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 <http://www.gnu.org/licenses/>.
-
- The software is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with the software. If not, see
- <http://www.gnu.org/licenses/>.
-
- For information regarding this software email:
- "Joseph Coffland" <joseph@buildbotics.com>
-
-\******************************************************************************/
-
-#pragma once
-
-#include "status.h"
-
-
-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);
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
--- /dev/null
+/******************************************************************************\
+
+ 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 <http://www.gnu.org/licenses/>.
+
+ The software is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the software. If not, see
+ <http://www.gnu.org/licenses/>.
+
+ For information regarding this software email:
+ "Joseph Coffland" <joseph@buildbotics.com>
+
+\******************************************************************************/
+
+#include "base64.h"
+
+#include "util.h"
+
+#include <ctype.h>
+
+
+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;
+}
--- /dev/null
+/******************************************************************************\
+
+ 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 <http://www.gnu.org/licenses/>.
+
+ The software is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the software. If not, see
+ <http://www.gnu.org/licenses/>.
+
+ For information regarding this software email:
+ "Joseph Coffland" <joseph@buildbotics.com>
+
+\******************************************************************************/
+
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+
+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);
\******************************************************************************/
-// Name Min, Max args, Help
-CMD(help, 0, 1, "Print this help screen")
-CMD(report, 1, 2, "[var] <enable>. 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")
#pragma once
+#include "status.h"
-#include <stdint.h>
#include <stdbool.h>
-#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();
--- /dev/null
+#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
+ "_": {}
+}
--- /dev/null
+/******************************************************************************\
+
+ 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 <http://www.gnu.org/licenses/>.
+
+ The software is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the software. If not, see
+ <http://www.gnu.org/licenses/>.
+
+ For information regarding this software email:
+ "Joseph Coffland" <joseph@buildbotics.com>
+
+\******************************************************************************/
+
+#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 <string.h>
+#include <stdio.h>
+
+
+// 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;
+}
#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
#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
(DEFER2(_EMAP2_INNER)()(ARG1, ARG2, ##__VA_ARGS__))
#define _EMAP2_INNER() EMAP2_INNER
-
}
+// Var callbacks
bool get_estop() {
return estop_triggered();
}
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;
+}
\******************************************************************************/
#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 <string.h>
#include <float.h>
-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;
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;
// 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]));
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
#pragma once
-#include "pgmspace.h"
+#include "config.h"
#include "status.h"
#include <stdbool.h>
-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();
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
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
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])
}
-void huanyang_init() {
+void hy_init() {
PR.PRPD &= ~PR_USART1_bm; // Disable power reduction
DIRCLR_PIN(RS485_RO_PIN); // Input
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;
}
-void huanyang_reset() {
+void hy_reset() {
_set_dre_interrupt(false);
_set_txc_interrupt(false);
_set_rxc_interrupt(false);
}
-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 {
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;}
#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();
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
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;
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();
}
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
#include <stdbool.h>
-// 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);
#include "util.h"
#include "exec.h"
#include "state.h"
-#include "queue.h"
#include "config.h"
#include <stdbool.h>
typedef struct {
+ bool active;
bool writing;
bool done;
// 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]);
}
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;
This file is part of the Buildbotics firmware.
Copyright (c) 2015 - 2017 Buildbotics LLC
- Copyright (c) 2010 Alex Forencich <alex@alexforencich.com>
All rights reserved.
This file ("the software") is free software: you can redistribute it
This file is part of the Buildbotics firmware.
Copyright (c) 2015 - 2017 Buildbotics LLC
- Copyright (c) 2010 Alex Forencich <alex@alexforencich.com>
All rights reserved.
This file ("the software") is free software: you can redistribute it
--- /dev/null
+/******************************************************************************\
+
+ 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 <http://www.gnu.org/licenses/>.
+
+ The software is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the software. If not, see
+ <http://www.gnu.org/licenses/>.
+
+ For information regarding this software email:
+ "Joseph Coffland" <joseph@buildbotics.com>
+
+\******************************************************************************/
+
+#include "config.h"
+#include "exec.h"
+#include "axis.h"
+#include "command.h"
+#include "util.h"
+
+#include <math.h>
+#include <string.h>
+#include <stdio.h>
+
+
+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);
+}
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
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();
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) {
\******************************************************************************/
-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")
--- /dev/null
+#include "cpp_magic.h"
+[
+#define STAT_MSG(NAME, MSG) [#NAME, MSG],
+#include "messages.def"
+#undef CMD
+ []
+]
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;
+++ /dev/null
-/******************************************************************************\
-
- 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 <http://www.gnu.org/licenses/>.
-
- The software is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with the software. If not, see
- <http://www.gnu.org/licenses/>.
-
- For information regarding this software email:
- "Joseph Coffland" <joseph@buildbotics.com>
-
-\******************************************************************************/
-
-#include "queue.h"
-
-#include <string.h>
-
-
-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;}
+++ /dev/null
-/******************************************************************************\
-
- 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 <http://www.gnu.org/licenses/>.
-
- The software is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with the software. If not, see
- <http://www.gnu.org/licenses/>.
-
- For information regarding this software email:
- "Joseph Coffland" <joseph@buildbotics.com>
-
-\******************************************************************************/
-
-#pragma once
-
-#include "config.h"
-#include "action.h"
-
-#include <stdint.h>
-#include <stdbool.h>
-
-
-// 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();
*/
#include <stdint.h>
+#include <stdbool.h>
#ifndef RING_BUF_NAME
#error Must define RING_BUF_NAME
#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
#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)));
}
#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
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
ticks++;
switch_rtc_callback();
- huanyang_rtc_callback();
+ hy_rtc_callback();
if (!(ticks & 255)) motor_rtc_callback();
lcd_rtc_callback();
}
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
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
void spindle_init() {
pwm_spindle_init();
- huanyang_init();
+ hy_init();
}
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;
}
}
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;
}
}
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
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
#include "state.h"
#include "exec.h"
-#include "queue.h"
+#include "command.h"
#include "stepper.h"
#include "spindle.h"
#include "report.h"
static struct {
state_t state;
- cycle_t cycle;
hold_reason_t hold_reason;
- bool pause;
bool hold_requested;
bool flush_requested;
}
-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");
state_t state_get() {return s.state;}
-cycle_t state_get_cycle() {return s.cycle;}
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;
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);
}
-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;}
// 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();
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);
}
}
// 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);}
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
} state_t;
-typedef enum {
- CYCLE_MACHINING,
- CYCLE_HOMING,
- CYCLE_PROBING,
- CYCLE_JOGGING,
-} cycle_t;
-
-
typedef enum {
HOLD_REASON_USER_PAUSE,
HOLD_REASON_PROGRAM_PAUSE,
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();
va_list args;
// Type
- printf_P(PSTR("\n{\"level\":\"%"PRPSTR"\", \"msg\":\""),
+ printf_P(PSTR("\n{\"level\":\"%"PRPSTR"\",\"msg\":\""),
status_level_pgmstr(level));
// Message
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');
}
-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);
#include "messages.def"
#undef STAT_MSG
+ STAT_MAX,
STAT_DO_NOT_EXCEED = 255 // Do not exceed 255
} stat_t;
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);
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
/// 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
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));
// 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)
}
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
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
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
--- /dev/null
+/******************************************************************************\
+
+ 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 <http://www.gnu.org/licenses/>.
+
+ The software is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the software. If not, see
+ <http://www.gnu.org/licenses/>.
+
+ For information regarding this software email:
+ "Joseph Coffland" <joseph@buildbotics.com>
+
+\******************************************************************************/
+
+#include "type.h"
+#include "base64.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <ctype.h>
+#include <stdlib.h>
+
+
+#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;
+}
--- /dev/null
+/******************************************************************************\
+
+ 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 <http://www.gnu.org/licenses/>.
+
+ The software is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the software. If not, see
+ <http://www.gnu.org/licenses/>.
+
+ For information regarding this software email:
+ "Joseph Coffland" <joseph@buildbotics.com>
+
+\******************************************************************************/
+
+#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)
--- /dev/null
+/******************************************************************************\
+
+ 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 <http://www.gnu.org/licenses/>.
+
+ The software is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the software. If not, see
+ <http://www.gnu.org/licenses/>.
+
+ For information regarding this software email:
+ "Joseph Coffland" <joseph@buildbotics.com>
+
+\******************************************************************************/
+
+#pragma once
+
+#include "pgmspace.h"
+
+#include <stdint.h>
+
+
+// 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);
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;
}
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)
}
}
return 0;
}
+
static FILE _stdout = FDEV_SETUP_STREAM(_usart_putchar, 0, _FDEV_SETUP_WRITE);
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
#define USART_TX_RING_BUF_SIZE 256
#define USART_RX_RING_BUF_SIZE 256
+
enum {
USART_BAUD_9600,
USART_BAUD_19200,
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
#include "util.h"
+#include "base64.h"
+
#include <stdint.h>
+#include <string.h>
+#include <math.h>
/// Fast inverse square root originally from Quake III Arena code. Original
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;
+}
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
#include "config.h"
+#include "status.h"
#include <stdint.h>
#include <math.h>
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)
#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 <stdint.h>
-#include <stdbool.h>
-#include <stdio.h>
#include <string.h>
-#include <stdlib.h>
-#include <ctype.h>
-#include <math.h>
-#include <inttypes.h>
-
-
-typedef uint16_t flags_t;
-typedef const char *string;
-typedef const PGM_P pstring;
-
+#include <stdio.h>
// 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 = "<bool>";
-#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"
var_code_count
};
+
// Var forward declarations
#define VAR(NAME, CODE, TYPE, INDEX, SET, ...) \
TYPE get_##NAME(IF(INDEX)(int index)); \
#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; \
#include "vars.def"
#undef VAR
+
// Last value
#define VAR(NAME, CODE, TYPE, INDEX, ...) \
static TYPE NAME##_state IF(INDEX)([INDEX]);
#include "vars.def"
#undef VAR
+
// Report
static uint8_t _report_var[(var_code_count >> 3) + 1] = {0,};
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) { \
(IF_ELSE(INDEX)(indexed_code_fmt, code_fmt), \
IF(INDEX)(INDEX##_LABEL[i],) #CODE); \
\
- var_print_##TYPE(value); \
+ type_print_##TYPE(value); \
} \
}
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);
}
-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"
}
-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));
}
// 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;
+}
#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")
#include <stdbool.h>
+
+float var_decode_float(const char *value);
bool var_parse_bool(const char *value);
void vars_init();
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();
td(:class="{attention: highlight_reason()}") {{get_state()}}
td
tr
- th Reason
+ th Message
td.reason(:class="{attention: highlight_reason()}") {{get_reason()}}
td
tr
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",
.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",
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 () {
LIBS += -lm
# Programming flags
+ifndef (PROGRAMMER)
PROGRAMMER = avrispmkII
#PROGRAMMER = jtag3pdi
+endif
PDEV = usb
AVRDUDE_OPTS = -c $(PROGRAMMER) -p $(MCU) -P $(PDEV)
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()
G28.3 %(axis)s[#<%(axis)s.hp>]
'''
+
class AVR():
def __init__(self, ctrl):
self.ctrl = ctrl
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)
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:
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')
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):
self.in_buf = self.in_buf[i + 1:]
if line:
+ log.info('> ' + line)
+
try:
msg = json.loads(line)
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
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):
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):
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):
--- /dev/null
+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('<f', x))[:-2].decode("utf-8")
+
+
+def decode_float(s):
+ return struct.unpack('<f', base64.b64decode(s + '=='))[0]
+
+
+def encode_axes(axes):
+ data = ''
+ for axis in 'xyzabc':
+ if axis in axes:
+ data += axis + encode_float(axes[axis])
+
+ return data
+
+
+def line_number(line): return '#ln=%d' % line
+
+
+def line(target, exitVel, maxJerk, times):
+ data = 'l'
+
+ data += encode_float(exitVel)
+ data += encode_float(maxJerk)
+ data += encode_axes(target)
+
+ # S-Curve time parameters
+ for i in range(7):
+ if times[i]:
+ data += str(i) + encode_float(times[i] / 60000) # to mins
+
+ return data
+
+
+def tool(tool): return '#t=%d' % tool
+def speed(speed): return '#s=:' + encode_float(speed)
+def dwell(seconds): return 'd' + encode_float(seconds)
+def pause(optional = False): 'P' + ('1' if optional else '0')
+def jog(axes): return 'j' + encode_axes(axes)
import inevent
from inevent.Constants import *
+import logging
+
+log = logging.getLogger('Jog')
# Listen for input events
self.lastV = self.v
try:
self.ctrl.avr.jog(self.v)
- except: pass
+ except Exception as e: log.error('Jog: %s', e)
self.ctrl.ioloop.call_later(0.25, self.callback)
--- /dev/null
+import json
+import logging
+import camotics.gplan as gplan
+import bbctrl.Cmd as Cmd
+
+log = logging.getLogger('Planner')
+
+
+
+class Planner():
+ def __init__(self, ctrl, path):
+ self.ctrl = ctrl
+ self.path = path
+
+ vars = ctrl.avr.vars
+
+ # Axis mapping for enabled motors
+ axis2motor = {}
+ for i in range(3):
+ if vars.get('%dpm' % i, False):
+ axis = 'xyzabc'[int(vars.get('%dan' % i))]
+ axis2motor[axis] = i
+
+ def get_vector(name, scale):
+ v = {}
+ for axis in 'xyzabc':
+ if axis in axis2motor:
+ motor = axis2motor[axis]
+ value = vars.get(str(motor) + name, None)
+ if value is not None:
+ v[axis] = value * scale
+ return v
+
+ # Starting position
+ start = {}
+ for axis in 'xyzabc':
+ if not axis in axis2motor: continue
+ value = vars.get(axis + 'p', None)
+ if value is not None: start[axis] = value
+
+ # Planner config
+ self.config = {
+ "start": start,
+ "max-vel": get_vector('vm', 1),
+ "max-jerk": get_vector('jm', 1000000),
+ # TODO max-accel and junction deviation & accel
+ }
+ log.info('Planner config: ' + json.dumps(self.config))
+
+ self.reset()
+
+
+ def reset(self):
+ self.planner = gplan.Planner(self.config)
+ self.planner.load('upload' + self.path)
+
+
+ def encode(self, block):
+ type = block['type']
+ line = block.get('line', None)
+
+ if type == 'line':
+ return Cmd.line(block['target'], block['exit-vel'],
+ block['max-jerk'], block['times'])
+
+ if type == 'tool': return Cmd.tool(block['tool'])
+ if type == 'speed': return Cmd.speed(block['speed'])
+ if type == 'dwell': return Cmd.dwell(block['seconds'])
+ if type == 'pause': return Cmd.pause(block['optional'])
+
+ raise Exception('Unknown planner type "%s"' % type)
+
+
+ def next(self):
+ if self.planner.has_more():
+ return self.encode(self.planner.next())
from bbctrl.Ctrl import Ctrl
from bbctrl.Pwr import Pwr
from bbctrl.I2C import I2C
+from bbctrl.Planner import Planner
+import bbctrl.Cmd as Cmd
def get_resource(path):
--- /dev/null
+// MIT License:
+//
+// Copyright (c) 2010-2013, Joe Walnes
+// 2013-2017, Drew Noakes
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+/**
+ * Smoothie Charts - http://smoothiecharts.org/
+ * (c) 2010-2013, Joe Walnes
+ * 2013-2017, Drew Noakes
+ *
+ * v1.0: Main charting library, by Joe Walnes
+ * v1.1: Auto scaling of axis, by Neil Dunn
+ * v1.2: fps (frames per second) option, by Mathias Petterson
+ * v1.3: Fix for divide by zero, by Paul Nikitochkin
+ * v1.4: Set minimum, top-scale padding, remove timeseries, add optional timer to reset bounds, by Kelley Reynolds
+ * v1.5: Set default frames per second to 50... smoother.
+ * .start(), .stop() methods for conserving CPU, by Dmitry Vyal
+ * options.interpolation = 'bezier' or 'line', by Dmitry Vyal
+ * options.maxValue to fix scale, by Dmitry Vyal
+ * v1.6: minValue/maxValue will always get converted to floats, by Przemek Matylla
+ * v1.7: options.grid.fillStyle may be a transparent color, by Dmitry A. Shashkin
+ * Smooth rescaling, by Kostas Michalopoulos
+ * v1.8: Set max length to customize number of live points in the dataset with options.maxDataSetLength, by Krishna Narni
+ * v1.9: Display timestamps along the bottom, by Nick and Stev-io
+ * (https://groups.google.com/forum/?fromgroups#!topic/smoothie-charts/-Ywse8FCpKI%5B1-25%5D)
+ * Refactored by Krishna Narni, to support timestamp formatting function
+ * v1.10: Switch to requestAnimationFrame, removed the now obsoleted options.fps, by Gergely Imreh
+ * v1.11: options.grid.sharpLines option added, by @drewnoakes
+ * Addressed warning seen in Firefox when seriesOption.fillStyle undefined, by @drewnoakes
+ * v1.12: Support for horizontalLines added, by @drewnoakes
+ * Support for yRangeFunction callback added, by @drewnoakes
+ * v1.13: Fixed typo (#32), by @alnikitich
+ * v1.14: Timer cleared when last TimeSeries removed (#23), by @davidgaleano
+ * Fixed diagonal line on chart at start/end of data stream, by @drewnoakes
+ * v1.15: Support for npm package (#18), by @dominictarr
+ * Fixed broken removeTimeSeries function (#24) by @davidgaleano
+ * Minor performance and tidying, by @drewnoakes
+ * v1.16: Bug fix introduced in v1.14 relating to timer creation/clearance (#23), by @drewnoakes
+ * TimeSeries.append now deals with out-of-order timestamps, and can merge duplicates, by @zacwitte (#12)
+ * Documentation and some local variable renaming for clarity, by @drewnoakes
+ * v1.17: Allow control over font size (#10), by @drewnoakes
+ * Timestamp text won't overlap, by @drewnoakes
+ * v1.18: Allow control of max/min label precision, by @drewnoakes
+ * Added 'borderVisible' chart option, by @drewnoakes
+ * Allow drawing series with fill but no stroke (line), by @drewnoakes
+ * v1.19: Avoid unnecessary repaints, and fixed flicker in old browsers having multiple charts in document (#40), by @asbai
+ * v1.20: Add SmoothieChart.getTimeSeriesOptions and SmoothieChart.bringToFront functions, by @drewnoakes
+ * v1.21: Add 'step' interpolation mode, by @drewnoakes
+ * v1.22: Add support for different pixel ratios. Also add optional y limit formatters, by @copacetic
+ * v1.23: Fix bug introduced in v1.22 (#44), by @drewnoakes
+ * v1.24: Fix bug introduced in v1.23, re-adding parseFloat to y-axis formatter defaults, by @siggy_sf
+ * v1.25: Fix bug seen when adding a data point to TimeSeries which is older than the current data, by @Nking92
+ * Draw time labels on top of series, by @comolosabia
+ * Add TimeSeries.clear function, by @drewnoakes
+ * v1.26: Add support for resizing on high device pixel ratio screens, by @copacetic
+ * v1.27: Fix bug introduced in v1.26 for non whole number devicePixelRatio values, by @zmbush
+ * v1.28: Add 'minValueScale' option, by @megawac
+ * Fix 'labelPos' for different size of 'minValueString' 'maxValueString', by @henryn
+ * v1.29: Support responsive sizing, by @drewnoakes
+ * v1.29.1: Include types in package, and make property optional, by @TrentHouliston
+ * v1.30: Fix inverted logic in devicePixelRatio support, by @scanlime
+ * v1.31: Support tooltips, by @Sly1024 and @drewnoakes
+ * v1.32: Support frame rate limit, by @dpuyosa
+ * v1.33: Use Date static method instead of instance, by @nnnoel
+ * Fix bug with tooltips when multiple charts on a page, by @jpmbiz70
+ */
+
+;(function(exports) {
+
+ // Date.now polyfill
+ Date.now = Date.now || function() { return new Date().getTime(); };
+
+ var Util = {
+ extend: function() {
+ arguments[0] = arguments[0] || {};
+ for (var i = 1; i < arguments.length; i++)
+ {
+ for (var key in arguments[i])
+ {
+ if (arguments[i].hasOwnProperty(key))
+ {
+ if (typeof(arguments[i][key]) === 'object') {
+ if (arguments[i][key] instanceof Array) {
+ arguments[0][key] = arguments[i][key];
+ } else {
+ arguments[0][key] = Util.extend(arguments[0][key], arguments[i][key]);
+ }
+ } else {
+ arguments[0][key] = arguments[i][key];
+ }
+ }
+ }
+ }
+ return arguments[0];
+ },
+ binarySearch: function(data, value) {
+ var low = 0,
+ high = data.length;
+ while (low < high) {
+ var mid = (low + high) >> 1;
+ if (value < data[mid][0])
+ high = mid;
+ else
+ low = mid + 1;
+ }
+ return low;
+ }
+ };
+
+ /**
+ * Initialises a new <code>TimeSeries</code> with optional data options.
+ *
+ * Options are of the form (defaults shown):
+ *
+ * <pre>
+ * {
+ * resetBounds: true, // enables/disables automatic scaling of the y-axis
+ * resetBoundsInterval: 3000 // the period between scaling calculations, in millis
+ * }
+ * </pre>
+ *
+ * Presentation options for TimeSeries are specified as an argument to <code>SmoothieChart.addTimeSeries</code>.
+ *
+ * @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 <code>TimeSeries</code> 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 <code>TimeSeries</code>, preserving chronological order.
+ *
+ * @param timestamp the position, in time, of this data point
+ * @param value the value of this data point
+ * @param sumRepeatedTimeStampValues if <code>timestamp</code> 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 <code>SmoothieChart</code>.
+ *
+ * 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:
+ *
+ * <pre>
+ * {
+ * 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)
+ * }
+ * </pre>
+ *
+ * @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('<span style="color:' + data[i].series.options.strokeStyle + '">' +
+ this.options.yMaxFormatter(data[i].value, this.options.labels.precision) + '</span>');
+ }
+
+ return lines.join('<br>');
+ };
+
+ 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 <code>TimeSeries</code> to this chart, with optional presentation options.
+ *
+ * Presentation options should be of the form (defaults shown):
+ *
+ * <pre>
+ * {
+ * lineWidth: 1,
+ * strokeStyle: '#ffffff',
+ * fillStyle: undefined
+ * }
+ * </pre>
+ */
+ 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 <code>TimeSeries</code> 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 <code>TimeSeries</code>.
+ *
+ * As you may use a single <code>TimeSeries</code> 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 <code>TimeSeries</code> 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 <code>SmoothieChart</code> 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);