From 09fc6b732af6fa6a286263c2d7d7f449014bc4d7 Mon Sep 17 00:00:00 2001 From: D Delmar Davis Date: Sat, 3 Mar 2018 22:33:10 -0800 Subject: Cleanup for platformio migration --- src/Clock.cpp | 217 ++++++++++++++ src/Clock.h | 133 +++++++++ src/Configuration.h | 65 +++++ src/Machine.cpp | 148 ++++++++++ src/Machine.h | 72 +++++ src/Monitor.cpp | 312 ++++++++++++++++++++ src/Monitor.h | 226 ++++++++++++++ src/SafetyThird.cpp | 47 +++ src/SafetyThird.h | 198 +++++++++++++ src/TaskScheduler.h | 824 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/ems-light.ino | 3 + src/keywords.h | 122 ++++++++ 12 files changed, 2367 insertions(+) create mode 100755 src/Clock.cpp create mode 100755 src/Clock.h create mode 100755 src/Configuration.h create mode 100755 src/Machine.cpp create mode 100755 src/Machine.h create mode 100755 src/Monitor.cpp create mode 100755 src/Monitor.h create mode 100755 src/SafetyThird.cpp create mode 100755 src/SafetyThird.h create mode 100755 src/TaskScheduler.h create mode 100755 src/ems-light.ino create mode 100755 src/keywords.h (limited to 'src') diff --git a/src/Clock.cpp b/src/Clock.cpp new file mode 100755 index 0000000..4f6431d --- /dev/null +++ b/src/Clock.cpp @@ -0,0 +1,217 @@ +/*-------------------------------------------------------------------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. +#ifdef ARDUINO_SAMD_ZERO +RTCZero rtc; +#endif +/* 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/src/Clock.h b/src/Clock.h new file mode 100755 index 0000000..690d72a --- /dev/null +++ b/src/Clock.h @@ -0,0 +1,133 @@ +/*---------------------------------------------------------------------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 "Configuration.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. +#ifdef ARDUINO_SAMD_ZERO +#include +extern RTCZero rtc; +#endif + +// 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/src/Configuration.h b/src/Configuration.h new file mode 100755 index 0000000..822846d --- /dev/null +++ b/src/Configuration.h @@ -0,0 +1,65 @@ +/*------------------------------------------------------------------ + * 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 SAFETY_LAMP_PIN 13 +// +// Define E_STOP_PIN to enable estop switch +// +//#define E_STOP_PIN x +#ifndef E_STOP_PULLED +#define E_STOP_PULLED 1 +#endif + +// +// Define LIGHT_CURTAIN pins to enable light curtain +// +//#define LIGHT_CURTAIN_OSDD_PIN 5 +//#define LIGHT_CURTAIN_START_PIN 6 +#define LIGHT_CURTAIN_START LOW +#define LIGHT_CURTAIN_STOP HIGH +#define LIGHT_CURTAIN_OK LOW +#define LIGHT_CURTAIN_NOT_CLEAR HIGH + +// +// Define K1_PIN if konactor is connected to machine +// +//#define K1_PIN x + +// +#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 MONITOR_UART Serial +#define MONITOR_BAUD 9600 + +#define FAKE_A_FLOAT(_f) int((_f)), int(abs((_f) - int(_f))*100) + +#endif diff --git a/src/Machine.cpp b/src/Machine.cpp new file mode 100755 index 0000000..1f9e8a5 --- /dev/null +++ b/src/Machine.cpp @@ -0,0 +1,148 @@ +#include "Machine.h" +#include "Monitor.h" +#include "Clock.h" +#include "SafetyThird.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(); + 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: + if (ENTERING) { + safety.redLightVal=RED_FULL_ON;//fix number foo + } else { + machine.setState(STATE_RUN_MODE); + } + break; + case STATE_RUN_MODE: + if (ENTERING) { + 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: + if (ENTERING) { + safety.redLightVal=255;//fix number foo + } else { + machine.setState(STATE_RUN_MODE); + } + break; + 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) { + // + monitor.log("-- Job (Re)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/src/Machine.h b/src/Machine.h new file mode 100755 index 0000000..db7c9fd --- /dev/null +++ b/src/Machine.h @@ -0,0 +1,72 @@ +#ifndef Machine_h +#define Machine_h +#include "Configuration.h" +#include "Monitor.h" +#include "TaskScheduler.h" +// 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 " + +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/src/Monitor.cpp b/src/Monitor.cpp new file mode 100755 index 0000000..fd77089 --- /dev/null +++ b/src/Monitor.cpp @@ -0,0 +1,312 @@ +/*---------------------------------------------------------------------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 +#ifndef MAGIC_KEY +#define MAGIC_KEY 0x7777 +#endif +#ifndef MAGIC_KEY_POS +#define MAGIC_KEY_POS 0x0800 +#endif +void jump2bootloader(){ + int16_t magic_key_pos = MAGIC_KEY_POS; + *(uint16_t *)magic_key_pos = MAGIC_KEY; + wdt_enable(WDTO_120MS); + for(;;); +} +void reboot(){ + int16_t magic_key_pos = MAGIC_KEY_POS; + *(uint16_t *)magic_key_pos = 0; + wdt_enable(WDTO_120MS); + for(;;); + +} +#endif + + + + + + diff --git a/src/Monitor.h b/src/Monitor.h new file mode 100755 index 0000000..2c01c65 --- /dev/null +++ b/src/Monitor.h @@ -0,0 +1,226 @@ +/*---------------------------------------------------------------------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 +*/ + +#ifndef Monitor_h +#define Monitor_h + +#include "Configuration.h" +class Monitor; +extern Monitor monitor; +#include "Machine.h" + +void jump2bootloader(); +void reboot(); + + +#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 *); + +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: + 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) { + if (verb=='!') { + monitor.warn("Rebooting!"); + MONITOR_UART.end(); + reboot(); + } else { + monitor.update("NAK","RST Requires verb!"); + } + } + + static void BLD(uint8_t kwIndex, uint8_t verb, char *args) { + if (verb=='!') { + monitor.warn("Jumping to Bootloader!"); + jump2bootloader(); + } else { + monitor.update("NAK","BLD Requires verb!"); + } + } + + // 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/src/SafetyThird.cpp b/src/SafetyThird.cpp new file mode 100755 index 0000000..180edd2 --- /dev/null +++ b/src/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/src/SafetyThird.h b/src/SafetyThird.h new file mode 100755 index 0000000..3b318a5 --- /dev/null +++ b/src/SafetyThird.h @@ -0,0 +1,198 @@ +/*-----------------------------------------------------------------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 "Configuration.h" +#include "Monitor.h" + +class SafetyThird; +extern SafetyThird safety; +#define RED_FULL_ON (255 * (1 << (ANALOG_WRITE_RESOLUTION - 8))) +#define RED_OFF 0 + +// 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; + // put the contactor variables back here + // mebby look at the light curtain as well + Task _doDoRunRun; + +public: + + bool _lightCurtain; + bool _lightCurtainReset; + +#ifdef LIGHT_CURTAIN_START_PIN + static void LCS(uint8_t kwIndex, uint8_t verb, char *args) { + // + // set logic here + // + monitor.update("LCS","%s",safety._lightCurtain?"ON":"OFF"); + safety.first(); + } + + static void LCR(uint8_t kwIndex, uint8_t verb, char *args) { + if (verb=='!') { + safety._lightCurtainReset = (atoi(args)); + digitalWrite(LIGHT_CURTAIN_START_PIN,safety._lightCurtainReset); + } + safety.first(); + monitor.update("LCR","%s",safety._lightCurtainReset?"ON":"OFF"); + } +#endif + + static void run() {safety.first();} + + void first () { // could also be called paranoia. + +#ifdef E_STOP_PIN + _eStop = (digitalRead(E_STOP_PIN)==E_STOP_PULLED ); +#else + _eStop = false; +#endif + +#ifdef LIGHT_CURTAIN_OSDD_PIN + _lightCurtain = (digitalRead(LIGHT_CURTAIN_OSDD_PIN) == LIGHT_CURTAIN_NOT_CLEAR); +#else + _lightCurtain = false; +#endif + if ((machine.inSafeStateToRun) + && (_eStop) + ) + { + monitor.update("STP","1 ESTOP Pushed: Initiating Emergency Stop"); + machine.setState(STATE_E_SHUTDOWN); + } + if (_eStop!=_prevEStop) { + _eStop = _prevEStop; + if (_eStop){ + // probably should warn or alert! + monitor.update("STP","1 ESTOP Pushed: Contactors are OFF"); +#ifdef K1_PIN + digitalWrite(K1_PIN,KONTACTOR_OFF); +#endif + } else { + monitor.update("STP","0 E-STOP Released: Contactors are ON"); +#ifdef K1_PIN + digitalWrite(K1_PIN,KONTACTOR_ON); +#endif + } + } + // + // this is rough... Fix when there is hardware to test with + // + if(_lightCurtain!=_prevLightCurtain) { + if (_lightCurtain) { +#ifdef K1_PIN + digitalWrite(K1_PIN,KONTACTOR_OFF); +#endif + } else { +#ifdef K1_PIN + digitalWrite(K1_PIN,KONTACTOR_OFF); +#endif≄ + } + monitor.update("LCS","%s",_lightCurtain?"ON":"OFF"); + } + _prevLightCurtain=_lightCurtain; + _prevEStop=_eStop; + } + uint redLightVal; // red led value prolly bad style + + void redLight(int newVal) { + if(newVal!=redLightVal) { + redLightVal=newVal; + monitor.update("RED","%d",redLightVal); + } +#ifdef SAFETY_LAMP_PIN + analogWrite(SAFETY_LAMP_PIN, safety.redLightVal * (1 << (ANALOG_WRITE_RESOLUTION - 8) ) ); +#endif + } + + void init() { +#ifdef E_STOP_PIN + pinMode (E_STOP_PIN, INPUT_PULLUP); +#endif + + // Might move this to machine or other more generic place. + // http://www.arduino.org/learning/reference/analogwriteresolution + analogWriteResolution(ANALOG_WRITE_RESOLUTION); +#ifdef LIGHT_CURTAIN_RESET_PIN + _lightCurtainReset = true; + digitalWrite(LIGHT_CURTAIN_START_PIN,LIGHT_CURTAIN_START); +#else + _lightCurtainReset = false; +#endif + _prevLightCurtain=false; + _prevEStop=false; + + redLight(redLightVal=RED_OFF); + _doDoRunRun.set(77L, TASK_FOREVER, &run); + machine.todolist.addTask(_doDoRunRun); + _doDoRunRun.enable(); + + monitor.registerAction(_RED_, &RED); + } +}; + +static void RED (uint8_t kwIndex, uint8_t verb,char *args) { + // monitor update? + safety.redLight(atol(args)); + +} + + +#endif + diff --git a/src/TaskScheduler.h b/src/TaskScheduler.h new file mode 100755 index 0000000..89ae8a2 --- /dev/null +++ b/src/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/src/ems-light.ino b/src/ems-light.ino new file mode 100755 index 0000000..26bc988 --- /dev/null +++ b/src/ems-light.ino @@ -0,0 +1,3 @@ +/* + * empty placeholder to keep the arduino preprocessor away from any real code + */ diff --git a/src/keywords.h b/src/keywords.h new file mode 100755 index 0000000..1cf39e1 --- /dev/null +++ b/src/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 "\ +"CMD[!:?]Command Mode "\ +"NOW[!:?]Time as an int "\ +"TIM[!:?]Time for humans "\ +"MLD[!:?]Enable Multiline"\ +"SOD[!: ]Start Multiline "\ +"EOD[!: ]End Multiline "\ +"RED[!: ]Led (0-255) "\ +"AMT[ :?]Ambient Temp "\ +"AMH[ :?]Ambient Humidity"\ +"PX1[ :?]Proximity Sense "\ +"AD1[ :?]Analog Input 1 "\ +"AD2[ :?]Analog Input 2 "\ +"AD3[ :?]Analog Input 3 "\ +"AD4[ :?]Analog Input 4 "\ +"AD5[ :?]Analog Input 5 "\ +"AD6[ :?]Analog Input 6 "\ +"AD7[ :?]Analog Input 7 "\ +"AD8[ :?]Analog Input 8 "\ +"DI1[ :?]Digital Input 1 "\ +"DI2[ :?]Digital Input 2 "\ +"DI3[ :?]Digital Input 3 "\ +"DI4[ :?]Digital Input 4 "\ +"DI5[ :?]Digital Input 5 "\ +"DI6[ :?]Digital Input 6 "\ +"DI7[ :?]Digital Input 7 "\ +"DI8[ :?]Digital Input 8 "\ +"DO1[!:?]Digital Output 1"\ +"DO2[!:?]Digital Output 2"\ +"DO3[!:?]Digital Output 3"\ +"DO4[!:?]Digital Output 4"\ +"DO5[!:?]Digital Output 5"\ +"DO6[!:?]Digital Output 6"\ +"DO7[!:?]Digital Output 7"\ +"DO8[!:?]Digital Output 8"\ +"TMS[!:?]Timer Set "\ +"TMR[!:?]Timer Run(ing) "\ +"TRM[!:?]Time Remaining "\ +"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" "CMD" "NOW" "TIM" "MLD" "SOD" \ +"EOD" "RED" "AMT" "AMH" "PX1" "AD1" "AD2" "AD3" "AD4" "AD5" "AD6" "AD7" "AD8" \ +"DI1" "DI2" "DI3" "DI4" "DI5" "DI6" "DI7" "DI8" "DO1" "DO2" "DO3" "DO4" "DO5" \ +"DO6" "DO7" "DO8" "TMS" "TMR" "TRM" "NOP" + + +enum keywordIndex { +_SYN_,_ACK_,_NAK_,_SWV_,_HWV_,_GIT_,_MEM_,_SSN_,_HLP_,_FTL_,_ALT_,_WAR_,_INF_, +_DBG_,_LOG_,_STC_,_DVL_,_LVL_,_RST_,_BLD_,_STP_,_CMD_,_NOW_,_TIM_,_MLD_,_SOD_, +_EOD_,_RED_,_AMT_,_AMH_,_PX1_,_AD1_,_AD2_,_AD3_,_AD4_,_AD5_,_AD6_,_AD7_,_AD8_, +_DI1_,_DI2_,_DI3_,_DI4_,_DI5_,_DI6_,_DI7_,_DI8_,_DO1_,_DO2_,_DO3_,_DO4_,_DO5_, +_DO6_,_DO7_,_DO8_,_TMS_,_TMR_,_TRM_,_NOP_}; + +// use enum to determine the size of the keyword arrays. +#define NKEYWORDS _NOP_ + 1 + +#endif -- cgit v1.2.3