2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-01-31 18:59:33 +00:00
fhem-mirror/fhem/contrib/arduino/ArduCounter3.30.ino

1772 lines
67 KiB
Arduino
Raw Normal View History

/*
* 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
12.8.19 - V3.33 fix handling of keepalive timeouts when millis wraps
V3.34 add RSSI output when devVerbose >= 5 in kealive responses
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 // include my SSID / secret */
/* 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 <EEPROM.h>
const char versionStr[] PROGMEM = "ArduCounter V3.34";
#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 <ESP8266WiFi.h> // =============================
#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
uint16_t keepAliveTimeout = 200;
uint32_t lastKeepAlive;
#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
lastKeepAlive = now;
#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 || ((now - lastDelayedTcpReports) > 30000)) {
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 = (uint32_t)values[0] * 1000;
if (values[1] < 1 || values[1] > 3600) {
PrintErrorMsg(); Output->println(values[1]);
return;
}
intervalMax = (uint32_t)values[1]* 1000;
if (values[2] > 3600) {
PrintErrorMsg(); Output->println(values[2]);
return;
}
intervalSml = (uint32_t)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) {
uint32_t now = millis();
if (values[0] == 1 && size > 0) {
Output->print(F("alive"));
#ifdef ESP8266
if (devVerbose >=5) {
Output->print(F(" RSSI "));
Output->print(WiFi.RSSI());
}
if (values[0] == 1 && size > 0 && size < 3 && Client1.connected()) {
tcpMode = true;
if (size == 2) {
keepAliveTimeout = values[1]; // timeout in seconds (on ESP side we use it times 3)
} else {
keepAliveTimeout = 200; // *3*1000 gives 10 minutes if nothing sent (should not happen)
}
}
lastKeepAlive = now;
#endif
Output->println();
}
}
#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((now - lastKeepAlive) > (keepAliveTimeout * 3000)) {
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;
lastKeepAlive = now;
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();
uint16_t interval2 = analogReadInterval + 2;
uint16_t interval3 = analogReadInterval + 4;
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) < interval2)
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) < interval3)
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();
}
}