/*
 * 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 board 
 *
 * the sketch uses pin change interrupts which can be anabled 
 * for any of the inputs on e.g. an arduino uno or a jeenode
 *
 * the pin change Interrupt handling used here 
 * is based on the arduino playground example on PCINT:
 * http://playground.arduino.cc/Main/PcInt
 *
 * Refer to avr-gcc header files, arduino source and atmega datasheet.
 */

/* 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
 */

/* to test pin 4 with interval 10-20 sec do
 *  4,2,1,30a
 *  10,20,2,0i
 */
 
/*
    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 byte 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)
        
        ToDo / Ideas:   
                                
                new index scheme to save memory:
                    define new array to map from pcintPin to new index, limit allowed pins.
                    unused pcintpins point to -1 (or some other unused number < 0) and comment states arduino pin number
                    instead of allowedPins array use new function from aPin to pcintPin
                    and then look up in new array for index or -1
*/ 
 
#include "pins_arduino.h"

const char versionStr[] PROGMEM = "ArduCounter V2.05";
const char errorStr[]   PROGMEM = "Error: ";

#define SERIAL_SPEED 38400
#define MAX_ARDUINO_PIN 24
#define MAX_PCINT_PIN 24
#define MAX_INPUT_NUM 8
#define MAX_HIST 20

/* arduino pins that are typically ok to use 
 * (some are left out because they are used 
 * as reset, serial, led or other things on most boards) */
byte allowedPins[MAX_ARDUINO_PIN] = 
  { 0,  0,  0,  3,  4, 5, 6, 7,
    8,  9, 10, 11, 12, 13,
   14, 15, 16, 17,  0, 0};


/* Pin change mask for each chip port */
volatile uint8_t *port_to_pcmask[] = {
  &PCMSK0,
  &PCMSK1,
  &PCMSK2
};

/* last PIN States to detect individual pin changes in ISR */
volatile static uint8_t PCintLast[3];

unsigned long intervalMin = 30000; // default 30 sec - report after this time if nothing else delays it
unsigned long intervalMax = 60000; // default 60 sec - report after this time if it didin't happen before
unsigned long intervalSml =  2000; // default 2 secs - continue count if timeDiff is less and intervalMax not over
unsigned int  countMin    =     1; // continue counting if count is less than this and intervalMax not over

unsigned long timeNextReport;

/* index to the following arrays is the internal PCINT pin number, not the arduino 
 * pin number because the PCINT pin number corresponds to the physical ports
 * and this saves time for mapping to the arduino numbers
 */

/* did we get first interrupt yet? */
volatile boolean initialized[MAX_PCINT_PIN];
 
/* individual counters for each real pin */
volatile unsigned long counter[MAX_PCINT_PIN];
volatile uint8_t counterIgn[MAX_PCINT_PIN];         // ignored first pulse after init
volatile unsigned int rejectCounter[MAX_PCINT_PIN];

/* millis at last level change (for measuring pulse length) */
volatile unsigned long lastChange[MAX_PCINT_PIN];

/* last valid level */
volatile uint8_t lastLevel[MAX_PCINT_PIN];

/* sum of pulse lengths for average output */
volatile unsigned long pulseWidthSum[MAX_PCINT_PIN];


/* count at last report to get difference */
unsigned long lastCount[MAX_PCINT_PIN];
unsigned int lastRejCount[MAX_PCINT_PIN];

/* history ring */
volatile uint8_t histIndex;
volatile uint8_t histPin[MAX_HIST];
volatile uint8_t histLevel[MAX_HIST];
volatile unsigned long histTime[MAX_HIST];
volatile unsigned long histLen[MAX_HIST];
volatile char histAct[MAX_HIST];
//volatile uint8_t histI1[MAX_HIST];


/* real arduino pin number for PCINT number if active - otherwise 0 */
uint8_t PCintActivePin[MAX_PCINT_PIN];

/* pin change mode (RISING etc.) as parameter for ISR */
uint8_t PCintMode[MAX_PCINT_PIN];

/* minimal pulse length in millis for filtering */
unsigned int pulseWidthMin[MAX_PCINT_PIN];

/* start of pulse for measuring length */
uint8_t pulseWidthStart[MAX_PCINT_PIN];             // FALLING or RISING as defined for each pin

/* start and end of an interval - typically set by first / last pulse */
volatile unsigned long intervalStart[MAX_PCINT_PIN];
volatile unsigned long intervalEnd[MAX_PCINT_PIN];

