New planner sending line moves
authorJoseph Coffland <joseph@cauldrondevelopment.com>
Sun, 31 Dec 2017 19:43:56 +0000 (11:43 -0800)
committerJoseph Coffland <joseph@cauldrondevelopment.com>
Sun, 14 Jan 2018 23:40:43 +0000 (15:40 -0800)
64 files changed:
src/avr/.gitignore
src/avr/Makefile
src/avr/src/action.c [deleted file]
src/avr/src/action.h [deleted file]
src/avr/src/axis.h
src/avr/src/base64.c [new file with mode: 0644]
src/avr/src/base64.h [new file with mode: 0644]
src/avr/src/command.def
src/avr/src/command.h
src/avr/src/command.json.in [new file with mode: 0644]
src/avr/src/commands.c [new file with mode: 0644]
src/avr/src/config.h
src/avr/src/cpp_magic.h
src/avr/src/estop.c
src/avr/src/exec.c
src/avr/src/exec.h
src/avr/src/hardware.c
src/avr/src/hardware.h
src/avr/src/huanyang.c
src/avr/src/huanyang.h
src/avr/src/i2c.c
src/avr/src/i2c.h
src/avr/src/jog.c
src/avr/src/lcd.c
src/avr/src/lcd.h
src/avr/src/line.c [new file with mode: 0644]
src/avr/src/main.c
src/avr/src/messages.def
src/avr/src/messages.json.in [new file with mode: 0644]
src/avr/src/motor.c
src/avr/src/queue.c [deleted file]
src/avr/src/queue.h [deleted file]
src/avr/src/ringbuf.def
src/avr/src/rtc.c
src/avr/src/rtc.h
src/avr/src/spindle.c
src/avr/src/spindle.h
src/avr/src/state.c
src/avr/src/state.h
src/avr/src/status.c
src/avr/src/status.h
src/avr/src/stepper.c
src/avr/src/stepper.h
src/avr/src/switch.c
src/avr/src/switch.h
src/avr/src/type.c [new file with mode: 0644]
src/avr/src/type.def [new file with mode: 0644]
src/avr/src/type.h [new file with mode: 0644]
src/avr/src/usart.c
src/avr/src/usart.h
src/avr/src/util.c
src/avr/src/util.h
src/avr/src/vars.c
src/avr/src/vars.def
src/avr/src/vars.h
src/jade/templates/control-view.jade
src/js/control-view.js
src/pwr/Makefile
src/py/bbctrl/AVR.py
src/py/bbctrl/Cmd.py [new file with mode: 0644]
src/py/bbctrl/Jog.py
src/py/bbctrl/Planner.py [new file with mode: 0644]
src/py/bbctrl/__init__.py
src/resources/js/smoothie.js [new file with mode: 0644]

index bc78328d1a405e59d1bd745e019233a26e3f9712..abe2d5df9a28c775eb7e835d8f54879d67ebcf14 100755 (executable)
@@ -10,3 +10,5 @@ build
 /*.elf
 /*.lss
 /*.map
+
+*.o
index 892c91e18313e4446b6110e88eaf70b2ff08b423..27ca41009845e1f5ee14c075811c37d6f3e9391f 100644 (file)
@@ -2,7 +2,7 @@
 PROJECT         = bbctrl-avr-firmware
 MCU             = atxmega192a3u
 CLOCK           = 32000000
-VERSION         = 0.4.0
+VERSION         = 0.5.0
 
 TARGET  = $(PROJECT).elf
 
@@ -30,8 +30,10 @@ EEFLAGS += --set-section-flags=.eeprom="alloc,load"
 EEFLAGS += --change-section-lma .eeprom=0 --no-change-warnings
 
 # Programming flags
+ifndef (PROGRAMMER)
 PROGRAMMER = avrispmkII
 #PROGRAMMER = jtag3pdi
+endif
 PDEV = usb
 AVRDUDE_OPTS = -c $(PROGRAMMER) -p $(MCU) -P $(PDEV)
 
@@ -44,16 +46,17 @@ FUSE5=0xeb
 # SRC
 SRC = $(wildcard src/*.c) $(wildcard src/plan/*.c)
 OBJ = $(patsubst src/%.c,build/%.o,$(SRC))
+JSON = vars command messages
+JSON := $(patsubst %,build/%.json,$(JSON))
 
 # Build
-all: $(PROJECT).hex build/vars.json size
+all: $(PROJECT).hex $(JSON) size
 
-build/vars.json: src/vars.def
+# JSON
+build/%.json: src/%.json.in src/%.def
+       cpp -Isrc $< | sed "/^#.*$$/d;s/'\(.\)'/\"\1\"/g" > $@
 
 # Compile
-build/%.json: src/%.json.in
-       cpp -Isrc $< | sed '/^#.*$$/d' > $@
-
 build/%.o: src/%.c
        @mkdir -p $(shell dirname $@)
        $(CC) $(INCLUDES) $(CFLAGS) -c -o $@ $<
diff --git a/src/avr/src/action.c b/src/avr/src/action.c
deleted file mode 100644 (file)
index 4cc1222..0000000
+++ /dev/null
@@ -1,136 +0,0 @@
-/******************************************************************************\
-
-                This file is part of the Buildbotics firmware.
-
-                  Copyright (c) 2015 - 2017 Buildbotics LLC
-                            All rights reserved.
-
-     This file ("the software") is free software: you can redistribute it
-     and/or modify it under the terms of the GNU General Public License,
-      version 2 as published by the Free Software Foundation. You should
-      have received a copy of the GNU General Public License, version 2
-     along with the software. If not, see <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;
-}
diff --git a/src/avr/src/action.h b/src/avr/src/action.h
deleted file mode 100644 (file)
index d8678fa..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-/******************************************************************************\
-
-                This file is part of the Buildbotics firmware.
-
-                  Copyright (c) 2015 - 2017 Buildbotics LLC
-                            All rights reserved.
-
-     This file ("the software") is free software: you can redistribute it
-     and/or modify it under the terms of the GNU General Public License,
-      version 2 as published by the Free Software Foundation. You should
-      have received a copy of the GNU General Public License, version 2
-     along with the software. If not, see <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);
index 79305b0e6458746cf08964a1626f5c11a543e6e7..810ea2d58c8b7e42d0cd24313e837147c97207c7 100644 (file)
@@ -3,7 +3,6 @@
                 This file is part of the Buildbotics firmware.
 
                   Copyright (c) 2015 - 2017 Buildbotics LLC
-                  Copyright (c) 2010 - 2015 Alden S. Hart, Jr.
                             All rights reserved.
 
      This file ("the software") is free software: you can redistribute it
diff --git a/src/avr/src/base64.c b/src/avr/src/base64.c
new file mode 100644 (file)
index 0000000..545e670
--- /dev/null
@@ -0,0 +1,151 @@
+/******************************************************************************\
+
+                This file is part of the Buildbotics firmware.
+
+                  Copyright (c) 2015 - 2017 Buildbotics LLC
+                            All rights reserved.
+
+     This file ("the software") is free software: you can redistribute it
+     and/or modify it under the terms of the GNU General Public License,
+      version 2 as published by the Free Software Foundation. You should
+      have received a copy of the GNU General Public License, version 2
+     along with the software. If not, see <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;
+}
diff --git a/src/avr/src/base64.h b/src/avr/src/base64.h
new file mode 100644 (file)
index 0000000..feaaa7b
--- /dev/null
@@ -0,0 +1,37 @@
+/******************************************************************************\
+
+                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);
index 130fa10a8e9cbb552c9bc72c13a8b45a406f7062..b4ecdb9cb627298d9d9ea830bf47ce8e23565e23 100644 (file)
 
 \******************************************************************************/
 
-//  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")
index 08dd4683dfab9bf16f03ab4447cf2e657e86b4c2..7d7c098313041a0d97ba7f8f1dd338013009ac89 100644 (file)
 
 #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();
diff --git a/src/avr/src/command.json.in b/src/avr/src/command.json.in
new file mode 100644 (file)
index 0000000..dbb2263
--- /dev/null
@@ -0,0 +1,12 @@
+#include "cpp_magic.h"
+{
+#define CMD(CODE, NAME, SYNC, HELP) \
+  #NAME: {                                      \
+    "code": CODE,                               \
+    "sync": IF_ELSE(SYNC)(true, false),         \
+    "help": HELP                                \
+  },
+#include "command.def"
+#undef CMD
+  "_": {}
+}
diff --git a/src/avr/src/commands.c b/src/avr/src/commands.c
new file mode 100644 (file)
index 0000000..9ff56d9
--- /dev/null
@@ -0,0 +1,116 @@
+/******************************************************************************\
+
+                This file is part of the Buildbotics firmware.
+
+                  Copyright (c) 2015 - 2017 Buildbotics LLC
+                            All rights reserved.
+
+     This file ("the software") is free software: you can redistribute it
+     and/or modify it under the terms of the GNU General Public License,
+      version 2 as published by the Free Software Foundation. You should
+      have received a copy of the GNU General Public License, version 2
+     along with the software. If not, see <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;
+}
index 02069cae2012911dfe994050e07c527b1c5eaca0..8a490fe914754533f00cc000393a71c0ca98bc78 100644 (file)
@@ -186,10 +186,11 @@ enum {
 #define SERIAL_PORT            USARTC0
 #define SERIAL_DRE_vect        USARTC0_DRE_vect
 #define SERIAL_RXC_vect        USARTC0_RXC_vect
+#define SERIAL_CTS_THRESH      4
 
 
 // Input
-#define INPUT_BUFFER_LEN       255 // text buffer size (255 max)
+#define INPUT_BUFFER_LEN       128 // text buffer size (255 max)
 
 
 // I2C
@@ -210,6 +211,6 @@ enum {
 #define CURRENT_SENSE_REF        2.75          // volts
 #define MAX_CURRENT              10            // amps
 #define JERK_MULTIPLIER          1000000.0
-#define QUEUE_SIZE               256
-#define EXEC_FILL_TARGET         64
+#define SYNC_QUEUE_SIZE          4096
+#define EXEC_FILL_TARGET         8
 #define EXEC_DELAY               250 // ms
index c7abc59eea5523f4e3ccd023b157c7b618226798..f5f2918f8016c173a10e8ef959d9eadf1be991f4 100644 (file)
   (DEFER2(_EMAP2_INNER)()(ARG1, ARG2, ##__VA_ARGS__))
 
 #define _EMAP2_INNER() EMAP2_INNER
-
index 50cb37a87c3f54f538b191b031179a49505445d4..a73dd1ade40c71589e273ba2b1998e3872811515 100644 (file)
@@ -122,6 +122,7 @@ void estop_clear() {
 }
 
 
+// Var callbacks
 bool get_estop() {
   return estop_triggered();
 }
@@ -137,3 +138,16 @@ void set_estop(bool value) {
 PGM_P get_estop_reason() {
   return status_to_pgmstr(_get_reason());
 }
+
+
+// Command callbacks
+stat_t command_estop(char *cmd) {
+  estop_trigger(STAT_ESTOP_USER);
+  return STAT_OK;
+}
+
+
+stat_t command_clear(char *cmd) {
+  estop_clear();
+  return STAT_OK;
+}
index 99c1d6cd01d00e94592224748ea53984257c1128..59798e8e6d4ecf5cf1e75318558e8fe9f2ce4841 100644 (file)
 \******************************************************************************/
 
 #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;
@@ -72,17 +52,11 @@ static struct {
   float feed_override;
   float spindle_override;
 
-  int scurve;
-  float time;
   float leftover_time;
 
   bool seek_error;
   bool seek_open;
   int seek_switch;
-
-  uint32_t last_empty;
-
-  segment_t seg;
 } ex;
 
 
