single, double, tripple click, longpress, repeat logic for contact inputs are added

This commit is contained in:
2020-01-07 00:15:13 +03:00
parent 8a3980272e
commit 951d0d4383
6 changed files with 425 additions and 55 deletions

View File

@@ -113,7 +113,7 @@ void Input::Parse()
void Input::setup() void Input::setup()
{ {
if (!isValid() || (!root)) return; if (!isValid() || (!root)) return;
/*
#ifndef CSSHDC_DISABLE #ifndef CSSHDC_DISABLE
if (inType == IN_CCS811) if (inType == IN_CCS811)
{ {
@@ -127,6 +127,51 @@ if (!isValid() || (!root)) return;
} }
// TODO rest types setup // TODO rest types setup
#endif #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) { switch (cause) {
case CHECK_INPUT: //Fast polling case CHECK_INPUT: //Fast polling
case CHECK_INTERRUPT: //Realtime polling
switch (inType) switch (inType)
{ {
case IN_PUSH_ON: case IN_PUSH_ON:
case IN_PUSH_ON | IN_ACTIVE_HIGH: case IN_PUSH_ON | IN_ACTIVE_HIGH:
case IN_PUSH_TOGGLE : case IN_PUSH_TOGGLE :
case IN_PUSH_TOGGLE | IN_ACTIVE_HIGH: case IN_PUSH_TOGGLE | IN_ACTIVE_HIGH:
contactPoll(); contactPoll(cause);
break; break;
case IN_ANALOG: case IN_ANALOG:
case IN_ANALOG | IN_ACTIVE_HIGH: case IN_ANALOG | IN_ACTIVE_HIGH:
analogPoll(); analogPoll(cause);
break; break;
// No fast polling // No fast polling
@@ -406,43 +452,331 @@ void Input::dht22Poll() {
} }
#endif #endif
void Input::contactPoll() { bool Input::executeCommand(aJsonObject* cmd, char* defCmd)
boolean currentInputState; {
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(" : ") <<endl;
if (item) debugSerial << item->valuestring<< F(" -> ")<<itemCommand<<endl;
if (emit) debugSerial << emit->valuestring<< F(" -> ")<<emitCommand<<endl;
if (emit && emitCommand) {
/* /*
#if defined(ARDUINO_ARCH_STM32) TODO implement
WiringPinMode inputPinMode; #ifdef WITH_DOMOTICZ
#endif if (getIdxField())
{ (newValue) ? publishDataToDomoticz(0, emit, "{\"command\":\"switchlight\",\"idx\":%s,\"switchcmd\":\"On\"}",
#if defined(__SAM3X8E__)||defined(ARDUINO_ARCH_AVR)||defined(ARDUINO_ARCH_ESP8266)||defined(ARDUINO_ARCH_ESP32) : publishDataToDomoticz(0,emit,"{\"command\":\"switchlight\",\"idx\":%s,\"switchcmd\":\"Off\"}",getIdxField()); getIdxField())
: publishDataToDomoticz(0, emit,
"{\"command\":\"switchlight\",\"idx\":%s,\"switchcmd\":\"Off\"}",
getIdxField());
} else
#endif #endif
*/ */
uint32_t inputPinMode;
uint8_t inputOnLevel; {
if (inType & IN_ACTIVE_HIGH) { char addrstr[MQTT_TOPIC_LENGTH];
inputOnLevel = HIGH; strncpy(addrstr,emit->valuestring,sizeof(addrstr));
inputPinMode = INPUT; if (mqttClient.connected() && !ethernetIdleCount)
} else { {
inputOnLevel = LOW; if (!strchr(addrstr,'/')) setTopic(addrstr,sizeof(addrstr),T_OUT,emit->valuestring);
inputPinMode = INPUT_PULLUP; mqttClient.publish(addrstr, emitCommand , true);
}
}
} // emit
if (item && itemCommand) {
//debugSerial <<F("Controlled item:")<< item->valuestring <<endl;
Item it(item->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<<F("Pended state retrieved:")<<newState;
} }
pinMode(pin, inputPinMode); else return true; // No pended State
else if (store->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); 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; if (store->bounce) store->bounce = store->bounce - 1;
else //confirmed change 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) if (currentInputState) { //react on leading edge only (change from 0 to 1)
store->logicState = !store->logicState; store->logicState = !store->logicState;
store->currentValue = currentInputState; store->lastValue = currentInputState;
onContactChanged(store->logicState); onContactChanged(store->logicState);
} }
} else { } else
store->logicState = currentInputState;
store->currentValue = currentInputState; {
onContactChanged(currentInputState); 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; // store->currentValue = currentInputState;
} }
@@ -451,23 +785,26 @@ void Input::contactPoll() {
} }
void Input::analogPoll() {
void Input::analogPoll(short cause) {
int16_t inputVal; int16_t inputVal;
int32_t mappedInputVal; // 10x inputVal int32_t mappedInputVal; // 10x inputVal
aJsonObject *inputMap = aJson.getObjectItem(inputObj, "map"); aJsonObject *inputMap = aJson.getObjectItem(inputObj, "map");
int16_t Noize = ANALOG_NOIZE; int16_t Noize = ANALOG_NOIZE;
short simple = 0; short simple = 0;
uint32_t inputPinMode; // uint32_t inputPinMode;
int max=1024*10; int max=1024*10;
int min=0; int min=0;
/*
if (inType & IN_ACTIVE_HIGH) { if (inType & IN_ACTIVE_HIGH) {
inputPinMode = INPUT; inputPinMode = INPUT;
} else { } else {
inputPinMode = INPUT_PULLUP; inputPinMode = INPUT_PULLUP;
} }
pinMode(pin, inputPinMode); pinMode(pin, inputPinMode);
*/
inputVal = analogRead(pin); inputVal = analogRead(pin);
// Mapping // Mapping
if (inputMap && inputMap->type == aJson_Array) if (inputMap && inputMap->type == aJson_Array)
@@ -522,11 +859,13 @@ void Input::analogPoll() {
void Input::onContactChanged(int newValue) { void Input::onContactChanged(int newValue) {
debugSerial << F("IN:") << (pin) << F("=") << newValue << endl;
aJsonObject *item = aJson.getObjectItem(inputObj, "item"); aJsonObject *item = aJson.getObjectItem(inputObj, "item");
aJsonObject *emit = aJson.getObjectItem(inputObj, "emit");
if (!item && !emit) return;
aJsonObject *scmd = aJson.getObjectItem(inputObj, "scmd"); aJsonObject *scmd = aJson.getObjectItem(inputObj, "scmd");
aJsonObject *rcmd = aJson.getObjectItem(inputObj, "rcmd"); aJsonObject *rcmd = aJson.getObjectItem(inputObj, "rcmd");
aJsonObject *emit = aJson.getObjectItem(inputObj, "emit"); debugSerial << F("LEGACY IN:") << (pin) << F("=") << newValue << endl;
if (emit) { if (emit) {
#ifdef WITH_DOMOTICZ #ifdef WITH_DOMOTICZ
if (getIdxField()) if (getIdxField())

View File

@@ -35,6 +35,22 @@ e-mail anklimov@gmail.com
#define IN_COUNTER 8 #define IN_COUNTER 8
#define IN_UPTIME 16 #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 SAME_STATE_ATTEMPTS 3
#define ANALOG_STATE_ATTEMPTS 6 #define ANALOG_STATE_ATTEMPTS 6
@@ -42,6 +58,15 @@ e-mail anklimov@gmail.com
#define CHECK_INPUT 1 #define CHECK_INPUT 1
#define CHECK_SENSOR 2 #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 // 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" } // "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 { typedef union {
long int aslong; long int aslong;
uint32_t timestamp; uint32_t timestamp;
// Analog input structure
struct { struct {
int8_t logicState; uint8_t reserved;
int8_t bounce; uint8_t reserved2;
int16_t currentValue; 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; } inStore;
@@ -121,8 +159,8 @@ public:
protected: protected:
void Parse(); void Parse();
void contactPoll(); void contactPoll(short cause);
void analogPoll(); void analogPoll(short cause);
void dht22Poll(); void dht22Poll();
@@ -140,4 +178,6 @@ protected:
bool publishDataToDomoticz(int , aJsonObject *, const char *format, ...); bool publishDataToDomoticz(int , aJsonObject *, const char *format, ...);
char* getIdxField(); char* getIdxField();
bool changeState(uint8_t newState, short cause);
bool executeCommand(aJsonObject* cmd, char* defCmd = NULL);
}; };

View File

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

View File

@@ -51,7 +51,7 @@
#define EEPROM_offset EEPROM_offset_NotAlligned + (4 -(EEPROM_offset_NotAlligned & 3)) #define EEPROM_offset EEPROM_offset_NotAlligned + (4 -(EEPROM_offset_NotAlligned & 3))
#ifndef INTERVAL_CHECK_INPUT #ifndef INTERVAL_CHECK_INPUT
#define INTERVAL_CHECK_INPUT 50 #define INTERVAL_CHECK_INPUT 15
#endif #endif
#ifndef INTERVAL_CHECK_SENSOR #ifndef INTERVAL_CHECK_SENSOR

View File

@@ -504,5 +504,13 @@ RebootFunc();
#endif #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 <timestamp)) ||
((timestamp<endTime) && ((currTime>endTime) || (currTime <timestamp)));
}
#pragma message(VAR_NAME_VALUE(debugSerial)) #pragma message(VAR_NAME_VALUE(debugSerial))
#pragma message(VAR_NAME_VALUE(SERIAL_BAUD)) #pragma message(VAR_NAME_VALUE(SERIAL_BAUD))

View File

@@ -59,3 +59,4 @@ char* setTopic(char* buf, int8_t buflen, topicType tt, const char* suffix = NULL
void printUlongValueToStr(char *valstr, unsigned long value); void printUlongValueToStr(char *valstr, unsigned long value);
void scan_i2c_bus(); void scan_i2c_bus();
void softRebootFunc(); void softRebootFunc();
bool isTimeOver(uint32_t timestamp, uint32_t currTime, uint32_t time, uint32_t modulo = 0xFFFFFFFF);