From 261ac02efc8361ac1e55d9be72cfab1c8ea55237 Mon Sep 17 00:00:00 2001 From: Donald Delmar Davis Date: Fri, 20 Oct 2017 10:46:30 -0700 Subject: Let's do this --- Arduino/bin/README_Load.txt | 38 ++ Arduino/bin/build.sh | 36 ++ Arduino/bin/load.sh | 18 + Arduino/ems/Clock.cpp | 216 ++++++++++++ Arduino/ems/Clock.h | 131 +++++++ Arduino/ems/Common.h | 106 ++++++ Arduino/ems/Heater.h | 263 ++++++++++++++ Arduino/ems/Machine.cpp | 170 +++++++++ Arduino/ems/Machine.h | 42 +++ Arduino/ems/Monitor.cpp | 278 +++++++++++++++ Arduino/ems/Monitor.h | 218 ++++++++++++ Arduino/ems/Pump.h | 200 +++++++++++ Arduino/ems/SafetyThird.cpp | 47 +++ Arduino/ems/SafetyThird.h | 179 ++++++++++ Arduino/ems/TaskScheduler.h | 824 +++++++++++++++++++++++++++++++++++++++++++ Arduino/ems/Thermocouple.cpp | 9 + Arduino/ems/Thermocouple.h | 178 ++++++++++ Arduino/ems/ems.ino | 3 + Arduino/ems/keywords.h | 122 +++++++ Arduino/libraries/readme.txt | 1 + 20 files changed, 3079 insertions(+) create mode 100755 Arduino/bin/README_Load.txt create mode 100755 Arduino/bin/build.sh create mode 100755 Arduino/bin/load.sh create mode 100755 Arduino/ems/Clock.cpp create mode 100755 Arduino/ems/Clock.h create mode 100755 Arduino/ems/Common.h create mode 100755 Arduino/ems/Heater.h create mode 100755 Arduino/ems/Machine.cpp create mode 100755 Arduino/ems/Machine.h create mode 100755 Arduino/ems/Monitor.cpp create mode 100755 Arduino/ems/Monitor.h create mode 100755 Arduino/ems/Pump.h create mode 100755 Arduino/ems/SafetyThird.cpp create mode 100755 Arduino/ems/SafetyThird.h create mode 100755 Arduino/ems/TaskScheduler.h create mode 100755 Arduino/ems/Thermocouple.cpp create mode 100755 Arduino/ems/Thermocouple.h create mode 100755 Arduino/ems/ems.ino create mode 100755 Arduino/ems/keywords.h create mode 100755 Arduino/libraries/readme.txt (limited to 'Arduino') diff --git a/Arduino/bin/README_Load.txt b/Arduino/bin/README_Load.txt new file mode 100755 index 0000000..b465e2b --- /dev/null +++ b/Arduino/bin/README_Load.txt @@ -0,0 +1,38 @@ +Instructions for loading compiled binary images + from a raspberry pi to a feather M0. + +First, update your pi +sudo apt-get update && apt-get upgrade +sudo apt-get install build-essential +sudo apt-get install libreadline-dev wx2.8-headers libwxgtk2.8-0 libwxgtk2.8-dev + +next visit the page https://github.com/shumatech/BOSSA. + clone this repo and note the folder name. + +next follow the instructions on the page to make the bossac and bossash files + +next create a script some thing like what is below: + +----------------------------------------------------- +# load.sh - flash your arduino using a locally installed bosac + +# Project Variables +INO_NAME=ems +PROJECT_PATH=/home/pi/ems/Arduino + +# Local System variables +PORT=ttyACM0 +PACKAGES_PATH=/home/pi/BOSSA/bin +BUILD_PATH=$PROJECT_PATH/build + +# Arduino compile tools are here: +BOSSAC_PATH=$PACKAGES_PATH + +# load to the feather if possible +$BOSSAC_PATH/bossac -i -d --port=$PORT -U true -i -e -w -v $BUILD_PATH/$INO_NAME.ino.bin -R +------------------------------------------------------- + +Note that if you do not know the port of the device, you can remove the --port=PORT and bossac will attempt to find the port. + + + diff --git a/Arduino/bin/build.sh b/Arduino/bin/build.sh new file mode 100755 index 0000000..fecf63a --- /dev/null +++ b/Arduino/bin/build.sh @@ -0,0 +1,36 @@ +# build.sh - build the ems arduino code and flash it + +# Project Variables +INO_NAME=ems +PROJECT_PATH=/home/pi/ems/Arduino + +# Local System variables +PORT=ttyACM0 +ARDUINO_PATH=/home/pi/arduino-1.6.12 +PACKAGES_PATH=/home/pi/.arduino15/packages + +# Arduino compile tools are here: +TOOL_PATH=$PACKAGES_PATH/arduino/tools +HARDWARE_PATH=$PACKAGES_PATH/arduino/hardware +TOOLS="-tools $ARDUINO_PATH/tools-builder -tools $ARDUINO_PATH/hardware/tools/avr -tools $PACKAGES_PATH" +HARDWARE="-hardware $HARDWARE_PATH -hardware $PACKAGES_PATH" +BUILD_PATH=$PROJECT_PATH/build +CMSIS_PATH=$PACKAGES_PATH/arduino/tools/CMSIS/4.0.0-atmel +GCC_PATH=$PACKAGES_PATH/arduino/tools/arm-none-eabi-gcc/4.8.3-2014q1 +BOSSAC_PATH=$PACKAGES_PATH/arduino/tools/bossac/1.7.0 +OPENOCD_PATH=$PACKAGES_PATH/arduino/tools/openocd/0.9.0-arduino +SOURCE_PATH=$PROJECT_PATH/ + +ARDUINO=/home/pi/arduino-1.6.12/arduino-builder +TOOLS_BUILDER=/home/pi/arduino-1.6.12/tools-builder + +# make sure our build folder exists +mkdir -p $BUILD_PATH + +# compile and install +$ARDUINO -dump-prefs -logger=machine $HARDWARE $TOOLS -built-in-libraries $ARDUINO_PATH/libraries -libraries $PROJECT_PATH/libraries -fqbn=adafruit:samd:adafruit_feather_m0 -vid-pid=0X239A_0X800B -ide-version=10612 -build-path $BUILD_PATH -warnings=all -prefs=build.warn_data_percentage=75 -prefs=runtime.tools.CMSIS.path=$CMSIS_PATH -prefs=runtime.tools.arm-none-eabi-gcc.path=$GCC_PATH -prefs=runtime.tools.bossac.path=$BOSSAC_PATH -prefs=runtime.tools.openocd.path=$OPENOCD_PATH -verbose $PROJECT_PATH/$INO_NAME/$INO_NAME.ino +$ARDUINO -compile -logger=machine $HARDWARE $TOOLS -built-in-libraries $ARDUINO_PATH/libraries -libraries $PROJECT_PATH/libraries -fqbn=adafruit:samd:adafruit_feather_m0 -vid-pid=0X239A_0X800B -ide-version=10612 -build-path $BUILD_PATH -warnings=all -prefs=build.warn_data_percentage=75 -prefs=runtime.tools.CMSIS.path=$CMSIS_PATH -prefs=runtime.tools.arm-none-eabi-gcc.path=$GCC_PATH -prefs=runtime.tools.bossac.path=$BOSSAC_PATH -prefs=runtime.tools.openocd.path=$OPENOCD_PATH -verbose $PROJECT_PATH/$INO_NAME/$INO_NAME.ino + +# load to the feather if possible +$BOSSAC_PATH/bossac -i -d --port=$PORT -U true -i -e -w -v $BUILD_PATH/$INO_NAME.ino.bin -R + diff --git a/Arduino/bin/load.sh b/Arduino/bin/load.sh new file mode 100755 index 0000000..0446f55 --- /dev/null +++ b/Arduino/bin/load.sh @@ -0,0 +1,18 @@ +# load.sh - flash your arduino using a locally installed bosac + +# Project Variables +INO_NAME=ems +PROJECT_PATH=/home/pi/ems/Arduino + +# Local System variables +PORT=ttyACM0 +PACKAGES_PATH=/home/pi/BOSSA/bin +BUILD_PATH=$PROJECT_PATH/build + +# Arduino compile tools are here: +BOSSAC_PATH=$PACKAGES_PATH + +# load to the feather if possible +#$BOSSAC_PATH/bossac -i -d -U true -i -e -w -v $BUILD_PATH/$INO_NAME.ino.bin -R +$BOSSAC_PATH/bossac -i -d --port=$PORT -U true -i -e -w -v $BUILD_PATH/$INO_NAME.ino.bin -R + 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 +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 + +#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[?!:] + * 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 +#include +#include +#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=0 && ndxavailable() ){ +// 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=0 && ndx + 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 +#include + +// 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 + +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 + +#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 +#include +#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 +#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[?!:] +* 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 diff --git a/Arduino/libraries/readme.txt b/Arduino/libraries/readme.txt new file mode 100755 index 0000000..96ce674 --- /dev/null +++ b/Arduino/libraries/readme.txt @@ -0,0 +1 @@ +For information on installing libraries, see: http://www.arduino.cc/en/Guide/Libraries -- cgit v1.2.3