/*
 * 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();
    }
}