/* millis at first interrupt in a reporting cycle */
volatile unsigned long firstPulse[MAX_PCINT_PIN];

/* millis at last report 
 * to find out when maxInterval is over
 * and report has to be done even if
 * no impulses were counted */
unsigned long lastReport[MAX_PCINT_PIN];

/* input data over serial port */
unsigned int commandData[MAX_INPUT_NUM];
uint8_t commandDataPointer = 0;



int digitalPinToPcIntPin(uint8_t aPin) {
  uint8_t pcintPin;                             // PCINT pin number for the pin to be added (index for most arrays)  
  uint8_t portIdx = digitalPinToPort(aPin)-2;   // index of port that this arduno pin belongs to for enabling interrupts
                                                // since the macro maps to defines PB(=2), PC(=3) and PD(=4), we subtract 2
                                                // to use the result as array index in this sketch

  if (portIdx == 1) {                     // now calculate the PCINT pin number that corresponds to the arduino pin number
     pcintPin = aPin - 6;                 // portIdx 1: PC0-PC5 (A0-A5 or D14-D19) is PCINT 8-13 (PC6 is reset)
  } else {                                // arduino numbering continues at D14 since PB6/PB7 are used for other things 
     pcintPin = portIdx*8 + (aPin % 8);   // portIdx 0: PB0-PB5 (D8-D13) is PCINT 0-5 (PB6/PB7 is crystal)
  }                                       // portIdx 2: PD0-PD7 (D0-D7) is PCINT 16-23
  return pcintPin;
}


/* Add a pin to be handled */
byte AddPinChangeInterrupt(uint8_t aPin) {
  volatile uint8_t *pcmask;             // pointer to PCMSK0 or 1 or 2 depending on the port corresponding to the pin
  
  uint8_t bitM = digitalPinToBitMask(aPin);  // mask to bit in PCMSK to enable pin change interrupt for this arduino pin 
  uint8_t port = digitalPinToPort(aPin);     // 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 */
byte RemovePinChangeInterrupt(uint8_t aPin) {
  volatile uint8_t *pcmask;

  uint8_t bitM = digitalPinToBitMask(aPin);
  uint8_t port = digitalPinToPort(aPin);

  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;
}



void PrintErrorMsg() {
  int len = strlen_P(errorStr);
  char myChar;
  for (unsigned char k = 0; k < len; k++) {
    myChar = pgm_read_byte_near(errorStr + k);
    Serial.print(myChar);
  }
}

void printVersion() {  
  int len = strlen_P(versionStr);
  char myChar;
  for (unsigned char k = 0; k < len; k++) {
    myChar = pgm_read_byte_near(versionStr + k);
    Serial.print(myChar);
  }
}


/* 
   common interrupt handler. "port" is the PCINT port index (0-2), not PB, PC or PD which are mapped to 2-4
   
   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 PCint(uint8_t port) {
    uint8_t bit;
    uint8_t curr;
    uint8_t delta;
    uint8_t level; 
    uint8_t pulseLevel;
    uint8_t pcintPin;
    unsigned long len;
    unsigned long 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];                 // xor gets bits that are different
    PCintLast[port] = curr;                         // store new pin state for next interrupt

    if ((delta &= *port_to_pcmask[port]) == 0)      // delta is pins that have changed. screen out non pcint pins.
        return; /* no handled pin changed */

    for (uint8_t i=0; i < 8; i++) {             // loop over each pin on the given port that changed
        bit = 0x01 << i;                         
        if (delta & bit) {                          // did this pin change?
            pcintPin = port * 8 + i;                // pcint pin numbers follow the bits, only arduino pin nums are special
            level = ((curr & bit) > 0);
            pulseLevel = (pulseWidthStart[pcintPin] == RISING); // RISING means that pulse is at high level
      
            len = now - lastChange[pcintPin];
            histIndex++;
            if (histIndex >= MAX_HIST) histIndex = 0;
            histPin[histIndex]   = pcintPin;
            histTime[histIndex]  = lastChange[pcintPin];
            histLen[histIndex]   = len;
            histLevel[histIndex] = !level;      // before it changed
            histAct[histIndex]   = ' ';
            //histI1[histIndex]    = lastLevel[pcintPin];
            
            // go on if mode is CHANGE, or if RISING and bit is high, or if mode is FALLING and bit is low.
            if (PCintMode[pcintPin] == CHANGE || level == pulseLevel) {

                if (pulseWidthMin[pcintPin]) {              // if minimal pulse length defined then check minimal pulse length and gap
        
                    if (len < pulseWidthMin[pcintPin]) {
                        lastChange[pcintPin] = now;
                        if (level != pulseLevel) {              // if change to gap level
                            rejectCounter[pcintPin]++;          // pulse too short
                            histAct[histIndex] = 'R';
                        } else {
                            histAct[histIndex] = 'X';
                        }
                    } else {

                        if (level == pulseLevel) { // edge does fit defined start, level is now pulse
                            // potential end of a valid gap, now we are at pulse level
                            if (lastLevel[pcintPin] == pulseLevel) {  // last remembered valid level was also pulse
                                // last remembered valid level was pulse, now the gap was confirmed.
                                histAct[histIndex] = 'G';
                            } else {
                                // last remembered valid level was a gap -> now we had another valid gap -> inbetween was only a spike -> ignore
                                histAct[histIndex] = 'G';
                            }
                    
                        } else {    // edge is a change to gap, level is now gap
                            // potential end of a valid pulse, now we are at gap level
                            if (lastLevel[pcintPin] != pulseLevel) {  // last remembered valid level was also gap
                                // last remembered valid level was a gap -> now we had valid new pulse -> count

                                intervalEnd[pcintPin] = now;                    // remember time of in case pulse will be the last in the interval
                                if (!firstPulse[pcintPin]) firstPulse[pcintPin] = now;    // time of first impulse in this reporting interval
                                if (initialized[pcintPin]) {
                                    counter[pcintPin]++;                    // count
                                } else {
                                    counter[pcintPin]++;                    // count
                                    counterIgn[pcintPin]++;                 // count as to be ignored for diff because it defines the start of the interval
                                    intervalStart[pcintPin] = now;              // if this is the very first impulse on this pin -> start interval now
                                    initialized[pcintPin] = true;           // and start counting the next impulse (so far counter is 0)
                                }
                                pulseWidthSum[pcintPin] += len;             // for average calculation
                                histAct[histIndex] = 'C';
                            } else {
                                // last remembered valid level was a pulse -> now we had another valid pulse
                                // inbetween was an invalid drop so pulse is already counted.
                                pulseWidthSum[pcintPin] += len;             // for average calculation
                                histAct[histIndex] = 'P';
                            }
                        }  // change to gap level

                        // remember this valid level as lastLevel
                        lastLevel[pcintPin] = !level;                       // before it changed
                        
                    } // if pulse is not too short
                                    
                } // if pulseWidth checking
            }
            lastChange[pcintPin] = now;
        } // if bit changed
    } // for 
}


