From 951d0d43831e67695fad5ae7b9cdb99e4d76eacc Mon Sep 17 00:00:00 2001 From: Andrey Klimov Date: Tue, 7 Jan 2020 00:15:13 +0300 Subject: [PATCH] single, double, tripple click, longpress, repeat logic for contact inputs are added --- lighthub/inputs.cpp | 403 ++++++++++++++++++++++++++++++++++++++++---- lighthub/inputs.h | 48 +++++- lighthub/mqtt.cpp | 18 -- lighthub/options.h | 2 +- lighthub/utils.cpp | 8 + lighthub/utils.h | 1 + 6 files changed, 425 insertions(+), 55 deletions(-) delete mode 100644 lighthub/mqtt.cpp diff --git a/lighthub/inputs.cpp b/lighthub/inputs.cpp index 9b87cd9..9bc4db5 100644 --- a/lighthub/inputs.cpp +++ b/lighthub/inputs.cpp @@ -113,7 +113,7 @@ void Input::Parse() void Input::setup() { if (!isValid() || (!root)) return; - +/* #ifndef CSSHDC_DISABLE if (inType == IN_CCS811) { @@ -127,6 +127,51 @@ if (!isValid() || (!root)) return; } // TODO rest types setup #endif +*/ +store->aslong=0; +uint8_t inputPinMode = INPUT; //if IN_ACTIVE_HIGH +switch (inType) +{ + case IN_PUSH_ON: + case IN_PUSH_TOGGLE : + inputPinMode = INPUT_PULLUP; + + case IN_PUSH_ON | IN_ACTIVE_HIGH: + case IN_PUSH_TOGGLE | IN_ACTIVE_HIGH: + pinMode(pin, inputPinMode); + + store->state=IS_IDLE; + break; + + case IN_ANALOG: + inputPinMode = INPUT_PULLUP; + + case IN_ANALOG | IN_ACTIVE_HIGH: + pinMode(pin, inputPinMode); + break; + + case IN_DHT22: + case IN_COUNTER: + case IN_UPTIME: + break; + + #ifndef CSSHDC_DISABLE + case IN_CCS811: + { + in_ccs811 ccs811(this); + ccs811.Setup(); + } + break; + + case IN_HDC1080: + { + in_hdc1080 hdc1080(this); + hdc1080.Setup(); + } + break; + #endif + +} //switch } @@ -140,17 +185,18 @@ if (!isValid()) return -1; switch (cause) { case CHECK_INPUT: //Fast polling + case CHECK_INTERRUPT: //Realtime polling switch (inType) { case IN_PUSH_ON: case IN_PUSH_ON | IN_ACTIVE_HIGH: case IN_PUSH_TOGGLE : case IN_PUSH_TOGGLE | IN_ACTIVE_HIGH: - contactPoll(); + contactPoll(cause); break; case IN_ANALOG: case IN_ANALOG | IN_ACTIVE_HIGH: - analogPoll(); + analogPoll(cause); break; // No fast polling @@ -406,43 +452,331 @@ void Input::dht22Poll() { } #endif -void Input::contactPoll() { - boolean currentInputState; +bool Input::executeCommand(aJsonObject* cmd, char* defCmd) +{ + if (!cmd) return false; + + switch (cmd->type) + { + case aJson_String: //legacy - no action + break; + case aJson_Array: //array - recursive iterate + { + aJsonObject * command = cmd->child; + while (command) + { + executeCommand(command,defCmd); + command = command->next; + } + } + break; + case aJson_Object: + { + aJsonObject *item = aJson.getObjectItem(cmd, "item"); + aJsonObject *icmd = aJson.getObjectItem(cmd, "icmd"); + aJsonObject *ecmd = aJson.getObjectItem(cmd, "ecmd"); + aJsonObject *emit = aJson.getObjectItem(cmd, "emit"); + + char * itemCommand; + if(icmd) itemCommand = icmd->valuestring; + else itemCommand = defCmd; + + char * emitCommand; + if(ecmd) emitCommand = ecmd->valuestring; + else emitCommand = defCmd; + + debugSerial << F("IN:") << (pin) << F(" : ") <valuestring<< F(" -> ")<valuestring<< F(" -> ")<valuestring,sizeof(addrstr)); +if (mqttClient.connected() && !ethernetIdleCount) +{ + if (!strchr(addrstr,'/')) setTopic(addrstr,sizeof(addrstr),T_OUT,emit->valuestring); + mqttClient.publish(addrstr, emitCommand , true); +} +} +} // emit + if (item && itemCommand) { + //debugSerial <valuestring <valuestring); + if (it.isValid()) it.Ctrl(itemCommand, true); + } +return true; +} +default: +return false; +} //switch type +} + +// TODO Polling via timed interrupt with CHECK_INTERRUPT cause +bool Input::changeState(uint8_t newState, short cause) +{ +if (!inputObj || !store) return false; + +if (newState == IS_REQSTATE) + if (store->delayedState && cause != CHECK_INTERRUPT) + { + // Requested delayed change State and safe moment + newState=store->reqState; //Retrieve requested state + debugSerial<delayedState) + return false; //State changing is postponed already (( giving up + +aJsonObject *cmd = NULL; + + switch (newState) + { + case IS_IDLE: + switch (store->state) + { + case IS_RELEASED: //click + cmd = aJson.getObjectItem(inputObj, "click"); + break; + case IS_RELEASED2: //doubleclick + cmd = aJson.getObjectItem(inputObj, "dclick"); + break; + case IS_PRESSED3: //tripple click + cmd = aJson.getObjectItem(inputObj, "tclick"); + break; + case IS_WAITPRESS: //do nothing + break; + default: //rcmd + cmd = aJson.getObjectItem(inputObj, "rcmd"); + ; + } + break; + case IS_PRESSED: //scmd + cmd = aJson.getObjectItem(inputObj, "scmd"); + break; + case IS_PRESSED2: //scmd2 + cmd = aJson.getObjectItem(inputObj, "scmd2"); + break; + case IS_PRESSED3: //scmd3 + cmd = aJson.getObjectItem(inputObj, "scmd3"); + break; + + case IS_RELEASED: //rcmd + case IS_WAITPRESS: + case IS_RELEASED2: + cmd = aJson.getObjectItem(inputObj, "rcmd"); + + break; + case IS_LONG: //lcmd + cmd = aJson.getObjectItem(inputObj, "lcmd"); + break; + case IS_REPEAT: //rpcmd + cmd = aJson.getObjectItem(inputObj, "rpcmd"); + break; + case IS_LONG2: //lcmd2 + cmd = aJson.getObjectItem(inputObj, "lcmd2"); + break; + case IS_REPEAT2: //rpcmd2 + cmd = aJson.getObjectItem(inputObj, "rpcmd2"); + break; + case IS_LONG3: //lcmd3 + cmd = aJson.getObjectItem(inputObj, "lcmd3"); + break; + case IS_REPEAT3: //rpcmd3 + cmd = aJson.getObjectItem(inputObj, "rpcmd3"); + break; + + } + if (!cmd) + { + store->state=newState; + return true; //nothing to do + } + if (cause != CHECK_INTERRUPT) + { + executeCommand(cmd); + //Executed + store->state=newState; + store->delayedState=false; + return true; + } + else + { + //Postpone actual execution + store->reqState=store->state; + store->delayedState=true; + return true; + } + +} + +void Input::contactPoll(short cause) { + boolean currentInputState; + if (!store) return; + + changeState(IS_REQSTATE,cause); //Check for postponed states transitions + + + uint8_t inputOnLevel; + if (inType & IN_ACTIVE_HIGH) inputOnLevel = HIGH; + else inputOnLevel = LOW; + + currentInputState = (digitalRead(pin) == inputOnLevel); - if (currentInputState != store->currentValue) // value changed + +switch (store->state) //Timer based transitions +{ + case IS_PRESSED: + if (isTimeOver(store->timestamp16,millis() & 0xFFFF,T_LONG,0xFFFF)) changeState(IS_LONG, cause); + break; + + case IS_LONG: + if (isTimeOver(store->timestamp16,millis() & 0xFFFF,T_RPT,0xFFFF)) + { + changeState(IS_REPEAT, cause); + store->timestamp16 = millis() & 0xFFFF; + } + break; + + case IS_REPEAT: + if (isTimeOver(store->timestamp16,millis() & 0xFFFF,T_RPT_PULSE,0xFFFF)) + { + changeState(IS_REPEAT, cause); + store->timestamp16 = millis() & 0xFFFF; + } + break; + + case IS_PRESSED2: + if (isTimeOver(store->timestamp16,millis() & 0xFFFF,T_LONG,0xFFFF)) changeState(IS_LONG2, cause); + break; + + case IS_LONG2: + if (isTimeOver(store->timestamp16,millis() & 0xFFFF,T_RPT,0xFFFF)) + { + changeState(IS_REPEAT2, cause); + store->timestamp16 = millis() & 0xFFFF; + } + break; + + case IS_REPEAT2: + if (isTimeOver(store->timestamp16,millis() & 0xFFFF,T_RPT_PULSE,0xFFFF)) + { + changeState(IS_REPEAT2, cause); + store->timestamp16 = millis() & 0xFFFF; + } + break; + + case IS_PRESSED3: + if (isTimeOver(store->timestamp16,millis() & 0xFFFF,T_LONG,0xFFFF)) changeState(IS_LONG3, cause); + break; + + case IS_LONG3: + if (isTimeOver(store->timestamp16,millis() & 0xFFFF,T_RPT,0xFFFF)) + { + changeState(IS_REPEAT3, cause); + store->timestamp16 = millis() & 0xFFFF; + } + break; + + case IS_REPEAT3: + if (isTimeOver(store->timestamp16,millis() & 0xFFFF,T_RPT_PULSE,0xFFFF)) + { + changeState(IS_REPEAT3, cause); + store->timestamp16 = millis() & 0xFFFF; + } + break; + + case IS_RELEASED: + case IS_RELEASED2: + case IS_WAITPRESS: + + + if (isTimeOver(store->timestamp16,millis() & 0xFFFF,T_IDLE,0xFFFF)) changeState(IS_IDLE, cause); + break; +} + + if (currentInputState != store->lastValue) // value changed { if (store->bounce) store->bounce = store->bounce - 1; else //confirmed change { - if (inType & IN_PUSH_TOGGLE) { + store->timestamp16 = millis() & 0xFFFF; //Saving timestamp of changing + + if (inType & IN_PUSH_TOGGLE) { //To refactore if (currentInputState) { //react on leading edge only (change from 0 to 1) store->logicState = !store->logicState; - store->currentValue = currentInputState; + store->lastValue = currentInputState; onContactChanged(store->logicState); } - } else { - store->logicState = currentInputState; - store->currentValue = currentInputState; - onContactChanged(currentInputState); + } else + + { + onContactChanged(currentInputState); //Legacy input - to remove later + + bool res = true; + if (currentInputState) //Button pressed state transitions + + switch (store->state) + { + case IS_IDLE: + res = changeState(IS_PRESSED, cause); + break; + + case IS_RELEASED: + case IS_WAITPRESS: + res = changeState(IS_PRESSED2, cause); + break; + + case IS_RELEASED2: + + res = changeState(IS_PRESSED3, cause); + break; + } + else + switch (store->state) //Button released state transitions + { + case IS_PRESSED: + res = changeState(IS_RELEASED, cause); + break; + + case IS_LONG: + case IS_REPEAT: + res = changeState(IS_WAITPRESS, cause); + break; + + case IS_PRESSED2: + res = changeState(IS_RELEASED2, cause); + break; + + case IS_LONG2: + case IS_REPEAT2: + case IS_LONG3: + case IS_REPEAT3: + case IS_PRESSED3: + res = changeState(IS_IDLE, cause); + break; + } + if (res) { //State changed or postponed + store->logicState = currentInputState; + store->lastValue = currentInputState; + } } // store->currentValue = currentInputState; } @@ -451,23 +785,26 @@ void Input::contactPoll() { } -void Input::analogPoll() { + +void Input::analogPoll(short cause) { int16_t inputVal; int32_t mappedInputVal; // 10x inputVal aJsonObject *inputMap = aJson.getObjectItem(inputObj, "map"); int16_t Noize = ANALOG_NOIZE; short simple = 0; - uint32_t inputPinMode; +// uint32_t inputPinMode; int max=1024*10; int min=0; - +/* if (inType & IN_ACTIVE_HIGH) { inputPinMode = INPUT; } else { inputPinMode = INPUT_PULLUP; } + pinMode(pin, inputPinMode); +*/ inputVal = analogRead(pin); // Mapping if (inputMap && inputMap->type == aJson_Array) @@ -522,11 +859,13 @@ void Input::analogPoll() { void Input::onContactChanged(int newValue) { - debugSerial << F("IN:") << (pin) << F("=") << newValue << endl; + aJsonObject *item = aJson.getObjectItem(inputObj, "item"); + aJsonObject *emit = aJson.getObjectItem(inputObj, "emit"); + if (!item && !emit) return; aJsonObject *scmd = aJson.getObjectItem(inputObj, "scmd"); aJsonObject *rcmd = aJson.getObjectItem(inputObj, "rcmd"); - aJsonObject *emit = aJson.getObjectItem(inputObj, "emit"); + debugSerial << F("LEGACY IN:") << (pin) << F("=") << newValue << endl; if (emit) { #ifdef WITH_DOMOTICZ if (getIdxField()) diff --git a/lighthub/inputs.h b/lighthub/inputs.h index bbb50d9..22005ce 100644 --- a/lighthub/inputs.h +++ b/lighthub/inputs.h @@ -35,6 +35,22 @@ e-mail anklimov@gmail.com #define IN_COUNTER 8 #define IN_UPTIME 16 +#define IS_IDLE 0 +#define IS_PRESSED 1 +#define IS_RELEASED 2 +#define IS_LONG 3 +#define IS_REPEAT 4 +#define IS_WAITPRESS 5 +#define IS_PRESSED2 6 +#define IS_RELEASED2 7 +#define IS_LONG2 8u +#define IS_REPEAT2 9u +#define IS_PRESSED3 10u +#define IS_LONG3 11u +#define IS_REPEAT3 12u +#define IS_REQSTATE 0xFF + + #define SAME_STATE_ATTEMPTS 3 #define ANALOG_STATE_ATTEMPTS 6 @@ -42,6 +58,15 @@ e-mail anklimov@gmail.com #define CHECK_INPUT 1 #define CHECK_SENSOR 2 +#define CHECK_INTERRUPT 3 +#define CHECK_DELAYED 4 + +#define T_LONG 1000 +#define T_IDLE 600 +#define T_RPT 300 +#define T_RPT_PULSE 200 + + // in syntaxis // "pin": { "T":"N", "emit":"out_emit", item:"out_item", "scmd": "ON,OFF,TOGGLE,INCREASE,DECREASE", "rcmd": "ON,OFF,TOGGLE,INCREASE,DECREASE", "rcmd":"repeat_command" } @@ -79,11 +104,24 @@ extern aJsonObject *inputs; typedef union { long int aslong; uint32_t timestamp; + // Analog input structure struct { - int8_t logicState; - int8_t bounce; + uint8_t reserved; + uint8_t reserved2; int16_t currentValue; }; + // Digital input structure + struct { + uint8_t reserved3:1; + uint8_t lastValue:1; + uint8_t logicState:1; + uint8_t delayedState:1; + uint8_t bounce:4; + uint8_t state:4; + uint8_t reqState:4; + uint16_t timestamp16; + + }; } inStore; @@ -121,8 +159,8 @@ public: protected: void Parse(); - void contactPoll(); - void analogPoll(); + void contactPoll(short cause); + void analogPoll(short cause); void dht22Poll(); @@ -140,4 +178,6 @@ protected: bool publishDataToDomoticz(int , aJsonObject *, const char *format, ...); char* getIdxField(); + bool changeState(uint8_t newState, short cause); + bool executeCommand(aJsonObject* cmd, char* defCmd = NULL); }; diff --git a/lighthub/mqtt.cpp b/lighthub/mqtt.cpp deleted file mode 100644 index 0f14c24..0000000 --- a/lighthub/mqtt.cpp +++ /dev/null @@ -1,18 +0,0 @@ -/* -int mqtt::publish(int value) -{}; - -int mqtt::publish(float value) -{ - -}; - -int mqtt::publish(char * value) -{ - char addrstr[MQTT_TOPIC_LENGTH]; - aJsonObject *emit = aJson.getObjectItem(in, "emit"); - strncpy(addrstr,emit->valuestring,sizeof(addrstr)); - if (!strchr(addrstr,'/')) setTopic(addrstr,sizeof(addrstr),T_OUT,emit->valuestring); - if mqttClient.connected() mqttClient.publish(addrstr, value, true); -}; -*/ diff --git a/lighthub/options.h b/lighthub/options.h index 7b962b4..f58996a 100644 --- a/lighthub/options.h +++ b/lighthub/options.h @@ -51,7 +51,7 @@ #define EEPROM_offset EEPROM_offset_NotAlligned + (4 -(EEPROM_offset_NotAlligned & 3)) #ifndef INTERVAL_CHECK_INPUT -#define INTERVAL_CHECK_INPUT 50 +#define INTERVAL_CHECK_INPUT 15 #endif #ifndef INTERVAL_CHECK_SENSOR diff --git a/lighthub/utils.cpp b/lighthub/utils.cpp index 0a63f56..08a3f44 100644 --- a/lighthub/utils.cpp +++ b/lighthub/utils.cpp @@ -504,5 +504,13 @@ RebootFunc(); #endif +bool isTimeOver(uint32_t timestamp, uint32_t currTime, uint32_t time, uint32_t modulo) +{ + uint32_t endTime=(timestamp + time) % modulo; + return ((currTime>endTime) && (currTime endTime) || (currTime