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()
{
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(" : ") <<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)
WiringPinMode inputPinMode;
#endif
#if defined(__SAM3X8E__)||defined(ARDUINO_ARCH_AVR)||defined(ARDUINO_ARCH_ESP8266)||defined(ARDUINO_ARCH_ESP32)
TODO implement
#ifdef WITH_DOMOTICZ
if (getIdxField())
{ (newValue) ? publishDataToDomoticz(0, emit, "{\"command\":\"switchlight\",\"idx\":%s,\"switchcmd\":\"On\"}",
: publishDataToDomoticz(0,emit,"{\"command\":\"switchlight\",\"idx\":%s,\"switchcmd\":\"Off\"}",getIdxField()); getIdxField())
: publishDataToDomoticz(0, emit,
"{\"command\":\"switchlight\",\"idx\":%s,\"switchcmd\":\"Off\"}",
getIdxField());
} else
#endif
*/
uint32_t inputPinMode;
uint8_t inputOnLevel;
if (inType & IN_ACTIVE_HIGH) {
inputOnLevel = HIGH;
inputPinMode = INPUT;
} else {
inputOnLevel = LOW;
inputPinMode = INPUT_PULLUP;
{
char addrstr[MQTT_TOPIC_LENGTH];
strncpy(addrstr,emit->valuestring,sizeof(addrstr));
if (mqttClient.connected() && !ethernetIdleCount)
{
if (!strchr(addrstr,'/')) setTopic(addrstr,sizeof(addrstr),T_OUT,emit->valuestring);
mqttClient.publish(addrstr, emitCommand , true);
}
pinMode(pin, inputPinMode);
}
} // 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;
}
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);
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 {
} 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->currentValue = currentInputState;
onContactChanged(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())

View File

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

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))
#ifndef INTERVAL_CHECK_INPUT
#define INTERVAL_CHECK_INPUT 50
#define INTERVAL_CHECK_INPUT 15
#endif
#ifndef INTERVAL_CHECK_SENSOR

View File

@@ -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 <timestamp)) ||
((timestamp<endTime) && ((currTime>endTime) || (currTime <timestamp)));
}
#pragma message(VAR_NAME_VALUE(debugSerial))
#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 scan_i2c_bus();
void softRebootFunc();
bool isTimeOver(uint32_t timestamp, uint32_t currTime, uint32_t time, uint32_t modulo = 0xFFFFFFFF);