/* show pulse history ring */
void showHistory() {
    uint8_t hi;
    Serial.println (F("D pulse history: "));
    unsigned long now = millis();
    unsigned long last;
    uint8_t start = (histIndex + 2) % MAX_HIST;
    for (uint8_t i = 0; i < MAX_HIST; i++) {
        hi = (start + i) % MAX_HIST;
        if (i == 0 || (last <= histTime[hi]+histLen[hi])) {
            Serial.print (F("D pin "));
            Serial.print (PCintActivePin[histPin[hi]]);
            Serial.print (F(" start "));
            Serial.print ((long) (histTime[hi] - now));
            Serial.print (F(" len "));
            Serial.print (histLen[hi]);
            Serial.print (F(" at "));
            Serial.print (histLevel[hi]);
            Serial.print (F(" "));
            Serial.print (histAct[hi]);
            Serial.println();
        }
        last = histTime[hi];
    }    
}


/* 
   report count and time for pins that are between min and max interval 
   
   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) -> no problem.
*/
void report() {
  int aPin;
  unsigned long count, countIgn, countDiff, realDiff;
  unsigned long timeDiff, now;
  unsigned long startT, endT;
  unsigned long avgLen;
  now = millis();
  
  for (int pcintPin=0; pcintPin < MAX_PCINT_PIN; pcintPin++) { // go through all observed pins as PCINT pin number
    aPin = PCintActivePin[pcintPin];                        // take saved arduino pin number
    if (aPin < 1) continue;                                 // 0 means pin is not active for reporting
    noInterrupts();
    startT   = intervalStart[pcintPin];
    endT     = intervalEnd[pcintPin];
    count    = counter[pcintPin];                           // get current counter (counts all pulses
    countIgn = counterIgn[pcintPin];                        // pulses that mark the beginning of an interval and should not be taken into calculation (happens after restart)
    interrupts();
        
    timeDiff  = endT - startT;                              // time between first and last impulse during interval
    countDiff = count - countIgn - lastCount[pcintPin];     // how many pulses during intervall since last report? (ignore forst pulse after device restart) 
    realDiff  = count - lastCount[pcintPin];                        // (works with wrapping)
    if((long)(now - (lastReport[pcintPin] + intervalMax)) >= 0) { // intervalMax is over
      if ((countDiff >= countMin) && (timeDiff > intervalSml) && (intervalMin != intervalMax)) {
        // normal procedure
        lastCount[pcintPin]  = count;                      // remember current count for next interval
        noInterrupts();
        intervalStart[pcintPin]  = endT;                       // time of last impulse in this interval becomes also time of first impulse in next
        counterIgn[pcintPin] = 0;
        interrupts();
      } else {
        // nothing counted or counts happened during a fraction of intervalMin only
        noInterrupts();
        intervalEnd[pcintPin]   = now;                        // don't calculate with last impulse, use now instead
        intervalStart[pcintPin]  = now;                        // start a new interval for next report now
        counterIgn[pcintPin] = 0;
        interrupts();
        lastCount[pcintPin] = count;                      // remember current count for next interval
        timeDiff  = now - startT;                         // special handling - calculation ends now instead of last impulse
      }        
    } else if((long)(now - (lastReport[pcintPin] + intervalMin)) >= 0) {  // minInterval has elapsed      
      if ((countDiff >= countMin) && (timeDiff > intervalSml)) {
        // normal procedure
        lastCount[pcintPin]  = count;                      // remember current count for next interval
        noInterrupts();
        intervalStart[pcintPin]  = endT;                       // time of last impulse in this interval becomes also time of first impulse in next
        counterIgn[pcintPin] = 0;
        interrupts();
      } else continue;              // not enough counted - wait                        
    } else continue;                // intervalMin not over - wait

    Serial.print(F("R"));           // R Report
    Serial.print(aPin);
    Serial.print(F(" C"));          // C - Count
    Serial.print(count);
    Serial.print(F(" D"));          // D - Count Diff (without pulse that marks the begin of an interval)
    Serial.print(countDiff);
    Serial.print(F(" R"));          // R - real Diff for incrementing long counter in Fhem - includes even the first pulse after restart
    Serial.print(realDiff);
    Serial.print(F(" T"));          // T - Time
    Serial.print(timeDiff);  
    Serial.print(F(" N"));          // N - now
    Serial.print((long)now);
    
    // rejected count ausgeben
    // evt auch noch average pulse len und gap len
    if (pulseWidthMin[pcintPin]) {  // check minimal pulse length and gap
      Serial.print(F(" X"));        // X Reject
      Serial.print(rejectCounter[pcintPin] - lastRejCount[pcintPin]);  
      noInterrupts();
      lastRejCount[pcintPin] = rejectCounter[pcintPin];
      interrupts();
    }

    if (realDiff) {
      Serial.print(F(" F"));        // F - first impulse after the one that started the interval
      Serial.print((long)firstPulse[pcintPin] - startT);
      Serial.print(F(" L"));        // L - last impulse - marking the end of this interval
      Serial.print((long)endT - startT);
      firstPulse[pcintPin] = 0;
      
      if (pulseWidthMin[pcintPin]) {// check minimal pulse length and gap
        noInterrupts();
        avgLen = pulseWidthSum[pcintPin] / countDiff;
        pulseWidthSum[pcintPin] = 0;
        interrupts();
        Serial.print(F(" A"));
        Serial.print(avgLen);
      }
    }   
    
    uint8_t hi;
    boolean first = true;
    uint8_t start = (histIndex + 2) % MAX_HIST;
	unsigned long last;
    Serial.print (F(" H"));
    for (uint8_t i = 0; i < MAX_HIST; i++) {
        hi = (start + i) % MAX_HIST;
        if (histPin[hi] == pcintPin) {
	        if (first || (last <= histTime[hi]+histLen[hi])) {
				if (!first) 
					Serial.print (F(", "));
				//Serial.print (F(""));
				Serial.print ((long) (histTime[hi] - now));
				Serial.print (F("/"));
				Serial.print (histLen[hi]);
				Serial.print (F(":"));
				Serial.print (histLevel[hi]);
				//Serial.print (F(" "));
				Serial.print (histAct[hi]);
				first = false;
			}
			last = histTime[hi];
        }
    }    
    
    Serial.println();    
    lastReport[pcintPin] = now;     // remember when we reported 
  }
}


