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.
This commit is contained in:
Gregg 2020-10-31 09:35:42 -05:00
parent 2dd30e0a51
commit 3979498b3c
7 changed files with 240 additions and 36 deletions

View File

@ -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()

View File

@ -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;i<nBlinks;i++){
digitalWrite(LED_BUILTIN,LOW);
delay(250);
digitalWrite(LED_BUILTIN,HIGH);
delay(250);
}
return(true); // return true
} // update
};

View File

@ -0,0 +1,38 @@
////////////////////////////////////////////////////
// DEVICE-SPECIFIC PROGRAMMABLE SWITCH SERVICES //
////////////////////////////////////////////////////
struct DEV_ProgButton : Service::StatelessProgrammableSwitch { // Stateless Programmable Switch
int buttonPin; // pin with programmable pushbutton
SpanCharacteristic *switchEvent; // reference to the ProgrammableSwitchEvent Characteristic
DEV_ProgButton(int buttonPin) : Service::StatelessProgrammableSwitch(){
switchEvent=new Characteristic::ProgrammableSwitchEvent(); // ProgrammableSwitchEvent Characteristic
new SpanButton(buttonPin); // create new SpanButton
this->buttonPin=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
}
};
//////////////////////////////////

View File

@ -1251,7 +1251,7 @@ void HAPClient::checkTimedWrites(){
void HAPClient::eventNotify(SpanBuf *pObj, int nObj, int ignoreClient){
for(int cNum=0;cNum<homeSpan.maxConnections;cNum++){ // loop over all connection slots
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
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)

View File

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

View File

@ -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,74 +1270,157 @@ 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)
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)
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)
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)
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)
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)
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)
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)
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);

View File

@ -23,6 +23,7 @@ enum {
GET_TYPE=8,
GET_EV=16,
GET_DESC=32,
GET_NV=64,
GET_ALL=255
};
@ -162,18 +163,19 @@ 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
BOOL=0,
UINT8=1,
UINT16=2,
UINT32=3,
UINT64=4,
INT=5,
FLOAT=6,
STRING=7
};
union UVal {