From 3979498b3cee95038e497faa096884736558c091 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 31 Oct 2020 09:35:42 -0500 Subject: [PATCH] Created Example 16 - Programmable Pushbutton Modified SpanCharacteristic::sprintfAttributes to streamline logic and add special handling for the ProgrammableSwitchEvent Characteristic as required by HAP: the value returned for the database or any read request must be set to null (i.e. "value":null). The only time the real value should be returned is when the device sends a EV/Notify message (when the button is pressed). Verified that the example works as expected! TO DO: Add functionality to allow for Service Namespace and Index label so that you can have multiple programmable pushbuttons in one service - this requires logic for HAP LINKED SERVICE functionality. --- .../16-ProgrammableSwitches.ino | 42 ++++++ .../16-ProgrammableSwitches/DEV_Identify.h | 38 ++++++ .../16-ProgrammableSwitches/DEV_ProgButton.h | 38 ++++++ src/HAP.cpp | 2 +- src/HAPConstants.h | 5 +- src/HomeSpan.cpp | 127 +++++++++++++++--- src/HomeSpan.h | 24 ++-- 7 files changed, 240 insertions(+), 36 deletions(-) create mode 100644 examples/16-ProgrammableSwitches/16-ProgrammableSwitches.ino create mode 100644 examples/16-ProgrammableSwitches/DEV_Identify.h create mode 100644 examples/16-ProgrammableSwitches/DEV_ProgButton.h diff --git a/examples/16-ProgrammableSwitches/16-ProgrammableSwitches.ino b/examples/16-ProgrammableSwitches/16-ProgrammableSwitches.ino new file mode 100644 index 0000000..02dc5e6 --- /dev/null +++ b/examples/16-ProgrammableSwitches/16-ProgrammableSwitches.ino @@ -0,0 +1,42 @@ + +//////////////////////////////////////////////////////////// +// // +// HomeSpan: A HomeKit implementation for the ESP32 // +// ------------------------------------------------ // +// // +// Example 16: Stateless Programmable Switches // +// * using linked-services // +// // +// // +//////////////////////////////////////////////////////////// + +#include "HomeSpan.h" +#include "DEV_ProgButton.h" +#include "DEV_Identify.h" + +void setup() { + + + Serial.begin(115200); + + homeSpan.begin(Category::Bridges,"HomeSpan Bridge"); + + new SpanAccessory(); + new DEV_Identify("Bridge #1","HomeSpan","123-ABC","HS Bridge","0.9",3); + new Service::HAPProtocolInformation(); + new Characteristic::Version("1.1.0"); + + new SpanAccessory(); + new DEV_Identify("PushButton Switch","HomeSpan","123-ABC","20mA LED","0.9",0); + + new DEV_ProgButton(23); + +} // end of setup() + +////////////////////////////////////// + +void loop(){ + + homeSpan.poll(); + +} // end of loop() diff --git a/examples/16-ProgrammableSwitches/DEV_Identify.h b/examples/16-ProgrammableSwitches/DEV_Identify.h new file mode 100644 index 0000000..5bd1ef3 --- /dev/null +++ b/examples/16-ProgrammableSwitches/DEV_Identify.h @@ -0,0 +1,38 @@ + +////////////////////////////////// +// DEVICE-SPECIFIC SERVICES // +////////////////////////////////// + +struct DEV_Identify : Service::AccessoryInformation { + + int nBlinks; // number of times to blink built-in LED in identify routine + SpanCharacteristic *identify; // reference to the Identify Characteristic + + DEV_Identify(char *name, char *manu, char *sn, char *model, char *version, int nBlinks) : Service::AccessoryInformation(){ + + new Characteristic::Name(name); // create all the required Characteristics with values set based on above arguments + new Characteristic::Manufacturer(manu); + new Characteristic::SerialNumber(sn); + new Characteristic::Model(model); + new Characteristic::FirmwareRevision(version); + identify=new Characteristic::Identify(); // store a reference to the Identify Characteristic for use below + + this->nBlinks=nBlinks; // store the number of times to blink the built-in LED + + pinMode(LED_BUILTIN,OUTPUT); // make sure built-in LED is set for output + } + + boolean update(){ + + for(int i=0;ibuttonPin=buttonPin; // save button pin number + + Serial.print("Configuring Programmable Pushbuton: Pin="); // initialization message + Serial.print(buttonPin); + Serial.print("\n"); + + } // end constructor + + void button(int pin, boolean isLong) override { + + LOG1("Found button press on pin: "); // always a good idea to log messages + LOG1(pin); + LOG1(" type: "); + LOG1(isLong?"LONG":"SHORT"); + LOG1("\n"); + + switchEvent->setVal(isLong?2:0); // set the value of the switchEvent Characteristic to 2 for long press or 0 for short press + + } + +}; + +////////////////////////////////// diff --git a/src/HAP.cpp b/src/HAP.cpp index b1d8d3c..d5841b8 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -1251,7 +1251,7 @@ void HAPClient::checkTimedWrites(){ void HAPClient::eventNotify(SpanBuf *pObj, int nObj, int ignoreClient){ for(int cNum=0;cNumclient && cNum!=ignoreClient){ // if there is a client connected to this slot and it is NOT flagged to be ignored (in cases where it is the client making a PUT request + if(hap[cNum]->client && cNum!=ignoreClient){ // if there is a client connected to this slot and it is NOT flagged to be ignored (in cases where it is the client making a PUT request) int nBytes=homeSpan.sprintfNotify(pObj,nObj,NULL,cNum); // get JSON response for notifications to client cNum - includes terminating null (will be recast to uint8_t* below) diff --git a/src/HAPConstants.h b/src/HAPConstants.h index 5730a7c..0f40a11 100644 --- a/src/HAPConstants.h +++ b/src/HAPConstants.h @@ -80,7 +80,8 @@ struct HapCharType { AA=8, TW=16, HD=32, - WR=64 + WR=64, + NV=128 }; struct HapCharList { @@ -142,7 +143,7 @@ struct HapCharList { HAPCHAR( PM10Density, C7, PR+EV ); HAPCHAR( PositionState, 72, PR+EV ); HAPCHAR( ProgramMode, D1, PR+EV ); - HAPCHAR( ProgrammableSwitchEvent, 73, PR+EV ); // this characteristic requires specical handling + HAPCHAR( ProgrammableSwitchEvent, 73, PR+EV+NV ); // NV = flag to indicate that HomeSpan should always return a null value, as required by HAP for this Characteristic HAPCHAR( RelativeHumidityDehumidifierThreshold, C9, PR+PW+EV ); HAPCHAR( RelativeHumidityHumidifierThreshold, CA, PR+PW+EV ); HAPCHAR( RemainingDuration, D4, PR+EV ); diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index f71f64a..074dcc8 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -923,7 +923,7 @@ int Span::sprintfNotify(SpanBuf *pObj, int nObj, char *cBuf, int conNum){ if(notifyFlag) // already printed at least one other characteristic nChars+=snprintf(cBuf?(cBuf+nChars):NULL,cBuf?64:0,","); // add preceeding comma before printing next characteristic - nChars+=pObj[i].characteristic->sprintfAttributes(cBuf?(cBuf+nChars):NULL,GET_AID); // get JSON attributes for characteristic + nChars+=pObj[i].characteristic->sprintfAttributes(cBuf?(cBuf+nChars):NULL,GET_AID+GET_NV); // get JSON attributes for characteristic notifyFlag=true; } // notification requested @@ -1270,75 +1270,158 @@ int SpanCharacteristic::sprintfAttributes(char *cBuf, int flags){ const char permCodes[][7]={"pr","pw","ev","aa","tw","hd","wr"}; + const char formatCodes[][8]={"bool","uint8","uint16","uint32","uint64","int","float","string"}; + nBytes+=snprintf(cBuf,cBuf?64:0,"{\"iid\":%d",iid); if(flags&GET_TYPE) nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"type\":\"%s\"",type); + if(perms&PR){ + + if(perms&NV && !(flags&GET_NV)){ + nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"value\":null"); + } else { + + switch(format){ + case BOOL: + nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"value\":%s",value.BOOL?"true":"false"); + break; + + case INT: + nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"value\":%d",value.INT); + break; + + case UINT8: + nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"value\":%u",value.UINT8); + break; + + case UINT16: + nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"value\":%u",value.UINT16); + break; + + case UINT32: + nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"value\":%lu",value.UINT32); + break; + + case UINT64: + nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"value\":%llu",value.UINT64); + break; + + case FLOAT: + nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"value\":%lg",value.FLOAT); + break; + + case STRING: + nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"value\":\"%s\"",value.STRING); + break; + + } // switch + } // print Characteristic value + } // permissions=PR + + if(flags&GET_META){ + nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"format\":\"%s\"",formatCodes[format]); + + if(range && (flags&GET_META)) + nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?128:0,",\"minValue\":%d,\"maxValue\":%d,\"minStep\":%d",range->min,range->max,range->step); + } + +/* + switch(format){ case BOOL: - if(perms&PR) - nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"value\":%s",value.BOOL?"true":"false"); + if(perms&PR){ + if(!(perms&NV)) + nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"value\":%s",value.BOOL?"true":"false"); + else + nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"value\":null"); + } if(flags&GET_META) nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"format\":\"bool\""); break; case INT: - if(perms&PR) - nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"value\":%d",value.INT); + if(perms&PR){ + if(!(perms&NV)) + nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"value\":%d",value.INT); + else + nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"value\":null"); + } if(flags&GET_META) nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"format\":\"int\""); break; case UINT8: - if(perms&PR) - nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"value\":%u",value.UINT8); + if(perms&PR){ + if(!(perms&NV)) + nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"value\":%u",value.UINT8); + else + nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"value\":null"); + } if(flags&GET_META) nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"format\":\"uint8\""); break; case UINT16: - if(perms&PR) - nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"value\":%u",value.UINT16); + if(perms&PR){ + if(!(perms&NV)) + nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"value\":%u",value.UINT16); + else + nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"value\":null"); + } if(flags&GET_META) nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"format\":\"uint16\""); break; case UINT32: - if(perms&PR) - nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"value\":%lu",value.UINT32); + if(perms&PR){ + if(!(perms&NV)) + nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"value\":%lu",value.UINT32); + else + nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"value\":null"); + } if(flags&GET_META) nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"format\":\"uint32\""); break; case UINT64: - if(perms&PR) - nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"value\":%llu",value.UINT64); + if(perms&PR){ + if(!(perms&NV)) + nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"value\":%llu",value.UINT64); + else + nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"value\":null"); + } if(flags&GET_META) nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"format\":\"uint64\""); break; case FLOAT: - if(perms&PR) - nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"value\":%lg",value.FLOAT); + if(perms&PR){ + if(!(perms&NV)) + nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"value\":%lg",value.FLOAT); + else + nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"value\":null"); + } if(flags&GET_META) nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"format\":\"float\""); break; case STRING: - if(perms&PR) - nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"value\":\"%s\"",value.STRING); + if(perms&PR){ + if(!(perms&NV)) + nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"value\":\"%s\"",value.STRING); + else + nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"value\":null"); + } if(flags&GET_META) nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",\"format\":\"string\""); break; } // switch - - if(range && (flags&GET_META)){ - nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?128:0,",\"minValue\":%d,\"maxValue\":%d,\"minStep\":%d",range->min,range->max,range->step); - } - +*/ + if(desc && (flags&GET_DESC)){ nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?128:0,",\"description\":\"%s\"",desc); } diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 9b3afc6..4b2bcec 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -23,6 +23,7 @@ enum { GET_TYPE=8, GET_EV=16, GET_DESC=32, + GET_NV=64, GET_ALL=255 }; @@ -162,19 +163,20 @@ struct SpanCharacteristic{ AA=8, TW=16, HD=32, - WR=64 + WR=64, + NV=128 }; - enum FORMAT { // HAP Table 6-5 - BOOL, - UINT8, - UINT16, - UINT32, - UINT64, - INT, - FLOAT, - STRING - }; + enum FORMAT { // HAP Table 6-5 + BOOL=0, + UINT8=1, + UINT16=2, + UINT32=3, + UINT64=4, + INT=5, + FLOAT=6, + STRING=7 + }; union UVal { boolean BOOL;