/* print status for one pin */
void showPin(byte pcintPin) {
    unsigned long newCount, countIgn, countDiff;
    unsigned long timeDiff;
    unsigned long avgLen;

    timeDiff  = intervalEnd[pcintPin] - intervalStart[pcintPin];
    newCount  = counter[pcintPin];
    countIgn  = counterIgn[pcintPin];
    countDiff = newCount - countIgn - lastCount[pcintPin];
    if (!timeDiff) 
        timeDiff = millis() - intervalStart[pcintPin];     

    Serial.print(F("PCInt pin "));
    Serial.print(pcintPin);
      
    Serial.print(F(", iMode "));
    switch (PCintMode[pcintPin]) {
        case RISING: Serial.print(F("rising")); break;
        case FALLING: Serial.print(F("falling")); break;
        case CHANGE: Serial.print(F("change")); break;
    }
    if (pulseWidthMin[pcintPin] > 0) {
        Serial.print(F(", min len "));
        Serial.print(pulseWidthMin[pcintPin]);
        Serial.print(F(" ms"));
        switch (pulseWidthStart[pcintPin]) {
          case RISING: Serial.print(F(" rising")); break;
          case FALLING: Serial.print(F(" falling")); break;
        }        
    } else {
        Serial.print(F(", no min len"));
    }
    Serial.print(F(", count "));
    Serial.print(newCount);
    Serial.print(F(" (+"));
    Serial.print(countDiff);
    Serial.print(F(") in "));
    Serial.print(timeDiff);
    Serial.print(F(" ms"));

    // rejected count ausgeben
    // evt auch noch average pulse len und gap len
    if (pulseWidthMin[pcintPin]) {      // check minimal pulse length and gap
        Serial.print(F(" Rej "));
        Serial.print(rejectCounter[pcintPin] - lastRejCount[pcintPin]);  
    }
    if (countDiff) {
        Serial.println();
        Serial.print(F("M   first at "));
        Serial.print((long)firstPulse[pcintPin] - lastReport[pcintPin]);
        Serial.print(F(", last at "));
        Serial.print((long)intervalEnd[pcintPin] - lastReport[pcintPin]);
        noInterrupts();
        avgLen = pulseWidthSum[pcintPin] / countDiff;
        interrupts();
        Serial.print(F(", avg len "));
        Serial.print(avgLen);
    }
}


