fhem-mirror/FHEM/firmware/ArduCounter.cpp
2020-06-10 16:48:23 +00:00

2220 lines
90 KiB
C++
Executable File

/*
* 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
Nano analog
21,2,0,50,2,98,115 A7=21, falling, no pullup, min 50, ir-out 2, thresholds 98/115
analog ESP 8266:
20v Verbose
17,3,0,50a A0, rising, no Pullup, MinLen 50
15,25t Level Diff Thresholds
for ESP8266 with D5 falling pullup 30
5,2,1,30a
20v
10,20,1,1i
for ESP32 pin 23
23,2,1,50a
10,20,1,1i
for ESP32 with A0 = 36
36,3,0,50a
25v
TTGO T-Display has right button at GPIO 35
35,2,0,50a
36,3,0,50,27a
*/
/*
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 printIntervals 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
16.8.19 - V3.35 fix a bug when analog support was disabled and a warning with an unused variable
19.8.19 - V4.00 start porting to ESP32.
21.12.19 - V4.10 Support for TTGO Lilygo Board (T-Display) with ST7789V 1,14 Zoll Display (240x135) see https://github.com/Xinyuan-LilyGO/TTGO-T-Display
or https://de.aliexpress.com/item/33048962331.html?spm=a2g0o.store_home.hotSpots_212315783.0
30.12.19 - V4.20 started to make analog support user defineable at runtime
reconnect when wifi is lost
20.1.2020 - rewrite many things ...
2.2.2020 integrate ESPOTA - see https://raw.githubusercontent.com/esp8266/Arduino/master/tools/espota.py
30.4.2020 V4.25 - show if compiled with display support, debug tcp disconnects
12.5.2020 V4.26 - restore fixes after a crash (resetWifi, displayMode 3, change commandData to uint32_t)
ToDo / Ideas:
max 10 printData entries, displayMode to cycle through pins to display in different views
detect analog Thresholds automatically and adjust over time
printPinHistory could be called independent of report to avoid of losing history data
*/
#include <Arduino.h>
#if defined(TFT_DISPLAY)
#include <TFT_eSPI.h>
#include <SPI.h>
#include <Wire.h>
#include <Button2.h>
#include "esp_adc_cal.h"
/*#include "bmp.h"*/
TFT_eSPI tft = TFT_eSPI(); // Invoke library, pins defined in User_Setup.h or as -D in platformio.ini
uint8_t lineCount; // initialized in each report call
#define TFT_BUTTON 0
#define displayModeMax 3
Button2 buttonA = Button2(TFT_BUTTON);
uint8_t displayMode = 1;
#endif
#include "pins_arduino.h"
#include <EEPROM.h>
const char versionStr[] PROGMEM = "ArduCounter V4.26";
// even with 38400 one report takes less than 1ms. However printing the report on the tft takes 2ms!
// but if analog interval is set to 10 or less and we output a lot to serial (devVerbose 50) then
// background tasks (serial?) eat up 5ms per round...
// this is much better at 115200! (obviously serial io is done interrupt driven at same core)
// TCP io also creates delays of 15-20ms between loop calls
// show cmd also takes > 10 ms
// serial output of hello msg takes long because the serial buffer is too small so Serial.print takes longer ...
//#define SERIAL_SPEED 38400
#define SERIAL_SPEED 115200
#define MAX_INPUT_NUM 16
#define pin2GPIO(P) ( pgm_read_byte( digital_pin_to_gpio_PGM + (P) ) )
#define FF 255
#if defined(ESP8266) || defined(ESP32) // Wifi stuff
#define WifiSupport 1
#if defined(ESP8266)
#include <ESP8266WiFi.h>
#elif defined(ESP32)
#include <WiFi.h>
#endif
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
#if defined(STATIC_WIFI)
#include "ArduCounterTestConfig.h"
#else
#include <WiFiManager.h>
WiFiManager wifiManager;
#endif
WiFiServer Server(80); // For ESP WiFi connection
WiFiClient Client1; // active TCP connection
WiFiClient Client2; // secound TCP connection to send reject message
boolean serverStarted; // to show the status once
boolean TCPconnected; // remember state of TCP connection so loss can be reported
boolean tcpMode = false; // remember if we had a tcp connection so we can delay report if disconnected
uint8_t delayedTcpReports = 0; // how often did we already delay reporting because tcp disconnected
uint32_t lastDelayedTcpReports = 0; // last time we delayed
uint16_t keepAliveTimeout = 200;
uint32_t lastKeepAlive;
uint32_t lastReconnectTry;
int reconnects = 0;
#endif
// function declaraions
void clearInput();
void restoreFromEEPROM();
void CmdSaveToEEPROM();
void CmdInterval();
void CmdThreshold();
void CmdAdd ();
void CmdRemove();
void CmdShow();
void CmdHello();
void CmdWait();
void CmdDevVerbose();
void CmdKeepAlive();
void CmdQuit();
void printWifiState(Print *Out);
void handleInput(char c);
/* ESP8266 pins that are typically ok to use
* (some might be set to FF (disallowed) because they are used
* as reset, serial, led or other things on most boards)
* maps printed pin numbers (aPin) to gpio pin numbers (via their macros if available)
* 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 */
#if defined(ESP8266) // ESP 8266 variables and definitions
#define MAX_PIN 7 // max number of pins that can be defined
#define MAX_HIST 20 // 20 history entries for ESP boards (can be increased)
#define MAX_APIN 18
const uint8_t PROGMEM digital_pin_to_gpio_PGM[] = {
D0, D1, D2, FF, // D0=16, D1=5, D2=4
FF, D5, D6, D7, // D5=14, D6 is my preferred pin for IR out, D7 for LED out
FF, FF, FF, FF,
FF, FF, FF, FF,
FF, A0 // A0 is gpio pin 17
};
/* ESP32 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 */
#elif defined(ESP32) // ESP32 variables and definitions
#define MAX_PIN 12
#define MAX_HIST 100 // 100 history entries for ESP boards (can be increased)
#define MAX_APIN 40
#if defined(TFT_DISPLAY) // TTGO T-Display
const uint8_t PROGMEM digital_pin_to_gpio_PGM[] = {
FF, FF, FF, FF, // pwm at boot, debug, LED, high at boot
04, FF, FF, FF, // 4 is ok, pwm at boot,
FF, FF, FF, FF, // 6-11 is flash
FF, FF, FF, FF, // 12-15 are used for JTAG
FF, 17, FF, FF, // only 17 is free. 16,18 and 19 for display
FF, 21, 22, FF, // 21-22 avaliable, 23 for display
FF, 25, 26, 27, // 25-26 avaliable, use 27 as irOut
FF, FF, FF, FF,
32, 33, 34, 35, // 32-35 avaliable (34/35 input only, 35 is right button)
36, FF, FF, 39}; // 36 is A0, is 39 avaliable but also input only
#else // normal ESP32 Devboard
const uint8_t PROGMEM digital_pin_to_gpio_PGM[] = {
FF, FF, FF, FF, // pwm at boot, debug, LED, high at boot
04, FF, FF, FF, // 4 is ok, pwm at boot,
FF, FF, FF, FF, // 6-11 is flash
FF, FF, FF, FF, // 12 is used at boot, 12-15 for JTAG, otherwise 13 is ok, 14/15 output pwm
16, 17, 18, 19,
FF, 21, 22, 23, // 21-23 avaliable
FF, 25, 26, 27, // 25-26 avaliable, use 27 as irOut
FF, FF, FF, FF,
32, 33, 34, 35, // 32-35 avaliable (34/35 input only)
36, FF, FF, 39}; // 36 is A0, 39 is avaliable but also input only
#endif
#elif defined(__AVR_ATmega328P__) // Arduino Nano
#define MAX_HIST 20 // 20 history entries for arduino boards
#define MAX_PIN 12 // max 20 counting pins at the same time
#define MAX_APIN 22
const uint8_t PROGMEM digital_pin_to_gpio_PGM[] = {
FF, FF, 2, 3, // 2 is typically ir out for analog,
4, 5, 6, 7,
8, 9, 10, 11,
12, 13, A0, A1, // 12 often is led out
A2, A3, A4, A5,
A6, A7 // A7 is typically analog in for ir
};
uint8_t pinIndexMap[MAX_APIN]; // map needed by 328p isr to map back from aPin to pinIndex in pinData
uint8_t firstPin[] = {8, 14, 0}; // first and last pin at port PB, PC and PD for arduino uno/nano
uint8_t lastPin[] = {13, 19, 7}; // not really needed. Instead check bit position (or bit != 0)
volatile uint8_t *port_to_pcmask[] = {&PCMSK0, &PCMSK1, &PCMSK2}; // Pin change mask for each port on arduino
volatile uint8_t PCintLast[3]; // last PIN States at io port to detect pin changes in arduino ISR
#endif // end of Nano / Uno specific stuff
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 - initialized in setup()
uint32_t lastTimeMillis; // millis at last show time
uint8_t enableHistory; // new flag to control collecting and reporting pin history
uint8_t enableSerialEcho; // echo tcp output on serial
uint8_t enablePinDebug; // show digital pin changes
uint8_t enableAnalogDebug; // show analog level sampling
uint8_t enableDevTime; // show device Time every hour
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 if timeDiff is less and intervalMax not over
uint16_t countMin = 2; // continue counting if count is less than this and intervalMax not over
uint8_t ledOutPin; // todo: not implemented yet
uint32_t lastReportCall;
#if defined(TFT_DISPLAY)
uint32_t lastPrintFlowCall;
#endif
typedef struct pinData {
// pin configuration data
uint8_t pinName; // printed pin Number for user input / output
uint8_t pulseWidthMin; // minimal pulse length in millis for filtering
uint8_t pulseLevel; // rising (1)/ falling (0) // only one bit needed
uint8_t pullupFlag; // 1 for pullup // only one bit needed
uint8_t analogFlag; // 1 for analog // only one bit needed
// counting data
volatile uint32_t counter; // counter for pulses
volatile uint32_t rejectCounter; // counter for rejected pulses (width too small)
volatile uint32_t intervalStart; // time of first impulse in interval
volatile uint32_t intervalEnd; // time of last impulse in interval
volatile uint32_t pulseWidthSum; // sum of all pulse widthes during interval (for average)
volatile uint8_t counterIgn; // counts first pulse that marks begin of the very first interval
// isr internal states
volatile uint8_t initialized; // set if first pulse has ben seen to start interval
volatile uint32_t lastChange; // millis at last level change (for measuring pulse length)
volatile uint8_t lastLevel; // level of input at last interrupt // only one bit needed
volatile uint8_t lastLongLevel; // last level that was longer than pulseWidthMin // only bit
// reporting data
uint32_t lastCount; // counter at last report (to get the delta count)
uint16_t lastRejCount; // reject counter at last report (to get the delta count)
uint32_t lastReport; // millis at last report to find out when maxInterval is over
uint8_t reportSequence; // sequence number for reports
uint8_t lastDebugLevel; // for debug output when a pin state changes
} pinData_t;
pinData_t pinData[MAX_PIN];
uint8_t maxPinIndex = 0; // the next available index (= number of indices used)
typedef struct analogData {
pinData_t *inPinData; // pointer to pinData structure for input pin (to call doCount)
uint8_t inPinName; // printed pin Number for user input / output (optinal here?)
uint8_t outPinName; // printed pin number to use for ir (convert using our macro)
uint16_t thresholdMin; // measurement for 0 level
uint16_t thresholdMax; // measurement for 1
uint8_t triggerState; // which level was it so far
uint16_t sumOff = 0; // sum of measured values during light off
uint16_t sumOn = 0; // sum of measured values during light on
uint16_t avgCnt = 0; // counter for average during one level
uint32_t avgSum = 0; // sum for average during one level
} analogData_t;
#define MAX_ANALOG 2
analogData_t analogData[MAX_ANALOG];
uint8_t maxAnalogIndex = 0; // the next available index
typedef struct histData {
volatile uint16_t seq; // history sequence number
volatile uint8_t pin; // pin for this entry
volatile uint8_t level; // gap/signal level for this entry
volatile uint16_t aLvl; // average analog level for this entry
volatile uint32_t time; // time for this entry
volatile uint32_t len; // time that this level was held
volatile char act; // action (count, reject, ...) as one char
} histData_t;
histData_t histData[MAX_HIST];
volatile uint8_t histIndex; // pointer to next entry in history ring
volatile uint16_t histNextSeq; // next seq number to use
uint16_t histLastOut; // seqnuence of last entry already reported
uint32_t commandData[MAX_INPUT_NUM]; // input data over serial port or network
uint8_t commandDataPointer = 0; // index pointer to next input value
uint8_t commandDataSize = 0; // number of input values specified in commandData array
char commandLetter; // the actual command letter
uint32_t commandValue = 0; // the current value for input function
uint32_t analogReadLast; // millis() at last analog read
uint32_t analogReadWait; // millis() during state machine
uint16_t analogReadInterval = 50; // interval at which to read analog values (miliseconds)
uint8_t analogReadState = 0; // to keep track of switching LED on/off, measuring etc.
uint8_t analogReadAmp = 3; // amplification for display
uint8_t analogReadSamples = 4; // samples to take with the light off - max 16 so sum can be an int 16
uint8_t analogReadCount = 0; // counter for sampling
uint32_t analogCallLast = 0; // last analog read call for debugging delays
#define MAX_UNIT 5 // 4 characters and a trailing zero
typedef struct printData {
uint8_t pin; // pin number that this unit information is for
uint32_t pulsesPerUnit; // number of pulses counted per unit
uint32_t pulsesPerUnitDiv; // divisor for ppu
char unit[MAX_UNIT]; // unit e.g. "l" or "kWh"
uint32_t flowUnitFactor; // to get from secounds to minutes or hours as desired
char flowUnit[MAX_UNIT]; // flow unit e.g. "7/h" or "W"
uint32_t intervalStart;
uint32_t lastCount;
} printData_t;
printData_t printData;
void initPinVars(pinData_t *pd, uint32_t now) {
uint8_t level = 0;
pd->pinName = FF; // inactive
pd->initialized = false; // no pulse seen yet
pd->pulseWidthMin = 0; // min pulse length
pd->counter = 0; // counters to 0
pd->counterIgn = 0;
pd->lastCount = 0;
pd->rejectCounter = 0;
pd->lastRejCount = 0;
pd->intervalStart = now; // time vars
pd->intervalEnd = now;
pd->lastChange = now;
pd->lastReport = now;
pd->reportSequence = 0;
if (!pd->analogFlag)
level = digitalRead(pin2GPIO(pd->pinName));
pd->lastLevel = level;
pd->lastLongLevel = level;
pd->lastDebugLevel = level; // for debug output
}
void initHistVars() {
histIndex = 0;
histNextSeq = 1;
for (uint8_t hIdx=0; hIdx < MAX_HIST; hIdx++) {
histData_t *hd = &histData[hIdx];
hd->seq = 0;
hd->pin = FF;
hd->level = 0;
hd->time = 0;
hd->len = 0;
hd->act = ' ';
}
}
void initialize() {
uint32_t now = millis();
bootTime = now; // with boot / reset time
bootWraps = millisWraps;
lastReportCall = now; // time for first output after intervalMin from now
analogReadLast = now;
histLastOut = 0;
lastTimeMillis = now;
enableHistory = 0; // pin change history
enableSerialEcho = 0; // echo tcp output on serial
enablePinDebug = 0; // show digital pin changes
enableAnalogDebug = 0; // analog changes
enableDevTime = 0; // show device time
maxPinIndex = 0;
maxAnalogIndex = 0;
analogReadState = 0;
initHistVars();
for (uint8_t pinIndex=0; pinIndex < MAX_PIN; pinIndex++)
initPinVars(&pinData[pinIndex], now); // not necessary but initialize anyway
#if defined TFT_DISPLAY
displayMode = 1;
printData.intervalStart = now;
printData.lastCount = 0;
lastPrintFlowCall = now;
#endif
#if defined(__AVR_ATmega328P__)
for (uint8_t aPin=0; aPin < MAX_APIN; aPin++)
pinIndexMap[aPin] = FF;
#endif
#if defined(WifiSupport)
lastKeepAlive = now;
serverStarted = false;
lastReconnectTry = now;
delayedTcpReports = 0;
lastDelayedTcpReports = 0;
reconnects = 0;
#endif
restoreFromEEPROM();
clearInput();
}
// search pinData for aPin and return the pinIndex
uint8_t findInPin (uint8_t aPin) {
for (uint8_t pinIndex = 0; pinIndex < maxPinIndex; pinIndex++) {
if (pinData[pinIndex].pinName == aPin) return pinIndex;
}
return FF;
}
// search analogData for oPin and return the analogIndex
uint8_t findOutPin (uint8_t oPin) {
for (uint8_t analogIndex = 0; analogIndex < maxAnalogIndex; analogIndex++) {
if (analogData[analogIndex].outPinName == oPin) return analogIndex;
}
return FF;
}
// check if pin is allowed
bool checkPin (uint8_t aPin) {
if (aPin >= MAX_APIN) return false; // pin number too big
if (pin2GPIO(aPin) == FF) return false; // pin is not allowed at all
return true;
}
// find analogData for given pinData
uint8_t findAnalogData(pinData_t *pd) {
for (uint8_t aIdx = 0; aIdx < maxAnalogIndex; aIdx++) {
if (analogData[aIdx].inPinData == pd)
return aIdx;
}
return FF;
}
void clearInput() {
commandDataPointer = 0;
commandDataSize = 0;
commandValue = 0;
for (uint8_t i=0; i < MAX_INPUT_NUM; i++)
commandData[i] = 0;
}
void PrintErrorMsg() {
Output->print(F("Error: "));
Output->print(F("command "));
Output->print(commandLetter);
Output->print(F(" "));
}
void PrintPinErrorMsg(uint8_t aPin) {
PrintErrorMsg();
Output->print(F("Illegal pin specification "));
Output->println(aPin);
}
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__)
#if defined (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"));
#elif defined(ESP32)
Output->print(F("ESP32"));
#else
Output->print(F("UNKNOWN"));
#endif
#if defined (ARDUINO_BOARD)
Output->print(F(" "));
Output->print(F(ARDUINO_BOARD));
#endif
#if defined (TFT_DISPLAY)
Output->print(F(" with display"));
#endif
Output->print(F(" compiled "));
Output->print(F(__DATE__ " " __TIME__));
#if defined(ESP8266)
#if defined (ARDUINO_ESP8266_RELEASE)
Output->print(F(" with core version "));
Output->print(F(ARDUINO_ESP8266_RELEASE));
#endif
#if defined (ARDUINO_ESP32_RELEASE)
Output->print(F(" with core version "));
Output->print(F(ARDUINO_ESP32_RELEASE));
#endif
#endif
}
void printPinChangeDebug(pinData_t *pd, uint8_t pinState) {
pd->lastDebugLevel = pinState;
Output->print(F("M pin ")); Output->print(pd->pinName);
Output->print(F(" changed to ")); Output->print(pinState);
Output->print(F(", histIdx ")); Output->print(histIndex);
Output->print(F(" seq ")); Output->print(histData[histIndex].seq);
Output->print(F(", count ")); Output->print(pd->counter);
Output->print(F(", reject ")); Output->print(pd->rejectCounter);
Output->println();
}
bool checkVal(uint8_t index, uint16_t min, uint16_t max = 0, bool doErr = true) {
if (index >= commandDataSize) {
if (doErr) {
PrintErrorMsg();
Output->print(F("missing parameter number ")); Output->println(index+1);
}
return false;
}
if (commandData[index] < min || (max > 0 && commandData[index] > max)) {
PrintErrorMsg();
Output->print(F("parameter number ")); Output->print(index+1);
Output->print(F(" (value ")); Output->print(commandData[index]);
Output->println(F(") is out of bounds"));
return false;
}
return true;
}
/*
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 doCount(pinData_t *pd, uint8_t level, uint16_t analogLevel=0) {
uint32_t now = millis();
uint32_t len = now - pd->lastChange;
char act = ' ';
if (len < pd->pulseWidthMin) { // len is too short
if (pd->lastLevel == pd->pulseLevel) { // if change to gap level (we just had a too short pulse)
act = 'R'; // -> reject
pd->rejectCounter++;
} else { // change to pulse level (we just had a too short gap)
act = 'X'; // -> reject gap / set action to X (gap too short)
}
} else { // len is big enough
if (pd->lastLevel != pd->pulseLevel) { // edge fits pulse start, level is pulse, before was gap
act = 'G'; // -> gap (even if betw. was a spike that we ignored)
} else { // edge is a change to gap, level is now gap
if (pd->lastLongLevel != pd->pulseLevel) { // last valid level was gap, now pulse
act = 'C'; // -> count
pd->counter++;
pd->intervalEnd = now; // remember time in case pulse is last in the interval
if (!pd->initialized) {
pd->intervalStart = now; // if first impulse on this pin -> start interval now
printData.intervalStart = now;
pd->initialized = true; // and start counting the next impulse (counter is 0)
pd->counterIgn++; // count ignored for diff because defines start of intv
}
pd->pulseWidthSum += len; // for average calculation
} else { // last valid level was pulse -> now another valid pulse
act = 'P'; // -> pulse was already counted, only short drop inbetween
pd->pulseWidthSum += len; // for average calculation
}
}
pd->lastLongLevel = pd->lastLevel; // remember this valid level as lastLongLevel
}
if (enableHistory) {
if (++histIndex >= MAX_HIST) histIndex = 0;
histData_t *hd = &histData[histIndex]; // write pin history
hd->seq = histNextSeq++; // fhem side detects wrapping
hd->pin = pd->pinName;
hd->time = pd->lastChange;
hd->level = pd->lastLevel;
hd->aLvl = analogLevel;
hd->len = len;
hd->act = act;
}
pd->lastChange = now;
pd->lastLevel = level;
}
/* Interrupt handlers and their installation */
#if defined(ESP8266) || defined(ESP32)
void IRAM_ATTR ESPISR(void* arg) { // common ISR for all Pins on ESP
// ESP32 now also defines IRAM_ATTR as ICACHE_RAM_ATTR
pinData_t *pd = (pinData_t *)arg;
doCount(pd, digitalRead(pin2GPIO(pd->pinName)));
}
#endif
/* Add a pin to be handled */
uint8_t AddPinChangeInterrupt(uint8_t pinIndex) {
uint8_t aPin = pinData[pinIndex].pinName;
uint8_t rPin = pin2GPIO(aPin);
#if defined(ESP8266) || defined(ESP32)
attachInterruptArg(digitalPinToInterrupt(rPin), ESPISR, &pinData[pinIndex], CHANGE);
#elif defined(__AVR_ATmega328P__)
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;
pinIndexMap[aPin] = pinIndex;
port -= 2; // from port (PB, PC, PD) to index in our array
PCintLast[port] = *portInputRegister(port+2); // save current inut state to detect changes in isr
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
#endif
return 1;
}
/* Remove a pin to be handled */
uint8_t RemovePinChangeInterrupt(uint8_t pinIndex) {
uint8_t aPin = pinData[pinIndex].pinName;
uint8_t rPin = pin2GPIO(aPin);
#if defined(ESP8266) || defined(ESP32)
detachInterrupt(digitalPinToInterrupt(rPin));
#elif defined(__AVR_ATmega328P__)
volatile uint8_t *pcmask;
uint8_t bitM = digitalPinToBitMask(rPin);
uint8_t port = digitalPinToPort(rPin);
if (port == NOT_A_PORT)
return 0;
pinIndexMap[aPin] = FF;
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);
}
#endif
return 1;
}
#if defined(__AVR_ATmega328P__)
/*
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;
// 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
// the printed pin numbers for the ports are sequential, starting with 8, 14 and 0 which we keep in an array
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 - todo: until bit == 0 ??
if (delta & bit) { // did this pin change?
pinIndex = pinIndexMap[aPin];
if (pinIndex != FF) // shound not be necessary but test anyway
doCount (&pinData[pinIndex], ((curr & bit) > 0)); // do the counting, history and so on
}
bit = bit << 1; // shift mask to go to next bit
}
}
ISR(PCINT0_vect) {
PCint(0);
}
ISR(PCINT1_vect) {
PCint(1);
}
ISR(PCINT2_vect) {
PCint(2);
}
#endif
void printAvailablePins() {
Output->print(F("C"));
boolean first = true;
for (uint8_t aPin=0; aPin < MAX_APIN; aPin++)
if (pin2GPIO(aPin) != FF) {
if (!first)
Output->print(F(","));
first = false;
Output->print(aPin); // show available pins
}
Output->println();
}
void printTime(uint32_t now) {
Output->print(F("N")); Output->print(now);
Output->print(F(",")); Output->print(millisWraps);
Output->print(F("B")); Output->print(bootTime);
Output->print(F(",")); Output->print(bootWraps);
Output->println();
}
void printIntervals() {
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->print(countMin);
Output->print(F(",")); Output->print(analogReadInterval);
Output->print(F(",")); Output->print(analogReadSamples);
Output->println();
}
void printVerboseFlags() {
Output->print(F("V")); Output->print(enableHistory);
Output->print(F(",")); Output->print(enableSerialEcho);
Output->print(F(",")); Output->print(enablePinDebug);
Output->print(F(",")); Output->print(enableAnalogDebug);
Output->print(F(",")); Output->print(enableDevTime);
Output->println();
}
#ifdef TFT_DISPLAY
void printUnitConfig() {
Output->print(F("U")); Output->print(printData.pin);
Output->print(F(",")); Output->print(printData.pulsesPerUnit);
Output->print(F(",")); Output->print(printData.pulsesPerUnitDiv);
Output->print(F(",")); Output->print(printData.unit);
Output->print(F(",")); Output->print(printData.flowUnitFactor);
Output->print(F(",")); Output->print(printData.flowUnit);
Output->println();
}
#endif
void printPinConfig(pinData_t *pd) {
Output->print(F("P")); Output->print(pd->pinName);
switch (pd->pulseLevel) {
case 1: Output->print(F("r")); break;
case 0: Output->print(F("f")); break;
default: Output->print(F("-")); break;
}
if (pd->pullupFlag)
Output->print(F("p"));
Output->print(F(" m")); Output->print(pd->pulseWidthMin);
if (pd->analogFlag) {
uint8_t analogIndex = findAnalogData(pd); // reuse or create analog entry for aPin with oPin
if (analogIndex != FF) {
analogData_t *ad = &analogData[analogIndex];
Output->print(F("out")); Output->print(ad->outPinName);
Output->print(F("t")); Output->print(ad->thresholdMin);
Output->print(F("/")); Output->print(ad->thresholdMax);
}
}
}
#if defined(TFT_DISPLAY)
void printTFTFlow(uint32_t timeDiff, uint32_t countDiff) {
char fs[10];
float flow;
if (timeDiff != 0 && printData.pulsesPerUnit != 0 && printData.pulsesPerUnitDiv != 0) {
flow = ((float)countDiff * 1000 * printData.flowUnitFactor)
/ (timeDiff * (printData.pulsesPerUnit / printData.pulsesPerUnitDiv));
} else {
flow = 0;
}
tft.fillRect(0,64,TFT_HEIGHT, TFT_WIDTH-64, TFT_BLACK);
tft.setCursor(0, 64);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
if (flow < 10000 && flow > -1000) {
sprintf (fs, "%07.2f", flow);
tft.setTextFont(7);
tft.setTextSize(1);
tft.print(fs);
tft.setTextFont(2);
tft.setTextSize(1);
tft.print(printData.flowUnit);
} else {
tft.setTextFont(4);
tft.setTextSize(1);
tft.print(F("flow too big"));
}
}
#endif
void printPinHistory(pinData_t *pd, uint32_t now) {
histData_t *hd;
uint8_t start = (histIndex + 2) % MAX_HIST; // start two after current slot in ring buffer
uint8_t count = 0;
uint8_t first = 0;
uint8_t pinName = pd->pinName;
for (uint8_t i = 0; i < MAX_HIST; i++) {
hd = &histData[(start + i) % MAX_HIST]; // entry relative to start in ring
if (hd->pin == pinName && ((long int)hd->seq - histLastOut) > 0) { // are there entries for this pin at all?
count++;
first = i;
break;
}
}
if (!count) {
// Output->println (F("M No Pin History"));
return;
}
Output->print (F("H")); Output->print(pinName); // printed pin number
Output->print (F(" "));
for (uint8_t i = first; i < MAX_HIST; i++) {
hd = &histData[(start + i) % MAX_HIST];
if (hd->pin == pinName && ((long int)hd->seq - histLastOut) > 0) { // include ignored drops / spikes
if (i != first) Output->print (F(", "));
Output->print (hd->seq); // sequence
Output->print (F(",")); Output->print ((long) (hd->time - now)); // time when level started
Output->print (F(":")); Output->print (hd->len); // length
Output->print (F("@")); Output->print (hd->level); // level (0/1)
if (pd->analogFlag) {
Output->print (F("/")); Output->print (hd->aLvl); // analog level
}
Output->print (hd->act); // action
histLastOut = hd->seq;
}
}
Output->println();
}
/*
lastCount and lastRejCount are only modified here (counters 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 printPinCounter(pinData_t *pd, 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 = pd->intervalStart; // start of interval (typically first pulse)
endT = pd->intervalEnd; // end of interval (last unless not enough)
count = pd->counter; // get current counter (counts all pulses
rejCount = pd->rejectCounter;
countIgn = pd->counterIgn; // pulses that mark the beginning of an interval
widthSum = pd->pulseWidthSum;
interrupts();
timeDiff = endT - startT; // time between first and last impulse
realDiff = count - pd->lastCount; // pulses during intervall
countDiff = realDiff - countIgn; // ignore forst pulse after device restart
rejDiff = rejCount - pd->lastRejCount;
if (!showOnly) { // real reporting sets the interval borders new
if((now - pd->lastReport) > intervalMax) {
// intervalMax is over
if ((countDiff >= countMin) && (timeDiff > intervalSml) && (intervalMin != intervalMax)) {
// normal procedure
noInterrupts(); // vars could be modified in ISR as well
pd->intervalStart = 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
pd->intervalStart = now; // start a new interval for next report now
pd->intervalEnd = now; // no last impulse, use now instead
interrupts();
timeDiff = now - startT; // special handling - calculation ends now
}
} else if (((now - pd->lastReport) > intervalMin)
&& (countDiff >= countMin) && (timeDiff > intervalSml)) {
// minInterval has elapsed and other conditions are ok
noInterrupts(); // vars could be modified in ISR as well
pd->intervalStart = endT; // time of last also time of first in next
interrupts();
} else {
return; // intervalMin and Max not over - dont report yet
}
noInterrupts();
pd->counterIgn = 0;
pd->pulseWidthSum = 0;
interrupts();
pd->lastCount = count; // remember current count for next interval
pd->lastRejCount = rejCount;
pd->lastReport = now; // remember when we reported
#if defined(WifiSupport)
delayedTcpReports = 0;
#endif
pd->reportSequence++;
} else {
Output->print(F("D")); // prefix with "D" if showOnly so it is not parsed as report line
}
Output->print(F("R")); Output->print(pd->pinName);
Output->print(F("C")); Output->print(count);
Output->print(F("D")); Output->print(countDiff);
Output->print(F("/")); Output->print(realDiff);
Output->print(F("T")); Output->print(timeDiff);
//Output->print(F("N")); Output->print(now); // moved to its own line N... dependent on new flag that time should be reported at all
//Output->print(F(",")); Output->print(millisWraps);
Output->print(F("X")); Output->print(rejDiff);
if (!showOnly) {
Output->print(F("S")); Output->print(pd->reportSequence);
}
if (countDiff > 0) {
Output->print(F("A")); Output->print(widthSum / countDiff);
}
Output->println();
#if defined(WifiSupport)
if (enableSerialEcho && tcpMode && !showOnly) {
Serial.print(F("D reported pin ")); Serial.print(pd->pinName);
Serial.print(F(" sequence ")); Serial.print(pd->reportSequence);
Serial.println(F(" over tcp "));
}
#endif
#if defined(TFT_DISPLAY)
if (displayMode == 1) {
if (lineCount < 4) {
tft.setTextFont(2);
tft.setTextSize(1);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setCursor(0,48+16*lineCount); // todo: how can this be separated from report? (timing)
tft.print(F("R")); tft.print(pd->pinName);
tft.print(F(" C")); tft.print(count);
tft.print(F(" D")); tft.print(countDiff);
tft.print(F("/")); tft.print(realDiff);
tft.print(F(" T")); tft.print(timeDiff);
tft.print(F(" X")); tft.print(rejDiff);
lineCount++;
}
} else if (displayMode == 3) {
if (pd->pinName == printData.pin)
printTFTFlow(timeDiff, countDiff);
}
#endif
}
#if defined(WifiSupport)
// called from show and new connectinn
void printWifiState(Print *Out) {
if (WiFi.status() == WL_CONNECTED) {
Out->print(F("D Connected to ")); Out->print(WiFi.SSID());
Out->print(F(" with IP ")); Out->print(WiFi.localIP());
Out->print(F(" RSSI ")); Out->print(WiFi.RSSI());
Out->println();
} else {
Out->println(F("D Wifi not connected"));
}
#if defined(TFT_DISPLAY)
if (displayMode > 0) {
//tft.fillRect(0, 0, TFT_HEIGHT, 31, TFT_BLACK); // oberen Teil löschen
tft.fillScreen(TFT_BLACK); // clear Wifi manager output as well
tft.setTextFont(2);
tft.setTextSize(1);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setCursor(0, 0);
if (WiFi.status() == WL_CONNECTED) {
tft.print(F("Connected to ")); tft.print(WiFi.SSID());
tft.setCursor(0, 16);
tft.print("IP "); tft.print(WiFi.localIP());
tft.print(F(" RSSI ")); tft.print(WiFi.RSSI());
} else {
tft.print(F("Wifi not Connected"));
tft.setCursor(0, 16);
tft.print(F("Retry "));
tft.print(reconnects);
}
}
#endif
}
#endif
/*
report count and time for pins that are between min and max interval
*/
bool reportDue(uint32_t now) {
if((now - lastReportCall) > intervalMin) // works fine when millis wraps.
return true; // intervalMin is over
else
for (uint8_t pinIndex=0; pinIndex < maxPinIndex; pinIndex++)
if((now - pinData[pinIndex].lastReport) > intervalMax)
return true; // active pin has not been reported for langer than intervalMax
return false;
}
bool delayIfDisconnected(uint32_t now) {
#if defined(WifiSupport)
if (tcpMode && !TCPconnected && (delayedTcpReports < 3)) {
if(delayedTcpReports == 0 || ((now - lastDelayedTcpReports) > 30000)) {
if (enableSerialEcho) {
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.print(now - lastDelayedTcpReports);
Serial.println();
}
delayedTcpReports++;
lastDelayedTcpReports = now;
return true; // continue delaying after message
} else
return true; // another 30 secs not over yet, just continue delaying without message
}
#endif
return false; // not TCP mode -> no delays anyway
}
#if defined(TFT_DISPLAY)
void handleTFTReport(uint32_t now) {
lineCount = 0; // for mode 0 where lines of pin reports are printed
if (displayMode == 2 && ((now - lastPrintFlowCall) > 5000)) {
lastPrintFlowCall = now;
uint8_t pinIndex = findInPin(printData.pin);
if (pinIndex == FF) // not used so far
return;
pinData_t *pd = &pinData[pinIndex]; // pinData entry to work with
uint32_t countDiff = pd->counter - printData.lastCount - pd->counterIgn; // ignore first pulse after device restart
uint32_t timeDiff = pd->intervalEnd - printData.intervalStart; // time between first and last impulse
printData.lastCount = pd->counter;
printData.intervalStart = pd->intervalEnd;
printTFTFlow(timeDiff, countDiff);
}
}
#endif
void report() {
uint32_t now = millis();
#if defined(TFT_DISPLAY)
handleTFTReport(now);
#endif
if (!reportDue(now)) return;
if (delayIfDisconnected(now)) return;
for (uint8_t pinIndex=0; pinIndex < maxPinIndex; pinIndex++) { // go through all observed pins as pinIndex
pinData_t *pd = &pinData[pinIndex];
printPinCounter (pd, false, now); // report pin counters if necessary
if (enableHistory)
printPinHistory(pd, now); // show pin history todo: call outside of report
}
lastReportCall = now; // check again after intervalMin or if intervalMax is over for a pin
}
void updateEEPROM(int &address, char value) {
if( EEPROM.read(address) != value){
EEPROM.write(address, value);
}
address++;
}
void save(int &address, uint16_t n, bool komma=true) {
char b[6];
sprintf(b, "%u", n);
uint8_t len = strlen(b);
if (len < 6)
for (uint8_t i=0; i<len; i++)
updateEEPROM(address, b[i]);
if(komma)
updateEEPROM(address, ',');
}
// store a string as sequence of text coded integers
void saveStr(int &address, char *s, uint8_t komma=1) {
uint8_t cCount = 0;
uint8_t byteNum = 0;
uint16_t val;
bool first = true;
while (cCount < MAX_UNIT && *(s+cCount) != 0) {
if (byteNum) {
val = (*(s+cCount) << 8) + val; // second char -> add as high byte
if (!first) updateEEPROM(address, ',');
save(address, val, false);
first = false;
byteNum = 0;
} else {
val = *(s+cCount); // first char
byteNum++;
}
cCount++;
}
if (byteNum) {
if (!first) updateEEPROM(address, ',');
save(address, val, false);
if (!val) {
updateEEPROM(address, ',');
save(address, 0, false);
}
}
if (komma)
updateEEPROM(address, ',');
}
void CmdSaveToEEPROM() {
int a = 0;
updateEEPROM(a, 'C');
updateEEPROM(a, 'f');
updateEEPROM(a, '2');
save(a,intervalMin / 1000); save(a,intervalMax / 1000);
save(a,intervalSml / 1000); save(a,countMin);
save(a,analogReadInterval); save(a,analogReadSamples,false);
updateEEPROM(a, 'i');
save(a,enableHistory); save(a,enableSerialEcho);
save(a,enablePinDebug); save(a,enableAnalogDebug);
save(a,enableDevTime,false);
updateEEPROM(a, 'v');
#if defined TFT_DISPLAY
save(a,printData.pin);
save(a,printData.pulsesPerUnit); save(a,printData.pulsesPerUnitDiv);
saveStr(a,printData.unit);
save(a,printData.flowUnitFactor); saveStr(a,printData.flowUnit,false);
updateEEPROM(a, 'u');
#endif
for (uint8_t pinIndex=0; pinIndex < maxPinIndex; pinIndex++) {
pinData_t *pd = &pinData[pinIndex];
uint8_t analogIndex = findAnalogData(pd); // find analog entry (if exists)
if (pd->analogFlag && analogIndex != FF) {
analogData_t *ad = &analogData[analogIndex];
save(a,pd->pinName); save(a,pd->pulseLevel ? 3:2);
save(a,pd->pullupFlag); save(a,pd->pulseWidthMin);
save(a,ad->outPinName); save(a,ad->thresholdMin); save(a,ad->thresholdMax,false);
updateEEPROM(a, 'a');
} else {
save(a,pd->pinName); save(a,pd->pulseLevel ? 3:2);
save(a,pd->pullupFlag); save(a,pd->pulseWidthMin,false);
updateEEPROM(a, 'a');
}
}
updateEEPROM(a, 0);
#if defined(ESP8266) || defined(ESP32)
EEPROM.commit();
#endif
Output->println(F("D config saved"));
}
void printEEPROM() {
int address = 0;
char c;
if (EEPROM.read(address) != 'C' || EEPROM.read(address+1) != 'f' || EEPROM.read(address+2) != '2') {
Output->println(F("D no config in EEPROM"));
return;
}
address = 3;
Output->print(F("D EEPROM Config: "));
while (address < 512 && (c = EEPROM.read(address++)) != 0) {
Output->print(c);
}
Output->println();
}
void restoreFromEEPROM() {
int address = 0;
char c;
if (EEPROM.read(address) != 'C' || EEPROM.read(address+1) != 'f' || EEPROM.read(address+2) != '2') {
Output->println(F("M no config in EEPROM"));
return;
}
address = 3;
Output->println(F("M restoring config from EEPROM: "));
while (address < 512 && (c = EEPROM.read(address++)) != 0) {
handleInput(c);
Output->print(c);
}
Output->println();
#if defined(TFT_DISPLAY)
if (displayMode == 1) {
tft.setTextFont(2);
tft.setTextSize(1);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setCursor(0, 32);
tft.print(F("Pin config loaded"));
}
#endif
}
void CmdInterval() {
if (!checkVal(0,1,3600)) return; // index 0 is interval min, min 1, max 3600 (1h)
if (!checkVal(1,1,3600)) return; // index 1 is interval max, min 1, max 3600 (1h)
if (!checkVal(2,0,3600)) return; // index 2 is interval small, max 3600 (1h)
if (!checkVal(3,0,100)) return; // index 3 is count min, max 100
intervalMin = commandData[0] * 1000; // convert to miliseconds
intervalMax = commandData[1] * 1000;
intervalSml = commandData[2] * 1000;
countMin = commandData[3];
if (checkVal(4,0,10000,false)) { // index 4 is optional analog read interval
analogReadInterval = (int)commandData[4];
}
if (checkVal(5,1,100,false)) { // index 5 is optional analog read samples
analogReadSamples = (uint8_t)commandData[5];
}
printIntervals();
}
// for backward compatibility - set thresholds for all analog pins at the same time
void CmdThreshold() {
if (!checkVal(0,1,1023)) return; // analog threshold min, min 1, max 1023
if (!checkVal(1,1,1023)) return; // analog threshold max, min 1, max 1023
for (uint8_t aIdx = 0; aIdx < maxAnalogIndex; aIdx++)
analogData[aIdx].thresholdMin = commandData[0];
for (uint8_t aIdx = 0; aIdx < maxAnalogIndex; aIdx++)
analogData[aIdx].thresholdMax = commandData[1];
Output->print(F("D analog thresholds set to ")); Output->print(commandData[0]);
Output->print(F(" ")); Output->println(commandData[1]);
}
/*
handle add command.
*/
void CmdAdd () {
uint8_t aPin = commandData[0]; // commandData[0] is pin number
if ( commandData[0] > 255 // more than one byte?
|| !checkPin(aPin) // is pin allowed?
|| (ledOutPin && aPin == ledOutPin) // is pin used as led out?
|| findOutPin(aPin) != FF) { // is pin used as output so far?
PrintPinErrorMsg(aPin);
return;
}
uint8_t rPin = pin2GPIO(aPin); // get gpio pin number for later use
uint8_t pinIndex = findInPin(aPin); // is pin already in use counting?
if (pinIndex == FF) { // not used so far
pinIndex = maxPinIndex; // use next available index
initPinVars(&pinData[pinIndex], millis());
}
pinData_t *pd = &pinData[pinIndex]; // pinData entry to work with
if (!checkVal(1,2,3)) return; // index 1 is pulse level, min 2, max 3, std error
pd->pulseLevel = (commandData[1] == 3); // 2 = falling -> pulseLevel 0, 3 = rising -> pulseLevel 1
pd->pinName = aPin; // save printed pin number for reporting
if (checkVal(2,0,1, false)) // index 2 is pullup, optional, no error message if omitted
pd->pullupFlag = commandData[2]; // as defined
else pd->pullupFlag = 0; // default to no pullup
if (!checkVal(3,1,1000, false))
commandData[3] = 2; // value 3 is min length, optional. Assume default 2 if invalid
pd->pulseWidthMin = commandData[3];
if (checkVal(4,1,MAX_APIN, false)) { // 4 - analog out pin number given
uint8_t oPin = commandData[4];
if (!checkPin(oPin) || (ledOutPin && oPin == ledOutPin) || findInPin(oPin) != FF) {
PrintPinErrorMsg(oPin); // pin alreday used as input or ledout (analog out would be ok)
return;
}
uint8_t analogIndex = findAnalogData(pd); // reuse or create analog entry for aPin with oPin
if (analogIndex == FF) {
if (maxAnalogIndex < MAX_ANALOG) {
analogIndex = maxAnalogIndex++; // new entry -> initialize, inc used analog pins
analogData_t *ad = &analogData[analogIndex];
ad->thresholdMin = 0;
ad->thresholdMax = 0;
ad->inPinData = pd;
} else {
PrintErrorMsg();
Output->println(F("too many analog pins"));
return;
}
}
pd->analogFlag = 1; // set analog flag
analogData_t *ad = &analogData[analogIndex];
ad->inPinName = aPin;
ad->outPinName = oPin;
pinMode (rPin, INPUT);
pinMode (pin2GPIO(oPin), OUTPUT);
if (checkVal(5,1,1023)) // analog threshold min, min 1, max 1023
ad->thresholdMin = commandData[5];
if (checkVal(6,1,1023)) // analog threshold max, min 1, max 1023
ad->thresholdMax = commandData[6];
} else {
if (pd->pullupFlag) pinMode (rPin, INPUT_PULLUP);
else pinMode (rPin, INPUT);
AddPinChangeInterrupt(pinIndex);
}
pd->pinName = aPin;
if (pinIndex == maxPinIndex) maxPinIndex++; // increment used entries in pinData
Output->print(F("D defined ")); printPinConfig(pd); Output->println();
}
/*
handle remove command.
*/
void CmdRemove() {
uint8_t aPin = commandData[0]; // commandData[0] is pin number
uint8_t pinIndex = findInPin(aPin);
if (commandData[0] > 255 // too big
|| pinIndex == FF) // pin is currently not used as input
return;
pinData_t *pd = &pinData[pinIndex]; // config entry to work with
if (!pd->analogFlag) {
RemovePinChangeInterrupt(pinIndex);
} else {
// find analog data entry
uint8_t analogIndex = FF;
for (uint8_t aIdx = 0; aIdx < maxAnalogIndex; aIdx++) {
if (analogData[aIdx].inPinData == pd) {
analogIndex = aIdx;
break;
}
}
if (analogIndex != FF) {
// copy idx +1 and following up and clear the last
// then dec max
for (uint8_t rIdx = analogIndex; (rIdx + 1) < maxAnalogIndex; rIdx++)
analogData[rIdx] = analogData[rIdx + 1];
analogData_t *lastAd = &analogData[--maxAnalogIndex];
lastAd->inPinData = 0;
lastAd->inPinName = 0;
lastAd->outPinName = 0;
}
}
for (uint8_t rIdx = pinIndex; (rIdx + 1) < maxPinIndex; rIdx++) {
if (!pinData[rIdx].analogFlag) {
RemovePinChangeInterrupt(rIdx+1);
}
pinData[rIdx] = pinData[rIdx + 1]; // move pinData entries after pinIndex up
if (!pinData[rIdx].analogFlag) {
AddPinChangeInterrupt(rIdx);
}
if (pd->analogFlag) {
for (uint8_t aIdx = 0; aIdx < maxAnalogIndex; aIdx++) {
if (analogData[aIdx].inPinData == &pinData[rIdx + 1])
analogData[aIdx].inPinData = &pinData[rIdx];
}
}
}
pinData_t *lastPd = &pinData[--maxPinIndex]; // clear the last one that is now no longer needed
initPinVars(lastPd, 0);
Output->print(F("D removed ")); Output->println(aPin);
}
void CmdClear () {
uint8_t aPin = commandData[0]; // commandData[0] is pin number
uint8_t pinIndex = findInPin(aPin); // is pin in use?
if (commandData[0] > 255 // too big
|| pinIndex == FF) { // not found
PrintPinErrorMsg(aPin); // no active pin
return;
}
pinData_t *pd = &pinData[pinIndex];
uint32_t now = millis();
pd->lastReport = now;
pd->lastCount = 0;
pd->lastRejCount = 0;
noInterrupts();
pd->intervalStart = pd->intervalEnd; // time of last pulse is now time of first pulse in this new interval
pd->counter = 0; // counters to 0
pd->counterIgn = 0;
pd->rejectCounter = 0;
pd->pulseWidthSum = 0;
interrupts();
Output->print(F("M cleared ")); Output->println(aPin);
}
/* give status report in between if requested over serial input */
void CmdShow() {
uint32_t now = millis();
Output->println();
Output->print(F("D Status: ")); printVersionMsg();
Output->println();
#if defined(WifiSupport)
printWifiState(Output); // print line with IP and RSSI
#endif
printIntervals();
printVerboseFlags();
#if defined(TFT_DISPLAY)
printUnitConfig();
#endif
for (uint8_t pinIndex=0; pinIndex < maxPinIndex; pinIndex++) {
pinData_t *pd = &pinData[pinIndex];
printPinConfig(pd);
Output->print(F(", "));
printPinCounter(pd, true, now);
}
printEEPROM();
Output->print(F("D Next report in ")); // Fhem side recognizes this as end of show command
Output->print(lastReportCall + intervalMin - millis());
Output->println(F(" milliseconds"));
}
void CmdHello() {
uint32_t now = millis();
Output->println();
printVersionMsg(); Output->println(F(" Hello"));
printTime(now); // print line with device time, e.g. N456,2 B789,3
printAvailablePins(); // print line with available pins, e.g. C2,3,4 ...
printIntervals(); // print line with configured intervals, e.g. I30,60,2,2 ...
printVerboseFlags(); // print line with configured verbose flags, e.g. V0,1,...
for (uint8_t pinIndex=0; pinIndex < maxPinIndex; pinIndex++) { // go through all observed pins as pinIndex
printPinConfig(&pinData[pinIndex]);
Output->println();
}
}
void CmdLED() {
// set monitor ouput LED and a max of 5 pins to monitor
// 12,2,3l would set pin 12 as LED and pins 2 and 3 to create LED switches when their level changes
// dont allow 0, check if already used as led, if not checkPin
uint8_t aPin = commandData[0]; // commandData[0] is led pin number
if (commandData[0] > 255 || !aPin || !checkPin(aPin)
|| findOutPin(aPin) != FF || findInPin(aPin) != FF) {
PrintPinErrorMsg(aPin); // illegal pin or already used for in/other output
return;
}
// save pin and validate other params / pins to monitor
}
uint8_t stuffString(char* str, uint8_t offset) {
uint8_t sIndex = 0;
uint8_t byteVal;
uint8_t cIndex;
for (cIndex = offset; (cIndex < MAX_INPUT_NUM) && (sIndex < MAX_UNIT-1); cIndex++) {
uint16_t value = commandData[cIndex];
for (uint8_t byteNum = 0; (byteNum < 2) && (sIndex < MAX_UNIT-1); byteNum++) {
byteVal = value & 0xFF;
if (!byteVal) break;
value = value >> 8;
str[sIndex++] = byteVal;
}
if (!byteVal) break;
}
str[sIndex] = 0;
return cIndex + 1;
}
void CmdUnits() {
#if defined(TFT_DISPLAY)
// set pulses per unit for a pin and a unit for printing consumption per minute / per hour
uint8_t next = 1;
uint8_t aPin = commandData[0]; // commandData[0] is pin number to display
if (commandData[0] > 255
|| !aPin || !checkPin(aPin)) { // is pin allowed?
PrintPinErrorMsg(aPin);
return;
}
printData.pin = aPin;
if (!checkVal(next,1)) return; // index 1 is pulsesPerUnit
printData.pulsesPerUnit = commandData[next++];
if (!checkVal(next,1)) return; // index 2 is pulsesPerUnitDiv
printData.pulsesPerUnitDiv = commandData[next++];
next = stuffString(printData.unit, next); // unit string starting at commandData[3]
if (!checkVal(next,1)) return; // index after first strng is flowUnitFactor
printData.flowUnitFactor = commandData[next++];
next = stuffString(printData.flowUnit, next); // unit string starting at commandData[2]
printUnitConfig();
#endif
}
void CmdDevVerbose() {
if (!checkVal(0,0,1)) return; // index 0 is enableHistory
if (!checkVal(1,0,10)) return; // index 1 is enableSerialEcho
if (!checkVal(2,0,1)) return; // index 2 is enablePinDebug
if (!checkVal(3,0,10)) return; // index 3 is enableAnalogDebug
if (!checkVal(4,0,1)) return; // index 3 is enableDevTime
enableHistory = (uint8_t)commandData[0];
enableSerialEcho = (uint8_t)commandData[1];
enablePinDebug = (uint8_t)commandData[2];
enableAnalogDebug = (uint8_t)commandData[3];
enableDevTime = (uint8_t)commandData[4];
printVerboseFlags();
}
void CmdKeepAlive() {
if (commandData[0] == 1 && commandDataSize > 0) {
Output->print(F("A"));
#if defined(WifiSupport)
uint32_t now = millis();
Output->print(F("R")); Output->print(WiFi.RSSI());
if (commandData[0] == 1 && commandDataSize > 0 && commandDataSize < 3 && Client1.connected()) {
tcpMode = true;
if (commandDataSize == 2) {
keepAliveTimeout = commandData[1]; // timeout in seconds (on ESP side we use it times 3)
} else {
keepAliveTimeout = 200; // *3 gives 10 minutes if nothing sent (should not happen)
}
}
lastKeepAlive = now;
#endif
Output->println();
}
}
void CmdQuit() {
#if defined(WifiSupport)
if (Client1.connected()) {
Client1.println(F("closing connection"));
Client1.stop();
tcpMode = false;
if (enableSerialEcho)
Serial.println(F("D TCP connection closed after Q command"));
return;
}
#endif
Serial.println(F("D TCP not connected"));
}
void CmdRestart() {
#if defined(ESP8266) || defined(ESP32)
ESP.restart();
#endif
// beim uno / nano ohne wlan einfach nur die Variablen zurücksetzen.
initialize();
}
void CmdWifiReset() {
#if defined(WifiSupport)
wifiManager.resetSettings();
#endif
}
/* theoretical issue: when connected via Wifi and serial at the same time, input might overlap */
void handleInput(char c) {
if (c == ',') { // Komma input, last value is finished
if (commandDataPointer < (MAX_INPUT_NUM - 1)) {
commandData[commandDataPointer++] = commandValue;
commandValue = 0;
}
}
else if ('0' <= c && c <= '9') { // digit input
commandValue = 10 * commandValue + c - '0';
commandDataSize = commandDataPointer + 1;
}
else if ('a' <= c && c <= 'z') { // letter input is command
commandLetter = c;
if (commandDataPointer < (MAX_INPUT_NUM - 1)) {
commandData[commandDataPointer] = commandValue;
}
if (enableSerialEcho > 1) {
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.println(commandDataSize);
}
switch (c) {
case 'a': // add a pin
CmdAdd(); break;
case 'c':
CmdClear(); break; // clear a counter for pin specifid
case 'd': // delete a pin
CmdRemove(); break;
case 'e': // save to EEPROM
CmdSaveToEEPROM(); break;
case 'h': // hello
CmdHello(); break;
case 'i': // interval
CmdInterval(); break;
case 'k': // keep alive
CmdKeepAlive(); break;
case 'l' : // led feedback
CmdLED(); break;
case 'q': // quit
CmdQuit(); break;
case 'r': // reset / restart
CmdRestart(); break;
case 's': // show
CmdShow(); break;
case 't': // thresholds for analog pins (legacy - moved to a)
CmdThreshold(); break;
case 'u': // pulses per unit for local output
CmdUnits(); break;
case 'v': // dev verbose
CmdDevVerbose(); break;
case 'w': // reset wifi settings
CmdWifiReset(); break;
default:
break;
}
clearInput();
//Serial.println(F("D End of command"));
}
}
void debugDigitalPinChanges() {
for (uint8_t pinIndex=0; pinIndex < maxPinIndex; pinIndex++) {
pinData_t *pd = &pinData[pinIndex];
if (!pd->analogFlag) {
uint8_t pinState = digitalRead(pin2GPIO(pd->pinName));
if (pinState != pd->lastDebugLevel)
printPinChangeDebug(pd, pinState);
}
}
}
#if defined(WifiSupport)
void printWifiStatus() {
Serial.print(F("D Wifi 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());
}
}
#if !defined(STATIC_WIFI)
void configModeCallback (WiFiManager *myWiFiManager) {
Serial.println("Entered config mode");
Serial.println(WiFi.softAPIP());
//if you used auto generated SSID, print it
Serial.println(myWiFiManager->getConfigPortalSSID());
#if defined(TFT_DISPLAY)
tft.fillScreen(TFT_BLUE);
tft.setTextFont(4);
tft.setTextSize(1);
tft.setTextColor(TFT_YELLOW , TFT_BLUE);
tft.setCursor(0,32); // x, y, TFT_HEIGHT=240, TFT_WIDTH=135
tft.print(F("Entered config mode "));
tft.setCursor(0, 100);
tft.print(myWiFiManager->getConfigPortalSSID());
#endif
}
#endif
void initWifi() {
TCPconnected = false;
WiFi.mode(WIFI_STA);
WiFi.setAutoConnect(true);
WiFi.setAutoReconnect(true);
delay (1000);
if (WiFi.status() != WL_CONNECTED) {
#if defined(STATIC_WIFI)
#if defined(TFT_DISPLAY)
tft.setTextFont(2);
tft.setTextSize(1);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setCursor(0,0);
tft.print(F("Conecting WiFi to ")); tft.print(ssid);
#endif
uint8_t counter = 0;
Serial.print(F("D Connecting WiFi to ")); Serial.println(ssid);
WiFi.begin(ssid, password); // connect with compiled strings
while (WiFi.status() != WL_CONNECTED) {
printWifiStatus();
delay(1000);
counter++;
if (counter > 2) {
#if defined(TFT_DISPLAY)
tft.setCursor(0,0);
tft.print(F("Retry conecting WiFi to")); tft.print(ssid);
#endif
Serial.println(F("D Retry connecting WiFi"));
WiFi.begin(ssid, password); // restart connecting
delay (1000);
counter = 0; // do forever until connected with retries
}
}
#else // connect using WifiManager if auto reconnect not successful
#if defined(TFT_DISPLAY)
tft.setCursor(0,0);
tft.print(F("try reconecting WiFi"));
#endif
Serial.println(F("D Try reconnecting WiFi"));
WiFi.begin();
delay(1000);
printWifiStatus();
if (WiFi.status() != WL_CONNECTED) {
#if defined(TFT_DISPLAY)
tft.setCursor(0,0);
tft.print(F("Retry reconecting WiFi"));
#endif
Serial.println(F("D Retry reconnecting WiFi"));
WiFi.begin();
delay (1000);
}
wifiManager.setConfigPortalBlocking(false);
wifiManager.setAPCallback(configModeCallback); //set callback that gets called when connecting to previous WiFi fails, and enters Access Point mode
wifiManager.autoConnect();
#endif
}
}
void handleConnections() {
IPAddress remote;
uint32_t now = millis();
if (WiFi.status() == WL_CONNECTED && !serverStarted) { // first call after connected
printWifiState (&Serial);
Server.begin(); // Start the TCP server
Serial.println(F("D TCP Server started"));
serverStarted = true; // remember we did this already
}
if (WiFi.status() != WL_CONNECTED) { // WiFi lost
if (serverStarted) { // first time we notice ...
printWifiState (&Serial); // show that we lost Wifi
Server.close(); // Stop the TCP server
serverStarted = false; // so printWifiState will be called after reconnect
Serial.println(F("D Wifi lost - TCP Server stopped"));
lastReconnectTry = now; // don't try to manually reconnect right now - Framework should do it
}
if ((now - lastReconnectTry) > 5000) {
printWifiState (&Serial); // show that we lost Wifi
Serial.println(F("D Try reconnecting WiFi"));
WiFi.begin(); // try to force reconnect (framework seems to not do it sometimes...)
lastReconnectTry = now;
reconnects++;
}
}
if (Client1.connected()) {
if (Client1.available()) {
handleInput(Client1.read()); // input from TCP
//Serial.println(F("D new Input over TCP"));
}
now = millis(); // get millis again to avoid keepalive timeout directly after first k command
if((now - lastKeepAlive) > (keepAliveTimeout*3000)) { // check keepAlive timout (* 3 secs)
Serial.println(F("D no keepalive - close"));
Output->println(F("D no keepalive - close"));
//Output->print(F("D timeout was "));
//Output->print(keepAliveTimeout);
//Output->print(F(" last at "));
//Output->print(lastKeepAlive);
//Output->print(F(" now "));
//Output->print(now);
Output->println();
Client1.stop(); // close connection due to keepalive timeout
}
Client2 = Server.available(); // refuse further connect attempts
if (Client2) {
remote = Client2.remoteIP();
Client2.println(F("conn busy"));
Client2.stop();
if (enableSerialEcho) {
Serial.print(F("D 2nd conn from ")); Serial.print(remote);
Serial.println(F(" rejected"));
}
}
} else { // no client connected right now
if (TCPconnected) { // client used to be connected, now disconnected
TCPconnected = false;
Output = &Serial;
if (enableSerialEcho)
Serial.println(F("D conn lost")); // report disconnect via serial
}
Client1 = Server.available();
if (Client1) { // accepting new connection
remote = Client1.remoteIP();
if (enableSerialEcho) {
Serial.print(F("D new conn from ")); Serial.print(remote);
Serial.println(F(" accepted"));
}
TCPconnected = true; // remember connection in case we loose it
Output = &Client1;
lastKeepAlive = now;
CmdHello(); // say hello to client
}
}
}
#endif
void handleTime() {
uint32_t now = millis();
if (now < lastMillis) millisWraps++;
lastMillis = now;
if (enableDevTime)
if ((long int)now - lastTimeMillis > (int32_t)60 * 60 * 1000) { // every 60 minutes
printTime(now);
lastTimeMillis = now;
}
}
void detectTrigger(analogData_t *ad, unsigned int val) {
uint32_t average;
uint8_t nextState = ad->triggerState; // initialize next trigger level to be the same as the old one
if (val > ad->thresholdMax) {
nextState = 1; // if above upper threshold then change to 1
} else if (val < ad->thresholdMin) {
nextState = 0; // if below lower threshold then change to 0
}
if (ad->avgCnt <= 65000) {
ad->avgSum += val;
ad->avgCnt++;
}
if (nextState != ad->triggerState) { // if level has changed
average = ad->avgSum / ad->avgCnt;
if (average > 4000) average = 4000;
doCount (ad->inPinData, nextState, (uint16_t) average);
ad->avgSum = 0;
ad->avgCnt = 0;
if (enablePinDebug)
printPinChangeDebug(ad->inPinData, nextState);
}
ad->triggerState = nextState; // save new level
}
void readAnalog() {
uint32_t now = millis();
char line[26];
uint16_t waitForOff = 0;
uint16_t waitForOn = 1;
//if (analogCallLast && (now - analogCallLast) > 5) {
// Output->print(F("D readAnalog call delay "));
// Output->println(now - analogCallLast);
//}
analogCallLast = now;
if ((now - analogReadLast) > analogReadInterval) { // time for next analog read?
switch (analogReadState) {
case 0: // initial state
for (uint8_t aIdx = 0; aIdx < maxAnalogIndex; aIdx++) {
analogData_t *ad = &analogData[aIdx];
digitalWrite(pin2GPIO(ad->outPinName) , LOW); // make sure IR LED is off for first read
analogReadCount = 0; // initialize sums and counter
ad->sumOff = 0; ad->sumOn = 0;
}
analogReadState = 1;
analogReadWait = millis();
break;
case 1: // wait before measuring
if ((now - analogReadWait) < waitForOff) // todo: wait in microseconds with micros() function, make witForOff configurable
return;
analogReadState = 2;
break;
case 2:
for (uint8_t aIdx = 0; aIdx < maxAnalogIndex; aIdx++) {
analogData_t *ad = &analogData[aIdx];
uint16_t sample = analogRead(pin2GPIO(ad->inPinName)); // read the analog in value (off)
ad->sumOff += sample;
if (enableAnalogDebug > 2) {
Output->print(F("M "));
Output->print(millis());
Output->print(F(", 0, "));
Output->print(sample);
Output->print(F(", "));
Output->print(analogReadCount);
Output->println();
}
}
if (++analogReadCount < analogReadSamples)
break;
for (uint8_t aIdx = 0; aIdx < maxAnalogIndex; aIdx++) {
analogData_t *ad = &analogData[aIdx];
digitalWrite(pin2GPIO(ad->outPinName), HIGH); // turn IR LED on
}
analogReadCount = 0;
analogReadState = 4;
analogReadWait = millis();
break;
case 4: // wait again before measuring
if ((now - analogReadWait) < waitForOn)
return;
analogReadState = 5;
break;
case 5:
int sensorDiff;
for (uint8_t aIdx = 0; aIdx < maxAnalogIndex; aIdx++) {
analogData_t *ad = &analogData[aIdx];
uint16_t sample = analogRead(pin2GPIO(ad->inPinName)); // read the analog in value (on)
ad->sumOn += sample;
if (enableAnalogDebug > 2) {
Output->print(F("M "));
Output->print(millis());
Output->print(F(", 1, "));
Output->print(sample);
Output->print(F(", "));
Output->print(analogReadCount);
Output->println();
}
}
if (++analogReadCount < analogReadSamples)
break;
for (uint8_t aIdx = 0; aIdx < maxAnalogIndex; aIdx++) {
analogData_t *ad = &analogData[aIdx];
digitalWrite(pin2GPIO(ad->outPinName) , LOW); // turn IR LED off again
sensorDiff = (ad->sumOn / analogReadSamples) - (ad->sumOff / analogReadSamples);
if (sensorDiff < 0) sensorDiff = 0;
if (sensorDiff > 4096) sensorDiff = 4096;
detectTrigger (ad, sensorDiff); // calculate level with triggers
if (enableAnalogDebug > 1) {
sprintf(line, "L%2d: %4d, %4d -> % 4d", ad->inPinName,
ad->sumOn / analogReadSamples, ad->sumOff / analogReadSamples, sensorDiff);
Output->println(line);
} else if (enableAnalogDebug) {
sprintf(line, "L%2d: % 4d", ad->inPinName, sensorDiff);
Output->println(line);
}
#if defined(TFT_DISPLAY)
if (displayMode == 1) {
int len = sensorDiff * analogReadAmp * TFT_HEIGHT / 4096;
tft.fillRect(0,TFT_WIDTH-10-(10*aIdx),len,10, TFT_YELLOW);
tft.fillRect(len,TFT_WIDTH-10-(10*aIdx),TFT_HEIGHT-len,10, TFT_BLACK);
}
#endif
}
analogReadState = 0;
analogReadLast = now;
break;
default:
analogReadState = 0;
Output->println(F("Error: wrong analog read state"));
break;
}
}
}
#if defined (TFT_DISPLAY)
/*
void ButtonHandlerChanged(Button2& btn) {
Serial.println("changed");
}
*/
void ButtonHandlerTap(Button2& btn) {
displayMode++;
if (displayMode > displayModeMax) displayMode = 0;
tft.setTextFont(2);
tft.setTextSize(1);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.fillRect(0, 32, TFT_HEIGHT, TFT_WIDTH-32, TFT_BLACK); // unteren Teil löschen
//tft.fillScreen(TFT_BLUE);
// test 240 (lange Seite) 135 (kurze)
//tft.fillRect(0, 32, TFT_HEIGHT-1, TFT_WIDTH-32-1, TFT_BLACK); // lässt einen 1-Pixel blauen Rand
tft.setCursor(TFT_HEIGHT-20, TFT_WIDTH-25); // x, y, TFT_HEIGHT=240, TFT_WIDTH=135
tft.print(displayMode);
/*
Serial.print(F("D displayMode "));
Serial.println(displayMode);
*/
if (displayMode == 2) {
tft.setCursor(0, 32);
tft.setTextFont(4);
tft.setTextSize(1);
tft.setTextColor(TFT_RED, TFT_BLACK);
tft.print(F("consumption 5s"));
} else if (displayMode == 3) {
tft.setCursor(0, 32);
tft.setTextFont(4);
tft.setTextSize(1);
tft.setTextColor(TFT_RED, TFT_BLACK);
tft.print(F("consumption dyn"));
}
}
#endif
void setup() {
#if defined (TFT_DISPLAY)
tft.init();
tft.fillScreen(TFT_BLACK);
tft.setRotation(1); // 1=Querformat (0 wäre Hochformat, TFT_Width / TFT_Height beziehen sich offenbar auf Hochformat)
tft.setTextFont(2);
tft.setTextSize(1);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
//buttonA.setChangedHandler(ButtonHandlerChanged);
//buttonA.setPressedHandler(pressed);
//buttonA.setReleasedHandler(released);
// captures any type of click, longpress or shortpress
buttonA.setTapHandler(ButtonHandlerTap);
//buttonA.setClickHandler(click);
//buttonA.setLongClickHandler(longClick);
//buttonA.setDoubleClickHandler(doubleClick);
//buttonA.setTripleClickHandler(tripleClick);
#endif
Serial.begin(SERIAL_SPEED); // initialize serial
#if defined(ESP8266) || defined (ESP32)
EEPROM.begin(100);
#endif
delay (500);
interrupts();
Serial.println();
Output = &Serial;
millisWraps = 0;
lastMillis = millis();
initialize();
CmdHello(); // started message to serial
#if defined(WifiSupport)
initWifi();
ArduinoOTA
.onStart([]() {
String type;
if (ArduinoOTA.getCommand() == U_FLASH)
type = "sketch";
else // U_SPIFFS
type = "filesystem";
// NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
Serial.println("Start updating " + type);
});
ArduinoOTA.onEnd([]() {
Serial.println("\nEnd");
});
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
});
ArduinoOTA.onError([](ota_error_t error) {
Serial.printf("Error[%u]: ", error);
if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
else if (error == OTA_END_ERROR) Serial.println("End Failed");
});
ArduinoOTA.begin();
#endif
}
/* Main Loop */
void loop() {
#if defined(WifiSupport)
#if !defined(STATIC_WIFI)
wifiManager.process(); // process config portal
#endif
ArduinoOTA.handle();
handleConnections(); // new TCP connection or input over TCP
#endif
#if defined (TFT_DISPLAY)
buttonA.loop(); // check for button events
#endif
handleTime(); // check if millis() wrapped (for reporting)
if (Serial.available()) handleInput(Serial.read()); // input over serial
readAnalog(); // analog measurements
if (enablePinDebug) debugDigitalPinChanges();
report(); // report counts if due
}