aboutsummaryrefslogtreecommitdiff
path: root/Arduino/ems
diff options
context:
space:
mode:
authorDonald Delmar Davis <don@suspectdevices.com>2017-10-20 10:46:30 -0700
committerDonald Delmar Davis <don@suspectdevices.com>2017-10-20 10:46:30 -0700
commit261ac02efc8361ac1e55d9be72cfab1c8ea55237 (patch)
tree4fbe0b23659cbd7c5f50c9e10f65d0441e6fee7c /Arduino/ems
Let's do this
Diffstat (limited to 'Arduino/ems')
-rwxr-xr-xArduino/ems/Clock.cpp216
-rwxr-xr-xArduino/ems/Clock.h131
-rwxr-xr-xArduino/ems/Common.h106
-rwxr-xr-xArduino/ems/Heater.h263
-rwxr-xr-xArduino/ems/Machine.cpp170
-rwxr-xr-xArduino/ems/Machine.h42
-rwxr-xr-xArduino/ems/Monitor.cpp278
-rwxr-xr-xArduino/ems/Monitor.h218
-rwxr-xr-xArduino/ems/Pump.h200
-rwxr-xr-xArduino/ems/SafetyThird.cpp47
-rwxr-xr-xArduino/ems/SafetyThird.h179
-rwxr-xr-xArduino/ems/TaskScheduler.h824
-rwxr-xr-xArduino/ems/Thermocouple.cpp9
-rwxr-xr-xArduino/ems/Thermocouple.h178
-rwxr-xr-xArduino/ems/ems.ino3
-rwxr-xr-xArduino/ems/keywords.h122
16 files changed, 2986 insertions, 0 deletions
diff --git a/Arduino/ems/Clock.cpp b/Arduino/ems/Clock.cpp
new file mode 100755
index 0000000..4f70e8f
--- /dev/null
+++ b/Arduino/ems/Clock.cpp
@@ -0,0 +1,216 @@
+/*-------------------------------------------------------------------Clock.cpp
+ * Authors: Joseph Wayne Dumoulin, Donald Delmar Davis, Suspect Devices
+ *
+ * Liscence: "Simplified BSD License"
+ *
+ * Copyright (c) 2016, Donald Delmar Davis, Suspect Devices
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in thedocumentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *-----------------------------------------------------------------------------
+ * The clock module is about time.
+ * This includes
+ * An RTC backed (if avaliable) time module for timestamps
+ * A shcedular for most tasks.
+ */
+
+#include "Monitor.h"
+#include "Clock.h"
+
+
+/*
+ * notes on sync weirdness.
+ *
+ * This class grew out of a system which used external RTC that had to share
+ * a buss with all sorts of sensors.
+ *
+ * Rather than hog the buss a running seconds count was maintained (unixtime)
+ * and the time variables were updated and the rtc referenced once a minute.
+ *
+ * With an internal rtc this gets a little krufty.
+ *
+ * On the other hand a less than perfect clock can be implimented on systems
+ * w/o access to RTCs by counting seconds using whatever millis is using.
+ *
+ * this is not currently implimented but we will leave the cruft so it can be.
+ *
+ */
+
+/* Create an rtc object mebby should be private to the class JOE?? */
+
+// add preprocessor code here for non M0 systems.
+RTCZero rtc;
+
+/* calculate build date and time (this should be referenceable from monitor for SWV ... */
+const byte build_seconds = ((__TIME__[6] - '0') * 10 + __TIME__[7] - '0');
+const byte build_minutes = ((__TIME__[3] - '0') * 10 + __TIME__[4] - '0');
+const byte build_hours = ((__TIME__[0] - '0') * 10 + __TIME__[1] - '0');
+
+
+const byte build_day = ((__DATE__[4] >= '0') ? (__DATE__[4] - '0') * 10 : 0) + \
+ (__DATE__[5] - '0') ;
+const byte build_month = ( \
+ (__DATE__[0] == 'J' && __DATE__[1] == 'a' && __DATE__[2] == 'n') ? 1 : \
+ (__DATE__[0] == 'F') ? 2 : \
+ (__DATE__[0] == 'M' && __DATE__[1] == 'a' && __DATE__[2] == 'r') ? 3 : \
+ (__DATE__[0] == 'A' && __DATE__[1] == 'p') ? 4 : \
+ (__DATE__[0] == 'M' && __DATE__[1] == 'a' && __DATE__[2] == 'y') ? 5 : \
+ (__DATE__[0] == 'J' && __DATE__[1] == 'u' && __DATE__[2] == 'n') ? 6 : \
+ (__DATE__[0] == 'J' && __DATE__[1] == 'u' && __DATE__[2] == 'l') ? 7 : \
+ (__DATE__[0] == 'A' && __DATE__[1] == 'u') ? 8 : \
+ (__DATE__[0] == 'S') ? 9 : \
+ (__DATE__[0] == 'O') ? 10 : \
+ (__DATE__[0] == 'N') ? 11 : \
+ (__DATE__[0] == 'D') ? 12 : \
+ /* error default */ 99 \
+ );
+const byte build_year = ( \
+ (__DATE__[ 9] - '0') * 10 + \
+ (__DATE__[10] - '0') \
+ );
+
+
+Clock clock;
+
+
+void Clock::init()
+
+{
+
+ rtc.begin(); // initialize RTC
+
+ // if the rtc is set in the early 00s Set the and date to build time initially
+ // otherwise assume that the battery was keeping it running appropriately.
+ if (!rtc.getYear()){
+ rtc.setTime(build_hours,build_minutes,build_seconds);
+ rtc.setDate(build_day, build_month, build_year);
+ }
+
+ monitor.debug("Clock module initialized");
+ monitor.registerAction(_TIM_, &TIM);
+ monitor.registerAction(_NOW_, &NOW);
+}
+
+// bracket with pre-processor defs ....
+bool Clock::RTCIsRunning(void) {
+ return (RTC->MODE2.CTRL.reg & RTC_MODE2_CTRL_ENABLE);
+}
+
+const uint8_t daysInMonth [] { 31,28,31,30,31,30,31,31,30,31,30,31 };
+
+// number of days since 2000/01/01, valid for 2001..2099
+static uint16_t date2days(uint16_t yy, uint8_t mm, uint8_t dd) {
+ if (yy >= 2000)
+ yy -= 2000;
+ uint16_t days = dd;
+ for (uint8_t i = 1; i < mm; ++i)
+ days += pgm_read_byte(daysInMonth + i - 1);
+ if (mm > 2 && yy % 4 == 0)
+ ++days;
+ return days + 365 * yy + (yy + 3) / 4 - 1;
+}
+
+static long time2long(uint16_t days, uint8_t hh, uint8_t mm, uint8_t ss) {
+ return ((days * 24L + hh) * 60 + mm) * 60 + ss;
+}
+
+static uint8_t conv2d(const char* p) {
+ uint8_t v = 0;
+ if ('0' <= *p && *p <= '9')
+ v = *p - '0';
+ return 10 * v + *++p - '0';
+}
+
+void Clock::calcUnixTime(void) {
+ uint32_t t;
+ uint16_t days = date2days(yOff, m, d);
+ t = time2long(days, hh, mm, ss) + SECONDS_FROM_1970_TO_2000 ;
+ unixtime = t;
+}
+
+static uint8_t bcd2bin (uint8_t val) { return val - 6 * (val >> 4); }
+static uint8_t bin2bcd (uint8_t val) { return val + 6 * (val / 10); }
+
+void Clock::set(time_t t) {
+ rtc.setEpoch((uint32_t)t);
+}
+
+void Clock::set(const char * dateString) {
+ //TIM:%02d/%02d/%04d %02d:%02d:%02d"
+ //TIM:mm/dd/yyyy hh:mm:ss
+ //0123456789012345678901234
+ uint8_t seconds,minutes,hours,days,leap,month;
+ long int yearoff;
+ char dateStringBuffer[24];
+ strncpy(dateStringBuffer,dateString,23);
+ dateStringBuffer[2]=dateStringBuffer[5]=dateStringBuffer[10]
+ =dateStringBuffer[13]=dateStringBuffer[16]=dateStringBuffer[19]='\0';
+ seconds = atoi(dateStringBuffer+17);
+ minutes = atoi(dateStringBuffer+14);
+ hours = atoi(dateStringBuffer+11);;
+ days = atoi(dateStringBuffer+3);
+ month = atoi(dateStringBuffer);
+ yearoff = atoi(dateStringBuffer+6) - (2000);
+ monitor.debug("?:%02d/%02d/%04d %02d:%02d:%02d",month,days,yearoff+(2000),hours,minutes,seconds);
+ rtc.setTime(hours,minutes,seconds);
+ rtc.setDate(days, month, yearoff);
+}
+
+uint8_t Clock::dayofweek() /* 0 = Sunday */
+{ int y=year();
+ int m=month();
+ int d=day();
+ static int t[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
+ y -= m < 3;
+ return (uint8_t) ((y + y/4 - y/100 + y/400 + t[m-1] + d) % 7);
+}
+
+// populate running values from RTC
+void Clock::sync() {
+ ss = rtc.getSeconds();
+ rtcisrunning = 1; //FIXME!!!
+ mm = rtc.getMinutes();
+ hh = rtc.getHours();
+ d = rtc.getDay();
+ m = rtc.getMonth();
+ yOff = rtc.getYear();
+#if DEBUG_SYNC
+ monitor.debug("RTC: SYNC mm=%d,ss=%d",mm,ss);
+#endif
+
+}
+
+void Clock::run() {
+
+}
+
+
+void timeStamp ( char *buffer ) {
+ sprintf(buffer,"%02d/%02d/%04d %02d:%02d:%02d",
+ clock.month(), clock.day(), clock.year(),
+ clock.hour(), clock.minute(), clock.second());
+}
+
+
+
+
diff --git a/Arduino/ems/Clock.h b/Arduino/ems/Clock.h
new file mode 100755
index 0000000..4b28dc3
--- /dev/null
+++ b/Arduino/ems/Clock.h
@@ -0,0 +1,131 @@
+/*---------------------------------------------------------------------Clock.h
+ * Authors: Joseph Wayne Dumoulin, Donald Delmar Davis, Suspect Devices
+ *
+ * Liscence: "Simplified BSD License"
+ *
+ * Copyright (c) 2016, Donald Delmar Davis, Suspect Devices
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in thedocumentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *-----------------------------------------------------------------------------
+ *
+ *
+ *
+ */
+
+#ifndef Clock_h
+#define Clock_h
+
+#include "Common.h"
+#include "Monitor.h"
+#include "TaskScheduler.h"
+
+#define SECONDS_PER_DAY 86400L
+#define SECONDS_FROM_1970_TO_2000 946684800
+#define DEBUG_SYNC 0
+#define CHARS_IN_TIMESTAMP 20
+
+// add preprocessor code here for non M0 systems.
+#include <RTCZero.h>
+extern RTCZero rtc;
+
+// forward
+class Clock;
+extern Clock clock;
+
+void timeStamp(char *);
+
+class Clock
+{
+private:
+
+
+public:
+
+
+ /*----------------------------------------------------------------------
+ * Monitor Callbacks
+ *--------------------------------------------------------------------*/
+
+ static void TIM (uint8_t kwIndex, uint8_t verb,char *args) {
+ static char thetimestamp[CHARS_IN_TIMESTAMP];
+ if (verb == '!') {
+ clock.set(args);
+ }
+ timeStamp(thetimestamp);
+ monitor.update("TIM","%s",thetimestamp);
+ }
+
+ static void NOW (uint8_t kwIndex, uint8_t verb,char *args) {
+ static char intbuff[25];
+ if (verb == '!') {
+ clock.set(atol(args));
+ }
+ ultoa(clock.time(),intbuff,10);
+ monitor.update("NOW","%s",intbuff);
+
+ }
+
+
+ Clock() {} ;
+ void init();
+ bool RTCIsRunning();// const { return (rtcisrunning==1); };
+ void set(time_t);
+ time_t get();
+ void set(const char *);
+
+// need to generalize this at some point.
+#if 0
+ uint16_t year() const { return 2000 + yOff; }
+ uint8_t month() const { return m; }
+ uint8_t day() const { return d; }
+ uint8_t hour() const { return hh % 24; }
+ uint8_t minute() const { return mm % 60; }
+ uint8_t second() const { return ss % 60; }
+ time_t time() const {return unixtime;}
+#else
+ uint16_t year() const { return 2000 + rtc.getYear(); }
+ uint8_t month() const { return rtc.getMonth(); }
+ uint8_t day() const { return rtc.getDay(); }
+ uint8_t hour() const { return rtc.getHours(); }
+ uint8_t minute() const { return rtc.getMinutes(); }
+ uint8_t second() const { return rtc.getSeconds(); }
+ time_t time() const {return rtc.getEpoch();}
+#endif
+ uint8_t dayofweek();
+ void tick(void);
+ void run(); // this is wrong.
+ bool rolledOver(); //??
+ void addToCalendar();
+ void removeFromCalendar();
+ void sync();
+ void calcUnixTime();
+
+protected:
+ uint8_t yOff, m, d, hh, mm, ss, rtcisrunning;
+ time_t unixtime;
+
+};
+
+
+#endif
diff --git a/Arduino/ems/Common.h b/Arduino/ems/Common.h
new file mode 100755
index 0000000..51e4f40
--- /dev/null
+++ b/Arduino/ems/Common.h
@@ -0,0 +1,106 @@
+/*------------------------------------------------------------------
+ * Common defines for EMS hardware
+ */
+
+#ifndef EMS_COMMON_h
+#define EMS_COMMON_h
+
+#include <Arduino.h>
+
+#define GIT_VERSION "xxxxxxxxxxxxxxxxx"
+#define MAX_PSN_LENGTH 33
+//
+// reccommend major and minor rev plus build date.
+//
+#ifndef FIRMWAREREV
+#define FIRMWAREREV "EMS1"
+#endif
+
+#define LAMP_PIN 13
+#define TS1_CS_PIN 19
+#define TS2_CS_PIN 18
+#define PS1_APIN 1
+
+#define POWER_PIN 17
+
+#define E_STOP_PIN 16
+#define E_STOP_PULLED HIGH
+#define E_STOP_OK HIGH
+
+#define OSDD_PIN 5
+#define LIGHT_CURTAIN_OK LOW
+#define CURTAIN_NOT_CLEAR HIGH
+
+#define LAMP_PIN 13
+#define HTR1_PIN 11
+#define HTR2_PIN 12
+#define PUMP_PIN 10
+#define START_PIN 6
+#define OK_TO_START LOW
+#define SOLENOID_PIN 9
+#define RELAY_6_PIN 14
+
+#define K1_PIN 14
+#define KONTACTOR_OFF LOW
+#define KONTACTOR_ON HIGH
+
+//
+#define POST_TIMESTAMP_DELIMETER ','
+
+// analog write resolution (new for m0s)
+#define ANALOG_WRITE_RESOLUTION 8
+
+// tzoffset from localtime (PDT) to GMT (7 hrs) in seconds
+#define LOCAL_TZ_OFFSET ( 7L * 3600L )
+
+// non volitile memory addresses go here......
+#define CLEAN_SHUTDOWN_EEPROM_ADDR ((uint8_t *)0)
+#define CLEAN_SHUTDOWN_CODE 0xAA //unlikely to randomly be in there
+#define LOG_LEVEL_EEPROM_ADDR ((uint8_t*) 2)
+#define DEBUG_LEVEL_EEPROM_ADDR ((uint8_t*) 3)
+
+
+#define DEFAULT_HEATER_SETPOINT 260.00
+#define DEFAULT_TIMER_SETPOINT 150
+
+
+#define MONITOR_UART Serial
+#define MONITOR_BAUD 9600
+//#define LOGGER_UART Serial1
+//#define LOGGER_BAUD 9600
+
+#define FAKE_A_FLOAT(_f) int((_f)), int(abs((_f) - int(_f))*100)
+
+// This stuff probably belongs in Machine.h
+
+#define STATENAMELEN 25
+
+enum machine_states {
+ STATE_OFF,
+ STATE_SELF_TEST_INIT,
+ STATE_WARM_UP,
+ STATE_RUN_MODE,
+ STATE_SHUTDOWN,
+ STATE_POST_FAILURE,
+ STATE_E_SHUTDOWN,
+ STATE_MANUAL_MODE,
+ STATE_DIAGNOSTICS_MODE,
+ STATE_UNKNOWN_STATE,
+};
+
+#define NSTATES (STATE_UNKNOWN_STATE)
+
+#define STATENAMES \
+"OFF\000 ",\
+"SELF TEST INIT\000 ",\
+"WARMUP\000 ",\
+"NORMAL OPERATION\000 ",\
+"SHUT DOWN\000 ",\
+"POST FAILURE\000 ",\
+"EMERGENCY STOP\000 ",\
+"MANUAL MODE ",\
+"DIAGNOSTICS MODE\000 ",\
+"UNKNOWN\000 "
+
+#endif // EMS_COMMON_H
+
diff --git a/Arduino/ems/Heater.h b/Arduino/ems/Heater.h
new file mode 100755
index 0000000..a498e27
--- /dev/null
+++ b/Arduino/ems/Heater.h
@@ -0,0 +1,263 @@
+/**************************************************
+ * Heater.h - Describe the heater interface
+ *
+ *
+ */
+ #ifndef HEATER_H
+ #define HEATER_H
+
+#include "Common.h"
+#include "TaskScheduler.h"
+#include "Monitor.h"
+#include "Machine.h"
+
+#include "Thermocouple.h"
+
+// move to common.h
+
+#define HEATER_OFF 0
+#define HEATER_OUT_MAX 255
+#define HEATER_OUT_MIN 0
+#define HEATER_MAX_TEMP 280.00
+#define HEATER_PID_SAMPLE_TIME 100L
+
+static void run();
+static void CheckHeaters();
+
+static void TMP (uint8_t kwIndex, uint8_t verb,char *args);
+static void HSW (uint8_t kwIndex, uint8_t verb,char *args);
+
+static void H1P (uint8_t kwIndex, uint8_t verb,char *args);
+static void H1I (uint8_t kwIndex, uint8_t verb,char *args);
+static void H1D (uint8_t kwIndex, uint8_t verb,char *args);
+
+static void H2P (uint8_t kwIndex, uint8_t verb,char *args);
+static void H2I (uint8_t kwIndex, uint8_t verb,char *args);
+static void H2D (uint8_t kwIndex, uint8_t verb,char *args);
+
+
+const double SampleTimeInSec = ((double)HEATER_PID_SAMPLE_TIME)/1000;
+
+
+
+class Heater {
+ private:
+
+ Task runHeaters;
+
+ public:
+ double setPoint;
+ bool isOn;
+ int H1Output, H2Output;
+ double H1SetPoint, H1LastInput, H1ITerm;
+ double H2SetPoint, H2LastInput, H2ITerm;
+
+ //TODO: P,I,D 'accessors'
+ double P1, I1, D1, P2, I2, D2;
+
+
+ void runThermostat(){
+ double input, output, error, i, d;
+
+ i = I1 * SampleTimeInSec;
+ d = D1 / SampleTimeInSec;
+ input = thermocouple.getTemp1();
+ error = H1SetPoint - input;
+
+ H1ITerm += (i * error);
+ if(H1ITerm > HEATER_OUT_MAX) {
+ H1ITerm = HEATER_OUT_MAX;
+ }
+ else if(H1ITerm < HEATER_OUT_MIN){
+ H1ITerm= HEATER_OUT_MIN;
+ }
+
+ output = P1 * error + H1ITerm - d * (input - H1LastInput);
+
+ if(output > HEATER_OUT_MAX){
+ output = HEATER_OUT_MAX;
+ }
+ else if(output < HEATER_OUT_MIN) {
+ output = HEATER_OUT_MIN;
+ }
+ H1Output = (int) output;
+
+ if(isOn) {
+ analogWrite(HTR1_PIN,H1Output);
+ }
+
+ H1LastInput = input;
+
+ input = thermocouple.getTemp2();
+ error = H2SetPoint - input;
+ i= I2 * SampleTimeInSec;
+ d= D2 / SampleTimeInSec;
+
+ H2ITerm += (i * error);
+ if(H2ITerm > HEATER_OUT_MAX) {
+ H2ITerm = HEATER_OUT_MAX;
+ }
+ else if(H2ITerm < HEATER_OUT_MIN){
+ H2ITerm= HEATER_OUT_MIN;
+ }
+
+ output = P2 * error + H2ITerm - d * (input - H2LastInput);
+ if(output > HEATER_OUT_MAX){
+ output = HEATER_OUT_MAX;
+ }
+ else if(output < HEATER_OUT_MIN) {
+ output = HEATER_OUT_MIN;
+ }
+
+ H2Output = (int) output;
+
+ if(isOn) {
+ analogWrite(HTR2_PIN,H2Output);
+ }
+
+ H2LastInput = input;
+
+ };
+
+ void init() {
+ pinMode(HTR1_PIN, OUTPUT);
+ pinMode(HTR2_PIN, OUTPUT);
+ analogWrite(HTR1_PIN, HEATER_OFF); // number foo
+ analogWrite(HTR2_PIN, HEATER_OFF);
+
+
+ P1=1;
+ I1=0.05;
+ D1=0.25;
+
+ P2=1;
+ I2=0.05;
+ D2=0.25;
+
+ isOn=0;
+ setPoint=0.0;
+
+ // register monitor actions
+ monitor.registerAction(_TMP_, &TMP);
+ monitor.registerAction(_HSW_, &HSW);
+
+ monitor.registerAction(_H1P_, &H1P);
+ monitor.registerAction(_H1I_, &H1I);
+ monitor.registerAction(_H1D_, &H1D);
+
+ monitor.registerAction(_H2P_, &H2P);
+ monitor.registerAction(_H2I_, &H2I);
+ monitor.registerAction(_H2D_, &H2D);
+
+ // TODO: check to see if turning on the heater will change the temp.
+
+ // set tasks for sceduled reads
+ runHeaters.set(HEATER_PID_SAMPLE_TIME, TASK_FOREVER, &run);
+ machine.todolist.addTask(runHeaters);
+ runHeaters.enable();
+ }
+
+ void StopHeaters() {
+ isOn=0;
+ CheckHeaters();
+ }
+
+
+
+};
+
+Heater heater;
+
+static void run() {
+ CheckHeaters();
+
+}
+
+
+// check heaters and change state if necessary
+// Check the temperature and turn the heater off if the target temp reached.
+// Turn the heater on of the temp is below he target.
+// TODO: add PID functionality here.
+static void CheckHeaters() {
+ double t;
+ t=thermocouple.getTemp1();
+ if (t>HEATER_MAX_TEMP) {
+ heater.isOn=false;
+ monitor.warn("overtmp d.%.02d", FAKE_A_FLOAT(t));
+ }
+ t=thermocouple.getTemp2();
+ if (t>HEATER_MAX_TEMP) {
+ heater.isOn=false;
+ monitor.warn("overtmp d.%.02d", FAKE_A_FLOAT(t));
+ }
+ if (heater.isOn){
+ heater.runThermostat();
+ } else {
+ analogWrite(HTR1_PIN, HEATER_OFF);
+ analogWrite(HTR2_PIN, HEATER_OFF);
+ }
+}
+
+
+
+
+// define or query P, I, and D for heater 1
+
+static void TMP(uint8_t kwIndex, uint8_t verb, char* args) {
+ if (verb == '!') {
+ heater.H1SetPoint = heater.H2SetPoint = heater.setPoint = atof(args);
+ }
+ monitor.update("TMP", "%d.%.02d", FAKE_A_FLOAT(heater.setPoint));
+}
+
+static void HSW(uint8_t kwIndex, uint8_t verb, char* args) {
+ //monitor.debug("atoi('ON')=%d",atoi("ON"));
+ if (verb == '!') {
+ heater.isOn = atoi(args);
+ }
+ monitor.update("HSW", "%d", heater.isOn);
+}
+
+static void H1P(uint8_t kwIndex, uint8_t verb, char* args) {
+ if (verb == '!') {
+ heater.P1 = atof(args);
+ }
+ monitor.update("H1P", "%d.%.02d", FAKE_A_FLOAT(heater.P1));
+}
+
+static void H1I(uint8_t kwIndex, uint8_t verb, char* args) {
+ if (verb == '!') {
+ heater.I1 = atof(args);
+ }
+ monitor.update("H1I", "%d.%.02d", FAKE_A_FLOAT(heater.I1));
+}
+static void H1D(uint8_t kwIndex, uint8_t verb, char* args) {
+ if (verb == '!') {
+ heater.D1 = atof(args);
+ }
+ monitor.update("H1D", "%d.%.02d", FAKE_A_FLOAT(heater.D1));
+}
+
+static void H2P(uint8_t kwIndex, uint8_t verb, char* args) {
+ if (verb == '!') {
+ heater.P2 = atof(args);
+ }
+ monitor.update("H2P", "%d.%.02d", FAKE_A_FLOAT(heater.P2));
+}
+
+static void H2I(uint8_t kwIndex, uint8_t verb, char* args) {
+ if (verb == '!') {
+ heater.I2 = atof(args);
+ }
+ monitor.update("H2I", "%d.%.02d", FAKE_A_FLOAT(heater.I2));
+}
+
+static void H2D(uint8_t kwIndex, uint8_t verb, char* args) {
+ if (verb == '!') {
+ heater.D1 = atof(args);
+ }
+
+ monitor.update("H2D", "%d.%.02d", FAKE_A_FLOAT(heater.D2));
+}
+
+ #endif // HEATER_H
diff --git a/Arduino/ems/Machine.cpp b/Arduino/ems/Machine.cpp
new file mode 100755
index 0000000..a6d2337
--- /dev/null
+++ b/Arduino/ems/Machine.cpp
@@ -0,0 +1,170 @@
+#include "Machine.h"
+#include "Monitor.h"
+#include "Thermocouple.h"
+#include "SafetyThird.h"
+#include "Heater.h"
+#include "Pump.h"
+
+const char statenames[][STATENAMELEN]={STATENAMES};
+Machine machine;
+
+
+
+void setup() {
+ machine.init();
+}
+
+// POST
+void Machine::init(){
+ monitor.init();
+ inSafeStateToRun=false;
+ setState(STATE_SELF_TEST_INIT);
+ safety.init();
+ thermocouple.init();
+ heater.init();
+ pump.init();
+ if (state() == STATE_SELF_TEST_INIT) {
+ inSafeStateToRun=true;
+ setState(STATE_WARM_UP);
+ }
+ monitorTask.set(100L, TASK_FOREVER, &Monitor::run);
+ machine.todolist.addTask(monitorTask);
+ monitorTask.enable();
+ machineTask.set(500L, TASK_FOREVER, &systemInCharge);
+ machine.todolist.addTask(machineTask);
+ machineTask.enable();
+ monitor.registerAction(_TMR_, &TMR);
+ monitor.registerAction(_TMS_, &TMS);
+ monitor.registerAction(_TRM_, &TRM);
+
+}
+
+const char * Machine::stateName(int s) { return statenames[s]; };
+void loop() {
+ machine.todolist.execute();
+}
+
+void Machine::setState(int newState) {
+ previousState=currentState;
+ currentState=newState;
+ monitor.update("STC","%.04d %s ==> %s",
+ newState,
+ stateName(previousState),
+ stateName(newState));
+}
+#define ENTERING (machine.previousState!=machine.currentState)
+// this is the state machine.
+
+static void systemInCharge(){
+ static time_t lastUpdate;
+
+ switch (machine.state()){
+
+ case STATE_OFF:
+ case STATE_SELF_TEST_INIT:break;
+
+ case STATE_WARM_UP:
+#ifdef SKIP_WARM_UP
+ if (ENTERING) {
+ monitor.log("-- STARTING_WARMUP -- (TS1=d.%.02d,TS2=d.%.02d"
+ ,FAKE_A_FLOAT(thermocouple.getTemp1())
+ ,FAKE_A_FLOAT(thermocouple.getTemp2())
+ );
+ pump.Press();
+ heater.setPoint=DEFAULT_HEATER_SETPOINT;
+ heater.isOn=true;
+ safety.redLed=255;//fix number foo
+ }
+ if ((thermocouple.getTemp1() >= heater.setPoint)
+ ||(thermocouple.getTemp2() >= heater.setPoint)
+ ) {
+ monitor.log("-- WARMUP COMPLETE -- (TS1=d.%.02d,TS2=d.%.02d"
+ ,FAKE_A_FLOAT(thermocouple.getTemp1())
+ ,FAKE_A_FLOAT(thermocouple.getTemp2())
+ );
+ pump.Release();
+ machine.setState(STATE_RUN_MODE);
+ }
+#else
+ machine.setState(STATE_RUN_MODE);
+#endif
+ break;
+ case STATE_RUN_MODE:
+ if (ENTERING) {
+ machine.timerIsRunning=false;
+ machine.timeRemaining=machine.timerSetting=DEFAULT_TIMER_SETPOINT;
+ monitor.update("TMS","%d",machine.timerSetting);
+ monitor.update("TRM","%d",machine.timeRemaining);
+ lastUpdate=clock.time();
+ }
+ time_t delta;
+ delta=lastUpdate=(clock.time()-lastUpdate);
+ if ((delta>0)
+ && (machine.timerIsRunning)
+ ){
+ machine.timeRemaining -= (int) delta;
+ if (machine.timeRemaining<0) {
+ machine.timeRemaining=0;
+ }
+ monitor.update("TRM","%d",machine.timeRemaining);
+ }
+ if ((machine.timeRemaining==0)
+ && machine.timerIsRunning
+ ){
+ pump.Release();
+ machine.timerIsRunning=false;
+ monitor.log("-- Job finished --");
+ machine.timeRemaining=machine.timerSetting;
+ monitor.update("TRM","%d",machine.timeRemaining);
+ monitor.update("TMR","%d",machine.timerIsRunning);
+ }
+ break;
+
+
+ case STATE_SHUTDOWN:
+ case STATE_POST_FAILURE:
+ case STATE_E_SHUTDOWN:
+ case STATE_MANUAL_MODE:
+ case STATE_DIAGNOSTICS_MODE:break;
+ case STATE_UNKNOWN_STATE:break;
+ }
+ machine.previousState=machine.currentState;
+ lastUpdate=clock.time();
+}
+/*----------------------------------------------------------------------
+ * Monitor Callbacks
+ *--------------------------------------------------------------------*/
+
+static void TMR (uint8_t kwIndex, uint8_t verb,char *args) {
+ if (verb == '!') {
+ bool wasRunning=machine.timerIsRunning;
+ bool start=machine.timerIsRunning=atoi(args);
+ if (start) {
+ pump.Press();
+ monitor.log("-- Job Started --"); //if was notrunning?
+ } else {
+ if (wasRunning) {
+ monitor.log("-- Job Paused --");
+ }
+ }
+ }
+ monitor.update("TMR","%d",machine.timerIsRunning);
+}
+
+static void TMS (uint8_t kwIndex, uint8_t verb,char *args) {
+
+ if (verb == '!') {
+ machine.timeRemaining=machine.timerSetting=atoi(args);
+ monitor.update("TRM","%d",machine.timeRemaining);
+ }
+ monitor.update("TMS","%d",machine.timerSetting);
+}
+
+static void TRM (uint8_t kwIndex, uint8_t verb,char *args) {
+
+ if (verb == '!') {
+ machine.timeRemaining=atoi(args);
+ monitor.update("TRM","%d",machine.timeRemaining);
+ }
+ monitor.update("TRM","%d",machine.timeRemaining);
+}
diff --git a/Arduino/ems/Machine.h b/Arduino/ems/Machine.h
new file mode 100755
index 0000000..a2680a9
--- /dev/null
+++ b/Arduino/ems/Machine.h
@@ -0,0 +1,42 @@
+#ifndef Machine_h
+#define Machine_h
+#include "Common.h"
+#include "Monitor.h"
+#include "TaskScheduler.h"
+
+class Machine;
+extern Machine machine;
+
+static void STC (uint8_t kwIndex, uint8_t verb,char *args);
+static void TMR (uint8_t kwIndex, uint8_t verb,char *args);
+static void TMS (uint8_t kwIndex, uint8_t verb,char *args);
+static void TRM (uint8_t kwIndex, uint8_t verb,char *args);
+static void systemInCharge();
+
+class Machine
+{
+private:
+ Task monitorTask;
+ Task machineTask;
+
+public:
+ int previousState;
+ int currentState;
+ int timerSetting;
+ bool timerIsRunning;
+ int timeRemaining;
+ bool inSafeStateToRun;
+
+ Machine() : currentState(STATE_OFF) {}
+ Scheduler todolist;
+ void init();
+ void setPreviousState(int newState); // {currentState=newState;};
+ void setState(int newState); // {currentState=newState;};
+ int state() { return currentState; };
+ const char * stateName(int) ;
+
+protected:
+
+};
+
+#endif
diff --git a/Arduino/ems/Monitor.cpp b/Arduino/ems/Monitor.cpp
new file mode 100755
index 0000000..b4a9de6
--- /dev/null
+++ b/Arduino/ems/Monitor.cpp
@@ -0,0 +1,278 @@
+/*---------------------------------------------------------------------Monitor.h
+ * Author: Joseph Wayne Dumoulin, Donald Delmar Davis, Suspect Devices
+ *
+ * Liscence: "Simplified BSD License"
+ *
+ * Copyright (c) 2016, Donald Delmar Davis, Suspect Devices
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in thedocumentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *-----------------------------------------------------------------------------
+ * The idea here is to create an automation freindly monitor protocol and debug
+ * interface. Because this needs to be efficient (and flexible) I have chosen a
+ * very simple interface which will access and may manipulate a devices public
+ * variables.
+ *
+ * Basic Syntax for the monitor is
+ * XXX[?!:] <variable stuff> <CR>
+ * where:
+ * XXX is a 3 letter command or variable.
+ * ! execute command or store value
+ * ? get value
+ * :<20 char timestamp>, value returned.
+ *
+ * keywords are described in keywords.h
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include "Monitor.h"
+#include "Clock.h"
+
+/*
+ * +1 is for the trailing null.
+ */
+const char keywords[((NKEYWORDS)*3)+1] = KEYWORDS;
+#define KWHELPLEN 26
+
+char kwhelp[((NKEYWORDS+1) * KWHELPLEN)+1] = HELPTEXT;
+char updateBuffer[MAX_RETURN_VALUE]; //allocate this once use it often.
+actionptr actions[NKEYWORDS];
+
+
+void Monitor::update(const char *data, const char *format, ...) {
+ va_list blarg;
+ updateBuffer[0]=data[0];updateBuffer[1]=data[1];updateBuffer[2]=data[2];updateBuffer[3]=':';
+ timeStamp(updateBuffer+4);
+ updateBuffer[23]=POST_TIMESTAMP_DELIMETER;
+ va_start(blarg, format);
+ vsnprintf(updateBuffer+24, MAX_RETURN_VALUE-24, format, blarg);
+ va_end(blarg);
+ if (!commandMode()) {
+ MONITOR_UART.println(updateBuffer);
+ MONITOR_UART.flush();
+ }
+};
+
+Monitor monitor;
+
+void Monitor::fatal(int newstate, const char *format, ...) {
+ va_list blarg;
+ update("FTL", format, blarg);
+ va_end(blarg);
+ //update("FTL","Fatal Error, New State = %s", soh, stateName(soh)); //maybe move this to setstate.
+ //sendActionOut();
+ //stateChange(soh);
+};
+
+void Monitor::debug(const char *format, ...) {
+ va_list blarg;
+// if (_debugLevel >= SEV_DEBUG)
+ update("DBG:", format, blarg);
+ va_end(blarg);
+};
+
+
+
+
+char * samd21m0psn(char * buffer) {
+
+ // https://gist.github.com/mgk/c9ec87436d2d679e5d08
+ volatile uint32_t val1, val2, val3, val4;
+ volatile uint32_t *ptr1 = (volatile uint32_t *)0x0080A00C;
+ val1 = *ptr1;
+ volatile uint32_t *ptr = (volatile uint32_t *)0x0080A040;
+ val2 = *ptr;
+ ptr++;
+ val3 = *ptr;
+ ptr++;
+ val4 = *ptr;
+ snprintf(buffer, MONITOR_SERIAL_NUMBER_CHARS ,"%8x%8x%8x%8x", val1, val2, val3, val4);
+
+}
+
+Monitor::Monitor() {
+
+ _debugLevel=EEPROM_NOT_SET;
+ samd21m0psn(this->_psn);
+ /*
+ * insure that all jump table pointers go somewhere
+ */
+ int k;
+
+ for (k=0;k<NKEYWORDS;k++) {
+ if (actions[k]==NULL) {
+ actions[k]=&NOP;
+ }
+ }
+}
+void Monitor::init(){
+
+
+ MONITOR_UART.begin(MONITOR_BAUD);
+ while (!MONITOR_UART)
+ delay(1); // wait for USB serial to initialize (BAD IDEA)
+// console=&MONITOR_UART;
+ MONITOR_UART.flush();
+
+
+
+ if (debugLevel()==EEPROM_NOT_SET) {
+ setDebugLevel(SEV_WARN); // default level?
+ }
+
+ monitor.setCommandMode(0);
+
+ clock.init(); // needed to timestamp debug statement.
+
+ registerAction(_DVL_, &DVL);
+ registerAction(_GIT_, &GIT);
+ registerAction(_HLP_, &HLP);
+ registerAction(_CMD_, &CMD); //
+ registerAction(_SSN_, &SSN);
+ registerAction(_SWV_, &SWV);
+ registerAction(_HWV_, &HWV);
+ registerAction(_RST_, &RST);
+ registerAction(_BLD_, &BLD);
+
+ //machine.todolist.addTask(__commands);
+ //__commands.enable();
+// monitor.update("monitor init");
+
+ monitor.debug("Monitor Init");
+
+}
+
+int commandBufferIndex=0;
+bool gotLine=false;
+char commandBuffer[84]={}; // rename and use constants.
+
+void Monitor::registerAction(uint8_t ndx, actionptr action){
+ if (ndx >=0 && ndx<NKEYWORDS) {
+ actions[ndx]=action;
+ }
+}
+
+void Monitor::run() {
+ int index;
+ char key[3];
+ char action;
+ char *arguments;
+ char ch;
+ int linlen;
+// if( (monitor.console!=NULL) && monitor.console->available() ){
+// while( monitor.console->available() && commandBufferIndex < MAX_MONITOR_LINE_LENGTH && ((ch=monitor.console->read()) != '\n') && ch !='\r' ) {
+// commandBuffer[commandBufferIndex++] = isprint(ch)?ch:'*';
+// monitor.console->flush();
+// } monitor.debug("monitor run");
+
+ if( (&MONITOR_UART!=NULL) && MONITOR_UART.available() ){
+ while(MONITOR_UART.available() && commandBufferIndex < MAX_MONITOR_LINE_LENGTH && ((ch=MONITOR_UART.read()) != '\n') && ch !='\r' ) {
+ commandBuffer[commandBufferIndex++] = isprint(ch)?ch:'*';
+ MONITOR_UART.flush();
+ }
+
+ if (commandBufferIndex && ((ch=='\n')||(ch=='\r'))) {
+ while (commandBufferIndex<4) { commandBuffer[commandBufferIndex++]='*';};
+ // things get questionable if there are less than 4 characters.
+ commandBuffer[commandBufferIndex]='\0';
+ commandBufferIndex=0;
+ gotLine=true;
+ }
+ }
+
+ if (gotLine) {
+ key[0]=toupper(commandBuffer[0]);
+ key[1]=toupper(commandBuffer[1]);
+ key[2]=toupper(commandBuffer[2]);
+ action=commandBuffer[3];
+ arguments=commandBuffer+4;
+ index=monitor.lookupIndex(key);
+ if (index>=0&&index<NKEYWORDS) {
+ actions[index](index,action,arguments);
+ } else {
+ monitor.update("NAK","%c%c%c[%c] is not understood HLP? for a list of keywords",
+ key[0],key[1],key[2],action); // better than no response...
+ }
+ gotLine=false;
+ }
+}
+
+int Monitor::lookupIndex(char *key) {
+ int ndx=0;
+ int ptr=0;
+ while (ndx<NKEYWORDS) {
+ if ( ( keywords[ptr]==toupper(key[0]) )
+ && ( keywords[ptr+1] == toupper(key[1]) )
+ && ( keywords[ptr+2] == toupper(key[2]) )
+ ) { return ndx;
+ } else {
+ ndx++;
+ ptr+=3;
+ }
+ }
+ return -1;
+}
+
+
+char keyBuff[4]; //fix or move this ???.
+
+char * Monitor::lookupKey(int ndx) {
+ int ptr=0;
+ keyBuff[0]=keyBuff[1]=keyBuff[2]='?';
+ if (ndx >=0 && ndx<NKEYWORDS) {
+ ptr=ndx*3;
+ keyBuff[0]=(keywords[ptr]);
+ keyBuff[1]=(keywords[ptr+1]);
+ keyBuff[2]=(keywords[ptr+2]);
+ keyBuff[3]='\0';
+ }
+
+ return keyBuff;
+}
+
+
+
+extern "C" char *sbrk(int i);
+
+int getFreeMemory()
+{
+
+ // avr specific
+ // extern int __heap_start, *__brkval;
+ // int v;
+ // return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
+ // return 0;
+ //https://learn.adafruit.com/adafruit-feather-m0-basic-proto/adapting-sketches-to-m0
+ char stack_dummy = 0;
+ return &stack_dummy - sbrk(0);
+
+}
+
+
+
+
+
+
+
diff --git a/Arduino/ems/Monitor.h b/Arduino/ems/Monitor.h
new file mode 100755
index 0000000..7841745
--- /dev/null
+++ b/Arduino/ems/Monitor.h
@@ -0,0 +1,218 @@
+/*---------------------------------------------------------------------Monitor.h
+ Author: Joseph Wayne Dumoulin, Donald Delmar Davis, Suspect Devices
+
+ Liscence: "Simplified BSD License"
+
+ Copyright (c) 2016, Donald Delmar Davis, Suspect Devices
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in thedocumentation
+ and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+ -----------------------------------------------------------------------------
+ The idea here is to create an automation freindly monitor protocol and debug
+ interface. Because this needs to be efficient (and flexible) I have chosen a
+ very simple interface which will access and may manipulate a devices public
+ variables.
+
+ Basic Syntax for the monitor is
+ XXX[?!:] <variable stuff> <CR>
+ where:
+ XXX is a 3 letter command or variable.
+ ! execute command or store value
+ ? get value
+ :<20 char timestamp>, value returned.
+
+
+ keywords are described in keywords.h
+*/
+
+#ifndef Monitor_h
+#define Monitor_h
+
+#include "Common.h"
+class Monitor;
+extern Monitor monitor;
+
+#include "Machine.h"
+#include "Adafruit_SleepyDog.h"
+#include "Reset.h"
+
+#include "TaskScheduler.h"
+#include <stdio.h>
+#include <stdarg.h>
+
+// fix this for processor the stubbed here is for the AVR
+#define EEPROM_NOT_SET 0xff
+#ifndef eeprom_read_byte
+#define eeprom_read_byte(x) (0xff)
+#endif
+#ifndef eeprom_write_byte
+# define eeprom_write_byte(x,v) (v)
+#endif
+
+#include "keywords.h"
+
+#define MONITOR_SERIAL_NUMBER_CHARS 20
+
+enum severityIndex {
+ SEV_NONE, SEV_FATAL, SEV_ALERT, SEV_WARN, SEV_INFO, SEV_DEBUG, SEV_LOG
+};
+
+#define MAX_RETURN_VALUE 104
+#define MAX_MONITOR_LINE_LENGTH (MAX_RETURN_VALUE-6)
+
+typedef void(*actionptr)(uint8_t, uint8_t, char *);
+//typedef void(actionfunc)(uint8_t, uint8_t, char *);
+
+extern char actionBuffer[MAX_RETURN_VALUE];
+extern actionptr actions[];
+
+
+#define MAX_PSN_LENGTH 33
+
+//forwards...
+//class Monitor;
+//extern Monitor monitor;
+class Machine;
+extern Machine machine;
+extern int getFreeMemory();
+extern char commandBuffer[];
+
+class Monitor
+{
+ private:
+ uint8_t _debugLevel;
+ uint8_t _commandMode;
+ uint8_t _logLevel;
+ char _psn[MAX_PSN_LENGTH]; // bracket this ... samd processor serial number
+ int get_free_memory();
+
+ int lookupIndex(char *key);
+
+
+ public:
+// Stream * console;
+ char * lookupKey(int ndx);
+
+ static char *keyword(int ndx);
+
+ /*--------------------------------------------------------------------------------
+ * built in callback routines.
+ *-------------------------------------------------------------------------------*/
+
+ static void ACK (uint8_t kwIndex, uint8_t verb, char *args) {
+ monitor.update("ACK","");
+ }
+
+ static void NOP(uint8_t kwIndex, uint8_t verb, char* args) {
+ monitor.update("NAK", "%s is not implemented!", monitor.lookupKey(kwIndex));
+ }
+
+ static void DVL(uint8_t kwIndex, uint8_t verb, char* args) {
+ if (verb == '!') {
+ monitor.setDebugLevel(atoi(args));
+ }
+ monitor.update("DVL", "%d", monitor.debugLevel());
+ }
+
+ static void MEM (uint8_t kwIndex, uint8_t verb,char *args) {
+ monitor.update("MEM","%d",getFreeMemory());
+ }
+ static void HWV (uint8_t kwIndex, uint8_t verb,char *args) {
+ monitor.update("HWV","%d",monitor.unitHardwareVersion());
+ }
+
+ static void CMD(uint8_t kwIndex, uint8_t verb, char *args) {
+ if (verb=='!') {
+ bool setIt = (atoi(args));
+ monitor.setCommandMode(setIt);
+ }
+ monitor.update("CMD","%s",monitor.commandMode()?"ON":"OFF");
+ }
+
+ static void SWV(uint8_t kwIndex, uint8_t verb, char *args) {
+ monitor.update("SWV","%s",FIRMWAREREV);
+ }
+
+ static void SSN(uint8_t kwIndex, uint8_t verb, char *args) {
+ monitor.update("SSN","%s",monitor.unitSerialNumber());
+ }
+
+ static void GIT(uint8_t kwIndex, uint8_t verb, char *args) {
+ monitor.update("GIT","%s",GIT_VERSION);
+ }
+
+ static void RST(uint8_t kwIndex, uint8_t verb, char *args) {
+ monitor.warn("Rebooting!");
+ MONITOR_UART.end();
+ Watchdog.enable(150);
+ }
+
+ static void BLD(uint8_t kwIndex, uint8_t verb, char *args) {
+ monitor.warn("Jumping to Bootloader!");
+ initiateReset(10);
+ }
+
+ // stubb
+ static void HLP(uint8_t kwIndex, uint8_t verb, char *args) {
+ monitor.update("NAK","HLP requires multi line data (MLD)");
+ }
+
+ Monitor();
+
+ static void run();
+
+ char *unitSerialNumber() {
+ return _psn;
+ };
+
+ int unitHardwareVersion() {
+ return 0;
+ };
+
+
+ void update(const char *, const char *format, ...);
+ void fatal(int, const char *format, ...);
+ void alert(const char *format, ...) { va_list blarg; update("ALT", format, blarg); va_end(blarg);};
+ void warn(const char *format, ...) { va_list blarg; update("WAR", format, blarg); va_end(blarg);};
+ void log(const char *format, ...) { va_list blarg; update("LOG", format, blarg); va_end(blarg);};
+ // void info(int);
+ void debug(const char *format, ...);
+ void setDebugLevel(uint8_t lvl) {
+ //eeprom_write_byte(DEBUG_LEVEL_EEPROM_ADDR, (_debugLevel = lvl));
+ _debugLevel = lvl;
+
+ }; //FIX
+ void setCommandMode(uint8_t mode) {
+ _commandMode = mode;
+ }
+ uint8_t commandMode() {
+ return _commandMode;
+ }
+ uint8_t debugLevel() {
+ return _debugLevel;// = eeprom_read_byte(DEBUG_LEVEL_EEPROM_ADDR);
+ }; //FIX
+ void init();
+ void registerAction(uint8_t, actionptr);
+};
+#endif
+
diff --git a/Arduino/ems/Pump.h b/Arduino/ems/Pump.h
new file mode 100755
index 0000000..c8293dd
--- /dev/null
+++ b/Arduino/ems/Pump.h
@@ -0,0 +1,200 @@
+/*************************************
+ * Pump.h - Manage the hydraulic pump.
+ */
+#ifndef PUMP_H
+#define PUMP_H
+
+#include "Common.h"
+#include "TaskScheduler.h"
+#include "Monitor.h"
+#include "Machine.h"
+#include "Clock.h"
+
+// move to common
+#define PUMP_ON HIGH
+#define PUMP_OFF LOW
+#define MAX_PUMP_RUN_SECONDS 5
+#define GOING_UP LOW
+#define GOING_DOWN HIGH
+#define SOLENOID_OFF LOW
+
+static void runThePump();
+static void readPumpPressure();
+
+static void PMS (uint8_t kwIndex, uint8_t verb,char *args);
+static void MUP (uint8_t kwIndex, uint8_t verb,char *args);
+static void MDN (uint8_t kwIndex, uint8_t verb,char *args);
+
+static void PS1 (uint8_t kwIndex, uint8_t verb,char *args);
+static void SOL (uint8_t kwIndex, uint8_t verb,char *args);
+
+class Pump {
+private:
+ Task checkPressure;
+ Task runPump;
+ bool wereWeGoingDown;
+ bool wasTheMotorOn;
+ long int startedAt;
+ bool motorOn;
+ bool goingDown;
+public:
+ int pressure;
+ bool pressureChanged;
+
+ void motor(bool state) {
+
+ motorOn=state;
+
+ if((motorOn==PUMP_ON) && (wasTheMotorOn==PUMP_OFF))
+ {
+ startedAt=clock.second();
+
+ }
+ digitalWrite(PUMP_PIN, motorOn?PUMP_ON:PUMP_OFF);
+ if (motorOn != wasTheMotorOn) {
+ monitor.update("PMS", "%d", motorOn);
+ }
+ wasTheMotorOn=motorOn;
+ }
+
+ bool isMotorOn() {return motorOn;}
+
+ bool areWeGoingDown() {return goingDown;}
+
+
+ void solenoid(bool state) {
+ goingDown=state;
+ digitalWrite(SOLENOID_PIN, goingDown?GOING_DOWN:GOING_UP);
+ if (goingDown != wereWeGoingDown) {
+ monitor.update("SOL", "%d", goingDown);
+ }
+ goingDown = wereWeGoingDown;
+
+
+ }
+
+ void init() {
+
+
+ pinMode(PUMP_PIN, OUTPUT);
+ digitalWrite(PUMP_PIN, PUMP_OFF);
+ pinMode(SOLENOID_PIN, OUTPUT);
+ digitalWrite(SOLENOID_PIN, GOING_UP);
+ StopPump();
+ // register monitor commands
+ monitor.registerAction(_PMS_, &PMS);
+ monitor.registerAction(_PS1_, &PS1);
+ monitor.registerAction(_SOL_, &SOL);
+ monitor.registerAction(_MUP_, &MUP);
+ monitor.registerAction(_MDN_, &MDN);
+
+ checkPressure.set(100L, TASK_FOREVER, &readPumpPressure);
+ machine.todolist.addTask(checkPressure);
+ checkPressure.enable();
+
+ runPump.set(100L, TASK_FOREVER, &runThePump);
+ machine.todolist.addTask(runPump);
+ runPump.enable();
+
+ }
+
+ void Press() {
+ solenoid(GOING_UP);
+ motor(PUMP_ON);
+ CheckPump(); // ??
+ }
+
+ void Release() {
+ solenoid(GOING_DOWN);
+ motor(PUMP_ON);
+ CheckPump();
+
+ }
+
+ void CheckPump() {
+ if((motorOn) && ((clock.second()-startedAt) > MAX_PUMP_RUN_SECONDS)){
+ monitor.debug("Motor run timed out");
+ motor(PUMP_OFF);
+ }
+
+ // check here for the home switch.
+
+ }
+
+ void StopPump() {
+ motor(PUMP_OFF);
+ solenoid(SOLENOID_OFF);
+ }
+
+// 'accessors'
+ bool updatePressure() {
+ int tol = 50; // number foo bad move to macro
+
+ int thisReading = analogRead(PS1_APIN);
+ if (thisReading < 224) {
+ pressure=0;
+ } else {
+ pressure=(thisReading-200)*10;
+ }
+ pressureChanged = abs(pressure - pressure) >= tol;
+ if (pressureChanged)
+ pressure = pressure;
+ return pressureChanged;
+ }
+
+ void setPressureChanged(bool v) {
+ pressureChanged = v;
+ }
+};
+
+
+Pump pump;
+
+static void runThePump() {
+ pump.CheckPump();
+}
+
+static void readPumpPressure() {
+ if (pump.updatePressure()) {
+ monitor.update("PS1", "%d", pump.pressure);
+ pump.setPressureChanged(false);
+ }
+}
+
+// should also take on as a value
+
+static void PMS (uint8_t kwIndex, uint8_t verb,char *args) {
+ if (verb == '!') {
+ pump.motor((bool) atoi(args));
+ } else {
+ monitor.update("PMS", "%d", pump.isMotorOn());
+ }
+}
+
+// should also take on as a value
+static void SOL (uint8_t kwIndex, uint8_t verb,char *args) {
+ if (verb == '!') {
+ pump.solenoid((bool)atoi(args));
+ } else {
+ monitor.update("SOL", "%d", pump.areWeGoingDown());
+ }
+}
+
+static void MUP (uint8_t kwIndex, uint8_t verb,char *args) {
+ monitor.update("ACK","Going up");
+ pump.Press();
+}
+
+static void MDN (uint8_t kwIndex, uint8_t verb,char *args) {
+ monitor.update("ACK", "Going down");
+ pump.Release();
+}
+
+
+static void PS1 (uint8_t kwIndex, uint8_t verb,char *args) {
+ monitor.update("PS1", "%d", pump.pressure);
+}
+
+
+#endif // PUMP_H
+
diff --git a/Arduino/ems/SafetyThird.cpp b/Arduino/ems/SafetyThird.cpp
new file mode 100755
index 0000000..180edd2
--- /dev/null
+++ b/Arduino/ems/SafetyThird.cpp
@@ -0,0 +1,47 @@
+/*---------------------------------------------------------------SafetyThird.cpp
+ *
+ * *********************** Don't put safety third !!!! *************************
+ *
+ * This is a reminder not to put safety third like your workplace does.
+ * (after corporate profits and management whim)
+ *
+ * Author: Donald Delmar Davis, Suspect Devices
+ * Liscence: "Simplified BSD License"
+ *
+ * Copyright (c) 2016, Donald Delmar Davis, Suspect Devices
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in thedocumentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *-----------------------------------------------------------------------------*/
+/*
+ * The safteyThird module is a place for items relating to the safety of machines.
+ * Examples include:
+ * Watchdog timer (a mechanism for keeping code from running away from its job)
+ * K1, K2 ( in large machinery a pair of Kontactors designed to drop power )
+ * E-stop ( A big red button or a lanyard for emergencies)
+ * Light Curtain (a device to keep fingers from getting into dangerous areas)
+ * red. (lights or other indicators that there may be problems.
+ */
+#include "SafetyThird.h"
+
+SafetyThird safety;
diff --git a/Arduino/ems/SafetyThird.h b/Arduino/ems/SafetyThird.h
new file mode 100755
index 0000000..ea3956c
--- /dev/null
+++ b/Arduino/ems/SafetyThird.h
@@ -0,0 +1,179 @@
+/*-----------------------------------------------------------------SafetyThird.h
+ *
+ * *********************** Don't put safety third !!!! *************************
+ *
+ * This is a reminder not to put safety third like your workplace does.
+ * (after corporate profits and management whim)
+ *
+ * Author: Donald Delmar Davis, Suspect Devices
+ * Liscence: "Simplified BSD License"
+ *
+ * Copyright (c) 2016, Donald Delmar Davis, Suspect Devices
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in thedocumentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *-----------------------------------------------------------------------------*/
+/*
+ * The safteyThird module is a place for items relating to the safety of machines.
+ * Examples include:
+ * Watchdog timer (a mechanism for keeping code from running away from its job)
+ * K1, K2 ( in large machinery a pair of Kontactors designed to drop power )
+ * E-stop ( A big red button or a lanyard for emergencies)
+ * Light Curtain (a device to keep fingers from getting into dangerous areas)
+ * red. (lights or other indicators that there may be problems.
+ */
+
+#ifndef SafetyThird_h
+#define SafetyThird_h
+
+
+#include "Common.h"
+#include "Monitor.h"
+
+class SafetyThird;
+extern SafetyThird safety;
+#if 0
+#define SAFE_STATE_TO_RUN(S) (\
+(S)==STATE_SELF_TEST_INIT\
+||(S)==STATE_WARM_UP||\
+(S)==STATE_NORMAL_OPERATION\
+)
+#else
+#define SAFE_STATE_TO_RUN(S) machine.inSafeStateToRun
+#endif
+
+// preprocessor logic for core specific WDT implimentation here....
+#include <Adafruit_SleepyDog.h>
+
+static void RED (uint8_t kwIndex, uint8_t verb,char *args) ;
+
+class SafetyThird {
+
+ private:
+ bool _eStop;
+ bool _prevEStop;
+ bool _prevLightCurtain;
+
+ Task _doDoRunRun;
+
+ public:
+
+ bool _lightCurtain;
+ bool _lightCurtainReset;
+
+ static void LCS(uint8_t kwIndex, uint8_t verb, char *args) {
+ safety.first();
+ monitor.update("LCS","%s",safety._lightCurtain?"ON":"OFF");
+ }
+
+ static void LCR(uint8_t kwIndex, uint8_t verb, char *args) {
+ if (verb=='!') {
+ safety._lightCurtainReset = (atoi(args));
+ digitalWrite(START_PIN,safety._lightCurtainReset);
+ }
+ safety.first();
+ monitor.update("LCR","%s",safety._lightCurtainReset?"ON":"OFF");
+ }
+
+ static void run() {safety.first();}
+
+ void first () { // could also be called paranoia.
+#ifdef CAN_READ_ESTOP
+ _eStop = (digitalRead(E_STOP_PIN)==E_STOP_PULLED );
+#else
+ _eStop = ! E_STOP_PULLED;
+#endif
+ _lightCurtain = (digitalRead(OSDD_PIN) == CURTAIN_NOT_CLEAR);
+ int state = machine.state();
+ if ((SAFE_STATE_TO_RUN (state))
+ && !(_eStop)
+ && !(_lightCurtain)
+ )
+ {
+ if (!_prevEStop){
+ monitor.update("STP","0 E-STOP Released: Contactors are ON");
+ }
+ if (!_prevLightCurtain){
+ monitor.update("LCS","1 WORK AREA CLEARED: Contactors are ON");
+ }
+
+ digitalWrite(K1_PIN,KONTACTOR_ON);
+ }
+ if (_eStop){
+ digitalWrite(K1_PIN,KONTACTOR_OFF); // in most cases this would already be done
+ if (!_prevEStop){
+ monitor.update("STP","1 ESTOP Pushed: Contactors are OFF");
+ // fatal....
+
+ }
+ }
+ if (_lightCurtain) {
+ digitalWrite(K1_PIN,KONTACTOR_OFF); // in most cases this would already be done
+ if(!_prevLightCurtain){
+
+ monitor.update("LCS","1 WORK AREA NOT CLEAR: Contactors are OFF");
+ // safety pause
+
+ }
+ }
+
+ _prevEStop=_eStop;
+ _prevLightCurtain=_lightCurtain;
+ }
+
+ uint8_t redLed; // red led value prolly bad style
+
+ void init() {
+ pinMode (E_STOP_PIN, INPUT_PULLUP);
+ pinMode (OSDD_PIN, INPUT_PULLUP);
+ pinMode (START_PIN, OUTPUT);
+ digitalWrite(START_PIN, OK_TO_START);
+ pinMode (K1_PIN, OUTPUT);
+ digitalWrite(K1_PIN, KONTACTOR_OFF);
+
+ // http://www.arduino.org/learning/reference/analogwriteresolution
+ analogWriteResolution(ANALOG_WRITE_RESOLUTION);
+ analogWrite(LAMP_PIN, redLed * (1 << (ANALOG_WRITE_RESOLUTION - 8) ) );
+
+ _doDoRunRun.set(77L, TASK_FOREVER, &run);
+ machine.todolist.addTask(_doDoRunRun);
+ _doDoRunRun.enable();
+
+ monitor.registerAction(_RED_, &RED);
+ monitor.registerAction(_LCS_, &LCS);
+ monitor.registerAction(_LCR_, &LCR);
+ }
+// bool cleanShutdown(); // let modules know if we are starting normally
+// void everythingOff();
+// void run();
+};
+
+
+static void RED (uint8_t kwIndex, uint8_t verb,char *args) {
+ safety.redLed = atol(args);
+ analogWrite(LAMP_PIN, safety.redLed * (1 << (ANALOG_WRITE_RESOLUTION - 8) ) );
+}
+
+
+#endif
+
diff --git a/Arduino/ems/TaskScheduler.h b/Arduino/ems/TaskScheduler.h
new file mode 100755
index 0000000..89ae8a2
--- /dev/null
+++ b/Arduino/ems/TaskScheduler.h
@@ -0,0 +1,824 @@
+// Cooperative multitasking library for Arduino version 2.0.2sd
+// Copyright (c) 2015 Anatoli Arkhipenko
+//
+// Changelog:
+// v1.0.0:
+// 2015-02-24 - Initial release
+// 2015-02-28 - added delay() and disableOnLastIteration() methods
+// 2015-03-25 - changed scheduler execute() method for a more precise delay calculation:
+// 1. Do not delay if any of the tasks ran (making request for immediate execution redundant)
+// 2. Delay is invoked only if none of the tasks ran
+// 3. Delay is based on the min anticipated wait until next task _AND_ the runtime of execute method itself.
+// 2015-05-11 - added restart() and restartDelayed() methods to restart tasks which are on hold after running all iterations
+// 2015-05-19 - completely removed delay from the scheduler since there are no power saving there. using 1 ms sleep instead
+//
+// v1.4.1:
+// 2015-09-15 - more careful placement of AVR-specific includes for sleep method (compatibility with DUE)
+// sleep on idle run is no longer a default and should be explicitly compiled with _TASK_SLEEP_ON_IDLE_RUN defined
+//
+// v1.5.0:
+// 2015-09-20 - access to currently executing task (for callback methods)
+// 2015-09-20 - pass scheduler as a parameter to the task constructor to append the task to the end of the chain
+// 2015-09-20 - option to create a task already enabled
+//
+// v1.5.1:
+// 2015-09-21 - bug fix: incorrect handling of active tasks via set() and setIterations().
+// Thanks to Hannes Morgenstern for catching this one
+//
+// v1.6.0:
+// 2015-09-22 - revert back to having all tasks disable on last iteration.
+// 2015-09-22 - deprecated disableOnLastIteration method as a result
+// 2015-09-22 - created a separate branch 'disable-on-last-iteration' for this
+// 2015-10-01 - made version numbers semver compliant (documentation only)
+//
+// v1.7.0:
+// 2015-10-08 - introduced callback run counter - callback methods can branch on the iteration number.
+// 2015-10-11 - enableIfNot() - enable a task only if it is not already enabled. Returns true if was already enabled, false if was disabled.
+// 2015-10-11 - disable() returns previous enable state (true if was enabled, false if was already disabled)
+// 2015-10-11 - introduced callback methods "on enable" and "on disable". On enable runs every time enable is called, on disable runs only if task was enabled
+// 2015-10-12 - new Task method: forceNextIteration() - makes next iteration happen immediately during the next pass regardless how much time is left
+//
+// v1.8.0:
+// 2015-10-13 - support for status request objects allowing tasks waiting on requests
+// 2015-10-13 - moved to a single header file to allow compilation control via #defines from the main sketch
+//
+// v1.8.1:
+// 2015-10-22 - implement Task id and control points to support identification of failure points for watchdog timer logging
+//
+// v1.8.2:
+// 2015-10-27 - implement Local Task Storage Pointer (allow use of same callback code for different tasks)
+// 2015-10-27 - bug: currentTask() method returns incorrect Task reference if called within OnEnable and OnDisable methods
+// 2015-10-27 - protection against infinite loop in OnEnable (if enable() methods are called within OnEnable)
+// 2015-10-29 - new currentLts() method in the scheduler class returns current task's LTS pointer in one call
+//
+// v1.8.3:
+// 2015-11-05 - support for task activation on a status request with arbitrary interval and number of iterations (0 and 1 are still default values)
+// 2015-11-05 - implement waitForDelayed() method to allow task activation on the status request completion delayed for one current interval
+// 2015-11-09 - added callback methods prototypes to all examples for Arduino IDE 1.6.6 compatibility
+// 2015-11-14 - added several constants to be used as task parameters for readability (e.g, TASK_FOREVER, TASK_SECOND, etc.)
+// 2015-11-14 - significant optimization of the scheduler's execute loop, including millis() rollover fix option
+//
+// v1.8.4:
+// 2015-11-15 - bug fix: Task alignment with millis() for scheduling purposes should be done after OnEnable, not before. Especially since OnEnable method can change the interval
+// 2015-11-16 - further optimizations of the task scheduler execute loop
+//
+// v1.8.5:
+// 2015-11-23 - bug fix: incorrect calculation of next task invocation in case callback changed the interval
+// 2015-11-23 - bug fix: Task::set() method calls setInterval() explicitly, therefore delaying the task in the same manner
+//
+// v1.9.0:
+// 2015-11-24 - packed three byte-long status variables into bit array structure data type - saving 2 bytes per each task instance
+//
+// v1.9.2:
+// 2015-11-28 - _TASK_ROLLOVER_FIX is deprecated (not necessary)
+// 2015-12-16 - bug fixes: automatic millis rollover support for delay methods
+// 2015-12-17 - new method for _TASK_TIMECRITICAL option: getStartDelay()
+//
+// v2.0.0:
+// 2015-12-22 - _TASK_PRIORITY - support for layered task prioritization
+//
+// v2.0.1:
+// 2016-01-02 - bug fix: issue#11 Xtensa compiler (esp8266): Declaration of constructor does not match implementation
+//
+// v2.0.2:
+// 2016-01-05 - bug fix: time constants wrapped inside compile option
+// 2016-01-05 - support for ESP8266 wifi power saving mode for _TASK_SLEEP_ON_IDLE_RUN compile option
+//
+// v2.1.0:
+// 2016-02-01 - support for microsecond resolution
+// 2016-02-02 - added Scheduler baseline start time reset method: startNow()
+
+/* ============================================
+Cooperative multitasking library code is placed under the MIT license
+Copyright (c) 2015 Anatoli Arkhipenko
+
+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.
+===============================================
+*/
+
+
+#include <Arduino.h>
+
+#ifndef _TASKSCHEDULER_H_
+#define _TASKSCHEDULER_H_
+
+/** ----------------------------------------
+ * The following "defines" control library functionality at compile time,
+ * and should be used in the main sketch depending on the functionality required
+ *
+ * #define _TASK_TIMECRITICAL // Enable monitoring scheduling overruns
+ * #define _TASK_SLEEP_ON_IDLE_RUN // Enable 1 ms SLEEP_IDLE powerdowns between tasks if no callback methods were invoked during the pass
+ * #define _TASK_STATUS_REQUEST // Compile with support for StatusRequest functionality - triggering tasks on status change events in addition to time only
+ * #define _TASK_WDT_IDS // Compile with support for wdt control points and task ids
+ * #define _TASK_LTS_POINTER // Compile with support for local task storage pointer
+ * #define _TASK_PRIORITY // Support for layered scheduling priority
+ * #define _TASK_MICRO_RES // Support for microsecond resolution
+ */
+
+
+ #ifdef _TASK_MICRO_RES
+
+ #undef _TASK_SLEEP_ON_IDLE_RUN // SLEEP_ON_IDLE has only millisecond resolution
+ #define _TASK_TIME_FUNCTION() micros()
+
+ #else
+
+ #define _TASK_TIME_FUNCTION() millis()
+
+ #endif // _TASK_MICRO_RES
+
+
+#ifdef _TASK_SLEEP_ON_IDLE_RUN
+
+#ifdef ARDUINO_ARCH_AVR
+#include <avr/sleep.h>
+#include <avr/power.h>
+#endif // ARDUINO_ARCH_AVR
+
+#ifdef ARDUINO_ARCH_ESP8266
+extern "C" {
+#include "user_interface.h"
+}
+#define _TASK_ESP8266_DLY_THRESHOLD 200L
+#endif // ARDUINO_ARCH_ESP8266
+
+#endif // _TASK_SLEEP_ON_IDLE_RUN
+
+#define TASK_IMMEDIATE 0
+#define TASK_FOREVER (-1)
+#define TASK_ONCE 1
+
+
+#ifndef _TASK_MICRO_RES
+
+#define TASK_SECOND 1000L
+#define TASK_MINUTE 60000L
+#define TASK_HOUR 3600000L
+
+#else
+
+#define TASK_SECOND 1000000L
+#define TASK_MINUTE 60000000L
+#define TASK_HOUR 3600000000L
+
+#endif // _TASK_MICRO_RES
+
+
+#ifdef _TASK_STATUS_REQUEST
+
+#define _TASK_SR_NODELAY 1
+#define _TASK_SR_DELAY 2
+
+class StatusRequest {
+ public:
+ StatusRequest() {iCount = 0; iStatus = 0; }
+ inline void setWaiting(unsigned int aCount = 1) { iCount = aCount; iStatus = 0; }
+ bool signal(int aStatus = 0);
+ void signalComplete(int aStatus = 0);
+ inline bool pending() { return (iCount != 0); }
+ inline bool completed() { return (iCount == 0); }
+ inline int getStatus() { return iStatus; }
+
+ private:
+ unsigned int iCount; // number of statuses to wait for. waiting for more that 65000 events seems unreasonable: unsigned int should be sufficient
+ int iStatus; // status of the last completed request. negative = error; zero = OK; >positive = OK with a specific status
+};
+#endif // _TASK_STATUS_REQUEST
+
+
+typedef struct {
+ bool enabled : 1; // indicates that task is enabled or not.
+ bool inonenable : 1; // indicates that task execution is inside OnEnable method (preventing infinite loops)
+#ifdef _TASK_STATUS_REQUEST
+ byte waiting : 2; // indication if task is waiting on the status request
+#endif
+} __task_status;
+
+class Scheduler;
+
+
+#ifdef _TASK_WDT_IDS
+ static unsigned int __task_id_counter = 0; // global task ID counter for assiging task IDs automatically.
+#endif // _TASK_WDT_IDS
+
+class Task {
+ friend class Scheduler;
+ public:
+ Task(unsigned long aInterval=0, long aIterations=0, void (*aCallback)()=NULL, Scheduler* aScheduler=NULL, bool aEnable=false, bool (*aOnEnable)()=NULL, void (*aOnDisable)()=NULL);
+#ifdef _TASK_STATUS_REQUEST
+ Task(void (*aCallback)()=NULL, Scheduler* aScheduler=NULL, bool (*aOnEnable)()=NULL, void (*aOnDisable)()=NULL);
+#endif // _TASK_STATUS_REQUEST
+
+ void enable();
+ bool enableIfNot();
+ void enableDelayed(unsigned long aDelay=0);
+ void delay(unsigned long aDelay=0);
+ void forceNextIteration();
+ void restart();
+ void restartDelayed(unsigned long aDelay=0);
+ bool disable();
+ inline bool isEnabled() { return iStatus.enabled; }
+ void set(unsigned long aInterval, long aIterations, void (*aCallback)(),bool (*aOnEnable)()=NULL, void (*aOnDisable)()=NULL);
+ void setInterval(unsigned long aInterval);
+ inline unsigned long getInterval() { return iInterval; }
+ void setIterations(long aIterations);
+ inline long getIterations() { return iIterations; }
+ inline unsigned long getRunCounter() { return iRunCounter; }
+ inline void setCallback(void (*aCallback)()) { iCallback = aCallback; }
+ inline void setOnEnable(bool (*aCallback)()) { iOnEnable = aCallback; }
+ inline void setOnDisable(void (*aCallback)()) { iOnDisable = aCallback; }
+#ifdef _TASK_TIMECRITICAL
+ inline long getOverrun() { return iOverrun; }
+ inline long getStartDelay() { return iStartDelay; }
+#endif // _TASK_TIMECRITICAL
+ inline bool isFirstIteration() { return (iRunCounter <= 1); }
+ inline bool isLastIteration() { return (iIterations == 0); }
+#ifdef _TASK_STATUS_REQUEST
+ void waitFor(StatusRequest* aStatusRequest, unsigned long aInterval = 0, long aIterations = 1);
+ void waitForDelayed(StatusRequest* aStatusRequest, unsigned long aInterval = 0, long aIterations = 1);
+ inline StatusRequest* getStatusRequest() {return iStatusRequest; }
+#endif // _TASK_STATUS_REQUEST
+#ifdef _TASK_WDT_IDS
+ inline void setId(unsigned int aID) { iTaskID = aID; }
+ inline unsigned int getId() { return iTaskID; }
+ inline void setControlPoint(unsigned int aPoint) { iControlPoint = aPoint; }
+ inline unsigned int getControlPoint() { return iControlPoint; }
+#endif // _TASK_WDT_IDS
+#ifdef _TASK_LTS_POINTER
+ inline void setLtsPointer(void *aPtr) { iLTS = aPtr; }
+ inline void* getLtsPointer() { return iLTS; }
+#endif // _TASK_LTS_POINTER
+
+ private:
+ void reset();
+
+ volatile __task_status iStatus;
+ volatile unsigned long iInterval; // execution interval in milliseconds (or microseconds). 0 - immediate
+ volatile unsigned long iDelay; // actual delay until next execution (usually equal iInterval)
+ volatile unsigned long iPreviousMillis; // previous invocation time (millis). Next invocation = iPreviousMillis + iInterval. Delayed tasks will "catch up"
+#ifdef _TASK_TIMECRITICAL
+ volatile long iOverrun; // negative if task is "catching up" to it's schedule (next invocation time is already in the past)
+ volatile long iStartDelay; // actual execution of the task's callback method was delayed by this number of millis
+#endif // _TASK_TIMECRITICAL
+ volatile long iIterations; // number of iterations left. 0 - last iteration. -1 - infinite iterations
+ long iSetIterations; // number of iterations originally requested (for restarts)
+ unsigned long iRunCounter; // current number of iteration (starting with 1). Resets on enable.
+ void (*iCallback)(); // pointer to the void callback method
+ bool (*iOnEnable)(); // pointer to the bolol OnEnable callback method
+ void (*iOnDisable)(); // pointer to the void OnDisable method
+ Task *iPrev, *iNext; // pointers to the previous and next tasks in the chain
+ Scheduler *iScheduler; // pointer to the current scheduler
+#ifdef _TASK_STATUS_REQUEST
+ StatusRequest *iStatusRequest; // pointer to the status request task is or was waiting on
+#endif // _TASK_STATUS_REQUEST
+#ifdef _TASK_WDT_IDS
+ unsigned int iTaskID; // task ID (for debugging and watchdog identification)
+ unsigned int iControlPoint; // current control point within the callback method. Reset to 0 by scheduler at the beginning of each pass
+#endif // _TASK_WDT_IDS
+#ifdef _TASK_LTS_POINTER
+ void *iLTS; // pointer to task's local storage. Needs to be recast to appropriate type (usually a struct).
+#endif // _TASK_LTS_POINTER
+};
+
+
+#ifdef _TASK_PRIORITY
+ static Scheduler* iCurrentScheduler;
+#endif // _TASK_PRIORITY
+
+class Scheduler {
+ friend class Task;
+ public:
+ Scheduler();
+ void init();
+ void addTask(Task& aTask);
+ void deleteTask(Task& aTask);
+ void disableAll(bool aRecursive = true);
+ void enableAll(bool aRecursive = true);
+ bool execute(); // Returns true if at none of the tasks' callback methods was invoked (true if idle run)
+ void startNow(bool aRecursive = true); // reset ALL active tasks to immediate execution NOW.
+ inline Task& currentTask() {return *iCurrent; }
+#ifdef _TASK_SLEEP_ON_IDLE_RUN
+ void allowSleep(bool aState = true);
+#endif // _TASK_SLEEP_ON_IDLE_RUN
+#ifdef _TASK_LTS_POINTER
+ inline void* currentLts() {return iCurrent->iLTS; }
+#endif // _TASK_LTS_POINTER
+#ifdef _TASK_TIMECRITICAL
+ inline bool isOverrun() { return (iCurrent->iOverrun < 0); }
+#endif // _TASK_TIMECRITICAL
+#ifdef _TASK_PRIORITY
+ void setHighPriorityScheduler(Scheduler* aScheduler);
+ static Scheduler& currentScheduler() { return *(iCurrentScheduler); };
+#endif // _TASK_PRIORITY
+
+ private:
+ Task *iFirst, *iLast, *iCurrent; // pointers to first, last and current tasks in the chain
+#ifdef _TASK_SLEEP_ON_IDLE_RUN
+ bool iAllowSleep; // indication if putting avr to IDLE_SLEEP mode is allowed by the program at this time.
+#endif // _TASK_SLEEP_ON_IDLE_RUN
+#ifdef _TASK_PRIORITY
+ Scheduler *iHighPriority; // Pointer to a higher priority scheduler
+#endif // _TASK_PRIORITY
+};
+
+
+// ------------------ TaskScheduler implementation --------------------
+
+/** Constructor, uses default values for the parameters
+ * so could be called with no parameters.
+ */
+inline Task::Task( unsigned long aInterval, long aIterations, void (*aCallback)(), Scheduler* aScheduler, bool aEnable, bool (*aOnEnable)(), void (*aOnDisable)() ) {
+ reset();
+ set(aInterval, aIterations, aCallback, aOnEnable, aOnDisable);
+ if (aScheduler) aScheduler->addTask(*this);
+#ifdef _TASK_STATUS_REQUEST
+ iStatusRequest = NULL;
+#endif // _TASK_STATUS_REQUEST
+#ifdef _TASK_WDT_IDS
+ iTaskID = ++__task_id_counter;
+#endif // _TASK_WDT_IDS
+ if (aEnable) enable();
+}
+
+
+#ifdef _TASK_STATUS_REQUEST
+
+/** Constructor with reduced parameter list for tasks created for
+ * StatusRequest only triggering (always immediate and only 1 iteration)
+ */
+inline Task::Task( void (*aCallback)(), Scheduler* aScheduler, bool (*aOnEnable)(), void (*aOnDisable)() ) {
+ reset();
+ set(TASK_IMMEDIATE, TASK_ONCE, aCallback, aOnEnable, aOnDisable);
+ if (aScheduler) aScheduler->addTask(*this);
+ iStatusRequest = NULL;
+#ifdef _TASK_WDT_IDS
+ iTaskID = ++__task_id_counter;
+#endif // _TASK_WDT_IDS
+}
+
+/** Signals completion of the StatusRequest by one of the participating events
+ * @param: aStatus - if provided, sets the return code of the StatusRequest: negative = error, 0 (default) = OK, positive = OK with a specific status code
+ * Negative status will complete Status Request fully (since an error occured).
+ * @return: true, if StatusRequest is complete, false otherwise (still waiting for other events)
+ */
+inline bool StatusRequest::signal(int aStatus) {
+ if ( iCount) { // do not update the status request if it was already completed
+ if (iCount > 0) --iCount;
+ if ( (iStatus = aStatus) < 0 ) iCount = 0; // if an error is reported, the status is requested to be completed immediately
+ }
+ return (iCount == 0);
+}
+
+inline void StatusRequest::signalComplete(int aStatus) {
+ if (iCount) { // do not update the status request if it was already completed
+ iCount = 0;
+ iStatus = aStatus;
+ }
+}
+
+/** Sets a Task to wait until a particular event completes
+ * @param: aStatusRequest - a pointer for the StatusRequest to wait for.
+ * If aStatusRequest is NULL, request for waiting is ignored, and the waiting task is not enabled.
+ */
+inline void Task::waitFor(StatusRequest* aStatusRequest, unsigned long aInterval, long aIterations) {
+ if ( ( iStatusRequest = aStatusRequest) ) { // assign internal StatusRequest var and check if it is not NULL
+ setIterations(aIterations);
+ setInterval(aInterval);
+ iStatus.waiting = _TASK_SR_NODELAY; // no delay
+ enable();
+ }
+}
+
+inline void Task::waitForDelayed(StatusRequest* aStatusRequest, unsigned long aInterval, long aIterations) {
+ if ( ( iStatusRequest = aStatusRequest) ) { // assign internal StatusRequest var and check if it is not NULL
+ setIterations(aIterations);
+ if ( aInterval ) setInterval(aInterval); // For the dealyed version only set the interval if it was not a zero
+ iStatus.waiting = _TASK_SR_DELAY; // with delay equal to the current interval
+ enable();
+ }
+}
+#endif // _TASK_STATUS_REQUEST
+
+/** Resets (initializes) the task/
+ * Task is not enabled and is taken out
+ * out of the execution chain as a result
+ */
+inline void Task::reset() {
+ iStatus.enabled = false;
+ iStatus.inonenable = false;
+ iPreviousMillis = 0;
+ iInterval = iDelay = 0;
+ iPrev = NULL;
+ iNext = NULL;
+ iScheduler = NULL;
+ iRunCounter = 0;
+#ifdef _TASK_TIMECRITICAL
+ iOverrun = 0;
+ iStartDelay = 0;
+#endif // _TASK_TIMECRITICAL
+#ifdef _TASK_WDT_IDS
+ iControlPoint = 0;
+#endif // _TASK_WDT_IDS
+#ifdef _TASK_LTS_POINTER
+ iLTS = NULL;
+#endif // _TASK_LTS_POINTER
+#ifdef _TASK_STATUS_REQUEST
+ iStatus.waiting = 0;
+#endif // _TASK_STATUS_REQUEST
+}
+
+/** Explicitly set Task execution parameters
+ * @param aInterval - execution interval in ms
+ * @param aIterations - number of iterations, use -1 for no limit
+ * @param aCallback - pointer to the callback method which executes the task actions
+ * @param aOnEnable - pointer to the callback method which is called on enable()
+ * @param aOnDisable - pointer to the callback method which is called on disable()
+ */
+inline void Task::set(unsigned long aInterval, long aIterations, void (*aCallback)(),bool (*aOnEnable)(), void (*aOnDisable)()) {
+ setInterval(aInterval);
+ iSetIterations = iIterations = aIterations;
+ iCallback = aCallback;
+ iOnEnable = aOnEnable;
+ iOnDisable = aOnDisable;
+}
+
+/** Sets number of iterations for the task
+ * if task is enabled, schedule for immediate execution
+ * @param aIterations - number of iterations, use -1 for no limit
+ */
+inline void Task::setIterations(long aIterations) {
+ iSetIterations = iIterations = aIterations;
+}
+
+/** Enables the task
+ * schedules it for execution as soon as possible,
+ * and resets the RunCounter back to zero
+ */
+inline void Task::enable() {
+ if (iScheduler) { // activation without active scheduler does not make sense
+ iRunCounter = 0;
+ if ( iOnEnable && !iStatus.inonenable ) {
+ Task *current = iScheduler->iCurrent;
+ iScheduler->iCurrent = this;
+ iStatus.inonenable = true; // Protection against potential infinite loop
+ iStatus.enabled = (*iOnEnable)();
+ iStatus.inonenable = false; // Protection against potential infinite loop
+ iScheduler->iCurrent = current;
+ }
+ else {
+ iStatus.enabled = true;
+ }
+ iPreviousMillis = _TASK_TIME_FUNCTION() - (iDelay = iInterval);
+ }
+}
+
+/** Enables the task only if it was not enabled already
+ * Returns previous state (true if was already enabled, false if was not)
+ */
+inline bool Task::enableIfNot() {
+ bool previousEnabled = iStatus.enabled;
+ if ( !previousEnabled ) enable();
+ return (previousEnabled);
+}
+
+/** Enables the task
+ * and schedules it for execution after a delay = aInterval
+ */
+inline void Task::enableDelayed(unsigned long aDelay) {
+ enable();
+ delay(aDelay);
+}
+
+/** Delays Task for execution after a delay = aInterval (if task is enabled).
+ * leaves task enabled or disabled
+ * if aDelay is zero, delays for the original scheduling interval from now
+ */
+inline void Task::delay(unsigned long aDelay) {
+// if (!aDelay) aDelay = iInterval;
+ iDelay = aDelay ? aDelay : iInterval;
+ iPreviousMillis = _TASK_TIME_FUNCTION(); // - iInterval + aDelay;
+}
+
+/** Schedules next iteration of Task for execution immediately (if enabled)
+ * leaves task enabled or disabled
+ * Task's original schedule is shifted, and all subsequent iterations will continue from this point in time
+ */
+inline void Task::forceNextIteration() {
+ iPreviousMillis = _TASK_TIME_FUNCTION() - (iDelay = iInterval);
+}
+
+/** Sets the execution interval.
+ * Task execution is delayed for aInterval
+ * Use enable() to schedule execution ASAP
+ * @param aInterval - new execution interval
+ */
+inline void Task::setInterval (unsigned long aInterval) {
+ iInterval = aInterval;
+ delay(); // iDelay will be updated by the delay() function
+}
+
+/** Disables task
+ * Task will no longer be executed by the scheduler
+ * Returns status of the task before disable was called (i.e., if the task was already disabled)
+ */
+inline bool Task::disable() {
+ bool previousEnabled = iStatus.enabled;
+ iStatus.enabled = false;
+ iStatus.inonenable = false;
+ if (previousEnabled && iOnDisable) {
+ Task *current = iScheduler->iCurrent;
+ iScheduler->iCurrent = this;
+ (*iOnDisable)();
+ iScheduler->iCurrent = current;
+ }
+ return (previousEnabled);
+}
+
+/** Restarts task
+ * Task will run number of iterations again
+ */
+inline void Task::restart() {
+ iIterations = iSetIterations;
+ enable();
+}
+
+/** Restarts task delayed
+ * Task will run number of iterations again
+ */
+inline void Task::restartDelayed(unsigned long aDelay) {
+ iIterations = iSetIterations;
+ enableDelayed(aDelay);
+}
+
+// ------------------ Scheduler implementation --------------------
+
+/** Default constructor.
+ * Creates a scheduler with an empty execution chain.
+ */
+inline Scheduler::Scheduler() {
+ init();
+}
+
+/** Initializes all internal varaibles
+ */
+inline void Scheduler::init() {
+ iFirst = NULL;
+ iLast = NULL;
+ iCurrent = NULL;
+#ifdef _TASK_PRIORITY
+ iHighPriority = NULL;
+#endif // _TASK_PRIORITY
+#ifdef _TASK_SLEEP_ON_IDLE_RUN
+ allowSleep(true);
+#endif // _TASK_SLEEP_ON_IDLE_RUN
+}
+
+/** Appends task aTask to the tail of the execution chain.
+ * @param &aTask - reference to the Task to be appended.
+ * @note Task can only be part of the chain once.
+ */
+inline void Scheduler::addTask(Task& aTask) {
+
+ aTask.iScheduler = this;
+// First task situation:
+ if (iFirst == NULL) {
+ iFirst = &aTask;
+ aTask.iPrev = NULL;
+ }
+ else {
+// This task gets linked back to the previous last one
+ aTask.iPrev = iLast;
+ iLast->iNext = &aTask;
+ }
+// "Previous" last task gets linked to this one - as this one becomes the last one
+ aTask.iNext = NULL;
+ iLast = &aTask;
+}
+
+/** Deletes specific Task from the execution chain
+ * @param &aTask - reference to the task to be deleted from the chain
+ */
+inline void Scheduler::deleteTask(Task& aTask) {
+ if (aTask.iPrev == NULL) {
+ if (aTask.iNext == NULL) {
+ iFirst = NULL;
+ iLast = NULL;
+ return;
+ }
+ else {
+ aTask.iNext->iPrev = NULL;
+ iFirst = aTask.iNext;
+ aTask.iNext = NULL;
+ return;
+ }
+ }
+
+ if (aTask.iNext == NULL) {
+ aTask.iPrev->iNext = NULL;
+ iLast = aTask.iPrev;
+ aTask.iPrev = NULL;
+ return;
+ }
+
+ aTask.iPrev->iNext = aTask.iNext;
+ aTask.iNext->iPrev = aTask.iPrev;
+ aTask.iPrev = NULL;
+ aTask.iNext = NULL;
+}
+
+/** Disables all tasks in the execution chain
+ * Convenient for error situations, when the only
+ * task remaining active is an error processing task
+ * @param aRecursive - if true, tasks of the higher priority chains are disabled as well recursively
+ */
+inline void Scheduler::disableAll(bool aRecursive) {
+ Task *current = iFirst;
+ while (current) {
+ current->disable();
+ current = current->iNext;
+ }
+#ifdef _TASK_PRIORITY
+ if (aRecursive && iHighPriority) iHighPriority->disableAll(true);
+#endif // _TASK_PRIORITY
+}
+
+
+/** Enables all the tasks in the execution chain
+ * @param aRecursive - if true, tasks of the higher priority chains are enabled as well recursively
+ */
+inline void Scheduler::enableAll(bool aRecursive) {
+ Task *current = iFirst;
+ while (current) {
+ current->enable();
+ current = current->iNext;
+ }
+#ifdef _TASK_PRIORITY
+ if (aRecursive && iHighPriority) iHighPriority->enableAll(true);
+#endif // _TASK_PRIORITY
+}
+
+/** Sets scheduler for the higher priority tasks (support for layered task priority)
+ * @param aScheduler - pointer to a scheduler for the higher priority tasks
+ */
+#ifdef _TASK_PRIORITY
+inline void Scheduler::setHighPriorityScheduler(Scheduler* aScheduler) {
+ if (aScheduler != this) iHighPriority = aScheduler; // Setting yourself as a higher priority one will create infinite recursive call
+#ifdef _TASK_SLEEP_ON_IDLE_RUN
+ if (iHighPriority) {
+ iHighPriority->allowSleep(false); // Higher priority schedulers should not do power management
+ }
+#endif // _TASK_SLEEP_ON_IDLE_RUN
+};
+#endif // _TASK_PRIORITY
+
+
+#ifdef _TASK_SLEEP_ON_IDLE_RUN
+inline void Scheduler::allowSleep(bool aState) {
+ iAllowSleep = aState;
+
+#ifdef ARDUINO_ARCH_ESP8266
+ wifi_set_sleep_type( iAllowSleep ? LIGHT_SLEEP_T : NONE_SLEEP_T );
+#endif // ARDUINO_ARCH_ESP8266
+
+}
+#endif // _TASK_SLEEP_ON_IDLE_RUN
+
+
+inline void Scheduler::startNow( bool aRecursive ) {
+ unsigned long t = _TASK_TIME_FUNCTION();
+
+ iCurrent = iFirst;
+ while (iCurrent) {
+ if ( iCurrent->iStatus.enabled ) iCurrent->iPreviousMillis = t - iCurrent->iDelay;
+ iCurrent = iCurrent->iNext;
+ }
+
+#ifdef _TASK_PRIORITY
+ if (aRecursive && iHighPriority) iHighPriority->startNow( true );
+#endif // _TASK_PRIORITY
+}
+
+/** Makes one pass through the execution chain.
+ * Tasks are executed in the order they were added to the chain
+ * There is no concept of priority
+ * Different pseudo "priority" could be achieved
+ * by running task more frequently
+ */
+inline bool Scheduler::execute() {
+ bool idleRun = true;
+ register unsigned long m, i; // millis, interval;
+
+#ifdef ARDUINO_ARCH_ESP8266
+ unsigned long t1 = micros();
+ unsigned long t2 = 0;
+#endif // ARDUINO_ARCH_ESP8266
+
+ iCurrent = iFirst;
+
+ while (iCurrent) {
+
+#ifdef _TASK_PRIORITY
+ // If scheduler for higher priority tasks is set, it's entire chain is executed on every pass of the base scheduler
+ if (iHighPriority) idleRun = iHighPriority->execute() && idleRun;
+ iCurrentScheduler = this;
+#endif // _TASK_PRIORITY
+
+ do {
+ if ( iCurrent->iStatus.enabled ) {
+
+#ifdef _TASK_WDT_IDS
+ // For each task the control points are initialized to avoid confusion because of carry-over:
+ iCurrent->iControlPoint = 0;
+#endif // _TASK_WDT_IDS
+
+ // Disable task on last iteration:
+ if (iCurrent->iIterations == 0) {
+ iCurrent->disable();
+ break;
+ }
+ m = _TASK_TIME_FUNCTION();
+ i = iCurrent->iInterval;
+
+#ifdef _TASK_STATUS_REQUEST
+ // If StatusRequest object was provided, and still pending, and task is waiting, this task should not run
+ // Otherwise, continue with execution as usual. Tasks waiting to StatusRequest need to be rescheduled according to
+ // how they were placed into waiting state (waitFor or waitForDelayed)
+ if ( iCurrent->iStatus.waiting ) {
+ if ( (iCurrent->iStatusRequest)->pending() ) break;
+ if (iCurrent->iStatus.waiting == _TASK_SR_NODELAY) {
+ iCurrent->iPreviousMillis = m - (iCurrent->iDelay = i);
+ }
+ else {
+ iCurrent->iPreviousMillis = m;
+ }
+ iCurrent->iStatus.waiting = 0;
+ }
+#endif // _TASK_STATUS_REQUEST
+
+ if ( m - iCurrent->iPreviousMillis < iCurrent->iDelay ) break;
+
+ if ( iCurrent->iIterations > 0 ) iCurrent->iIterations--; // do not decrement (-1) being a signal of never-ending task
+ iCurrent->iRunCounter++;
+ iCurrent->iPreviousMillis += iCurrent->iDelay;
+
+#ifdef _TASK_TIMECRITICAL
+ // Updated_previous+current interval should put us into the future, so iOverrun should be positive or zero.
+ // If negative - the task is behind (next execution time is already in the past)
+ unsigned long p = iCurrent->iPreviousMillis;
+ iCurrent->iOverrun = (long) ( p + i - m );
+ iCurrent->iStartDelay = (long) ( m - p );
+#endif // _TASK_TIMECRITICAL
+
+ iCurrent->iDelay = i;
+ if ( iCurrent->iCallback ) {
+ ( *(iCurrent->iCallback) )();
+ idleRun = false;
+ }
+ }
+ } while (0); //guaranteed single run - allows use of "break" to exit
+ iCurrent = iCurrent->iNext;
+ }
+
+#ifdef _TASK_SLEEP_ON_IDLE_RUN
+ if (idleRun && iAllowSleep) {
+
+#ifdef ARDUINO_ARCH_AVR // Could be used only for AVR-based boards.
+ set_sleep_mode(SLEEP_MODE_IDLE);
+ sleep_enable();
+ /* Now enter sleep mode. */
+ sleep_mode();
+
+ /* The program will continue from here after the timer timeout ~1 ms */
+ sleep_disable(); /* First thing to do is disable sleep. */
+#endif // ARDUINO_ARCH_AVR
+
+#ifdef ARDUINO_ARCH_ESP8266
+// to do: find suitable sleep function for esp8266
+ t2 = micros() - t1;
+ if (t2 < _TASK_ESP8266_DLY_THRESHOLD) delay(1); // ESP8266 implementation of delay() uses timers and yield
+#endif // ARDUINO_ARCH_ESP8266
+ }
+#endif // _TASK_SLEEP_ON_IDLE_RUN
+
+ return (idleRun);
+}
+
+
+
+#endif /* _TASKSCHEDULER_H_ */
diff --git a/Arduino/ems/Thermocouple.cpp b/Arduino/ems/Thermocouple.cpp
new file mode 100755
index 0000000..c06528c
--- /dev/null
+++ b/Arduino/ems/Thermocouple.cpp
@@ -0,0 +1,9 @@
+/******************************************************************
+ * Thermocouple.cpp - type description for temperature measurement.
+ */
+
+#include "Thermocouple.h"
+
+Thermocouples thermocouple;
+
+
diff --git a/Arduino/ems/Thermocouple.h b/Arduino/ems/Thermocouple.h
new file mode 100755
index 0000000..13e72a3
--- /dev/null
+++ b/Arduino/ems/Thermocouple.h
@@ -0,0 +1,178 @@
+/******************************************************************
+ * Thermocouple.h - type description for temperature measurement.
+ *
+ * this should be generalized into a temperature sensor.
+ *
+ */
+
+ #ifndef THERMOCOUPLE_H
+ #define THERMOCOUPLE_H
+
+
+#include <SPI.h>
+#include "Adafruit_MAX31855.h"
+
+#include "Common.h"
+#include "TaskScheduler.h"
+#include "Monitor.h"
+#include "Machine.h"
+
+static double CtoF(double C) {
+ return (9.0*C)/5.0 + 32.0;
+}
+
+// output temperature values when they have changed
+static void readPlate1Temp();
+static void readPlate2Temp();
+static void readAmbientTemp();
+static void TS1 (uint8_t kwIndex, uint8_t verb,char *args);
+static void TS2 (uint8_t kwIndex, uint8_t verb,char *args);
+static void AMT (uint8_t kwIndex, uint8_t verb,char *args);
+
+class Thermocouples {
+ private:
+ Adafruit_MAX31855 _therm1;
+ double _temp1;
+ bool _temp1changed;
+
+ Adafruit_MAX31855 _therm2;
+ double _temp2;
+ bool _temp2changed;
+
+ // ambient temp is made up of the avg ambient temp from each thermocouple
+ double _ambientTemp;
+ bool _ambChanged;
+
+ Task _temp1Read;
+ Task _temp2Read;
+ Task _ambientTempRead;
+
+ public:
+ Thermocouples() : _therm1(TS1_CS_PIN), _therm2(TS2_CS_PIN) {}
+ Adafruit_MAX31855* therm1() {return &_therm1;}
+ Adafruit_MAX31855* therm2() {return &_therm2;}
+
+ // temperature 1 changes
+ bool updateTemp1() {
+ double tol = 1;
+ double temp = therm1()->readFarenheit();
+ _temp1changed = fabs(_temp1 - temp) >= tol;
+ if (_temp1changed)
+ _temp1 = temp;
+ return _temp1changed;
+ }
+
+ void setTemp1Changed(bool v) {
+ _temp1changed = v;
+ }
+
+ // temperature 2 changes
+ bool updateTemp2() {
+ double tol = 1;
+ double temp = therm2()->readFarenheit();
+ _temp2changed = fabs(_temp2 - temp) >= tol;
+ if (_temp2changed)
+ _temp2 = temp;
+ return _temp2changed;
+ }
+
+ void setTemp2Changed(bool v) {
+ _temp2changed = v;
+ }
+
+ // Check for Ambient temperature changes
+ bool updateAmbientTemp() {
+ double tol = 1;
+ // Farenheit temperature
+ double avgTemp = CtoF((therm1()->readInternal() + therm2()->readInternal())/2.0);
+ _ambChanged = fabs(_ambientTemp - avgTemp) >= tol;
+ if (_ambChanged) {
+ _ambientTemp = avgTemp;
+ }
+ return _ambChanged;
+ }
+
+ void setAmbChanged(bool v) {
+ _ambChanged = v;
+ }
+
+ double getTemp1() { return _temp1; }
+ double getTemp2() { return _temp2; }
+ double getAmb() { return _ambientTemp; }
+
+ bool updateTemp2(double temp);
+
+ void init() {
+ monitor.registerAction(_TS1_, &TS1);
+ monitor.registerAction(_TS2_, &TS2);
+ monitor.registerAction(_AMT_, &AMT);
+
+ // set tasks for sceduled reads
+ _temp1Read.set(100L, TASK_FOREVER, &readPlate1Temp);
+ machine.todolist.addTask(_temp1Read);
+ _temp1Read.enable();
+
+ _temp2Read.set(100L, TASK_FOREVER, &readPlate2Temp);
+ machine.todolist.addTask(_temp2Read);
+ _temp2Read.enable();
+
+ _ambientTempRead.set(100L, TASK_FOREVER, &readAmbientTemp);
+ machine.todolist.addTask(_ambientTempRead);
+ _ambientTempRead.enable();
+ }
+
+
+};
+
+extern Thermocouples thermocouple;
+
+/*----------------------------------------------------------------------
+ * Schedule Callbacks
+ *--------------------------------------------------------------------*/
+// return the temp only if the plate temp has changed
+static void readPlate1Temp() {
+ if (thermocouple.updateTemp1()) {
+ double _f = thermocouple.getTemp1();
+ monitor.update("TS1", "%d.%.02d", int(_f),int(abs(_f - int(_f))*100));
+ thermocouple.setTemp1Changed(false);
+ }
+}
+
+static void readPlate2Temp() {
+ if (thermocouple.updateTemp2()) {
+ double _f = thermocouple.getTemp2();
+ monitor.update("TS2", "%d.%.02d", int(_f),int(abs(_f - int(_f))*100));
+ thermocouple.setTemp2Changed(false);
+ }
+}
+
+static void readAmbientTemp() {
+ if (thermocouple.updateAmbientTemp()) {
+ double _f = thermocouple.getAmb();
+ monitor.update("AMT", "%d.%.02d", int(_f),int(abs(_f - int(_f))*100));
+ thermocouple.setAmbChanged(false);
+ }
+}
+
+/*----------------------------------------------------------------------
+ * Monitor Callbacks
+ *--------------------------------------------------------------------*/
+
+// unconditionally return temp
+static void TS1 (uint8_t kwIndex, uint8_t verb,char *args) {
+ double _f = thermocouple.getTemp1();
+ monitor.update("TS1", "%d.%.02d", int(_f),int(abs(_f - int(_f))*100));
+}
+
+static void TS2 (uint8_t kwIndex, uint8_t verb,char *args) {
+ double _f = thermocouple.getTemp2();
+ monitor.update("TS2", "%d.%.02d", int(_f),int(abs(_f - int(_f))*100));
+}
+
+static void AMT (uint8_t kwIndex, uint8_t verb,char *args) {
+ double _f = thermocouple.getAmb();
+ monitor.update("AMT", "%d.%.02d", int(_f),int(abs(_f - int(_f))*100));
+}
+
+
+ #endif // THERMOCOUPLE_H
diff --git a/Arduino/ems/ems.ino b/Arduino/ems/ems.ino
new file mode 100755
index 0000000..26bc988
--- /dev/null
+++ b/Arduino/ems/ems.ino
@@ -0,0 +1,3 @@
+/*
+ * empty placeholder to keep the arduino preprocessor away from any real code
+ */
diff --git a/Arduino/ems/keywords.h b/Arduino/ems/keywords.h
new file mode 100755
index 0000000..6e9c6e8
--- /dev/null
+++ b/Arduino/ems/keywords.h
@@ -0,0 +1,122 @@
+
+/*---------------------------------------------------------------------kewords.h
+ * Copyright 2012-2018 Donald Delmar Davis, Suspect Devices. All Rights Reserved
+ *
+ * !!!!!!!!!!!! ..... This may or may not be free software ..... !!!!!!!!!!!!!!!
+ *
+ * This file is autogenerated and while created by and needed for several open
+ * source modules the vocabulary is specific to the project and the routines
+ * referenced will be liscenced on a per project basis
+ *----------------------------------------------------------------------------*/
+
+/*----------------------------- Monitor keywords--------------------------------
+* The idea here is to create an automation freindly monitor protocol and debug
+* interfacse. Because this needs to be efficient (and flexible) I have chosen a
+* very simple interface which will access and may manipulate the devices public
+* variables.
+*
+* Basic Syntax for the monitor is
+* XXX[?!:] <variable stuff> <CR>
+* where:
+* XXX is a 3 letter command or variable.
+* ! execute command or store value
+* ? request a value
+* : value sent as a response or an update.
+* simple example
+* (reboot device)
+* RST!
+* (set red led value)
+* RED!255
+* RED:255
+* (request pressure sensor, recieve sensor value
+* note: unsolicited temperature sensor update )
+* PS1?
+* PS1:1240.2
+* TS1:150.5
+*
+*/
+#ifndef keywords_h
+#define keywords_h
+
+
+#define HELPTEXT\
+"SYN[! ]sync (hello) "\
+"ACK[ : ]acknowlege(yes) "\
+"NAK[ : ]negative (no) "\
+"SWV[ :?]Software Vers. "\
+"HWV[ :?]Hardware Vers. "\
+"GIT[ :?]GIT Repo Vers. "\
+"MEM[ :?]Avaliable Mem. "\
+"SSN[ :?]Unit Serial No. "\
+"HLP[ :?]help "\
+"FTL[!: ]Fatal Error "\
+"ALT[!: ]Alert "\
+"WAR[!: ]Warning "\
+"INF[!: ]Info "\
+"DBG[!: ]Debugging Info "\
+"LOG[!: ]Log "\
+"STC[!:?]State Change "\
+"DVL[!:?]Display Level "\
+"LVL[!:?]Log Level "\
+"RST[! ]Reboot "\
+"BLD[! ]Reboot to loader"\
+"STP[!:?]Sim/Chk E-Stop "\
+"LCS[!:?]Light Curt. Stop"\
+"LCR[!: ]Light Curt. Rst "\
+"NOW[!:?]Time as an int "\
+"TIM[!:?]Time for humans "\
+"TS1[ :?]Temp Sensor 1 "\
+"TS2[ :?]Temp Sensor 2 "\
+"TMP[ :?]Temp Setting "\
+"AMT[ :?]Ambient Temp "\
+"AMH[ :?]Ambient Humidity"\
+"RED[!: ]Led (0-255) "\
+"HSW[!:?]Heaters (ON/OFF)"\
+"H1S[!:?]Heater 1(ON/OFF)"\
+"H1V[!:?]Heater 1 (0-255)"\
+"H1P[!:?]Heater 1 P value"\
+"H1I[!:?]Heater 1 I value"\
+"H1D[!:?]Heater 1 D value"\
+"H2S[!:?]Heater 1(ON/OFF)"\
+"H2V[!:?]Heater 1 (0-255)"\
+"H2P[!:?]Heater 2 P value"\
+"H2I[!:?]Heater 2 I value"\
+"H2D[!:?]Heater 2 D value"\
+"PMS[!:?]Pump Sw (ON/OFF)"\
+"PSP[ :?]Pressure P value"\
+"PSI[ :?]Pressure I value"\
+"PSD[ :?]Pressure D value"\
+"PS1[ :?]Pressure Sensor "\
+"MUP[!: ]Manual up "\
+"MDN[!: ]Manual down "\
+"TMS[!:?]Timer setting "\
+"TMR[!:?]Timer Run(ON/OF)"\
+"TRM[ :?]Time Remaining "\
+"SOL[!:?]Solenoid(ON/OFF)"\
+"HOM[ :?]Home Switch "\
+"CMD[!:?]Command Mode "\
+"MLD[!:?]Enable Multiline"\
+"SOD[!: ]Start Multiline "\
+"EOD[!: ]End Multiline "\
+"NOP[!: ]Not implemented"
+
+
+#define KEYWORDS \
+"SYN" "ACK" "NAK" "SWV" "HWV" "GIT" "MEM" "SSN" "HLP" "FTL" "ALT" "WAR" "INF" \
+"DBG" "LOG" "STC" "DVL" "LVL" "RST" "BLD" "STP" "LCS" "LCR" "NOW" "TIM" "TS1" \
+"TS2" "TMP" "AMT" "AMH" "RED" "HSW" "H1S" "H1V" "H1P" "H1I" "H1D" "H2S" "H2V" \
+"H2P" "H2I" "H2D" "PMS" "PSP" "PSI" "PSD" "PS1" "MUP" "MDN" "TMS" "TMR" "TRM" \
+"SOL" "HOM" "CMD" "MLD" "SOD" "EOD" "NOP"
+
+
+enum keywordIndex {
+_SYN_,_ACK_,_NAK_,_SWV_,_HWV_,_GIT_,_MEM_,_SSN_,_HLP_,_FTL_,_ALT_,_WAR_,_INF_,
+_DBG_,_LOG_,_STC_,_DVL_,_LVL_,_RST_,_BLD_,_STP_,_LCS_,_LCR_,_NOW_,_TIM_,_TS1_,
+_TS2_,_TMP_,_AMT_,_AMH_,_RED_,_HSW_,_H1S_,_H1V_,_H1P_,_H1I_,_H1D_,_H2S_,_H2V_,
+_H2P_,_H2I_,_H2D_,_PMS_,_PSP_,_PSI_,_PSD_,_PS1_,_MUP_,_MDN_,_TMS_,_TMR_,_TRM_,
+_SOL_,_HOM_,_CMD_,_MLD_,_SOD_,_EOD_,_NOP_};
+
+// use enum to determine the size of the keyword arrays.
+#define NKEYWORDS _NOP_ + 1
+
+#endif