/* give status report in between if requested over serial input */
void showCmd() {
  Serial.print(F("M Status: "));
  printVersion();
  Serial.println();
  Serial.print(F("M normal interval "));
  Serial.println(intervalMin);
  Serial.print(F("M max interval "));
  Serial.println(intervalMax);
  Serial.print(F("M min interval "));
  Serial.println(intervalSml);
  Serial.print(F("M min count "));
  Serial.println(countMin);
  
  for (byte pcintPin=0; pcintPin < MAX_PCINT_PIN; pcintPin++) {
    int aPin = PCintActivePin[pcintPin];
    if (aPin > 0) {
      Serial.print(F("M pin "));
      Serial.print(aPin);
      Serial.print(F(" "));
      showPin(pcintPin);      
      Serial.println();
    }
  }
  Serial.print(F("M Next report in "));
  Serial.print(timeNextReport - millis());
  Serial.print(F(" Milliseconds"));
  Serial.println();  
  showHistory();
}



/*
    handle add command.
    todo: check size and clear options not passed
*/
void addCmd(unsigned int *values, byte size) {
  uint8_t pcintPin;                         // PCINT pin number for the pin to be added (used as index for most arrays)
  byte mode = 2;
  uint8_t port;
  unsigned int pw;
  unsigned long now = millis();
  

  //Serial.println(F("M Add called"));
  int aPin = values[0];                     // value 0 is pin number
  pcintPin = digitalPinToPcIntPin(aPin);
  if (aPin >= MAX_ARDUINO_PIN || aPin < 1 
        || allowedPins[aPin] == 0 || pcintPin > MAX_PCINT_PIN) {
    PrintErrorMsg(); 
    Serial.print(F("Illegal pin specification "));
    Serial.println(aPin);
    return;
  }; 
  port = digitalPinToPort(aPin) - 2;
  
  switch (values[1]) {                      // value 1 is rising / falling etc.
    case 2:
      mode = FALLING;
      pulseWidthStart[pcintPin] = FALLING;
      break;
    case 3:
      mode = RISING;       
      pulseWidthStart[pcintPin] = RISING;
      break;
    case 1:
      mode = CHANGE;
      break;
    default:
      PrintErrorMsg(); 
      Serial.print(F("Illegal mode for pin specification "));
      Serial.println(aPin);
  }
  
  pinMode (aPin, INPUT);
  if (size > 2 && values[2]) {              // value 2 is pullup
    digitalWrite (aPin, HIGH);              // enable pullup resistor
  }

  if (size > 3 && values[3] > 0) {          // value 3 is min length (if given)
    pw = values[3];
    mode = CHANGE;
  } else {
    pw = 0;
  }  
  
  if (!AddPinChangeInterrupt(aPin)) {       // add Pin Change Interrupt
    PrintErrorMsg(); Serial.println(F("AddInt"));
    return;
  }  
  PCintMode[pcintPin] = mode;               // save mode for ISR which uses the pcintPin as index 

  pulseWidthMin[pcintPin] = pw;             // minimal pulse width in millis, 0 if not specified in add cmd  todo: needs fixing! values[3] might contain data from last command
  
  if (PCintActivePin[pcintPin] != aPin) {   // in case this pin is not already active counting
      PCintLast[port]          = *portInputRegister(port+2);   // current pin states at port      
      PCintActivePin[pcintPin] = aPin;      // save real arduino pin number and flag this pin as active for reporting
      initialized[pcintPin]    = false;     // initialize arrays for this pin
      counter[pcintPin]        = 0;            
      counterIgn[pcintPin]     = 0;            
      lastCount[pcintPin]      = 0;
      intervalStart[pcintPin]  = now;     
      intervalEnd[pcintPin]    = now;
      lastReport[pcintPin]     = now;       // next reporting cycle is probably earlier than now+intervalMin (already started) so report will be later than next interval     
      lastChange[pcintPin]     = now;
      rejectCounter[pcintPin]  = 0;        
      lastRejCount[pcintPin]   = 0;
  }
  Serial.print(F("M defined pin ")); 
  Serial.print(aPin);
  Serial.print(F(" ")); 
  showPin(pcintPin);    
  Serial.println();
}


