From 20347e7d1bd7e9ae0ce9dc3e67d4c213fda6ee14 Mon Sep 17 00:00:00 2001 From: StefanStrobel <> Date: Sat, 10 Aug 2019 12:48:05 +0000 Subject: [PATCH] ArduCounter: new firmware source git-svn-id: https://svn.fhem.de/fhem/trunk@19975 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/contrib/arduino/ArduCounter3.30.ino | 1758 ++++++++++++++++++++++ 1 file changed, 1758 insertions(+) create mode 100755 fhem/contrib/arduino/ArduCounter3.30.ino diff --git a/fhem/contrib/arduino/ArduCounter3.30.ino b/fhem/contrib/arduino/ArduCounter3.30.ino new file mode 100755 index 000000000..0bf4680d1 --- /dev/null +++ b/fhem/contrib/arduino/ArduCounter3.30.ino @@ -0,0 +1,1758 @@ +/* + * Sketch for counting impulses in a defined interval + * e.g. for power meters with an s0 interface that can be + * connected to an input of an arduino or esp8266 board + * + * the sketch uses pin change interrupts which can be anabled + * for any of the inputs on e.g. an arduino uno, jeenode, wemos d1 etc. + * + * the pin change Interrupt handling for arduinos used here + * is based on the arduino playground example on PCINT: + * http://playground.arduino.cc/Main/PcInt which is outdated. + * + * see https://github.com/GreyGnome/EnableInterrupt for a newer library (not used here) + * and also + * https://playground.arduino.cc/Main/PinChangeInterrupt + * http://www.avrfreaks.net/forum/difference-between-signal-and-isr + * + * Refer to avr-gcc header files, arduino source and atmega datasheet. + */ + +/* Arduino Uno / Nano Pin to interrupt map: + * D0-D7 = PCINT 16-23 = PCIR2 = PD = PCIE2 = pcmsk2 + * D8-D13 = PCINT 0-5 = PCIR0 = PB = PCIE0 = pcmsk0 + * A0-A5 (D14-D19) = PCINT 8-13 = PCIR1 = PC = PCIE1 = pcmsk1 + */ + +/* test cmds analog ESP: + * 20v Verbose + * 17,3,0,50a A0, rising, no Pullup, MinLen 50 + * 15,25t Level Diff Thresholds + * + * for ESP with D5 falling pullup 30 + * 5,2,1,30a + * 20v + * 10,20,1,1i + */ + +/* + Changes: + V1.2 + 27.10.16 - use noInterrupts in report() + - avoid reporting very short timeDiff in case of very slow impulses after a report + - now reporting is delayed if impulses happened only within in intervalSml + - reporting is also delayed if less than countMin pulses counted + - extend command "int" for optional intervalSml and countMin + 29.10.16 - allow interval Min >= Max or Sml > Min + which changes behavior to take fixed calculation interval instead of timeDiff between pulses + -> if intervalMin = intervalMax, counting will allways follow the reporting interval + 3.11.16 - more noInterrupt blocks when accessing the non uint8_t volatiles in report + V1.3 + 4.11.16 - check min pulse width and add more output, + - prefix show output with M + V1.4 + 10.11.16 - restructure add Cmd + - change syntax for specifying minPulseLengh + - res (reset) command + V1.6 + 13.12.16 - new startup message logic?, newline before first communication? + 18.12.16 - replace all code containing Strings, new communication syntax and parsing from Jeelink code + V1.7 + 2.1.17 - change message syntax again, report time as well, first and last impulse are reported + relative to start of intervall not start of reporting intervall + V1.8 + 4.1.17 - fixed a missing break in the case statement for pin definition + 5.1.17 - cleanup debug logging + 14.10.17 - fix a bug where last port state was not initialized after interrupt attached but this is necessary there + 23.11.17 - beautify code, add comments, more debugging for users with problematic pulse creation devices + 28.12.17 - better reportung of first pulse (even if only one pulse and countdiff is 0 but realdiff is 1) + 30.12.17 - rewrite PCInt, new handling of min pulse length, pulse history ring + 1.1.18 - check len in add command, allow pin 8 and 13 + 2.1.18 - add history per pin to report line, show negative starting times in show history + 3.1.18 - little reporting fix (start pos of history report) + + V2.0 + 17.1.18 - rewrite many things - use pin number instead of pcIntPinNumber as index, split interrupt handler for easier porting to ESP8266, ... + V2.23 + 10.2.18 - new commands for check alive and quit, send setup message after reboot also over tcp + remove reporting time of first pulse (now we hava history) + remove pcIntMode (is always change now) + pulse min interval is now always checked and defaults to 2 if not set + march 2018 many changes more to support ESP8266 + 7.3.18 - change pin config output, fix pullup (V2.26), store config in eeprom and read it back after boot + 22.4.18 - many changes, delay report if tcp mode and disconnected, verbose levels, ... + 13.5.18 - V2.36 Keepalive also on Arduino side + 9.12.18 - V3.0 start implementing analog input for old ferraris counters + 6.1.19 - V3.1 showIntervals in hello + 19.1.19 - V3.12 support for ESP with analog + 24.2.19 - V3.13 fix internal pin to GPIO mapping (must match ISR functions) when ESP8266 and analog support + - V3.14 added return of devVerbose upon startup + 27.6.19 - V3.20 replace timeNextReport with lastReportCall to avoid problem with data tyoes on ESP + fix a bug with analog counting on the ESP + 20.7.19 - nicer debug output for analog leves + 21.7.19 - V3.30 replace delay during analog read with millis() logic, optimize waiting times for analog read + 10.8.19 - V3.32 add ICACHE_RAM_ATTR for ISRs and remove remaining long casts (bug) when handling time + + ToDo / Ideas: + make analogInterval available in Fhem + save analogInterval to Flash + detect analog Threasholds automatically and adjust over time + make wifi ssid / secret configurable + +*/ + +/* Remove this before compiling */ +/* #define TestConfig */ + + +/* allow printing of every pin change to Serial */ +#define debugPins 1 + +/* allow tracking of pulse lengths */ +#define pulseHistory 1 + +/* support analog input for ferraris counters with IR light hardware */ +#define analogIR 1 + +/* use a sample config at boot */ +// #define debugCfg 1 + +#include "pins_arduino.h" +#include + +const char versionStr[] PROGMEM = "ArduCounter V3.32"; + +#define SERIAL_SPEED 38400 +#define MAX_INPUT_NUM 8 + +#ifdef analogIR +int sensorValueOff = 0; // value read from the photo transistor when ir LED is off +int sensorValueOn = 0; // value read from the photo transistor when ir LED is on +int analogThresholdMin = 100; // min value of analog input +int analogThresholdMax = 110; // max value of analog input +uint32_t lastAnalogRead; // millis() at last analog read +uint16_t analogReadInterval = 20; // interval at which to read analog values +uint8_t analogReadState = 0; // to keep track of switching LED on/off + +uint8_t triggerState; // todo: use existing arrays instead + +// save measurement during same level as sum and count to get average and then put in history when doCount is called +// but how do we do this before we can detect the levels? + +#endif + +#ifdef ESP8266 // ESP variables and definitions +#include // ============================= + +#ifdef TestConfig +#include "ArduCounterTestConfig.h" +#else +const char* ssid = "MySSID"; +const char* password = "secret"; +#endif + +WiFiServer Server(80); // For ESP WiFi connection +WiFiClient Client1; // active TCP connection +WiFiClient Client2; // secound TCP connection to send reject message +boolean Client1Connected; // remember state of TCP connection +boolean Client2Connected; // remember state of TCP connection + +boolean tcpMode = false; +uint8_t delayedTcpReports = 0; // how often did we already delay reporting because tcp disconnected +uint32_t lastDelayedTcpReports = 0; // last time we delayed + +#define MAX_HIST 20 // 20 history entries for ESP boards (can be increased) + +#ifdef analogIR // code for ESP with analog pin and reflection light barrier support (test) + +#define MAX_APIN 18 +#define MAX_PIN 9 + +/* ESP8266 pins that are typically ok to use + * (some might be set to -1 (disallowed) because they are used + * as reset, serial, led or other things on most boards) + * maps printed pin numbers (aPin) to sketch internal index numbers */ +short allowedPins[MAX_APIN] = // ESP 8266 with analog: + { 0, 1, 2, -1, // printed pin numbers 0,1,2 are ok to be used + -1, 5, -1, -1, // printed pin number 5 is ok to be used + -1, -1, -1, -1, // 8-11 not avaliable + -1, -1, -1, -1, // 12-15 not avaliable + -1, 8 }; // 16 not available, 17 is analog + +/* Wemos / NodeMCU Pins 3,4 and 8 (GPIO 0,2 and 15) define boot mode and therefore + * can not be used to connect to signal */ + +/* Map from sketch internal pin index to real chip IO pin number (not aPin, e.g. for ESP) + Note that the internal numbers might be different from the printed + pin numbers (e.g. pin 0 is in index 0 but real chip pin number 16! */ +short internalPins[MAX_PIN] = + { D0, D1, D2, D3, // map from internal pin Index to + D4, D5, D6, D7, // real GPIO pin numbers / defines + A0 }; // D0=16, D1=5, D2=4, D5=14, A0=17 + + + +uint8_t analogPins[MAX_PIN] = + { 0,0,0,0,0,0,0,0,1 }; // ESP pin A0 (pinIndex 8, internal 17) is analog + +const int analogInPin = A0; // Analog input pin that the photo transistor is attached to (internally number 17) +const int irOutPin = D6; // Digital output pin that the IR-LED is attached to +const int ledOutPin = D7; // Signal LED output pin + +#else // code for ESP without analog pin and reflection light barrier support + + +#define MAX_APIN 8 +#define MAX_PIN 8 + +/* ESP8266 pins that are typically ok to use + * (some might be set to -1 (disallowed) because they are used + * as reset, serial, led or other things on most boards) + * maps printed pin numbers to sketch internal index numbers */ +short allowedPins[MAX_APIN] = // ESP 8266 without analog: + { 0, 1, 2, -1, // printed pin numbers 0,1,2 are ok to be used, 3 not + -1, 5, 6, 7}; // printed pin numbers 5-7 are ok to be used, 4 not, >8 not + +/* Wemos / NodeMCU Pins 3,4 and 8 (GPIO 0,2 and 15) define boot mode and therefore + * can not be used to connect to signal */ + +/* Map from sketch internal pin index to real chip IO pin number (not aPin, e.g. for ESP) + Note that the internal numbers might be different from the printed + pin numbers (e.g. pin 0 is in index 0 but real chip pin number 16! */ +short internalPins[MAX_PIN] = + { D0, D1, D2, D3, // printed pin numbers 0, 1, 2, 3 (3 should not be used and could be removed here) + D5, D5, D6, D7}; // printed pin numbers 4, 5, 6, 7 (4 should not be used and could be removed here) + // D0=16, D1=5, D2=4, D5=14, A0=17, ... + +#endif // end of ESP section without analog reading + + + + +#else // Arduino Uno or Nano variables and definitions + // ============================================= + +#define MAX_HIST 20 // 20 history entries for arduino boards + +/* arduino pins that are typically ok to use + * (some might be set to -1 (disallowed) because they are used + * as reset, serial, led or other things on most boards) + * maps printed pin numbers to sketch internal index numbers */ + +#ifdef analogIR + +/* 2 is used for IR out, 12 for signal, A7 for In */ +#define MAX_APIN 22 +#define MAX_PIN 18 + +short allowedPins[MAX_APIN] = + {-1, -1, -1, 0, /* arduino pin 0 - 3 to internal index or -1 if pin is reserved */ + 1, 2, 3, 4, /* arduino pin 4 - 7 to internal index */ + 5, 6, 7, 8, /* arduino pin 8 - 11 to internal index */ + -1, 9, 10, 11, /* arduino pin 12, 13, A0, A1 / 14, 15 to internal index or -1 if pin is reserved*/ + 12, 13, 14, 15, /* arduino pin A2 - A5 / 16 - 19 to internal index */ + 16, 17 }; /* arduino pin A6, A7 to internal index */ + +/* Map from sketch internal pin index to real chip IO pin number */ +short internalPins[MAX_PIN] = + { 3, 4, 5, 6, /* index 0 - 3 map to pins 3 - 6 */ + 7, 8, 9, 10, /* index 4 - 7 map to pins 7 - 10 */ + 11, 12, 14, 15, /* index 8 - 11 map to pins 11,13, A0 - A1 */ + 16, 17, 18, 19, /* index 12 - 15 map to pins A2 - A5 */ + 20, 21 }; /* index 16 - 17 map to pin A6, A7 */ + +uint8_t analogPins[MAX_PIN] = + { 0,0,0,0, /* everything except Arduino A7 (pinIndex 17, internal 21) is digital by default */ + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,1 }; + +const int analogInPin = A7; // Arduino analog input pin that the photo transistor is attached to (internal 21) +const int irOutPin = 2; // Digital output pin that the IR-LED is attached to +const int ledOutPin = 12; // Signal LED output pin + + +#else +/* no analog IR support -> all Nano pins including analog available für digital counting */ + +#define MAX_APIN 22 +#define MAX_PIN 20 +short allowedPins[MAX_APIN] = + {-1, -1, 0, 1, /* arduino pin 0 - 3 to internal Pin index or -1 if pin is reserved */ + 2, 3, 4, 5, /* arduino pin 4 - 7 to internal Pin index or -1 if pin is reserved */ + 6, 7, 8, 9, /* arduino pin 8 - 11 to internal Pin index or -1 if pin is reserved */ + 10, 11, 12, 13, /* arduino pin 12, 13, A0, A1 to internal Pin index or -1 if pin is reserved */ + 14, 15, 16, 17, /* arduino pin A2 - A5 / 16 - 19 to internal Pin index or -1 if pin is reserved */ + 18, 19 }; /* arduino pin A6, A7 to internal Pin index or -1 if pin is reserved */ + +/* Map from sketch internal pin index to real chip IO pin number */ +short internalPins[MAX_PIN] = + { 2, 3, 4, 5, /* index 0 - 3 map to pins 2 - 5 */ + 6, 7, 8, 9, /* index 4 - 7 map to pins 6 - 9 */ + 10, 11, 12, 13, /* index 8 - 11 map to pins 10 - 13 */ + 14, 15, 16, 17, /* index 12 - 15 map to pins A0 - A3 */ + 18, 19, 20, 21 }; /* index 16 - 19 map to pins A4 - A7 */ + +uint8_t analogPins[MAX_PIN] = + { 0,0,0,0, /* everything is digital by default */ + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0 }; + +#endif + +/* first and last pin at port PB, PC and PD for arduino uno/nano */ +uint8_t firstPin[] = {8, 14, 0}; // aPin -> allowedPins[] -> pinIndex +uint8_t lastPin[] = {13, 19, 7}; + +/* Pin change mask for each chip port on the arduino platform */ +volatile uint8_t *port_to_pcmask[] = { + &PCMSK0, + &PCMSK1, + &PCMSK2 +}; + +/* last PIN States at io port to detect individual pin changes in arduino ISR */ +volatile static uint8_t PCintLast[3]; + +#endif + + +Print *Output; // Pointer to output device (Serial / TCP connection with ESP8266) +uint32_t bootTime; +uint16_t bootWraps; // counter for millis wraps at last reset +uint16_t millisWraps; // counter to track when millis counter wraps +uint32_t lastMillis; // milis at last main loop iteration +uint8_t devVerbose; // >=10 shows pin changes, >=5 shows pin history + +#ifdef debugPins +uint8_t lastState[MAX_PIN]; // for debug output when a pin state changes +#endif + +uint32_t intervalMin = 30000; // default 30 sec - report after this time if nothing else delays it +uint32_t intervalMax = 60000; // default 60 sec - report after this time if it didin't happen before +uint32_t intervalSml = 2000; // default 2 secs - continue count if timeDiff is less and intervalMax not over +uint16_t countMin = 2; // continue counting if count is less than this and intervalMax not over + +uint32_t lastReportCall; +#ifdef ESP8266 +uint32_t expectK; +#endif + +/* index to the following arrays is the internal pin index number */ + +volatile boolean initialized[MAX_PIN]; // did we get first interrupt yet? +short activePin[MAX_PIN]; // printed arduino pin number for index if active - otherwise -1 +uint16_t pulseWidthMin[MAX_PIN]; // minimal pulse length in millis for filtering +uint8_t pulseLevel[MAX_PIN]; // start of pulse for measuring length - 0 / 1 as defined for each pin +uint8_t pullup[MAX_PIN]; // pullup configuration state + +volatile uint32_t counter[MAX_PIN]; // real pulse counter +volatile uint8_t counterIgn[MAX_PIN]; // ignored first pulse after init +volatile uint16_t rejectCounter[MAX_PIN]; // counter for rejected pulses that are shorter than pulseWidthMin +uint32_t lastCount[MAX_PIN]; // counter at last report (to get the delta count) +uint16_t lastRejCount[MAX_PIN]; // reject counter at last report (to get the delta count) + +volatile uint32_t lastChange[MAX_PIN]; // millis at last level change (for measuring pulse length) +volatile uint8_t lastLevel[MAX_PIN]; // level of input at last interrupt +volatile uint8_t lastLongLevel[MAX_PIN]; // last level that was longer than pulseWidthMin + +volatile uint32_t pulseWidthSum[MAX_PIN]; // sum of pulse lengths for average calculation +uint8_t reportSequence[MAX_PIN]; // sequence number for reports + + +#ifdef pulseHistory +volatile uint8_t histIndex; // pointer to next entry in history ring +volatile uint16_t histNextSeq; // next seq number to use +volatile uint16_t histSeq[MAX_HIST]; // history sequence number +volatile uint8_t histPin[MAX_HIST]; // pin for this entry +volatile uint8_t histLevel[MAX_HIST]; // level for this entry +volatile uint32_t histTime[MAX_HIST]; // time for this entry +volatile uint32_t histLen[MAX_HIST]; // time that this level was held +volatile char histAct[MAX_HIST]; // action (count, reject, ...) as one char +#endif + +volatile uint32_t intervalStart[MAX_PIN]; // start of an interval - typically set by first / last pulse +volatile uint32_t intervalEnd[MAX_PIN]; // end of an interval - typically set by first / last pulse +uint32_t lastReport[MAX_PIN]; // millis at last report to find out when maxInterval is over + +uint16_t commandData[MAX_INPUT_NUM]; // input data over serial port or network +uint8_t commandDataPointer = 0; // index pointer to next input value +uint16_t value; // the current value for input function + + +void initPinVars(short pinIndex, uint32_t now) { + uint8_t level = 0; + activePin[pinIndex] = -1; // inactive (-1) + initialized[pinIndex] = false; // no pulse seen yet + pulseWidthMin[pinIndex] = 0; // min pulse length + counter[pinIndex] = 0; // counter to 0 + counterIgn[pinIndex] = 0; + lastCount[pinIndex] = 0; + rejectCounter[pinIndex] = 0; + lastRejCount[pinIndex] = 0; + intervalStart[pinIndex] = now; // time vars + intervalEnd[pinIndex] = now; + lastChange[pinIndex] = now; + lastReport[pinIndex] = now; + reportSequence[pinIndex] = 0; +#ifdef analogIR + if (!analogPins[pinIndex]) { + level = digitalRead(internalPins[pinIndex]); + } +#else + level = digitalRead(internalPins[pinIndex]); +#endif + lastLevel[pinIndex] = level; +#ifdef debugPins + lastState[pinIndex] = level; // for debug output +#endif + /* todo: add analogPins, upper and lower limits for analog */ +} + + +void initialize() { + uint32_t now = millis(); + for (uint8_t pinIndex=0; pinIndex < MAX_PIN; pinIndex++) { + initPinVars(pinIndex, now); + } + bootTime = now; // with boot / reset time + bootWraps = millisWraps; + lastAnalogRead = now; + lastReportCall = now; // time for first output after intervalMin from now + devVerbose = 0; +#ifndef ESP8266 + for (uint8_t port=0; port <= 2; port++) { + PCintLast[port] = *portInputRegister(port+2); // current pin states at port for PCInt handler + } +#endif +#ifdef debugCfg + debugSetup(); +#endif + restoreFromEEPROM(); +#ifdef ESP8266 + expectK = now + 600000; // max 10 Minutes (to be checked on Fhem module side as well +#endif +} + + +/* + do counting and set start / end time of interval. + reporting is not triggered from here. + + only here counter[] is modified + intervalEnd[] is set here and in report + intervalStart[] is set in case a pin was not initialized yet and in report +*/ +static void inline doCount(uint8_t pinIndex, uint8_t level, uint32_t now) { + uint32_t len = now - lastChange[pinIndex]; + char act = ' '; + +#ifdef pulseHistory + histIndex++; + if (histIndex >= MAX_HIST) histIndex = 0; + histSeq[histIndex] = histNextSeq++; + histPin[histIndex] = pinIndex; + histTime[histIndex] = lastChange[pinIndex]; + histLen[histIndex] = len; + histLevel[histIndex] = lastLevel[pinIndex]; +#endif + if (len < pulseWidthMin[pinIndex]) { // pulse was too short + lastChange[pinIndex] = now; + if (lastLevel[pinIndex] == pulseLevel[pinIndex]) { // if change to gap level + rejectCounter[pinIndex]++; // inc reject counter and set action to R (pulse too short) + act = 'R'; + } else { + act = 'X'; // set action to X (gap too short) + } + } else { + if (lastLevel[pinIndex] != pulseLevel[pinIndex]) { // edge does fit defined pulse start, level is now pulse, before it was gap + act = 'G'; // now the gap is confirmed (even if inbetween was a spike that we ignored) + } else { // edge is a change to gap, level is now gap + if (lastLongLevel[pinIndex] != pulseLevel[pinIndex]) { // last remembered valid level was also gap -> now we had valid new pulse -> count + counter[pinIndex]++; // count + intervalEnd[pinIndex] = now; // remember time of in case pulse will be the last in the interval + if (!initialized[pinIndex]) { + intervalStart[pinIndex] = now; // if this is the very first impulse on this pin -> start interval now + initialized[pinIndex] = true; // and start counting the next impulse (so far counter is 0) + counterIgn[pinIndex]++; // count as to be ignored for diff because it defines the start of the interval + } + pulseWidthSum[pinIndex] += len; // for average calculation + act = 'C'; + } else { // last remembered valid level was a pulse -> now we had another valid pulse + pulseWidthSum[pinIndex] += len; // for average calculation + act = 'P'; // pulse was already counted, only short drop inbetween + } + } + lastLongLevel[pinIndex] = lastLevel[pinIndex]; // remember this valid level as lastLongLevel + } +#ifdef pulseHistory + histAct[histIndex] = act; +#endif + lastChange[pinIndex] = now; + lastLevel[pinIndex] = level; +} + + +/* Interrupt handlers and their installation + * on Arduino and ESP8266 platforms + */ + +#ifndef ESP8266 +/* Add a pin to be handled (Arduino code) */ +uint8_t AddPinChangeInterrupt(uint8_t rPin) { + volatile uint8_t *pcmask; // pointer to PCMSK0 or 1 or 2 depending on the port corresponding to the pin + uint8_t bitM = digitalPinToBitMask(rPin); // mask to bit in PCMSK to enable pin change interrupt for this arduino pin + uint8_t port = digitalPinToPort(rPin); // port that this arduno pin belongs to for enabling interrupts + if (port == NOT_A_PORT) + return 0; + port -= 2; // from port (PB, PC, PD) to index in our array + pcmask = port_to_pcmask[port]; // point to PCMSK0 or 1 or 2 depending on the port corresponding to the pin + *pcmask |= bitM; // set the pin change interrupt mask through a pointer to PCMSK0 or 1 or 2 + PCICR |= 0x01 << port; // enable the interrupt + return 1; +} + + +/* Remove a pin to be handled (Arduino code) */ +uint8_t RemovePinChangeInterrupt(uint8_t rPin) { + volatile uint8_t *pcmask; + uint8_t bitM = digitalPinToBitMask(rPin); + uint8_t port = digitalPinToPort(rPin); + if (port == NOT_A_PORT) + return 0; + port -= 2; // from port (PB, PC, PD) to index in our array + pcmask = port_to_pcmask[port]; + *pcmask &= ~bitM; // clear the bit in the mask. + if (*pcmask == 0) { // if that's the last one, disable the interrupt. + PCICR &= ~(0x01 << port); + } + return 1; +} + + +// now set the arduino interrupt service routines and call the common handler with the port index number +ISR(PCINT0_vect) { + PCint(0); +} +ISR(PCINT1_vect) { + PCint(1); +} +ISR(PCINT2_vect) { + PCint(2); +} + +/* + common function for arduino pin change interrupt handlers. "port" is the PCINT port index (0-2) as passed from above, not PB, PC or PD which are mapped to 2-4 +*/ +static void PCint(uint8_t port) { + uint8_t bit; + uint8_t curr; + uint8_t delta; + short pinIndex; + uint32_t now = millis(); + + // get the pin states for the indicated port. + curr = *portInputRegister(port+2); // current pin states at port (add 2 to get from index to PB, PC or PD) + delta = (curr ^ PCintLast[port]) & *port_to_pcmask[port]; // xor gets bits that are different and & screens out non pcint pins + PCintLast[port] = curr; // store new pin state for next interrupt + + if (delta == 0) return; // no handled pin changed + + bit = 0x01; // start mit rightmost (least significant) bit in a port + for (uint8_t aPin = firstPin[port]; aPin <= lastPin[port]; aPin++) { // loop over each pin on the given port that changed + if (delta & bit) { // did this pin change? + pinIndex = allowedPins[aPin]; + if (pinIndex > 0) { // shound not be necessary but test anyway + doCount (pinIndex, ((curr & bit) > 0), now); // do the counting, history and so on + } + } + bit = bit << 1; // shift mask to go to next bit + } +} + + +#else +/* Add a pin to be handled (ESP8266 code) */ + +/* attachInterrupt needs to be given an individual function for each interrrupt . + * since we cant pass the pin value into the ISR or we need to use an + * internal function __attachInnterruptArg ... but then we need a fixed reference for the pin numbers ... +*/ + +void ICACHE_RAM_ATTR ESPISR4() { // ISR for real pin GPIO 4 / pinIndex 2 + doCount(2, digitalRead(4), millis()); + // called with pinIndex, level, now +} + +void ICACHE_RAM_ATTR ESPISR5() { // ISR for real pin GPIO 5 / pinIndex 1 + doCount(1, digitalRead(5), millis()); +} + +void ICACHE_RAM_ATTR ESPISR12() { // ISR for real pin GPIO 12 / pinIndex 6 + doCount(6, digitalRead(12), millis()); +} + +void ICACHE_RAM_ATTR ESPISR13() { // ISR for real pin GPIO 13 / pinIndex 7 + doCount(7, digitalRead(13), millis()); +} + +void ICACHE_RAM_ATTR ESPISR14() {// ISR for real pin GPIO 14 / pinIndex 5 + doCount(5, digitalRead(14), millis()); +} + +void ICACHE_RAM_ATTR ESPISR16() { // ISR for real pin GPIO 16 / pinIndex 0 + doCount(0, digitalRead(16), millis()); +} + +uint8_t AddPinChangeInterrupt(uint8_t rPin) { + switch(rPin) { + case 4: + attachInterrupt(digitalPinToInterrupt(rPin), ESPISR4, CHANGE); + break; + case 5: + attachInterrupt(digitalPinToInterrupt(rPin), ESPISR5, CHANGE); + break; + case 12: + attachInterrupt(digitalPinToInterrupt(rPin), ESPISR12, CHANGE); + break; + case 13: + attachInterrupt(digitalPinToInterrupt(rPin), ESPISR13, CHANGE); + break; + case 14: + attachInterrupt(digitalPinToInterrupt(rPin), ESPISR14, CHANGE); + break; + case 16: + attachInterrupt(digitalPinToInterrupt(rPin), ESPISR16, CHANGE); + break; + default: + PrintErrorMsg(); Output->println(F("attachInterrupt")); + } + return 1; +} +#endif + + +void PrintErrorMsg() { + Output->print(F("Error: ")); +} + +void printVersionMsg() { + uint8_t len = strlen_P(versionStr); + char myChar; + for (unsigned char k = 0; k < len; k++) { + myChar = pgm_read_byte_near(versionStr + k); + Output->print(myChar); + } + Output->print(F(" on ")); +#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__) +#ifdef ARDUINO_AVR_NANO + Output->print(F("NANO")); +#else + Output->print(F("UNO")); +#endif +#elif defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega16U4__) + Output->print(F("Leonardo")); +#elif defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) + Output->print(F("Mega")); +#elif defined(ESP8266) + Output->print(F("ESP8266")); +#else + Output->print(F("UNKNOWN")); +#endif +#ifdef ARDUINO_BOARD + Output->print(F(" ")); + Output->print(F(ARDUINO_BOARD)); +#endif + Output->print(F(" compiled ")); + Output->print(F(__DATE__ " " __TIME__)); +#if defined(ESP8266) + Output->print(F(" with core version ")); + Output->print(F(ARDUINO_ESP8266_RELEASE)); +#endif +} + + +void showIntervals() { + Output->print(F("I")); + Output->print(intervalMin / 1000); + Output->print(F(" ")); + Output->print(intervalMax / 1000); + Output->print(F(" ")); + Output->print(intervalSml / 1000); + Output->print(F(" ")); + Output->println(countMin); +} + + +#ifdef analogIR +void showThresholds() { + Output->print(F("T")); + Output->print(analogThresholdMin); + Output->print(F(" ")); + Output->println(analogThresholdMax); +} +#endif + + +void showPinConfig(short pinIndex) { + Output->print(F("P")); + Output->print(activePin[pinIndex]); + switch (pulseLevel[pinIndex]) { + case 1: Output->print(F(" rising")); break; + case 0: Output->print(F(" falling")); break; + default: Output->print(F(" -")); break; + } + if (pullup[pinIndex]) + Output->print(F(" pullup")); + Output->print(F(" min ")); + Output->print(pulseWidthMin[pinIndex]); +} + +#ifdef pulseHistory +void showPinHistory(short pinIndex, uint32_t now) { + uint8_t hi; + uint8_t start = (histIndex + 2) % MAX_HIST; + uint8_t count = 0; + uint32_t last; + boolean first = true; + + for (uint8_t i = 0; i < MAX_HIST; i++) { + hi = (start + i) % MAX_HIST; + if (histPin[hi] == pinIndex) + if (first || (last <= histTime[hi]+histLen[hi])) count++; + } + if (!count) { + // Output->println (F("M No Pin History")); + return; + } + + Output->print (F("H")); // start with H + Output->print (activePin[pinIndex]); // printed pin number + Output->print (F(" ")); + for (uint8_t i = 0; i < MAX_HIST; i++) { + hi = (start + i) % MAX_HIST; + if (histPin[hi] == pinIndex) { + if (first || (last <= histTime[hi]+histLen[hi])) { + if (!first) Output->print (F(", ")); + Output->print (histSeq[hi]); // sequence + Output->print (F("s")); + Output->print ((long) (histTime[hi] - now)); // time when level started + Output->print (F("/")); + Output->print (histLen[hi]); // length + Output->print (F("@")); + Output->print (histLevel[hi]); // level (0/1) + Output->print (histAct[hi]); // action + first = false; + } + last = histTime[hi]; + } + } + Output->println(); +} +#endif + +/* + lastCount[] is only modified here (count at time of last reporting) + intervalEnd[] is modified here and in ISR - disable interrupts in critcal moments to avoid garbage in var + intervalStart[] is modified only here or for very first Interrupt in ISR +*/ +void showPinCounter(short pinIndex, boolean showOnly, uint32_t now) { + uint32_t count, countDiff, realDiff; + uint32_t startT, endT, timeDiff, widthSum; + uint16_t rejCount, rejDiff; + uint8_t countIgn; + + noInterrupts(); // copy counters while they cant be changed in isr + startT = intervalStart[pinIndex]; // start of interval (typically first pulse) + endT = intervalEnd[pinIndex]; // end of interval (last unless not enough) + count = counter[pinIndex]; // get current counter (counts all pulses + rejCount = rejectCounter[pinIndex]; + countIgn = counterIgn[pinIndex]; // pulses that mark the beginning of an interval + widthSum = pulseWidthSum[pinIndex]; + interrupts(); + + timeDiff = endT - startT; // time between first and last impulse + realDiff = count - lastCount[pinIndex]; // pulses during intervall + countDiff = realDiff - countIgn; // ignore forst pulse after device restart + rejDiff = rejCount - lastRejCount[pinIndex]; + + if (!showOnly) { // real reporting sets the interval borders new + if((now - lastReport[pinIndex]) >= intervalMax) { + // intervalMax is over + if ((countDiff >= countMin) && (timeDiff > intervalSml) && (intervalMin != intervalMax)) { + // normal procedure + noInterrupts(); // vars could be modified in ISR as well + intervalStart[pinIndex] = endT; // time of last impulse becomes first in next + interrupts(); + } else { + // nothing counted or counts happened during a fraction of intervalMin only + noInterrupts(); // vars could be modified in ISR as well + intervalStart[pinIndex] = now; // start a new interval for next report now + intervalEnd[pinIndex] = now; // no last impulse, use now instead + interrupts(); + timeDiff = now - startT; // special handling - calculation ends now + } + } else if (((now - lastReport[pinIndex]) >= intervalMin) + && (countDiff >= countMin) && (timeDiff > intervalSml)) { + // minInterval has elapsed and other conditions are ok + noInterrupts(); // vars could be modified in ISR as well + intervalStart[pinIndex] = endT; // time of last also time of first in next + interrupts(); + } else { + return; // intervalMin and Max not over - dont report yet + } + noInterrupts(); + counterIgn[pinIndex] = 0; + pulseWidthSum[pinIndex] = 0; + interrupts(); + lastCount[pinIndex] = count; // remember current count for next interval + lastRejCount[pinIndex] = rejCount; + lastReport[pinIndex] = now; // remember when we reported +#ifdef ESP8266 + delayedTcpReports = 0; +#endif + reportSequence[pinIndex]++; + } + Output->print(F("R")); // R Report + Output->print(activePin[pinIndex]); + Output->print(F(" C")); // C - Count + Output->print(count); + Output->print(F(" D")); // D - Count Diff (without pulse that marks the begin) + Output->print(countDiff); + Output->print(F("/")); // R - real Diff for long counter - includes first after restart + Output->print(realDiff); + Output->print(F(" T")); // T - Time + Output->print(timeDiff); + Output->print(F(" N")); // N - now + Output->print(now); + Output->print(F(",")); + Output->print(millisWraps); + Output->print(F(" X")); // X Reject + Output->print(rejDiff); + + if (!showOnly) { + Output->print(F(" S")); // S - Sequence number + Output->print(reportSequence[pinIndex]); + } + if (countDiff > 0) { + Output->print(F(" A")); + Output->print(widthSum / countDiff); + } + Output->println(); +#ifdef ESP8266 + if (tcpMode && !showOnly) { + Serial.print(F("D reported pin ")); + Serial.print(activePin[pinIndex]); + Serial.print(F(" sequence ")); + Serial.print(reportSequence[pinIndex]); + Serial.println(F(" over tcp ")); + } +#endif + +} + + +/* + report count and time for pins that are between min and max interval +*/ + +boolean reportDue() { + uint32_t now = millis(); + boolean doReport = false; // check if report needs to be called + if((now - lastReportCall) >= intervalMin) // works fine when millis wraps. + doReport = true; // intervalMin is over + else + for (uint8_t pinIndex=0; pinIndex < MAX_PIN; pinIndex++) + if (activePin[pinIndex] >= 0) + if((now - lastReport[pinIndex]) >= intervalMax) + doReport = true; // active pin has not been reported for langer than intervalMax + return doReport; +} + + + +void report() { + uint32_t now = millis(); +#ifdef ESP8266 + if (tcpMode && !Client1Connected && (delayedTcpReports < 3)) { + if(delayedTcpReports == 0 || ((long)(now - (lastDelayedTcpReports + (1 * 30 * 1000))) > 0)) { + Serial.print(F("D report called but tcp is disconnected - delaying (")); + Serial.print(delayedTcpReports); + Serial.print(F(")")); + Serial.print(F(" now ")); + Serial.print(now); + Serial.print(F(" last ")); + Serial.print(lastDelayedTcpReports); + Serial.print(F(" diff ")); + Serial.println(now - lastDelayedTcpReports); + delayedTcpReports++; + lastDelayedTcpReports = now; + return; + } else return; + } +#endif + + for (uint8_t pinIndex=0; pinIndex < MAX_PIN; pinIndex++) { // go through all observed pins as pinIndex + if (activePin[pinIndex] >= 0) { + showPinCounter (pinIndex, false, now); // report pin counters if necessary +#ifdef pulseHistory + if (devVerbose >= 5) + showPinHistory(pinIndex, now); // show pin history if verbose >= 5 +#endif + } + } + lastReportCall = now; // check again after intervalMin or if intervalMax is over for a pin +} + + +/* give status report in between if requested over serial input */ +void showCmd() { + uint32_t now = millis(); + Output->print(F("M Status: ")); + printVersionMsg(); + Output->println(); + + showIntervals(); +#ifdef analogIR + showThresholds(); +#endif + Output->print(F("V")); + Output->println(devVerbose); + + for (uint8_t pinIndex=0; pinIndex < MAX_PIN; pinIndex++) { + if (activePin[pinIndex] >= 0) { + showPinConfig(pinIndex); + Output->print(F(", ")); + showPinCounter(pinIndex, true, now); +#ifdef pulseHistory + showPinHistory(pinIndex, now); +#endif + } + } + showEEPROM(); + Output->print(F("M Next report in ")); + Output->print(lastReportCall + intervalMin - millis()); + Output->print(F(" milliseconds")); + Output->println(); +} + + +void helloCmd() { + uint32_t now = millis(); + Output->println(); + printVersionMsg(); + Output->print(F(" Hello, pins ")); + boolean first = true; + for (uint8_t aPin=0; aPin < MAX_APIN; aPin++) { + if (allowedPins[aPin] >= 0) { + if (!first) { + Output->print(F(",")); + } else { + first = false; + } + Output->print(aPin); + } + } + Output->print(F(" available")); + Output->print(F(" T")); + Output->print(now); + Output->print(F(",")); + Output->print(millisWraps); + Output->print(F(" B")); + Output->print(bootTime); + Output->print(F(",")); + Output->print(bootWraps); + + Output->println(); + showIntervals(); +#ifdef analogIR + showThresholds(); +#endif + Output->print(F("V")); + Output->println(devVerbose); + + for (uint8_t pinIndex=0; pinIndex < MAX_PIN; pinIndex++) { // go through all observed pins as pinIndex + if (activePin[pinIndex] >= 0) { + showPinConfig(pinIndex); + Output->println(); + } + } +} + + + +/* + handle add command. +*/ +void addCmd(uint16_t *values, uint8_t size) { + uint16_t pulseWidth; + uint32_t now = millis(); + + uint8_t aPin = values[0]; // values[0] is pin number + if (aPin >= MAX_APIN || allowedPins[aPin] < 0) { + PrintErrorMsg(); + Output->print(F("Illegal pin specification ")); + Output->println(aPin); + return; + }; + uint8_t pinIndex = allowedPins[aPin]; + uint8_t rPin = internalPins[pinIndex]; + + if (activePin[pinIndex] != aPin) { // in case this pin is not already active counting + #ifndef ESP8266 + uint8_t port = digitalPinToPort(rPin) - 2; + PCintLast[port] = *portInputRegister(port+2); + #endif + initPinVars(pinIndex, now); + activePin[pinIndex] = aPin; // save arduino pin number and flag this pin as active for reporting + } + + if (values[1] < 2 || values[1] > 3) { // values[1] is level (3->rising / 2->falling) + PrintErrorMsg(); + Output->print(F("Illegal pulse level specification for pin ")); + Output->println(aPin); + } + pulseLevel[pinIndex] = (values[1] == 3); // 2 = falling -> pulseLevel 0, 3 = rising -> pulseLevel 1 + +#ifdef analogIR + if (size > 2 && values[2] && !analogPins[pinIndex]) { +#else + if (size > 2 && values[2]) { +#endif + pinMode (rPin, INPUT_PULLUP); // values[2] is pullup flag + pullup[pinIndex] = 1; + } else { + pinMode (rPin, INPUT); + pullup[pinIndex] = 0; + } + + if (size > 3 && values[3] > 0) { // value 3 is min length + pulseWidth = values[3]; + } else { + pulseWidth = 2; + } + + /* todo: add upper and lower limits for analog pins as option here and in Fhem module */ + + pulseWidthMin[pinIndex] = pulseWidth; + +#ifdef analogIR + if (!analogPins[pinIndex]) { +#endif + if (!AddPinChangeInterrupt(rPin)) { // add Pin Change Interrupt + PrintErrorMsg(); + Output->println(F("AddInt")); + return; + } +#ifdef analogIR + } +#endif + Output->print(F("M defined ")); + showPinConfig(pinIndex); + Output->println(); +} + + +/* + handle rem command. +*/ +void removeCmd(uint16_t *values, uint8_t size) { + uint8_t aPin = values[0]; + if (size < 1 || aPin >= MAX_APIN || allowedPins[aPin] < 0) { + PrintErrorMsg(); + Output->print(F("Illegal pin specification ")); + Output->println(aPin); + return; + }; + uint8_t pinIndex = allowedPins[aPin]; + +#ifdef analogIR + if (!analogPins[pinIndex]) { +#endif +#ifdef ESP8266 + detachInterrupt(digitalPinToInterrupt(internalPins[pinIndex])); +#else + if (!RemovePinChangeInterrupt(internalPins[pinIndex])) { + PrintErrorMsg(); + Output->println(F("RemInt")); + return; + } +#endif +#ifdef analogIR + } +#endif + initPinVars(pinIndex, 0); + Output->print(F("M removed ")); + Output->println(aPin); +} + + + +void intervalCmd(uint16_t *values, uint8_t size) { + /*Serial.print(F("D int ptr is ")); + Serial.println(size);*/ + if (size < 4) { // i command always gets 4 values: min, max, sml, cntMin + PrintErrorMsg(); + Output->print(F("interval expects 4 values but got ")); + Output->println(size); + return; + } + if (values[0] < 1 || values[0] > 3600) { + PrintErrorMsg(); Output->println(values[0]); + return; + } + intervalMin = (long)values[0] * 1000; + + if (values[1] < 1 || values[1] > 3600) { + PrintErrorMsg(); Output->println(values[1]); + return; + } + intervalMax = (long)values[1]* 1000; + + if (values[2] > 3600) { + PrintErrorMsg(); Output->println(values[2]); + return; + } + intervalSml = (long)values[2] * 1000; + + if (values[3] > 100) { + PrintErrorMsg(); Output->println(values[3]); + return; + } + countMin = values[3]; + + Output->print(F("M intervals set to ")); + Output->print(values[0]); + Output->print(F(" ")); + Output->print(values[1]); + Output->print(F(" ")); + Output->print(values[2]); + Output->print(F(" ")); + Output->println(values[3]); +} + +#ifdef analogIR +void thresholdCmd(uint16_t *values, uint8_t size) { + if (size < 2) { // t command gets 2 values: min, max + PrintErrorMsg(); + Output->print(F("size")); + Output->println(); + return; + } + if (values[0] < 1 || values[0] > 1023) { + PrintErrorMsg(); Output->println(values[0]); + return; + } + analogThresholdMin = (int)values[0]; + + if (values[1] < 1 || values[1] > 1023) { + PrintErrorMsg(); Output->println(values[1]); + return; + } + analogThresholdMax = (int)values[1]; + + Output->print(F("M analog thresholds set to ")); + Output->print(values[0]); + Output->print(F(" ")); + Output->println(values[1]); +} + +void waitCmd(uint16_t *values, uint8_t size) { + if (size < 1) { // w command gets 1 value: analogReadInterval + PrintErrorMsg(); + Output->print(F("size")); + Output->println(); + return; + } + if (values[0] < 1 || values[0] > 999) { + PrintErrorMsg(); Output->println(values[0]); + return; + } + analogReadInterval = (int)values[0]; + + Output->print(F("M analog read delay set to ")); + Output->println(values[0]); +} +#endif + + +void devVerboseCmd(uint16_t *values, uint8_t size) { + if (size < 1) { // v command gets 1 value: verbose level + PrintErrorMsg(); + Output->print(F("size")); + Output->println(); + return; + } + if (values[0] > 50) { + PrintErrorMsg(); Output->println(values[0]); + return; + } + devVerbose = values[0]; + Output->print(F("M devVerbose set to ")); + Output->println(values[0]); +} + + +void keepAliveCmd(uint16_t *values, uint8_t size) { + if (values[0] == 1 && size > 0) { + Output->println(F("alive")); + } +#ifdef ESP8266 + if (values[0] == 1 && size > 0 && size < 3 && Client1.connected()) { + tcpMode = true; + if (size == 2) { + expectK = millis() + values[1] * 2500; + } else { + expectK = millis() + 600000; // 10 Minutes if nothing sent (should not happen) + } + } +#endif +} + + +#ifdef ESP8266 +void quitCmd() { + if (Client1.connected()) { + Client1.println(F("closing connection")); + Client1.stop(); + tcpMode = false; + Serial.println(F("M TCP connection closed")); + } else { + Serial.println(F("M TCP not connected")); + } +} +#endif + + + +void updateEEPROM(int &address, byte value) { + if( EEPROM.read(address) != value){ + EEPROM.write(address, value); + } + address++; +} + + +void updateEEPROMSlot(int &address, char cmd, int v1, int v2, int v3, int v4) { + updateEEPROM(address, cmd); // I / A + updateEEPROM(address, v1 & 0xff); + updateEEPROM(address, v1 >> 8); + updateEEPROM(address, v2 & 0xff); + updateEEPROM(address, v2 >> 8); + updateEEPROM(address, v3 & 0xff); + updateEEPROM(address, v3 >> 8); + updateEEPROM(address, v4 & 0xff); + updateEEPROM(address, v4 >> 8); +} + +/* todo: include analogPins as well as analog limits in save / restore */ + +void saveToEEPROMCmd() { + int address = 0; + uint8_t slots = 1; + updateEEPROM(address, 'C'); + updateEEPROM(address, 'f'); + updateEEPROM(address, 'g'); + for (uint8_t pinIndex=0; pinIndex < MAX_PIN; pinIndex++) + if (activePin[pinIndex] >= 0) slots ++; +#ifdef analogIR + slots ++; +#endif + updateEEPROM(address, slots); // number of defined pins + intervall definition + updateEEPROMSlot(address, 'I', (uint16_t)(intervalMin / 1000), (uint16_t)(intervalMax / 1000), + (uint16_t)(intervalSml / 1000), (uint16_t)countMin); +#ifdef analogIR + updateEEPROMSlot(address, 'T', (uint16_t)analogThresholdMin, (uint16_t)analogThresholdMax, 0, 0); +#endif + for (uint8_t pinIndex=0; pinIndex < MAX_PIN; pinIndex++) + if (activePin[pinIndex] >= 0) + updateEEPROMSlot(address, 'A', (uint16_t)activePin[pinIndex], (uint16_t)(pulseLevel[pinIndex] ? 3:2), + (uint16_t)pullup[pinIndex], (uint16_t)pulseWidthMin[pinIndex]); +#ifdef ESP8266 + EEPROM.commit(); +#endif + Serial.print(F("config saved, ")); + Serial.print(slots); + Serial.print(F(", ")); + Serial.println(address); +} + + +void showEEPROM() { + int address = 0; + uint16_t v1, v2, v3, v4; + char cmd; + if (EEPROM.read(address) != 'C' || EEPROM.read(address+1) != 'f' || EEPROM.read(address+2) != 'g') { + Output->println(F("M no config in EEPROM")); + return; + } + address = 3; + uint8_t slots = EEPROM.read(address++); + if (slots > MAX_PIN + 2) { + Output->println(F("M illegal config in EEPROM")); + return; + } + Output->println(); + Output->print(F("M EEPROM Config: ")); + Output->print((char) EEPROM.read(0)); + Output->print((char) EEPROM.read(1)); + Output->print((char) EEPROM.read(2)); + Output->print(F(" Slots: ")); + Output->print((int) EEPROM.read(3)); + Output->println(); + for (uint8_t slot=0; slot < slots; slot++) { + cmd = EEPROM.read(address); + v1 = EEPROM.read(address+1) + (((uint16_t)EEPROM.read(address+2)) << 8); + v2 = EEPROM.read(address+3) + (((uint16_t)EEPROM.read(address+4)) << 8); + v3 = EEPROM.read(address+5) + (((uint16_t)EEPROM.read(address+6)) << 8); + v4 = EEPROM.read(address+7) + (((uint16_t)EEPROM.read(address+8)) << 8); + address = address + 9; + Output->print(F("M Slot: ")); + Output->print(cmd); + Output->print(F(" ")); + Output->print(v1); + Output->print(F(",")); + Output->print(v2); + Output->print(F(",")); + Output->print(v3); + Output->print(F(",")); + Output->print(v4); + Output->println(); + } +} + + +void restoreFromEEPROM() { + int address = 0; + if (EEPROM.read(address) != 'C' || EEPROM.read(address+1) != 'f' || EEPROM.read(address+2) != 'g') { + Serial.println(F("M no config in EEPROM")); + return; + } + address = 3; + uint8_t slots = EEPROM.read(address++); + if (slots > MAX_PIN + 1 || slots < 1) { + Serial.println(F("M illegal config in EEPROM")); + return; + } + Serial.println(F("M restoring config from EEPROM")); + char cmd; + for (uint8_t slot=0; slot < slots; slot++) { + cmd = EEPROM.read(address); + commandData[0] = EEPROM.read(address+1) + (((uint16_t)EEPROM.read(address+2)) << 8); + commandData[1] = EEPROM.read(address+3) + (((uint16_t)EEPROM.read(address+4)) << 8); + commandData[2] = EEPROM.read(address+5) + (((uint16_t)EEPROM.read(address+6)) << 8); + commandData[3] = EEPROM.read(address+7) + (((uint16_t)EEPROM.read(address+8)) << 8); + address = address + 9; + commandDataPointer = 4; + if (cmd == 'I') intervalCmd(commandData, commandDataPointer); +#ifdef analogIR + if (cmd == 'T') thresholdCmd(commandData, commandDataPointer); +#endif + if (cmd == 'A') addCmd(commandData, commandDataPointer); + } + commandDataPointer = 0; + value = 0; + for (uint8_t i=0; i < MAX_INPUT_NUM; i++) + commandData[i] = 0; + +} + + +void handleInput(char c) { + if (c == ',') { // Komma input, last value is finished + if (commandDataPointer < (MAX_INPUT_NUM - 1)) { + commandData[commandDataPointer++] = value; + value = 0; + } + } + else if ('0' <= c && c <= '9') { // digit input + value = 10 * value + c - '0'; + } + else if ('a' <= c && c <= 'z') { // letter input is command + + if (devVerbose > 0) { + commandData[commandDataPointer] = value; + Serial.print(F("D got ")); + for (short v = 0; v <= commandDataPointer; v++) { + if (v > 0) Serial.print(F(",")); + Serial.print(commandData[v]); + } + Serial.print(c); + Serial.print(F(" size ")); + Serial.print(commandDataPointer+1); + Serial.println(); + } + + switch (c) { + case 'a': // add a pin + commandData[commandDataPointer] = value; + addCmd(commandData, commandDataPointer+1); + break; + case 'd': // delete a pin + commandData[commandDataPointer] = value; + removeCmd(commandData, commandDataPointer+1); + break; + case 'e': // save to EEPROM + saveToEEPROMCmd(); + break; + case 'f': // flash ota + // OTA flash from HTTP Server + break; + case 'h': // hello + helloCmd(); + break; + case 'i': // interval + commandData[commandDataPointer] = value; + intervalCmd(commandData, commandDataPointer+1); + break; + case 'k': // keep alive + commandData[commandDataPointer] = value; + keepAliveCmd(commandData, commandDataPointer+1); + break; +#ifdef ESP8266 + case 'q': // quit + quitCmd(); + break; +#endif + case 'r': // reset + initialize(); + break; + case 's': // show + showCmd(); + break; +#ifdef analogIR + case 't': // thresholds for analog pin + commandData[commandDataPointer] = value; + thresholdCmd(commandData, commandDataPointer+1); + break; +#endif + case 'v': // dev verbose + commandData[commandDataPointer] = value; + devVerboseCmd(commandData, commandDataPointer+1); + break; +#ifdef analogIR + case 'w': // wait - delay between analog reads + commandData[commandDataPointer] = value; + waitCmd(commandData, commandDataPointer+1); + break; +#endif + default: + break; + } + commandDataPointer = 0; + value = 0; + for (uint8_t i=0; i < MAX_INPUT_NUM; i++) + commandData[i] = 0; + //Serial.println(F("D End of command")); + } +} + +#ifdef debugCfg +/* do sample config so we don't need to configure pins after each reboot */ +void debugSetup() { + commandData[0] = 10; + commandData[1] = 20; + commandData[2] = 3; + commandData[3] = 0; + commandDataPointer = 4; + intervalCmd(commandData, commandDataPointer); + + commandData[0] = 1; // pin 1 + commandData[1] = 2; // falling + commandData[2] = 1; // pullup + commandData[3] = 30; // min Length + commandDataPointer = 4; + addCmd(commandData, commandDataPointer); + + commandData[0] = 2; // pin 2 + addCmd(commandData, commandDataPointer); + +/* + commandData[0] = 5; // pin 5 + addCmd(commandData, commandDataPointer); + + commandData[0] = 6; // pin 6 + addCmd(commandData, commandDataPointer); +*/ +} +#endif + + +#ifdef debugPins +void debugPinChanges() { + for (uint8_t pinIndex=0; pinIndex < MAX_PIN; pinIndex++) { + short aPin = activePin[pinIndex]; + if (aPin > 0) { + uint8_t rPin = internalPins[pinIndex]; + uint8_t pinState = digitalRead(rPin); + + if (pinState != lastState[pinIndex]) { + lastState[pinIndex] = pinState; + Output->print(F("M pin ")); + Output->print(aPin); + Output->print(F(" (internal ")); + Output->print(rPin); + Output->print(F(")")); + Output->print(F(" changed to ")); + Output->print(pinState); +#ifdef pulseHistory + Output->print(F(", histIdx ")); + Output->print(histIndex); +#endif + Output->print(F(", count ")); + Output->print(counter[pinIndex]); + Output->print(F(", reject ")); + Output->print(rejectCounter[pinIndex]); + Output->println(); + } + } + } +} +#endif + + +#ifdef ESP8266 +void connectWiFi() { + Client1Connected = false; + Client2Connected = false; + + // Connect to WiFi network + WiFi.mode(WIFI_STA); + delay (1000); + if (WiFi.status() != WL_CONNECTED) { + Serial.print(F("M Connecting WiFi to ")); + Serial.println(ssid); + WiFi.begin(ssid, password); // authenticate + while (WiFi.status() != WL_CONNECTED) { + Serial.print(F("M Status is ")); + switch (WiFi.status()) { + case WL_CONNECT_FAILED: + Serial.println(F("Connect Failed")); + break; + case WL_CONNECTION_LOST: + Serial.println(F("Connection Lost")); + break; + case WL_DISCONNECTED: + Serial.println(F("Disconnected")); + break; + case WL_CONNECTED: + Serial.println(F("Connected")); + break; + default: + Serial.println(WiFi.status()); + } + delay(1000); + } + Serial.println(); + Serial.print(F("M WiFi connected to ")); + Serial.println(WiFi.SSID()); + } else { + Serial.print(F("M WiFi already connected to ")); + Serial.println(WiFi.SSID()); + } + + // Start the server + Server.begin(); + Serial.println(F("M Server started")); + + // Print the IP address + Serial.print(F("M Use this IP: ")); + Serial.println(WiFi.localIP()); +} + + +void handleConnections() { + IPAddress remote; + uint32_t now = millis(); + + if (Client1Connected) { + if((long)(now - expectK) >= 0) { + Serial.println(F("M no keepalive from Client - disconnecting")); + Client1.stop(); + } + } + if (Client1.available()) { + handleInput(Client1.read()); + //Serial.println(F("M new Input over TCP")); + } + if (Client1.connected()) { + Client2 = Server.available(); + if (Client2) { + Client2.println(F("connection already busy")); + remote = Client2.remoteIP(); + Client2.stop(); + Serial.print(F("M second connection from ")); + Serial.print(remote); + Serial.println(F(" rejected")); + } + } else { + if (Client1Connected) { // client used to be connected, now disconnected + Client1Connected = false; + Output = &Serial; + Serial.println(F("M connection to client lost")); + } + Client1 = Server.available(); + if (Client1) { // accepting new connection + remote = Client1.remoteIP(); + Serial.print(F("M new connection from ")); + Serial.print(remote); + Serial.println(F(" accepted")); + Client1Connected = true; + Output = &Client1; + expectK = now + 600000; // max 10 Minutes (to be checked on Fhem module side as well + helloCmd(); // say hello to client + } + } +} +#endif + + +void handleTime() { + uint32_t now = millis(); + if (now < lastMillis) millisWraps++; + lastMillis = now; +} + + +#ifdef analogIR +void detectTrigger(int val) { + uint8_t nextState = triggerState; + if (val > analogThresholdMax) { + nextState = 1; + } else if (val < analogThresholdMin) { + nextState = 0; + } + if (nextState != triggerState) { + triggerState = nextState; + if (ledOutPin) + digitalWrite(ledOutPin, triggerState); + + short pinIndex = allowedPins[analogInPin]; // ESP pin A0 (pinIndex 8, internal 17) or Arduino A7 (pinIndex 17, internal 21) + uint32_t now = millis(); + doCount (pinIndex, triggerState, now); // do the counting, history and so on + +#ifdef debugPins + if (devVerbose >= 10) { + short rPin = internalPins[pinIndex]; + Output->print(F("M pin ")); + Output->print(analogInPin); + Output->print(F(" (index ")); + Output->print(pinIndex); + Output->print(F(", internal ")); + Output->print(rPin); + Output->print(F(" ) ")); + Output->print(F(" to ")); + Output->print(nextState); +#ifdef pulseHistory + Output->print(F(" histIdx ")); + Output->print(histIndex); +#endif + Output->print(F(" count ")); + Output->print(counter[pinIndex]); + Output->print(F(" reject ")); + Output->print(rejectCounter[pinIndex]); + Output->println(); + } +#endif + } +} + +void readAnalog() { + short AIndex = allowedPins[analogInPin]; + if (AIndex >= 0 && activePin[AIndex] >= 0) { // analog Pin active? + uint32_t now = millis(); + if (now - lastAnalogRead > analogReadInterval) { // time for next analog read? + switch (analogReadState) { + case 0: // initial state + digitalWrite(irOutPin, LOW); // switch IR LED is off + analogReadState = 1; + break; + case 1: // wait before measuring + if (now - lastAnalogRead < analogReadInterval + 2) + return; + analogReadState = 2; + break; + case 2: + sensorValueOff = analogRead(analogInPin); // read the analog in value (off) + analogReadState = 3; + break; + case 3: + digitalWrite(irOutPin, HIGH); // turn IR LED on + analogReadState = 4; + break; + case 4: // wait again before measuring + if (now - lastAnalogRead < analogReadInterval + 4) + return; + analogReadState = 5; + break; + case 5: + sensorValueOn = analogRead(analogInPin); // read the analog in value (on) + analogReadState = 0; + lastAnalogRead = now; + detectTrigger (sensorValueOn - sensorValueOff); + if (devVerbose >= 25) { + char line[20]; + sprintf(line, "L%4d, %4d -> % 4d", sensorValueOn, sensorValueOff, sensorValueOn - sensorValueOff); + Output->println(line); + } else if (devVerbose >= 20) { + Output->print(F("L")); + Output->println(sensorValueOn - sensorValueOff); + } + break; + default: + analogReadState = 0; + Output->println(F("D error: wrong analog read state")); + break; + } + } + } +} +#endif + + +void setup() { + Serial.begin(SERIAL_SPEED); // initialize serial +#ifdef ESP8266 + EEPROM.begin(100); +#endif + delay (500); + interrupts(); + Serial.println(); + Output = &Serial; + millisWraps = 0; + lastMillis = millis(); + initialize(); +#ifdef analogIR + pinMode(irOutPin, OUTPUT); + if (ledOutPin) + pinMode(ledOutPin, OUTPUT); +#endif + helloCmd(); // started message to serial +#ifdef ESP8266 + connectWiFi(); +#endif +} + + +/* Main Loop */ +void loop() { + handleTime(); + if (Serial.available()) handleInput(Serial.read()); +#ifdef ESP8266 + handleConnections(); +#endif +#ifdef analogIR + readAnalog(); +#endif +#ifdef debugPins + if (devVerbose >= 10) { + debugPinChanges(); + } +#endif + if (reportDue()) { + report(); + } +}