@@ -95,35 +69,26 @@ void exec_init() {
   // TODO implement feedhold
   // TODO implement move stepping
   // TODO implement overrides
+  // TODO implement optional pause
 }
 
 
-// Var callbacks
-int32_t get_line() {return ex.line;}
-uint8_t get_tool() {return ex.tool;}
-float get_velocity() {return ex.velocity;}
-float get_acceleration() {return ex.accel;}
-float get_jerk() {return ex.jerk;}
-float get_feed_override() {return ex.feed_override;}
-float get_speed_override() {return ex.spindle_override;}
-float get_axis_mach_coord(int axis) {return exec_get_axis_position(axis);}
-
-void set_tool(uint8_t tool) {ex.tool = tool;}
-void set_feed_override(float value) {ex.feed_override = value;}
-void set_speed_override(float value) {ex.spindle_override = value;}
-void set_axis_mach_coord(int axis, float position) {
-  exec_set_axis_position(axis, position);
+void exec_get_position(float p[AXES]) {
+  memcpy(p, ex.position, sizeof(ex.position));
 }
 
 
-bool exec_is_busy() {return ex.busy;}
 float exec_get_axis_position(int axis) {return ex.position[axis];}
-void exec_set_axis_position(int axis, float p) {ex.position[axis] = p;}
 void exec_set_velocity(float v) {ex.velocity = v;}
+float exec_get_velocity() {return ex.velocity;}
+void exec_set_acceleration(float a) {ex.accel = a;}
+void exec_set_jerk(float j) {ex.jerk = j;}
 void exec_set_line(int line) {ex.line = line;}
+void exec_set_cb(exec_cb_t cb) {ex.cb = cb;}
 
 
 stat_t exec_move_to_target(float time, const float target[]) {
+  ASSERT(isfinite(time));
   ASSERT(isfinite(target[AXIS_X]) && isfinite(target[AXIS_Y]) &&
          isfinite(target[AXIS_Z]) && isfinite(target[AXIS_A]) &&
          isfinite(target[AXIS_B]) && isfinite(target[AXIS_C]));
@@ -150,194 +115,30 @@ stat_t exec_move_to_target(float time, const float target[]) {
 void exec_reset_encoder_counts() {st_set_position(ex.position);}
 
 
-static float _compute_distance(double t, double v, double a, double j) {
-  // v * t + 1/2 * a * t^2 + 1/6 * j * t^3
-  return t * (v + t * (0.5 * a + 1.0 / 6.0 * j * t));
+stat_t exec_next() {
+  if (!ex.cb && !command_exec()) return STAT_NOOP; // Queue empty
+  if (!ex.cb) return STAT_EAGAIN; // Non-exec command
+  return ex.cb(); // Exec
 }
 
 
-static float _compute_velocity(double t, double a, double j) {
-  // a * t + 1/2 * j * t^2
-  return t * (a + 0.5 * j * t);
-}
-
-
-static void _segment_init(float time) {
-  ASSERT(isfinite(time) && 0 < time && time < 0x10000 * SEGMENT_TIME);
-
-  // Init segment
-  ex.seg.new = false;
-  ex.time = time;
-  ex.seg.step = 0;
-  ex.seg.steps = ceil(ex.time / SEGMENT_TIME);
-  ex.seg.delta = time / ex.seg.steps;
-  if (ex.scurve == 0) ex.seg.dist = 0;
-
-  // Record starting position and compute unit vector
-  float length = 0;
-  for (int i = 0; i < AXES; i++) {
-    ex.seg.position[i] = ex.position[i];
-    ex.seg.unit[i] = ex.seg.target[i] - ex.position[i];
-    length = ex.seg.unit[i] * ex.seg.unit[i];
-  }
-  length = sqrt(length);
-  for (int i = 0; i < AXES; i++) ex.seg.unit[i] /= length;
-
-  // Compute axis limited jerk
-  if (ex.scurve == 0) {
-    ex.seg.jerk = FLT_MAX;
-
-    for (int i = 0; i < AXES; i++)
-      if (ex.seg.unit[i]) {
-        double j = fabs(axis_get_jerk_max(i) / ex.seg.unit[i]);
-        if (j < ex.seg.jerk) ex.seg.jerk = j;
-      }
-  }
-
-  // Jerk
-  switch (ex.scurve) {
-  case 0: case 6: ex.jerk = ex.seg.jerk; break;
-  case 2: case 4: ex.jerk = -ex.seg.jerk; break;
-  default: ex.jerk = 0;
-  }
-
-  // Acceleration
-  switch (ex.scurve) {
-  case 1: ex.accel = ex.seg.jerk * ex.time; break;
-  case 3: ex.accel = 0; break;
-  case 5: ex.accel = -ex.seg.jerk * ex.time; break;
-  }
-}
-
-
-static stat_t _move_distance(float dist, bool end) {
-  // Compute target position from distance
-  float target[AXES];
-  for (int i = 0; i < AXES; i++)
-    target[i] = ex.seg.position[i] + ex.seg.unit[i] * dist;
-
-  // Check if we have reached the end of the segment
-  for (int i = 0; end && i < AXES; i++)
-    if (0.000001 < fabs(ex.seg.target[i] - target[i])) end = false;
-
-  // Do move
-  return exec_move_to_target(ex.seg.delta, end ? ex.seg.target : target);
-}
-
-
-static stat_t _segment_body() {
-  // Compute time, distance and velocity offsets
-  float t = ex.seg.delta * (ex.seg.step + 1);
-  float d = _compute_distance(t, ex.seg.vel, ex.accel, ex.jerk);
-  float v = _compute_velocity(t, ex.accel, ex.jerk);
-
-  // Update velocity
-  exec_set_velocity(ex.seg.vel + v);
-
-  return _move_distance(ex.seg.dist + d, false);
-}
-
-
-static void _set_scurve(int scurve) {
-  ex.scurve = scurve;
-  ex.seg.new = true;
-}
-
-
-static stat_t _segment_end() {
-  // Update distance and velocity
-  ex.seg.dist += _compute_distance(ex.time, ex.seg.vel, ex.accel, ex.jerk);
-  ex.seg.vel += _compute_velocity(ex.time, ex.accel, ex.jerk);
-
-  // Set final segment velocity
-  exec_set_velocity(ex.seg.vel);
-
-  // Automatically advance S-curve segment
-  if (++ex.scurve == 7) ex.scurve = 0;
-  _set_scurve(ex.scurve);
-
-  return _move_distance(ex.seg.dist, true);
-}
-
-
-static stat_t _scurve_action(float time) {
-  if (time <= 0) return STAT_NOOP; // Skip invalid curves
-  if (ex.seg.new) _segment_init(time);
-  return ++ex.seg.step == ex.seg.steps ? _segment_end() : _segment_body();
-}
-
-
-static void _set_output() {
-  int output = queue_head_left();
-  bool enable = queue_head_right();
-
-  switch (output) {
-  case 0: coolant_set_mist(enable); break;
-  case 1: coolant_set_flood(enable); break;
-  }
-}
-
-
-static void _seek() {
-  ex.seek_switch = queue_head_left();
-  ex.seek_open = queue_head_right() & (1 << 0);
-  ex.seek_error = queue_head_right() & (1 << 1);
-}
-
-
-static void _set_home() {
-  axis_set_homed(queue_head_left(), queue_head_right());
-}
-
-
-static void _pause(bool optional) {
-  // TODO Initial immediate feedhold
-}
-
-
-stat_t exec_next_action() {
-  if (queue_is_empty()) {
-    ex.busy = false;
-    ex.last_empty = rtc_get_time();
-    return STAT_NOOP;
-  }
+// Variable callbacks
+int32_t get_line() {return ex.line;}
+uint8_t get_tool() {return ex.tool;}
+float get_velocity() {return ex.velocity;}
+float get_acceleration() {return ex.accel;}
+float get_jerk() {return ex.jerk;}
+float get_feed_override() {return ex.feed_override;}
+float get_speed_override() {return ex.spindle_override;}
+float get_axis_position(int axis) {return ex.position[axis];}
 
-  // On restart wait a bit to give queue a chance to fill
-  if (!ex.busy && queue_get_fill() < EXEC_FILL_TARGET &&
-      !rtc_expired(ex.last_empty + EXEC_DELAY)) return STAT_NOOP;
-
-  ex.busy = true;
-  stat_t status = STAT_NOOP;
-
-  switch (queue_head()) {
-  case ACTION_SCURVE: _set_scurve(queue_head_int()); break;
-  case ACTION_DATA: status = _scurve_action(queue_head_float()); break; // TODO
-  case ACTION_VELOCITY: ex.seg.vel = queue_head_float(); break;
-  case ACTION_X: ex.seg.target[AXIS_X] = queue_head_float(); break;
-  case ACTION_Y: ex.seg.target[AXIS_Y] = queue_head_float(); break;
-  case ACTION_Z: ex.seg.target[AXIS_Z] = queue_head_float(); break;
-  case ACTION_A: ex.seg.target[AXIS_A] = queue_head_float(); break;
-  case ACTION_B: ex.seg.target[AXIS_B] = queue_head_float(); break;
-  case ACTION_C: ex.seg.target[AXIS_C] = queue_head_float(); break;
-  case ACTION_SEEK: _seek(); break;
-  case ACTION_OUTPUT: _set_output(); break;
-  case ACTION_DWELL: st_prep_dwell(queue_head_float()); status = STAT_OK; break;
-  case ACTION_PAUSE: _pause(queue_head_bool()); status = STAT_PAUSE; break;
-  case ACTION_TOOL: set_tool(queue_head_int()); break;
-  case ACTION_SPEED: spindle_set_speed(queue_head_float()); break;
-  case ACTION_JOG: status = jog_exec(); break;
-  case ACTION_LINE_NUM: exec_set_line(queue_head_int()); break;
-  case ACTION_SET_HOME: _set_home(); break;
-  default: status = STAT_INTERNAL_ERROR; break;
-  }
+void set_tool(uint8_t tool) {ex.tool = tool;}
+void set_feed_override(float value) {ex.feed_override = value;}
+void set_speed_override(float value) {ex.spindle_override = value;}
+void set_axis_position(int axis, float p) {ex.position[axis] = p;}
 
-  if (status != STAT_EAGAIN) queue_pop();
 
-  switch (status) {
-  case STAT_NOOP: return STAT_EAGAIN;
-  case STAT_PAUSE: return STAT_NOOP;
-  case STAT_EAGAIN: return STAT_OK;
-  default: break;
-  }
-  return status;
-}
+// Command callbacks
+stat_t command_opt_pause(char *cmd) {command_push(*cmd, 0); return STAT_OK;}
+unsigned command_opt_pause_size() {return 0;}
+void command_opt_pause_exec(void *data) {} // TODO pause if requested
index 6ae311f1c70c57a785b1f3a8f97ea38a13e908e1..f2832a98a5574d2a56c51fbdfefeb2ec866a977d 100644 (file)
 #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();
index bd3de2b6640f22e3fdf1e3ae09e4313a7b2179e9..36b77d3216232400a2f68e642f70190701a4dcbd 100644 (file)
@@ -3,7 +3,6 @@
                 This file is part of the Buildbotics firmware.
 
                   Copyright (c) 2015 - 2017 Buildbotics LLC
-                  Copyright (c) 2010 - 2015 Alden S. Hart, Jr.
                             All rights reserved.
 
      This file ("the software") is free software: you can redistribute it
index 56ea39ee3534f1c0b3d99a966e2befbe66f5ec92..4f1acf6593116dd6b8a419320146c13ce0b703ef 100644 (file)
@@ -3,8 +3,6 @@
                 This file is part of the Buildbotics firmware.
 
                   Copyright (c) 2015 - 2017 Buildbotics LLC
-                  Copyright (c) 2010 - 2015 Alden S. Hart, Jr.
-                  Copyright (c) 2012 - 2015 Rob Giseburt
                             All rights reserved.
 
      This file ("the software") is free software: you can redistribute it
index 6a22951c919a1f5a37e8cd6ae784aeb64bbe3901..39d22789a2e286d995ac8e92d989ce0c99133472 100644 (file)
@@ -123,10 +123,10 @@ typedef struct {
   uint16_t rated_rpm;
 
   uint8_t status;
-} huanyang_t;
+} hy_t;
 
 
-static huanyang_t ha = {0};
+static hy_t ha = {0};
 
 
 #define CTRL_STATUS_RESPONSE(R) ((uint16_t)R[4] << 8 | R[5])
@@ -403,7 +403,7 @@ ISR(HUANYANG_RXC_vect) {
 }
 
 
-void huanyang_init() {
+void hy_init() {
   PR.PRPD &= ~PR_USART1_bm; // Disable power reduction
 
   DIRCLR_PIN(RS485_RO_PIN); // Input
@@ -422,11 +422,11 @@ void huanyang_init() {
   USARTD1.CTRLB = USART_RXEN_bm | USART_TXEN_bm | USART_CLK2X_bm;
 
   ha.id = HUANYANG_ID;
-  huanyang_reset();
+  hy_reset();
 }
 
 
-void huanyang_set(float speed) {
+void hy_set(float speed) {
   if (ha.speed != speed) {
     if (ha.debug) STATUS_DEBUG("huanyang: speed=%0.2f", speed);
     ha.speed = speed;
@@ -435,7 +435,7 @@ void huanyang_set(float speed) {
 }
 
 
-void huanyang_reset() {
+void hy_reset() {
   _set_dre_interrupt(false);
   _set_txc_interrupt(false);
   _set_rxc_interrupt(false);
@@ -464,7 +464,7 @@ void huanyang_reset() {
 }
 
 
-void huanyang_rtc_callback() {
+void hy_rtc_callback() {
   if (ha.last && rtc_expired(ha.last + HUANYANG_TIMEOUT)) {
     if (ha.retry < HUANYANG_RETRIES) _retry_command();
     else {
@@ -488,28 +488,28 @@ void huanyang_rtc_callback() {
                      sent, received, ha.response_length);
       }
 
-      huanyang_reset();
+      hy_reset();
     }
   }
 }
 
 
-void huanyang_stop() {
-  huanyang_set(0);
-  huanyang_reset();
+void hy_stop() {
+  hy_set(0);
+  hy_reset();
 }
 
 
-uint8_t get_huanyang_id() {return ha.id;}
-void set_huanyang_id(uint8_t value) {ha.id = value;}
-bool get_huanyang_debug() {return ha.debug;}
-void set_huanyang_debug(bool value) {ha.debug = value;}
-bool get_huanyang_connected() {return ha.connected;}
-float get_huanyang_freq() {return ha.actual_freq;}
-float get_huanyang_current() {return ha.actual_current;}
-uint16_t get_huanyang_rpm() {return ha.actual_rpm;}
-uint16_t get_huanyang_temp() {return ha.temperature;}
-float get_huanyang_max_freq() {return ha.max_freq;}
-float get_huanyang_min_freq() {return ha.min_freq;}
-uint16_t get_huanyang_rated_rpm() {return ha.rated_rpm;}
-float get_huanyang_status() {return ha.status;}
+uint8_t get_hy_id() {return ha.id;}
+void set_hy_id(uint8_t value) {ha.id = value;}
+bool get_hy_debug() {return ha.debug;}
+void set_hy_debug(bool value) {ha.debug = value;}
+bool get_hy_connected() {return ha.connected;}
+float get_hy_freq() {return ha.actual_freq;}
+float get_hy_current() {return ha.actual_current;}
+uint16_t get_hy_rpm() {return ha.actual_rpm;}
+uint16_t get_hy_temp() {return ha.temperature;}
+float get_hy_max_freq() {return ha.max_freq;}
+float get_hy_min_freq() {return ha.min_freq;}
+uint16_t get_hy_rated_rpm() {return ha.rated_rpm;}
+float get_hy_status() {return ha.status;}
index e2e3ec8f44441ddf544bd52be74f516a3345d7f9..3e2847162a40fe0a488615f9565b55c8a2e4cde3 100644 (file)
@@ -30,8 +30,8 @@
 #include "spindle.h"
 
 
-void huanyang_init();
-void huanyang_set(float speed);
-void huanyang_reset();
-void huanyang_rtc_callback();
-void huanyang_stop();
+void hy_init();
+void hy_set(float speed);
+void hy_reset();
+void hy_rtc_callback();
+void hy_stop();
index 7ed451019c02c031c8b38c8f96822c74f0f1bb86..45fa9d071ff26c6aca2d707314a428d72660900c 100644 (file)
@@ -3,7 +3,6 @@
                 This file is part of the Buildbotics firmware.
 
                   Copyright (c) 2015 - 2017 Buildbotics LLC
-                  Copyright (c) 2010 - 2013 Alden S. Hart Jr.
                             All rights reserved.
 
      This file ("the software") is free software: you can redistribute it
@@ -36,7 +35,7 @@
 typedef struct {
   i2c_read_cb_t read_cb;
   i2c_write_cb_t write_cb;
-  uint8_t data[I2C_MAX_DATA];
+  uint8_t data[I2C_MAX_DATA + 1];
   uint8_t length;
   bool done;
   bool write;
@@ -53,8 +52,10 @@ static void _i2c_reset_command() {
 
 
 static void _i2c_end_command() {
-  if (i2c.length && !i2c.write && i2c.read_cb)
-    i2c.read_cb(*i2c.data, i2c.data + 1, i2c.length - 1);
+  if (i2c.length && !i2c.write && i2c.read_cb) {
+    i2c.data[i2c.length] = 0; // Null terminate
+    i2c.read_cb(i2c.data, i2c.length);
+  }
 
   _i2c_reset_command();
 }
index 83ba8c1a9bb8b07ddcc7674b442cb382c3fa06ec..373664fe52862bf236678a326685eb2dbe701e8a 100644 (file)
@@ -3,7 +3,6 @@
                 This file is part of the Buildbotics firmware.
 
                   Copyright (c) 2015 - 2017 Buildbotics LLC
-                  Copyright (c) 2010 - 2013 Alden S. Hart Jr.
                             All rights reserved.
 
      This file ("the software") is free software: you can redistribute it
 
 #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);
 
 
index e8ae33d28475598e09b7d020988b967b51bc8a4f..4a75ab0e5bc2c0dc585dc3165da5003ab70d8619 100644 (file)
@@ -31,7 +31,6 @@
 #include "util.h"
 #include "exec.h"
 #include "state.h"
-#include "queue.h"
 #include "config.h"
 
 #include <stdbool.h>
@@ -56,6 +55,7 @@ typedef struct {
 
 
 typedef struct {
+  bool active;
   bool writing;
   bool done;
 
@@ -257,20 +257,17 @@ stat_t jog_exec() {
 
   // Check if we are done
   if (jr.done) {
-    // Update machine position
-    //mach_set_position_from_runtime();
-    state_set_cycle(CYCLE_MACHINING); // Default cycle
-    state_pause_queue(false);
+    exec_set_cb(0);
+    jr.active = false;
 
     return STAT_NOOP; // Done, no move executed
   }
 
   // Compute target from velocity
   float target[AXES];
+  exec_get_position(target);
   for (int axis = 0; axis < AXES; axis++) {
-    target[axis] = exec_get_axis_position(axis) +
-      jr.axes[axis].velocity * SEGMENT_TIME;
-
+    target[axis] += jr.axes[axis].velocity * SEGMENT_TIME;
     target[axis] = _limit_position(axis, target[axis]);
   }
 
@@ -279,33 +276,37 @@ stat_t jog_exec() {
   stat_t status = exec_move_to_target(SEGMENT_TIME, target);
   if (status != STAT_OK) return status;
 
-  return STAT_EAGAIN;
+  return STAT_OK;
 }
 
 
-uint8_t command_jog(int argc, char *argv[]) {
-  if (state_get_cycle() != CYCLE_JOGGING &&
-      (state_get() != STATE_READY || state_get_cycle() != CYCLE_MACHINING))
-    return STAT_NOOP;
 
-  float velocity[AXES];
+stat_t command_jog(char *cmd) {
+  // Ignore jog commands when not already idle
+  if (!jr.active && state_get() != STATE_READY) return STAT_NOOP;
 
-  for (int axis = 0; axis < AXES; axis++)
-    if (axis < argc - 1) velocity[axis] = atof(argv[axis + 1]);
-    else velocity[axis] = 0;
+  // Skip command code
+  cmd++;
+
+  // Get velocities
+  float velocity[AXES] = {0,};
+  stat_t status = decode_axes(&cmd, velocity);
+  if (status) return status;
+
+  // Check for end of command
+  if (*cmd) return STAT_INVALID_ARGUMENTS;
 
   // Reset
-  if (state_get_cycle() != CYCLE_JOGGING) memset(&jr, 0, sizeof(jr));
+  if (!jr.active) memset(&jr, 0, sizeof(jr));
 
   jr.writing = true;
   for (int axis = 0; axis < AXES; axis++)
     jr.axes[axis].next = velocity[axis];
   jr.writing = false;
 
-  if (state_get_cycle() != CYCLE_JOGGING) {
-    state_set_cycle(CYCLE_JOGGING);
-    state_pause_queue(true);
-    queue_push(ACTION_JOG);
+  if (!jr.active) {
+    jr.active = true;
+    exec_set_cb(jog_exec);
   }
 
   return STAT_OK;
index 2050b94f43eff4bbc3f4cc1f8c07b50b5a079c53..43f57dddce86dfb9da2fe83052650ea8e88aed87 100644 (file)
@@ -3,7 +3,6 @@
                 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
index 96826f0a73848024d9d073013a017fc582d6615f..7bc2148ea5cd3cbd37598ebfda63df5364a39a18 100644 (file)
@@ -3,7 +3,6 @@
                 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
diff --git a/src/avr/src/line.c b/src/avr/src/line.c
new file mode 100644 (file)
index 0000000..4566308
--- /dev/null
@@ -0,0 +1,240 @@
+/******************************************************************************\
+
+                This file is part of the Buildbotics firmware.
+
+                  Copyright (c) 2015 - 2017 Buildbotics LLC
+                            All rights reserved.
+
+     This file ("the software") is free software: you can redistribute it
+     and/or modify it under the terms of the GNU General Public License,
+      version 2 as published by the Free Software Foundation. You should
+      have received a copy of the GNU General Public License, version 2
+     along with the software. If not, see <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);
+}
index 642b39106bf11a89e66ea789887d257f6d390bca..a0504b991431e82d3d4d6a03af4f7cf65975d9f0 100644 (file)
@@ -3,8 +3,6 @@
                 This file is part of the Buildbotics firmware.
 
                   Copyright (c) 2015 - 2017 Buildbotics LLC
-                  Copyright (c) 2010 - 2015 Alden S. Hart, Jr.
-                  Copyright (c) 2013 - 2015 Robert Giseburt
                             All rights reserved.
 
      This file ("the software") is free software: you can redistribute it
@@ -65,7 +63,7 @@ int main() {
   stepper_init();                 // steppers
   motor_init();                   // motors
   switch_init();                  // switches
-  exec_init();                      // motion exec
+  exec_init();                    // motion exec
   vars_init();                    // configuration variables
   estop_init();                   // emergency stop handler
   command_init();
@@ -73,8 +71,8 @@ int main() {
   sei();                          // enable interrupts
 
   // Splash
-  fprintf_P(stdout, PSTR("\n{\"firmware\": \"Buildbotics AVR\", "
-                         "\"version\": \"" VERSION "\"}\n"));
+  fprintf_P(stdout, PSTR("\n{\"firmware\":\"Buildbotics AVR\","
+                         "\"version\":\"" VERSION "\"}\n"));
 
   // Main loop
   while (true) {
index b002b2c2371aa55236647b2415972c6c5ae56a64..8e43a170e71ea9f197a5b157943cba34d86d9e25 100644 (file)
 
 \******************************************************************************/
 
-STAT_MSG(OK, "OK")
-STAT_MSG(EAGAIN, "Run command again")
-STAT_MSG(NOOP, "No op")
-STAT_MSG(PAUSE, "Pause")
-STAT_MSG(INTERNAL_ERROR, "Internal error")
-STAT_MSG(ESTOP_USER, "User triggered EStop")
-STAT_MSG(ESTOP_SWITCH, "Switch triggered EStop")
-STAT_MSG(UNRECOGNIZED_NAME, "Unrecognized command or variable name")
-STAT_MSG(INVALID_COMMAND, "Invalid command")
-STAT_MSG(INVALID_ARGUMENTS, "Invalid argument(s) to command")
-STAT_MSG(TOO_MANY_ARGUMENTS, "Too many arguments to command")
-STAT_MSG(TOO_FEW_ARGUMENTS, "Too few arguments to command")
+STAT_MSG(OK,                   "OK")
+STAT_MSG(EAGAIN,               "Run command again")
+STAT_MSG(NOOP,                 "No op")
+STAT_MSG(PAUSE,                "Pause")
+STAT_MSG(INTERNAL_ERROR,       "Internal error")
+STAT_MSG(ESTOP_USER,           "User triggered EStop")
+STAT_MSG(ESTOP_SWITCH,         "Switch triggered EStop")
+STAT_MSG(UNRECOGNIZED_NAME,    "Unrecognized command or variable name")
+STAT_MSG(INVALID_COMMAND,      "Invalid command")
+STAT_MSG(INVALID_ARGUMENTS,    "Invalid argument(s) to command")
+STAT_MSG(TOO_MANY_ARGUMENTS,   "Too many arguments to command")
+STAT_MSG(TOO_FEW_ARGUMENTS,    "Too few arguments to command")
 STAT_MSG(COMMAND_NOT_ACCEPTED, "Command not accepted at this time")
-STAT_MSG(MACHINE_ALARMED, "Machine alarmed - Command not processed")
-STAT_MSG(EXPECTED_MOVE, "A move was expected but none was queued")
-STAT_MSG(QUEUE_FULL, "Queue full")
-
-// End of stats marker
-STAT_MSG(MAX, "")
+STAT_MSG(MACHINE_ALARMED,      "Machine alarmed - Command not processed")
+STAT_MSG(EXPECTED_MOVE,        "A move was expected but none was queued")
+STAT_MSG(QUEUE_FULL,           "Queue full")
+STAT_MSG(QUEUE_EMPTY,          "Queue empty")
+STAT_MSG(BAD_FLOAT,            "Failed to parse float")
+STAT_MSG(INVALID_VARIABLE,     "Invalid variable")
+STAT_MSG(INVALID_VALUE,        "Invalid value")
+STAT_MSG(BUFFER_OVERFLOW,      "Buffer overflow")
+STAT_MSG(BAD_SEG_TIME,         "Bad s-curve segment time")
diff --git a/src/avr/src/messages.json.in b/src/avr/src/messages.json.in
new file mode 100644 (file)
index 0000000..d12e38f
--- /dev/null
@@ -0,0 +1,7 @@
+#include "cpp_magic.h"
+[
+#define STAT_MSG(NAME, MSG) [#NAME, MSG],
+#include "messages.def"
+#undef CMD
+  []
+]
index 60f3e1d737deaca51727961c9672cec35d76f3ac..4a8142bf503077e362708bbc060814abb0d95b24 100644 (file)
@@ -283,16 +283,15 @@ void motor_load_move(int motor) {
 
 
 void motor_prep_move(int motor, float time, float target) {
+  // Validate input
+  ASSERT(0 <= motor && motor < MOTORS);
   ASSERT(isfinite(target));
-  int32_t position = _position_to_steps(motor, target);
 
   motor_t *m = &motors[motor];
-
-  // Validate input
-  ASSERT(0 <= motor && motor < MOTORS);
   ASSERT(!m->prepped);
 
   // Travel in half steps
+  int32_t position = _position_to_steps(motor, target);
   int24_t half_steps = position - m->position;
   m->position = position;
 
diff --git a/src/avr/src/queue.c b/src/avr/src/queue.c
deleted file mode 100644 (file)
index 7a350bc..0000000
+++ /dev/null
@@ -1,103 +0,0 @@
-/******************************************************************************\
-
-                This file is part of the Buildbotics firmware.
-
-                  Copyright (c) 2015 - 2017 Buildbotics LLC
-                            All rights reserved.
-
-     This file ("the software") is free software: you can redistribute it
-     and/or modify it under the terms of the GNU General Public License,
-      version 2 as published by the Free Software Foundation. You should
-      have received a copy of the GNU General Public License, version 2
-     along with the software. If not, see <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;}
diff --git a/src/avr/src/queue.h b/src/avr/src/queue.h
deleted file mode 100644 (file)
index f375135..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-/******************************************************************************\
-
-                This file is part of the Buildbotics firmware.
-
-                  Copyright (c) 2015 - 2017 Buildbotics LLC
-                            All rights reserved.
-
-     This file ("the software") is free software: you can redistribute it
-     and/or modify it under the terms of the GNU General Public License,
-      version 2 as published by the Free Software Foundation. You should
-      have received a copy of the GNU General Public License, version 2
-     along with the software. If not, see <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();
index 40f57cb2d5dcd02d58d95017162a9ab04f67db11..bb08720270fe3ec49d80fadd6e9353165735c2ca 100644 (file)
@@ -51,6 +51,7 @@
  */
 
 #include <stdint.h>
+#include <stdbool.h>
 
 #ifndef RING_BUF_NAME
 #error Must define RING_BUF_NAME
@@ -65,7 +66,7 @@
 #endif
 
 #ifndef RING_BUF_INDEX_TYPE
-#define RING_BUF_INDEX_TYPE uint8_t
+#define RING_BUF_INDEX_TYPE volatile uint8_t
 #endif
 
 #ifndef RING_BUF_FUNC
 #endif
 
 #define RING_BUF_STRUCT CONCAT(RING_BUF_NAME, _ring_buf_t)
-#define RING_BUF CONCAT(RING_BUF_NAME, _ring_buf)
+#define RING_BUF RING_BUF_NAME
 
 typedef struct {
   RING_BUF_TYPE buf[RING_BUF_SIZE];
-  volatile RING_BUF_INDEX_TYPE head;
-  volatile RING_BUF_INDEX_TYPE tail;
-  volatile RING_BUF_INDEX_TYPE tran_tail;
-  volatile bool transaction;
+  RING_BUF_INDEX_TYPE head;
+  RING_BUF_INDEX_TYPE tail;
 } RING_BUF_STRUCT;
 
 static RING_BUF_STRUCT RING_BUF;
 
 
 RING_BUF_FUNC void CONCAT(RING_BUF_NAME, _init)() {
-  RING_BUF.head = RING_BUF.tail = RING_BUF.tran_tail = RING_BUF.transaction = 0;
+  RING_BUF.head = RING_BUF.tail = 0;
 }
 
 
-#define RING_BUF_INC CONCAT(RING_BUF_NAME, _inc)
-RING_BUF_FUNC RING_BUF_INDEX_TYPE RING_BUF_INC(RING_BUF_INDEX_TYPE x) {
-  return (x + 1) & RING_BUF_MASK;
-}
+#define RING_BUF_INC(x) (((x) + 1) & RING_BUF_MASK)
+
+
+#ifdef RING_BUF_ATOMIC_COPY
+
+#define RING_BUF_ATOMIC_READ_INDEX(INDEX)                               \
+  RING_BUF_FUNC RING_BUF_INDEX_TYPE CONCAT(RING_BUF_NAME, _read_##INDEX)() { \
+    RING_BUF_INDEX_TYPE index;                                          \
+    RING_BUF_ATOMIC_COPY(index, RING_BUF.INDEX);                        \
+    return index;                                                       \
+  }
+
+
+#define _RING_BUF_ATOMIC_WRITE_INDEX(INDEX, TARGET)                     \
+  RING_BUF_FUNC void CONCAT(RING_BUF_NAME, _write_##INDEX)              \
+  (RING_BUF_INDEX_TYPE value) {                                         \
+    RING_BUF_ATOMIC_COPY(TARGET, value);                                \
+  }
+
+
+#define RING_BUF_ATOMIC_WRITE_INDEX(INDEX)                              \
+  _RING_BUF_ATOMIC_WRITE_INDEX(INDEX, RING_BUF.INDEX)
+
 
+RING_BUF_ATOMIC_READ_INDEX(head);
+RING_BUF_ATOMIC_READ_INDEX(tail);
+RING_BUF_ATOMIC_WRITE_INDEX(head);
+RING_BUF_ATOMIC_WRITE_INDEX(tail);
 
-RING_BUF_FUNC int CONCAT(RING_BUF_NAME, _empty)() {
-  return RING_BUF.head == RING_BUF.tail;
+#define RING_BUF_READ_INDEX(INDEX) CONCAT(RING_BUF_NAME, _read_##INDEX)()
+#define RING_BUF_WRITE_INDEX(INDEX, VALUE) \
+  CONCAT(RING_BUF_NAME, _write_##INDEX)(VALUE)
+
+
+#else // RING_BUF_ATOMIC_COPY
+#define RING_BUF_READ_INDEX(INDEX) RING_BUF.INDEX
+#define RING_BUF_WRITE_INDEX(INDEX, VALUE) RING_BUF.INDEX = VALUE
+#endif // RING_BUF_ATOMIC_COPY
+
+
+RING_BUF_FUNC bool CONCAT(RING_BUF_NAME, _empty)() {
+  return RING_BUF_READ_INDEX(head) == RING_BUF_READ_INDEX(tail);
 }
 
 
-RING_BUF_FUNC int CONCAT(RING_BUF_NAME, _full)() {
-  return RING_BUF.head == RING_BUF_INC(RING_BUF.tail);
+RING_BUF_FUNC bool CONCAT(RING_BUF_NAME, _full)() {
+  return RING_BUF_READ_INDEX(head) == RING_BUF_INC(RING_BUF_READ_INDEX(tail));
 }
 
 
-RING_BUF_FUNC int CONCAT(RING_BUF_NAME, _fill)() {
-  return (RING_BUF.tail - RING_BUF.head) & RING_BUF_MASK;
+RING_BUF_FUNC RING_BUF_INDEX_TYPE CONCAT(RING_BUF_NAME, _fill)() {
+  return
+    (RING_BUF_READ_INDEX(tail) - RING_BUF_READ_INDEX(head)) & RING_BUF_MASK;
 }
 
 
-RING_BUF_FUNC int CONCAT(RING_BUF_NAME, _space)() {
-  return RING_BUF_SIZE - CONCAT(RING_BUF_NAME, _fill)();
+RING_BUF_FUNC RING_BUF_INDEX_TYPE CONCAT(RING_BUF_NAME, _space)() {
+  return (RING_BUF_SIZE - 1) - CONCAT(RING_BUF_NAME, _fill)();
 }
 
 
 RING_BUF_FUNC RING_BUF_TYPE CONCAT(RING_BUF_NAME, _peek)() {
-  return RING_BUF.buf[RING_BUF.head];
+  return RING_BUF.buf[RING_BUF_READ_INDEX(head)];
 }
 
 
 RING_BUF_FUNC RING_BUF_TYPE CONCAT(RING_BUF_NAME, _get)(int offset) {
-  return RING_BUF.buf[(RING_BUF.head + offset) & RING_BUF_MASK];
+  return RING_BUF.buf[(RING_BUF_READ_INDEX(head) + offset) & RING_BUF_MASK];
 }
 
 
 RING_BUF_FUNC void CONCAT(RING_BUF_NAME, _pop)() {
-  RING_BUF.head = RING_BUF_INC(RING_BUF.head);
+  RING_BUF_WRITE_INDEX(head, RING_BUF_INC(RING_BUF_READ_INDEX(head)));
+}
+
+
+RING_BUF_FUNC RING_BUF_TYPE CONCAT(RING_BUF_NAME, _next)() {
+  RING_BUF_TYPE x = CONCAT(RING_BUF_NAME, _peek)();
+  CONCAT(RING_BUF_NAME, _pop)();
+  return x;
 }
 
 
 RING_BUF_FUNC void CONCAT(RING_BUF_NAME, _push)(RING_BUF_TYPE data) {
-  RING_BUF.buf[RING_BUF.tail] = data;
-  RING_BUF.tail = RING_BUF_INC(RING_BUF.tail);
+  RING_BUF.buf[RING_BUF_READ_INDEX(tail)] = data;
+  RING_BUF_WRITE_INDEX(tail, RING_BUF_INC(RING_BUF_READ_INDEX(tail)));
 }
 
 
@@ -158,3 +199,12 @@ RING_BUF_FUNC void CONCAT(RING_BUF_NAME, _push)(RING_BUF_TYPE data) {
 #undef RING_BUF_TYPE
 #undef RING_BUF_INDEX_TYPE
 #undef RING_BUF_FUNC
+
+#undef RING_BUF_READ_INDEX
+#undef RING_BUF_WRITE_INDEX
+
+#ifdef RING_BUF_ATOMIC_COPY
+#undef RING_BUF_ATOMIC_COPY
+#undef RING_BUF_ATOMIC_READ_INDEX
+#undef RING_BUF_ATOMIC_WRITE_INDEX
+#endif // RING_BUF_ATOMIC_COPY
index f3bc3f39610cccff3b83223bac1202f2a8ec97cf..d37f9e1ea1054759566253b2fba1f7767fb06169 100644 (file)
@@ -3,7 +3,6 @@
                 This file is part of the Buildbotics firmware.
 
                   Copyright (c) 2015 - 2017 Buildbotics LLC
-                  Copyright (c) 2010 - 2013 Alden S. Hart Jr.
                             All rights reserved.
 
      This file ("the software") is free software: you can redistribute it
@@ -46,7 +45,7 @@ ISR(RTC_OVF_vect) {
   ticks++;
 
   switch_rtc_callback();
-  huanyang_rtc_callback();
+  hy_rtc_callback();
   if (!(ticks & 255)) motor_rtc_callback();
   lcd_rtc_callback();
 }
index 2c040bb286fd1af64e09c30aa84bda305d478419..c25ebd07772b2c5a3d39fd40f1368a9e828f5b31 100644 (file)
@@ -3,7 +3,6 @@
                 This file is part of the Buildbotics firmware.
 
                   Copyright (c) 2015 - 2017 Buildbotics LLC
-                  Copyright (c) 2010 - 2013 Alden S. Hart Jr.
                             All rights reserved.
 
      This file ("the software") is free software: you can redistribute it
index 2349895b901e81d87d65406d111daa36ec3375c7..46cd355d684bd7e0baa79643bbc80501c8afed2c 100644 (file)
@@ -3,7 +3,6 @@
                 This file is part of the Buildbotics firmware.
 
                   Copyright (c) 2015 - 2017 Buildbotics LLC
-                  Copyright (c) 2010 - 2015 Alden S. Hart, Jr.
                            All rights reserved.
 
      This file ("the software") is free software: you can redistribute it
@@ -52,7 +51,7 @@ static spindle_t spindle = {0};
 
 void spindle_init() {
   pwm_spindle_init();
-  huanyang_init();
+  hy_init();
 }
 
 
@@ -61,7 +60,7 @@ void spindle_set_speed(float speed) {
 
   switch (spindle.type) {
   case SPINDLE_TYPE_PWM: pwm_spindle_set(spindle_get_speed()); break;
-  case SPINDLE_TYPE_HUANYANG: huanyang_set(spindle_get_speed()); break;
+  case SPINDLE_TYPE_HUANYANG: hy_set(spindle_get_speed()); break;
   }
 }
 
@@ -74,7 +73,7 @@ float spindle_get_speed() {
 void spindle_stop() {
   switch (spindle.type) {
   case SPINDLE_TYPE_PWM: pwm_spindle_stop(); break;
-  case SPINDLE_TYPE_HUANYANG: huanyang_stop(); break;
+  case SPINDLE_TYPE_HUANYANG: hy_stop(); break;
   }
 }
 
index 102753b8b52601051928d9c4a7f580439ba1237b..2d55a300e734e010f00bb2f9af603fc3555ba60d 100644 (file)
@@ -3,7 +3,6 @@
                 This file is part of the Buildbotics firmware.
 
                   Copyright (c) 2015 - 2017 Buildbotics LLC
-                  Copyright (c) 2010 - 2015 Alden S. Hart, Jr.
                             All rights reserved.
 
      This file ("the software") is free software: you can redistribute it
index 742df898119b46b4c6d1ed87f81b7432a8fab2f3..3d70f5f33065735d4dd2858b67d43854d241083f 100644 (file)
@@ -3,8 +3,6 @@
                 This file is part of the Buildbotics firmware.
 
                   Copyright (c) 2015 - 2017 Buildbotics LLC
-                  Copyright (c) 2013 - 2015 Alden S. Hart, Jr.
-                  Copyright (c) 2013 - 2015 Robert Giseburt
                             All rights reserved.
 
      This file ("the software") is free software: you can redistribute it
@@ -30,7 +28,7 @@
 #include "state.h"
 
 #include "exec.h"
-#include "queue.h"
+#include "command.h"
 #include "stepper.h"
 #include "spindle.h"
 #include "report.h"
@@ -40,9 +38,7 @@
 
 static struct {
   state_t state;
-  cycle_t cycle;
   hold_reason_t hold_reason;
-  bool pause;
 
   bool hold_requested;
   bool flush_requested;
@@ -68,18 +64,6 @@ PGM_P state_get_pgmstr(state_t state) {
 }
 
 
-PGM_P state_get_cycle_pgmstr(cycle_t cycle) {
-  switch (cycle) {
-  case CYCLE_MACHINING:   return PSTR("MACHINING");
-  case CYCLE_HOMING:      return PSTR("HOMING");
-  case CYCLE_PROBING:     return PSTR("PROBING");
-  case CYCLE_JOGGING:     return PSTR("JOGGING");
-  }
-
-  return PSTR("INVALID");
-}
-
-
 PGM_P state_get_hold_reason_pgmstr(hold_reason_t reason) {
   switch (reason) {
   case HOLD_REASON_USER_PAUSE:    return PSTR("USER");
@@ -94,7 +78,6 @@ PGM_P state_get_hold_reason_pgmstr(hold_reason_t reason) {
 
 
 state_t state_get() {return s.state;}
-cycle_t state_get_cycle() {return s.cycle;}
 
 
 static void _set_state(state_t state) {
@@ -106,29 +89,6 @@ static void _set_state(state_t state) {
 }
 
 
-void state_set_cycle(cycle_t cycle) {
-  if (s.cycle == cycle) return; // No change
-
-  if (s.state != STATE_READY && cycle != CYCLE_MACHINING) {
-    STATUS_ERROR(STAT_INTERNAL_ERROR, "Cannot transition to %S while %S",
-                 state_get_cycle_pgmstr(cycle),
-                 state_get_pgmstr(s.state));
-    return;
-  }
-
-  if (s.cycle != CYCLE_MACHINING && cycle != CYCLE_MACHINING) {
-    STATUS_ERROR(STAT_INTERNAL_ERROR,
-                 "Cannot transition to cycle %S while in %S",
-                 state_get_cycle_pgmstr(cycle),
-                 state_get_cycle_pgmstr(s.cycle));
-    return;
-  }
-
-  s.cycle = cycle;
-  report_request();
-}
-
-
 void state_set_hold_reason(hold_reason_t reason) {
   if (s.hold_reason == reason) return; // No change
   s.hold_reason = reason;
@@ -142,18 +102,10 @@ bool state_is_resuming() {return s.resume_requested;}
 
 bool state_is_quiescent() {
   return (state_get() == STATE_READY || state_get() == STATE_HOLDING) &&
-    !st_is_busy() && !exec_is_busy();
-}
-
-
-bool state_is_ready() {
-  return queue_get_room() && !state_is_resuming() && !s.pause;
+    !st_is_busy() && !command_is_busy();
 }
 
 
-void state_pause_queue(bool x) {s.pause = x;}
-
-
 void state_optional_pause() {
   if (s.optional_pause_requested) {
     state_set_hold_reason(HOLD_REASON_USER_PAUSE);
@@ -176,27 +128,12 @@ void state_running() {
 }
 
 
-void state_idle() {
-  if (state_get() == STATE_RUNNING) _set_state(STATE_READY);
-}
-
-
-void state_estop() {
-  _set_state(STATE_ESTOPPED);
-  state_pause_queue(false);
-}
-
-
+void state_idle() {if (state_get() == STATE_RUNNING) _set_state(STATE_READY);}
+void state_estop() {_set_state(STATE_ESTOPPED);}
 void state_request_hold() {s.hold_requested = true;}
 void state_request_start() {s.start_requested = true;}
 void state_request_flush() {s.flush_requested = true;}
-
-
-void state_request_resume() {
-  if (s.flush_requested) s.resume_requested = true;
-}
-
-
+void state_request_resume() {if (s.flush_requested) s.resume_requested = true;}
 void state_request_optional_pause() {s.optional_pause_requested = true;}
 
 
@@ -237,8 +174,7 @@ void state_callback() {
 
   // Only flush queue when idle or holding.
   if (s.flush_requested && state_is_quiescent()) {
-
-    if (!queue_is_empty()) queue_flush();
+    command_flush_queue();
 
     // Stop spindle
     spindle_stop();
@@ -258,7 +194,7 @@ void state_callback() {
 
     if (state_get() == STATE_HOLDING) {
       // Check if any moves are buffered
-      if (!queue_is_empty()) _set_state(STATE_RUNNING);
+      if (command_get_count()) _set_state(STATE_RUNNING);
       else _set_state(STATE_READY);
     }
   }
@@ -267,5 +203,4 @@ void state_callback() {
 
 // Var callbacks
 PGM_P get_state() {return state_get_pgmstr(state_get());}
-PGM_P get_cycle() {return state_get_cycle_pgmstr(state_get_cycle());}
 PGM_P get_hold_reason() {return state_get_hold_reason_pgmstr(s.hold_reason);}
index 5d42cc51a0121e4f9ada7be3b3abe91e45164f6c..dac87763fc69aaf09ca124e144bebd6c8f63f517 100644 (file)
@@ -3,8 +3,6 @@
                 This file is part of the Buildbotics firmware.
 
                   Copyright (c) 2015 - 2017 Buildbotics LLC
-                  Copyright (c) 2013 - 2015 Alden S. Hart, Jr.
-                  Copyright (c) 2013 - 2015 Robert Giseburt
                             All rights reserved.
 
      This file ("the software") is free software: you can redistribute it
@@ -43,14 +41,6 @@ typedef enum {
 } state_t;
 
 
-typedef enum {
-  CYCLE_MACHINING,
-  CYCLE_HOMING,
-  CYCLE_PROBING,
-  CYCLE_JOGGING,
-} cycle_t;
-
-
 typedef enum {
   HOLD_REASON_USER_PAUSE,
   HOLD_REASON_PROGRAM_PAUSE,
@@ -61,19 +51,14 @@ typedef enum {
 
 
 PGM_P state_get_pgmstr(state_t state);
-PGM_P state_get_cycle_pgmstr(cycle_t cycle);
 PGM_P state_get_hold_reason_pgmstr(hold_reason_t reason);
 
 state_t state_get();
-cycle_t state_get_cycle();
-void state_set_cycle(cycle_t cycle);
 void state_set_hold_reason(hold_reason_t reason);
 
 bool state_is_flushing();
 bool state_is_resuming();
 bool state_is_quiescent();
-bool state_is_ready();
-void state_pause_queue(bool x);
 
 void state_optional_pause();
 void state_holding();
index c7912867d64001786675e20fc45aa0429b4ba5c6..8351b5db8d6d59da708361c71b51f63261a8e5e5 100644 (file)
@@ -73,7 +73,7 @@ stat_t status_message_P(const char *location, status_level_t level,
   va_list args;
 
   // Type
-  printf_P(PSTR("\n{\"level\":\"%"PRPSTR"\", \"msg\":\""),
+  printf_P(PSTR("\n{\"level\":\"%"PRPSTR"\",\"msg\":\""),
            status_level_pgmstr(level));
 
   // Message
@@ -82,15 +82,15 @@ stat_t status_message_P(const char *location, status_level_t level,
     vfprintf_P(stdout, msg, args);
     va_end(args);
 
-  } else printf_P("%" PRPSTR, status_to_pgmstr(code));
+  } else printf_P(PSTR("%" PRPSTR), status_to_pgmstr(code));
 
   putchar('"');
 
   // Code
-  if (code) printf_P(PSTR(", \"code\": %d"), code);
+  if (code) printf_P(PSTR(",\"code\":%d"), code);
 
   // Location
-  if (location) printf_P(PSTR(", \"where\": \"%"PRPSTR"\""), location);
+  if (location) printf_P(PSTR(",\"where\":\"%"PRPSTR"\""), location);
 
   putchar('}');
   putchar('\n');
@@ -99,21 +99,6 @@ stat_t status_message_P(const char *location, status_level_t level,
 }
 
 
-void status_help() {
-  putchar('{');
-
-  for (int i = 0; i < STAT_MAX; i++) {
-    if (i) putchar(',');
-    putchar('\n');
-    printf_P(PSTR("  \"%d\": \"%"PRPSTR"\""), i, status_to_pgmstr(i));
-  }
-
-  putchar('\n');
-  putchar('}');
-  putchar('\n');
-}
-
-
 /// Alarm state; send an exception report and stop processing input
 stat_t status_alarm(const char *location, stat_t code, const char *msg) {
   status_message_P(location, STAT_LEVEL_ERROR, code, msg);
index 5e2b91d493d7393e40c0d3994eb94dc7153acd63..3cb5933729916e6afe472df468d59242766c7dec 100644 (file)
@@ -39,6 +39,7 @@ typedef enum {
 #include "messages.def"
 #undef STAT_MSG
 
+  STAT_MAX,
   STAT_DO_NOT_EXCEED = 255 // Do not exceed 255
 } stat_t;
 
@@ -58,7 +59,6 @@ const char *status_level_pgmstr(status_level_t level);
 stat_t status_error(stat_t code);
 stat_t status_message_P(const char *location, status_level_t level,
                         stat_t code, const char *msg, ...);
-void status_help();
 
 /// Enter alarm state. returns same status code
 stat_t status_alarm(const char *location, stat_t status, const char *msg);
index 660a51f16c2b8f6c70083860ea53fb02ffcbb2fe..48d2ec817d4a3b8a2384dfb9da1b8f7e05f9580f 100644 (file)
@@ -3,8 +3,6 @@
                 This file is part of the Buildbotics firmware.
 
                   Copyright (c) 2015 - 2017 Buildbotics LLC
-                  Copyright (c) 2010 - 2015 Alden S. Hart, Jr.
-                  Copyright (c) 2013 - 2015 Robert Giseburt
                             All rights reserved.
 
      This file ("the software") is free software: you can redistribute it
@@ -109,7 +107,7 @@ bool st_is_busy() {return st.busy;}
 /// ADC channel 0 triggered by load ISR as a "software" interrupt.
 ISR(STEP_LOW_LEVEL_ISR) {
   while (true) {
-    stat_t status = exec_next_action();
+    stat_t status = exec_next();
 
     switch (status) {
     case STAT_NOOP: st.busy = false;  break; // No command executed
@@ -204,7 +202,7 @@ ISR(STEP_TIMER_ISR) {_load_move();}
 
 
 void st_prep_line(float time, const float target[]) {
-  // Trap conditions that would prevent queueing the line
+  // Trap conditions that would prevent queuing the line
   ASSERT(!st.move_ready);
   ASSERT(isfinite(time));
 
@@ -214,7 +212,8 @@ void st_prep_line(float time, const float target[]) {
 
   // Prepare motor moves
   for (int motor = 0; motor < MOTORS; motor++)
-    motor_prep_move(motor, time, target[motor_get_axis(motor)]);
+    //if (motor_is_enabled(motor))
+      motor_prep_move(motor, time, target[motor_get_axis(motor)]);
 
   st.move_queued = true; // signal prep buffer ready (do this last)
 }
index 7b3ea9b0899e142ca3a1cbed3ce8789fab0f6358..7fd52a001978669cd75fcdfca580ce9fe65e5315 100644 (file)
@@ -3,8 +3,6 @@
                 This file is part of the Buildbotics firmware.
 
                   Copyright (c) 2015 - 2017 Buildbotics LLC
-                  Copyright (c) 2010 - 2015 Alden S. Hart, Jr.
-                  Copyright (c) 2012 - 2015 Rob Giseburt
                             All rights reserved.
 
      This file ("the software") is free software: you can redistribute it
index 489c5548e0e4ccda94aff0c745eea0c652070d5f..ca676b271e9d7e5474f8f7289f3102b7ba5f7cbe 100644 (file)
@@ -3,7 +3,6 @@
                 This file is part of the Buildbotics firmware.
 
                   Copyright (c) 2015 - 2017 Buildbotics LLC
-                  Copyright (c) 2010 - 2015 Alden S. Hart, Jr.
                             All rights reserved.
 
      This file ("the software") is free software: you can redistribute it
index aad8ad10c06a37b90609de6d8774eb781b9eace4..387dcdf81ce90fcb93e3ae2e465bee8af06f34fa 100644 (file)
@@ -3,7 +3,6 @@
                 This file is part of the Buildbotics firmware.
 
                   Copyright (c) 2015 - 2017 Buildbotics LLC
-                  Copyright (c) 2010 - 2015 Alden S. Hart, Jr.
                            All rights reserved.
 
      This file ("the software") is free software: you can redistribute it
diff --git a/src/avr/src/type.c b/src/avr/src/type.c
new file mode 100644 (file)
index 0000000..09724ae
--- /dev/null
@@ -0,0 +1,188 @@
+/******************************************************************************\
+
+                This file is part of the Buildbotics firmware.
+
+                  Copyright (c) 2015 - 2017 Buildbotics LLC
+                            All rights reserved.
+
+     This file ("the software") is free software: you can redistribute it
+     and/or modify it under the terms of the GNU General Public License,
+      version 2 as published by the Free Software Foundation. You should
+      have received a copy of the GNU General Public License, version 2
+     along with the software. If not, see <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;
+}
diff --git a/src/avr/src/type.def b/src/avr/src/type.def
new file mode 100644 (file)
index 0000000..ab5447f
--- /dev/null
@@ -0,0 +1,41 @@
+/******************************************************************************\
+
+                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)
diff --git a/src/avr/src/type.h b/src/avr/src/type.h
new file mode 100644 (file)
index 0000000..d568461
--- /dev/null
@@ -0,0 +1,67 @@
+/******************************************************************************\
+
+                This file is part of the Buildbotics firmware.
+
+                  Copyright (c) 2015 - 2017 Buildbotics LLC
+                            All rights reserved.
+
+     This file ("the software") is free software: you can redistribute it
+     and/or modify it under the terms of the GNU General Public License,
+      version 2 as published by the Free Software Foundation. You should
+      have received a copy of the GNU General Public License, version 2
+     along with the software. If not, see <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);
index 89f14dcb6688f53d51de5c2615d9f634785f0687..61d3d919808203e1be766ab01d882c202c2d7480 100644 (file)
@@ -55,8 +55,10 @@ static void _set_dre_interrupt(bool enable) {
 
 static void _set_rxc_interrupt(bool enable) {
   if (enable) {
+    if (SERIAL_CTS_THRESH <= rx_buf_space())
+      OUTCLR_PIN(SERIAL_CTS_PIN); // CTS Lo (enable)
+
     SERIAL_PORT.CTRLA |= USART_RXCINTLVL_HI_gc;
-    if (4 <= rx_buf_space()) OUTCLR_PIN(SERIAL_CTS_PIN); // CTS Lo (enable)
 
   } else SERIAL_PORT.CTRLA &= ~USART_RXCINTLVL_HI_gc;
 }
@@ -78,9 +80,9 @@ ISR(SERIAL_RXC_vect) {
   if (rx_buf_full()) _set_rxc_interrupt(false); // Disable interrupt
 
   else {
-    uint8_t data = SERIAL_PORT.DATA;
-    rx_buf_push(data);
-    if (rx_buf_space() < 4) OUTSET_PIN(SERIAL_CTS_PIN); // CTS Hi (disable)
+    rx_buf_push(SERIAL_PORT.DATA);
+    if (rx_buf_space() < SERIAL_CTS_THRESH)
+      OUTSET_PIN(SERIAL_CTS_PIN); // CTS Hi (disable)
   }
 }
 
@@ -90,6 +92,7 @@ static int _usart_putchar(char c, FILE *f) {
   return 0;
 }
 
+
 static FILE _stdout = FDEV_SETUP_STREAM(_usart_putchar, 0, _FDEV_SETUP_WRITE);
 
 
@@ -180,8 +183,7 @@ void usart_puts(const char *s) {while (*s) usart_putc(*s++);}
 int8_t usart_getc() {
   while (rx_buf_empty()) continue;
 
-  uint8_t data = rx_buf_peek();
-  rx_buf_pop();
+  uint8_t data = rx_buf_next();
 
   _set_rxc_interrupt(true); // Enable interrupt
 
index 915d3622fd0027aaa42537aeeee87fe496a075f7..d806a7d92262ed75f55c532157fcde6eb0e8f90f 100644 (file)
@@ -34,6 +34,7 @@
 #define USART_TX_RING_BUF_SIZE 256
 #define USART_RX_RING_BUF_SIZE 256
 
+
 enum {
   USART_BAUD_9600,
   USART_BAUD_19200,
index 1a76542a6ef49b27872257a550daf393605292f4..a1acd9037ea7cfe6ef751b9d1fcdf17f05dd4c25 100644 (file)
@@ -3,7 +3,6 @@
                 This file is part of the Buildbotics firmware.
 
                   Copyright (c) 2015 - 2017 Buildbotics LLC
-                  Copyright (c) 2010 - 2015 Alden S. Hart, Jr.
                             All rights reserved.
 
      This file ("the software") is free software: you can redistribute it
 
 #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
@@ -49,3 +52,24 @@ float invsqrt(float x) {
 
   return u.f;
 }
+
+
+bool decode_float(char **s, float *f) {
+  bool ok = b64_decode_float(*s, f) && isfinite(*f);
+  *s += 6;
+  return ok;
+}
+
+
+stat_t decode_axes(char **cmd, float axes[AXES]) {
+  while (**cmd) {
+    const char *names = "xyzabc";
+    const char *match = strchr(names, **cmd);
+    if (!match) break;
+    char *s = *cmd + 1;
+    if (!decode_float(&s, &axes[match - names])) return STAT_BAD_FLOAT;
+    *cmd = s;
+  }
+
+  return STAT_OK;
+}
index 3ec44b8ef72eda33b659b9268e9f4d256285d149..b431f8a0db7f228e8ddd22756a30b766a2d0cf07 100644 (file)
@@ -3,7 +3,6 @@
                 This file is part of the Buildbotics firmware.
 
                   Copyright (c) 2015 - 2017 Buildbotics LLC
-                  Copyright (c) 2010 - 2014 Alden S. Hart, Jr.
                             All rights reserved.
 
      This file ("the software") is free software: you can redistribute it
@@ -30,6 +29,7 @@
 
 
 #include "config.h"
+#include "status.h"
 
 #include <stdint.h>
 #include <math.h>
@@ -65,6 +65,9 @@ inline static bool fp_ZERO(float a) {return fabs(a) < EPSILON;}
 inline static bool fp_FALSE(float a) {return fp_ZERO(a);}
 inline static bool fp_TRUE(float a) {return !fp_ZERO(a);}
 
+bool decode_float(char **s, float *f);
+stat_t decode_axes(char **cmd, float axes[AXES]);
+
 // Constants
 #define MM_PER_INCH 25.4
 #define INCHES_PER_MM (1 / 25.4)
index 8bda4221d2241f016a8875d3fe4de897458e28c7..ae92830af2aaa914d070fadafcbe44881a0e9bdd 100644 (file)
 
 #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"
@@ -187,6 +52,7 @@ enum {
   var_code_count
 };
 
+
 // Var forward declarations
 #define VAR(NAME, CODE, TYPE, INDEX, SET, ...)          \
   TYPE get_##NAME(IF(INDEX)(int index));                \
@@ -196,6 +62,40 @@ enum {
 #include "vars.def"
 #undef VAR
 
+
+// Set callback union
+typedef union {
+#define TYPEDEF(TYPE, ...) void (*set_##TYPE)(TYPE);
+#include "type.def"
+#undef TYPEDEF
+
+#define TYPEDEF(TYPE, ...) void (*set_##TYPE##_index)(int i, TYPE);
+#include "type.def"
+#undef TYPEDEF
+} set_cb_u;
+
+
+// Get callback union
+typedef union {
+#define TYPEDEF(TYPE, ...) TYPE (*get_##TYPE)();
+#include "type.def"
+#undef TYPEDEF
+
+#define TYPEDEF(TYPE, ...) TYPE (*get_##TYPE##_index)(int i);
+#include "type.def"
+#undef TYPEDEF
+} get_cb_u;
+
+
+typedef struct {
+  type_t type;
+  char name[5];
+  int8_t index;
+  get_cb_u get;
+  set_cb_u set;
+} var_info_t;
+
+
 // Var names & help
 #define VAR(NAME, CODE, TYPE, INDEX, SET, REPORT, HELP)      \
   static const char NAME##_name[] PROGMEM = #NAME;          \
@@ -204,6 +104,7 @@ enum {
 #include "vars.def"
 #undef VAR
 
+
 // Last value
 #define VAR(NAME, CODE, TYPE, INDEX, ...)       \
   static TYPE NAME##_state IF(INDEX)([INDEX]);
@@ -211,6 +112,7 @@ enum {
 #include "vars.def"
 #undef VAR
 
+
 // Report
 static uint8_t _report_var[(var_code_count >> 3) + 1] = {0,};
 
@@ -266,7 +168,7 @@ void vars_report(bool full) {
     TYPE last = (NAME##_state)IF(INDEX)([i]);                           \
     bool report = _get_report_var(var_code_##CODE);                     \
                                                                         \
-    if ((report && !var_eq_##TYPE(value, last)) || full) {              \
+    if ((report && !type_eq_##TYPE(value, last)) || full) {             \
       (NAME##_state)IF(INDEX)([i]) = value;                             \
                                                                         \
       if (!reported) {                                                  \
@@ -278,7 +180,7 @@ void vars_report(bool full) {
         (IF_ELSE(INDEX)(indexed_code_fmt, code_fmt),                    \
          IF(INDEX)(INDEX##_LABEL[i],) #CODE);                           \
                                                                         \
-      var_print_##TYPE(value);                                          \
+      type_print_##TYPE(value);                                         \
     }                                                                   \
   }
 
@@ -291,7 +193,6 @@ void vars_report(bool full) {
   hw_restore_watchdog(wd_state);
 }
 
-
 void vars_report_all(bool enable) {
 #define VAR(NAME, CODE, TYPE, INDEX, SET, REPORT, ...)                  \
   _set_report_var(var_code_##CODE, enable);
@@ -332,22 +233,28 @@ static char *_resolve_name(const char *_name) {
 }
 
 
-bool vars_print(const char *_name) {
+static bool _find_var(const char *_name, var_info_t *info) {
   char *name = _resolve_name(_name);
   if (!name) return false;
 
-  int i;
-#define VAR(NAME, CODE, TYPE, INDEX, ...)                               \
+  int i = -1;
+  memset(info, 0, sizeof(var_info_t));
+  strcpy(info->name, name);
+
+#define VAR(NAME, CODE, TYPE, INDEX, SET, ...)                          \
   if (!strcmp(IF_ELSE(INDEX)(name + 1, name), #CODE)) {                 \
     IF(INDEX)                                                           \
       (i = strchr(INDEX##_LABEL, name[0]) - INDEX##_LABEL;              \
        if (INDEX <= i) return false);                                   \
                                                                         \
-    printf("{\"%s\":", _name);                                          \
-    var_print_##TYPE(get_##NAME(IF(INDEX)(i)));                         \
-    putchar('}');                                                       \
+    info->type = TYPE_##TYPE;                                           \
+    info->index = i;                                                    \
+    info->get.IF_ELSE(INDEX)(get_##TYPE##_index, get_##TYPE) = get_##NAME; \
+                                                                        \
+    IF(SET)(info->set.IF_ELSE(INDEX)                                    \
+            (set_##TYPE##_index, set_##TYPE) = set_##NAME;)             \
                                                                         \
-    return true;                                                        \
+      return true;                                                      \
   }
 
 #include "vars.def"
@@ -357,85 +264,63 @@ bool vars_print(const char *_name) {
 }
 
 
-bool vars_set(const char *_name, const char *value) {
-  char *name = _resolve_name(_name);
-  if (!name) return false;
-
-  int i;
-#define VAR(NAME, CODE, TYPE, INDEX, SET, ...)                          \
-  IF(SET)                                                               \
-    (if (!strcmp(IF_ELSE(INDEX)(name + 1, name), #CODE)) {              \
-      IF(INDEX)                                                         \
-        (i = strchr(INDEX##_LABEL, name[0]) - INDEX##_LABEL;            \
-         if (INDEX <= i) return false);                                 \
-                                                                        \
-      TYPE x = var_parse_##TYPE(value);                                 \
-      set_##NAME(IF(INDEX)(i,) x);                                      \
-                                                                        \
-      return true;                                                      \
-    })                                                                  \
+static type_u _get(type_t type, int8_t index, get_cb_u cb) {
+  type_u value;
 
-#include "vars.def"
-#undef VAR
+  switch (type) {
+#define TYPEDEF(TYPE, ...)                                              \
+    case TYPE_##TYPE:                                                   \
+      if (index == -1) value._##TYPE = cb.get_##TYPE();                 \
+      value._##TYPE = cb.get_##TYPE##_index(index);                     \
+      break;
+#include "type.def"
+#undef TYPEDEF
+  }
 
-  return false;
+  return value;
 }
 
 
-float vars_get_number(const char *_name) {
-  char *name = _resolve_name(_name);
-  if (!name) return 0;
+static void _set(type_t type, int8_t index, set_cb_u cb, type_u value) {
+  switch (type) {
+#define TYPEDEF(TYPE, ...)                                              \
+    case TYPE_##TYPE:                                                   \
+      if (index == -1) cb.set_##TYPE(value._##TYPE);                    \
+      else cb.set_##TYPE##_index(index, value._##TYPE);                 \
+      break;
+#include "type.def"
+#undef TYPEDEF
+  }
+}
 
-  int i;
-#define VAR(NAME, CODE, TYPE, INDEX, SET, ...)                          \
-  if (!strcmp(IF_ELSE(INDEX)(name + 1, name), #CODE)) {                 \
-    IF(INDEX)                                                           \
-      (i = strchr(INDEX##_LABEL, name[0]) - INDEX##_LABEL;              \
-       if (INDEX <= i) return 0);                                       \
-                                                                        \
-    TYPE x = get_##NAME(IF(INDEX)(i));                                  \
-    return var_##TYPE##_to_float(x);                                    \
-  }                                                                     \
 
-#include "vars.def"
-#undef VAR
+bool vars_print(const char *name) {
+  var_info_t info;
+  if (!_find_var(name, &info)) return false;
+
+  printf("{\"%s\":", info.name);
+  type_print(info.type, _get(info.type, info.index, info.get));
+  putchar('}');
+  putchar('\n');
 
-  return 0;
+  return true;
 }
 
 
-int vars_parser(char *vars) {
-  if (!*vars || *vars != '{') return STAT_OK;
-  vars++;
-
-  while (*vars) {
-    while (isspace(*vars)) vars++;
-    if (*vars == '}') return STAT_OK;
-    if (*vars != '"') return STAT_COMMAND_NOT_ACCEPTED;
-
-    // Parse name
-    vars++; // Skip "
-    const char *name = vars;
-    while (*vars && *vars != '"') vars++;
-    *vars++ = 0;
-
-    while (isspace(*vars)) vars++;
-    if (*vars != ':') return STAT_COMMAND_NOT_ACCEPTED;
-    vars++;
-    while (isspace(*vars)) vars++;
-
-    // Parse value
-    const char *value = vars;
-    while (*vars && *vars != ',' && *vars != '}') vars++;
-    if (*vars) {
-      char c = *vars;
-      *vars++ = 0;
-      vars_set(name, value);
-      if (c == '}') break;
-    }
-  }
+bool vars_set(const char *name, const char *value) {
+  var_info_t info;
+  if (!_find_var(name, &info)) return false;
 
-  return STAT_OK;
+  _set(info.type, info.index, info.set, type_parse(info.type, value));
+
+  return true;
+}
+
+
+float vars_get_number(const char *name) {
+  var_info_t info;
+  if (!_find_var(name, &info)) return 0;
+  return type_to_float(info.type, _get(info.type, info.index, info.get));
 }
 
 
@@ -446,11 +331,82 @@ void vars_print_help() {
   // Save and disable watchdog
   uint8_t wd_state = hw_disable_watchdog();
 
-#define VAR(NAME, CODE, TYPE, ...)                               \
-  printf_P(fmt, #CODE, NAME##_name, TYPE##_name, NAME##_help);
+#define VAR(NAME, CODE, TYPE, ...)                                      \
+  printf_P(fmt, #CODE, NAME##_name, type_get_##TYPE##_name_pgm(), NAME##_help);
 #include "vars.def"
 #undef VAR
 
   // Restore watchdog
   hw_restore_watchdog(wd_state);
 }
+
+
+// Command callbacks
+stat_t command_var(char *cmd) {
+  cmd++; // Skip command code
+
+  if (*cmd == '$' && !cmd[1]) {
+    report_request_full();
+    return STAT_OK;
+  }
+
+  // Get or set variable
+  char *value = strchr(cmd, '=');
+  if (value) {
+    *value++ = 0;
+    if (vars_set(cmd, value)) return STAT_OK;
+
+  } else if (vars_print(cmd)) return STAT_OK;
+
+  STATUS_ERROR(STAT_UNRECOGNIZED_NAME, "'%s'", cmd);
+  return STAT_UNRECOGNIZED_NAME;
+}
+
+
+typedef struct {
+  type_t type;
+  int8_t index;
+  set_cb_u set;
+  type_u value;
+} var_cmd_t;
+
+
+stat_t command_sync_var(char *cmd) {
+  // Get value
+  char *value = strchr(cmd + 1, '=');
+  if (!value) return STAT_INVALID_COMMAND;
+  *value++ = 0;
+
+  var_info_t info;
+  if (!_find_var(cmd + 1, &info)) return STAT_UNRECOGNIZED_NAME;
+
+  var_cmd_t buffer;
+
+  buffer.type = info.type;
+  buffer.index = info.index;
+  buffer.set = info.set;
+  buffer.value = type_parse(info.type, value);
+
+  command_push(*cmd, &buffer);
+
+  return STAT_OK;
+}
+
+
+unsigned command_sync_var_size() {return sizeof(var_cmd_t);}
+
+
+void command_sync_var_exec(char *data) {
+  var_cmd_t *buffer = (var_cmd_t *)data;
+  _set(buffer->type, buffer->index, buffer->set, buffer->value);
+}
+
+
+stat_t command_report(char *cmd) {
+  bool enable = cmd[1] != '0';
+
+  if (cmd[2]) vars_report_var(cmd + 2, enable);
+  else vars_report_all(enable);
+
+  return STAT_OK;
+}
index c586995053fb87b223eb26de1c096b160cf982ee..59f9d1c4946efb76a27dfa045399b222e178f26b 100644 (file)
 #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")
index 4ad37b7accea395c7c69a168a8314078d169f1da..abafea41e4682dbe9891def1c5c9896e207f7b21 100644 (file)
@@ -31,6 +31,8 @@
 
 #include <stdbool.h>
 
+
+float var_decode_float(const char *value);
 bool var_parse_bool(const char *value);
 
 void vars_init();
@@ -41,5 +43,4 @@ void vars_report_var(const char *code, bool enable);
 bool vars_print(const char *name);
 bool vars_set(const char *name, const char *value);
 float vars_get_number(const char *name);
-int vars_parser(char *vars);
 void vars_print_help();
index d69fef8f659fec184f4e3968186db0bd6c1f80a7..c1ae4a323ed2f66bb5877a4f2c43b72d22490d16 100644 (file)
@@ -76,7 +76,7 @@ script#control-view-template(type="text/x-template")
         td(:class="{attention: highlight_reason()}") {{get_state()}}
         td
       tr
-        th Reason
+        th Message
         td.reason(:class="{attention: highlight_reason()}") {{get_reason()}}
         td
       tr
@@ -134,8 +134,7 @@ script#control-view-template(type="text/x-template")
 
       button.pure-button(
         title="{{state.x == 'RUNNING' ? 'Pause' : 'Start'}} program.",
-        @click="start_pause",
-        :disabled="state.c == 'HOMING' || !file")
+        @click="start_pause", :disabled="!file")
         .fa(:class="state.x == 'RUNNING' ? 'fa-pause' : 'fa-play'")
 
       button.pure-button(title="Stop program.", @click="stop",
@@ -143,7 +142,7 @@ script#control-view-template(type="text/x-template")
         .fa.fa-stop
 
       button.pure-button(title="Pause program at next optional stop (M1).",
-        @click="optional_pause", :disabled="state.c == 'HOMING'")
+        @click="optional_pause")
         .fa.fa-stop-circle-o
 
       button.pure-button(title="Execute one program step.", @click="step",
index 3b92f091c7e160570c37fd9728fa7bd9b6973ac6..96107fcdcb09b3a5facaac598e5774e0cc706da9 100644 (file)
@@ -81,11 +81,7 @@ module.exports = {
 
 
   methods: {
-    get_state: function () {
-      var state = this.state.x || '';
-      if (state == 'RUNNING' && this.state.c) state = this.state.c;
-      return state;
-    },
+    get_state: function () {return this.state.x || ''},
 
 
     get_reason: function () {
index 69a69d3bf78f182dd3c3441977564001a439d1df..b3c9ce2943114451f985101094ad98ffa3e7de6d 100644 (file)
@@ -20,8 +20,10 @@ LDFLAGS += $(COMMON) -Wl,-u,vfprintf -lprintf_flt -lm
 LIBS += -lm
 
 # Programming flags
+ifndef (PROGRAMMER)
 PROGRAMMER = avrispmkII
 #PROGRAMMER = jtag3pdi
+endif
 PDEV = usb
 AVRDUDE_OPTS = -c $(PROGRAMMER) -p $(MCU) -P $(PDEV)
 
index 93810395af2ca6bcdbf185e581d5bad8b4cb42fe..08df97378f2cecc420e4d83a23b9ef737dce2d4f 100644 (file)
@@ -6,23 +6,10 @@ import logging
 from collections import deque
 
 import bbctrl
-
+import bbctrl.Cmd as Cmd
 
 log = logging.getLogger('AVR')
 
-# These constants must be kept in sync with i2c.h from the AVR code
-I2C_NULL           = 0
-I2C_ESTOP          = 1
-I2C_CLEAR          = 2
-I2C_PAUSE          = 3
-I2C_OPTIONAL_PAUSE = 4
-I2C_RUN            = 5
-I2C_STEP           = 6
-I2C_FLUSH          = 7
-I2C_REPORT         = 8
-I2C_REBOOT         = 9
-
-
 machine_state_vars = '''
   xp yp zp ap bp cp u s f t fm pa cs ao pc dm ad fo so mc fc
 '''.split()
@@ -43,6 +30,7 @@ axis_homing_procedure = '''
   G28.3 %(axis)s[#<%(axis)s.hp>]
 '''
 
+
 class AVR():
     def __init__(self, ctrl):
         self.ctrl = ctrl
@@ -75,7 +63,8 @@ class AVR():
         if self.stream is not None:
             raise Exception('Busy, cannot start new GCode file')
 
-        self.stream = bbctrl.GCodeStream(path)
+        log.info('Running ' + path)
+        self.stream = bbctrl.Planner(self.ctrl, path)
         self.set_write(True)
 
 
@@ -98,8 +87,9 @@ class AVR():
 
 
     def _i2c_command(self, cmd, byte = None, word = None):
-        log.info('I2C: %d' % cmd)
+        log.info('I2C: ' + cmd)
         retry = 5
+        cmd = ord(cmd[0])
 
         while True:
             try:
@@ -131,11 +121,11 @@ class AVR():
         self.queue_command('$$') # Refresh all vars, must come after above
 
 
-    def report(self): self._i2c_command(I2C_REPORT)
+    def report(self): self._i2c_command(Cmd.REPORT)
 
 
     def load_next_command(self, cmd):
-        log.info('Serial: ' + cmd)
+        log.info('< ' + cmd)
         self.command = bytes(cmd.strip() + '\n', 'utf-8')
 
 
@@ -148,8 +138,11 @@ class AVR():
 
 
     def serial_handler(self, fd, events):
-        if self.ctrl.ioloop.READ & events: self.serial_read()
-        if self.ctrl.ioloop.WRITE & events: self.serial_write()
+        try:
+            if self.ctrl.ioloop.READ & events: self.serial_read()
+            if self.ctrl.ioloop.WRITE & events: self.serial_write()
+        except Exception as e:
+            log.error('Serial handler error:', e)
 
 
     def serial_write(self):
@@ -201,6 +194,8 @@ class AVR():
             self.in_buf = self.in_buf[i + 1:]
 
             if line:
+                log.info('> ' + line)
+
                 try:
                     msg = json.loads(line)
 
@@ -254,12 +249,7 @@ class AVR():
 
 
     def _update_lcd(self, msg):
-        if 'x' in msg or 'c' in msg:
-            v = self.vars
-            state = v.get('x', 'INIT')
-            if 'c' in v and state == 'RUNNING': state = v['c']
-
-            self.lcd_page.text('%-9s' % state, 0, 0)
+        if 'x' in msg: self.lcd_page.text('%-9s' % self.vars['x'], 0, 0)
 
         # Show enabled axes
         row = 0
@@ -293,10 +283,10 @@ class AVR():
     def jog(self, axes):
         if self.stream is not None: raise Exception('Busy, cannot jog')
 
-        # TODO jogging via I2C
+        _axes = {}
+        for i in range(len(axes)): _axes["xyzabc"[i]] = axes[i]
 
-        axes = ["{:6.5f}".format(x) for x in axes]
-        self.queue_command('$jog ' + ' '.join(axes))
+        self.queue_command(Cmd.jog(_axes))
 
 
     def set(self, index, code, value):
@@ -321,8 +311,8 @@ class AVR():
                     self.queue_command(line.strip())
 
 
-    def estop(self): self._i2c_command(I2C_ESTOP)
-    def clear(self): self._i2c_command(I2C_CLEAR)
+    def estop(self): self._i2c_command(Cmd.ESTOP)
+    def clear(self): self._i2c_command(Cmd.CLEAR)
 
 
     def start(self, path):
@@ -330,25 +320,25 @@ class AVR():
 
         if path:
             self._start_sending_gcode(path)
-            self._i2c_command(I2C_RUN)
+            self._i2c_command(Cmd.RUN)
 
 
     def step(self, path):
-        self._i2c_command(I2C_STEP)
+        self._i2c_command(Cmd.STEP)
         if self.stream is None and path and self.vars.get('x', '') == 'READY':
             self._start_sending_gcode(path)
 
 
     def stop(self):
-        self._i2c_command(I2C_FLUSH)
+        self._i2c_command(Cmd.FLUSH)
         self._stop_sending_gcode()
         # Resume processing once current queue of GCode commands has flushed
-        self.queue_command('$resume')
+        self.queue_command('c')
 
 
-    def pause(self): self._i2c_command(I2C_PAUSE)
-    def unpause(self): self._i2c_command(I2C_RUN)
-    def optional_pause(self): self._i2c_command(I2C_OPTIONAL_PAUSE)
+    def pause(self): self._i2c_command(Cmd.PAUSE, byte = 0)
+    def unpause(self): self._i2c_command(Cmd.RUN)
+    def optional_pause(self): self._i2c_command(Cmd.PAUSE, byte = 1)
 
 
     def set_position(self, axis, position):
diff --git a/src/py/bbctrl/Cmd.py b/src/py/bbctrl/Cmd.py
new file mode 100644 (file)
index 0000000..9d05d1e
--- /dev/null
@@ -0,0 +1,56 @@
+import struct
+import base64
+import logging
+
+log = logging.getLogger('Cmd')
+
+# TODO, sync this up with AVR code
+REPORT = 'r'
+PAUSE  = 'P'
+ESTOP  = 'E'
+CLEAR  = 'C'
+FLUSH  = 'F'
+STEP   = 'S'
+RUN    = 'p'
+
+
+def encode_float(x):
+    return base64.b64encode(struct.pack('<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)
index 48e5de06dd9e8278ba70cf3fa7c69500c8c761f6..d153a02525bc4d3a6fdf89f9fba74c060e9c5a49 100644 (file)
@@ -1,5 +1,8 @@
 import inevent
 from inevent.Constants import *
+import logging
+
+log = logging.getLogger('Jog')
 
 
 # Listen for input events
@@ -48,7 +51,7 @@ class Jog(inevent.JogHandler):
             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)
 
diff --git a/src/py/bbctrl/Planner.py b/src/py/bbctrl/Planner.py
new file mode 100644 (file)
index 0000000..545d408
--- /dev/null
@@ -0,0 +1,76 @@
+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())
index daa56f6a48b3a8efcbe26e2cd74b38a8f5b3f4c8..dba6c243a6b6a5a5d80081b4f9e8531276b36d06 100755 (executable)
@@ -20,6 +20,8 @@ from bbctrl.Jog import Jog
 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):
diff --git a/src/resources/js/smoothie.js b/src/resources/js/smoothie.js
new file mode 100644 (file)
index 0000000..f53e83a
--- /dev/null
@@ -0,0 +1,1025 @@
+// 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);