/*
    handle rem command.
*/
void removeCmd(unsigned int *values, byte size) {
  uint8_t pcintPin;                         // PCINT pin number for the pin to be added (used as index for most arrays) 
  int aPin = values[0];
  pcintPin = digitalPinToPcIntPin(aPin);
  if (size < 1 || aPin >= MAX_ARDUINO_PIN || aPin < 1 
        || allowedPins[aPin] == 0 || pcintPin > MAX_PCINT_PIN) {
    PrintErrorMsg(); 
    Serial.print(F("Illegal pin specification "));
    Serial.println(aPin);
    return;
  };
  
  if (!RemovePinChangeInterrupt(aPin)) {      
    PrintErrorMsg(); Serial.println(F("RemInt"));
    return;
  }
  
  PCintActivePin[pcintPin] = 0;
  initialized[pcintPin]    = false;         // reset for next add
  counter[pcintPin]        = 0;            
  counterIgn[pcintPin]     = 0;            
  lastCount[pcintPin]      = 0;
  pulseWidthMin[pcintPin]  = 0;
  lastRejCount[pcintPin]   = 0;
  rejectCounter[pcintPin]  = 0;

  Serial.print(F("M removed "));
  Serial.println(aPin);
}



void intervalCmd(unsigned int *values, byte size) {
  if (size < 4) {               // i command always gets 4 values: min, max, sml, cntMin
    PrintErrorMsg();
    Serial.print(F("size"));
    Serial.println();
    return;
  }
  if (values[0] < 1 || values[0] > 3600) {
    PrintErrorMsg(); Serial.println(values[0]);
    return;
  }
  intervalMin = (long)values[0] * 1000;
  if (millis() + intervalMin < timeNextReport)
    timeNextReport = millis() + intervalMin;

  if (values[1] < 1 || values[1] > 3600) {
    PrintErrorMsg(); Serial.println(values[1]);
    return;
  }
  intervalMax = (long)values[1]* 1000;
 
  if (values[2] > 3600) {
    PrintErrorMsg(); Serial.println(values[2]);
    return;
  }
  intervalSml = (long)values[2] * 1000;    
  countMin = values[3];

  Serial.print(F("M intervals set to ")); 
  Serial.print(values[0]);
  Serial.print(F(" ")); 
  Serial.print(values[1]);
  Serial.print(F(" ")); 
  Serial.print(values[2]);
  Serial.print(F(" ")); 
  Serial.print(values[3]);
  Serial.println();
}


void helloCmd() {
  Serial.println();
  printVersion();
  Serial.println(F("Hello"));
}


static void HandleSerialPort(char c) {
  static unsigned int value;

  if (c == ',') {
    if (commandDataPointer < (MAX_INPUT_NUM - 1)) {
        commandData[commandDataPointer] = value;
        commandDataPointer++;
        value = 0;
    }
  }
  else if ('0' <= c && c <= '9') {
    value = 10 * value + c - '0';
  }
  else if ('a' <= c && c <= 'z') {
    switch (c) {
    case 'a':
      commandData[commandDataPointer] = value;
      addCmd(commandData, ++commandDataPointer);
      break;
    case 'd':
      commandData[commandDataPointer] = value;
      removeCmd(commandData, ++commandDataPointer);
      break;
    case 'i':
      commandData[commandDataPointer] = value;
      intervalCmd(commandData, ++commandDataPointer);
      break;
    case 'r':
      initialize();
      break;
    case 's':
      showCmd();
      break;
    case 'h':
      helloCmd();
      break;
    default:
      //PrintErrorMsg(); Serial.println();
      break;
    }
    commandDataPointer = 0;
    value = 0;
    for (byte i=0; i < MAX_INPUT_NUM; i++)
        commandData[i] = 0;     
  }
}



SIGNAL(PCINT0_vect) {
  PCint(0);
}
SIGNAL(PCINT1_vect) {
  PCint(1);
}
SIGNAL(PCINT2_vect) {
  PCint(2);
}



void initialize() {
  unsigned long now = millis();

  Serial.println();
  printVersion();
  Serial.println(F(" Started"));
  
  for (int pcintPin=0; pcintPin < MAX_PCINT_PIN; pcintPin++) {
    PCintActivePin[pcintPin] = 0;           // set all pins to inactive (0)
    initialized[pcintPin]    = false;       // initialize arrays for this pin
    counter[pcintPin]        = 0;    
    counterIgn[pcintPin]     = 0;    
    lastCount[pcintPin]      = 0;
    intervalStart[pcintPin]  = now;     
    intervalEnd[pcintPin]    = now;
    lastChange[pcintPin]     = now;
    pulseWidthMin[pcintPin]  = 0;
    rejectCounter[pcintPin]  = 0;        
    lastRejCount[pcintPin]   = 0;
    lastReport[pcintPin]     = now;
  }   

  for (unsigned int port=0; port <= 2; port++) {
    PCintLast[port] = *portInputRegister(port+2); // current pin states at port
  }
  
  timeNextReport = millis() + intervalMin;      // time for first output
}


void setup() {
    Serial.begin(SERIAL_SPEED);                 // initialize serial
    delay (500);
    interrupts();
    initialize();  
}


/*
   Main Loop  
   checks if report should be called because timeNextReport is reached
      or lastReport for one pin is older than intervalMax   
   timeNextReport is only set here (and when interval is changed / at setup)
*/
void loop() {
  unsigned long now = millis();
  
  if (Serial.available()) {
    HandleSerialPort(Serial.read());
  }
  boolean doReport  = false;                     // check if report nedds to be called
  if((long)(now - timeNextReport) >= 0)          // works fine when millis wraps.
    doReport = true;                             // intervalMin is over 
  else 
    for (byte pcintPin=0; pcintPin < MAX_PCINT_PIN; pcintPin++)  
      if (PCintActivePin[pcintPin] > 0)
        if((long)(now - (lastReport[pcintPin] + intervalMax)) >= 0)
          doReport = true;                       // active pin has not been reported for langer than intervalMax

  if (doReport) {    
    report();
    timeNextReport = now + intervalMin;          // do it again after intervalMin millis
  }
}

