From 2e3daef92aac467bfc36c48faf5d4359be067f6a Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 9 Aug 2020 22:15:58 -0500 Subject: [PATCH] Created Example 13 Created checkEvents(), which is a simplified (better) version of checkTimedResets(). To do: re-write checkTimedResets() to mimic checkEvents(); --- .../13-EventNotifications.ino | 60 +++++ .../13-EventNotifications/DEV_Identify.h | 38 ++++ .../Expert/13-EventNotifications/DEV_LED.h | 205 ++++++++++++++++++ .../13-EventNotifications/DEV_Temperature.h | 34 +++ src/HAP.cpp | 32 ++- src/HAP.h | 3 +- src/HomeSpan.cpp | 20 +- src/HomeSpan.h | 17 +- src/Services.h | 2 + 9 files changed, 404 insertions(+), 7 deletions(-) create mode 100644 examples/Expert/13-EventNotifications/13-EventNotifications.ino create mode 100644 examples/Expert/13-EventNotifications/DEV_Identify.h create mode 100644 examples/Expert/13-EventNotifications/DEV_LED.h create mode 100644 examples/Expert/13-EventNotifications/DEV_Temperature.h diff --git a/examples/Expert/13-EventNotifications/13-EventNotifications.ino b/examples/Expert/13-EventNotifications/13-EventNotifications.ino new file mode 100644 index 0000000..056d41c --- /dev/null +++ b/examples/Expert/13-EventNotifications/13-EventNotifications.ino @@ -0,0 +1,60 @@ + +//////////////////////////////////////////////////////////// +// // +// HomeSpan: A HomeKit implementation for the ESP32 // +// ------------------------------------------------ // +// // +// Example 13: Event Notifications // +// - implementing a temperature sensor // +// // +//////////////////////////////////////////////////////////// + +#include "HomeSpan.h" +#include "DEV_LED.h" +#include "DEV_Identify.h" +#include "DEV_Temperature.h" + +void setup() { + + // Example 11 illustrates how to control an RGB LED to set any color and brightness. + // The configuration below should look familiar by now. We've created a new derived Service, + // call RgbLED to house all the required logic. You'll find all the code in DEV_LED.h. + // For completeness, this configuration also contains an on/off LED and a dimmable LED as shown + // in prior examples. + + 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("On/Off LED","HomeSpan","123-ABC","20mA LED","0.9",0); + new DEV_LED(16); // Create an On/Off LED attached to pin 16 + + new SpanAccessory(); + new DEV_Identify("Dimmable LED","HomeSpan","123-ABC","20mA LED","0.9",0); + new DEV_DimmableLED(0,17); // Create a Dimmable LED using PWM channel 0, attached to pin 17 + + new SpanAccessory(); + new DEV_Identify("RGB LED","HomeSpan","123-ABC","20mA LED","0.9",0); + new DEV_RgbLED(1,2,3,32,22,23); // Create an RGB LED using PWM channels 1,2,3, attached to pins 32,22,23 (for R, G, and B LED anodes) + + new SpanAccessory(); + new DEV_Identify("Temp Sensor","HomeSpan","123-ABC","Celsius","0.9",0); + new DEV_TempSensor(); // Create a Temperature Sensor + new SpanEvent(60000); + +} // end of setup() + +////////////////////////////////////// + +void loop(){ + + homeSpan.poll(); + +} // end of loop() diff --git a/examples/Expert/13-EventNotifications/DEV_Identify.h b/examples/Expert/13-EventNotifications/DEV_Identify.h new file mode 100644 index 0000000..7e6826f --- /dev/null +++ b/examples/Expert/13-EventNotifications/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 + } + + StatusCode update(){ + + for(int i=0;iledPin=ledPin; + pinMode(ledPin,OUTPUT); + + Serial.print("Configuring On/Off LED: Pin="); // initialization message + Serial.print(ledPin); + Serial.print("\n"); + + } // end constructor + + StatusCode update(){ // update() method + + LOG1("Updating On/Off LED on pin="); + LOG1(ledPin); + LOG1(": Current Power="); + LOG1(power->getVal()?"true":"false"); + LOG1(" New Power="); + LOG1(power->getNewVal()?"true":"false"); + LOG1("\n"); + + digitalWrite(ledPin,power->getNewVal()); + + return(StatusCode::OK); // return OK status code + + } // update +}; + +////////////////////////////////// + +struct DEV_DimmableLED : Service::LightBulb { // Dimmable LED + + PwmPin *pwmPin; // reference to PWM Pin + int ledPin; // pin number defined for this LED + int channel; // PWM channel used for this LED (should be unique for each LED) + SpanCharacteristic *power; // reference to the On Characteristic + SpanCharacteristic *level; // reference to the Brightness Characteristic + + DEV_DimmableLED(int channel, int ledPin) : Service::LightBulb(){ // constructor() method + + power=new Characteristic::On(); + + level=new Characteristic::Brightness(50); // Brightness Characteristic with an initial value of 50% + new SpanRange(5,100,1); // sets the range of the Brightness to be from a min of 5%, to a max of 100%, in steps of 1% + + this->channel=channel; // save the channel number (from 0-15) + this->ledPin=ledPin; // save LED pin number + this->pwmPin=new PwmPin(channel, ledPin); // configure the PWM channel and attach the specified ledPin + + Serial.print("Configuring Dimmable LED: Pin="); // initialization message + Serial.print(ledPin); + Serial.print(" Channel="); + Serial.print(channel); + Serial.print("\n"); + + } // end constructor + + StatusCode update(){ // update() method + + LOG1("Updating Dimmable LED on pin="); + LOG1(ledPin); + LOG1(": Current Power="); + LOG1(power->getVal()?"true":"false"); + LOG1(" Current Brightness="); + LOG1(level->getVal()); + + if(power->updated()){ + LOG1(" New Power="); + LOG1(power->getNewVal()?"true":"false"); + } + + if(level->updated()){ + LOG1(" New Brightness="); + LOG1(level->getNewVal()); + } + + LOG1("\n"); + + pwmPin->set(channel,power->getNewVal()*level->getNewVal()); + + return(StatusCode::OK); // return OK status code + + } // update +}; + +////////////////////////////////// + +struct DEV_RgbLED : Service::LightBulb { // RGB LED (Command Cathode) + + PwmPin *redPin, *greenPin, *bluePin; + int redChannel, greenChannel, blueChannel; + + SpanCharacteristic *power; // reference to the On Characteristic + SpanCharacteristic *H; // reference to the Hue Characteristic + SpanCharacteristic *S; // reference to the Saturation Characteristic + SpanCharacteristic *V; // reference to the Brightness Characteristic + + DEV_RgbLED(int redChannel, int greenChannel, int blueChannel, int redPin, int greenPin, int bluePin) : Service::LightBulb(){ // constructor() method + + power=new Characteristic::On(); + H=new Characteristic::Hue(0); // instantiate the Hue Characteristic with an initial value of 0 out of 360 + S=new Characteristic::Saturation(0); // instantiate the Saturation Characteristic with an initial value of 0% + V=new Characteristic::Brightness(100); // instantiate the Brightness Characteristic with an initial value of 100% + new SpanRange(5,100,1); // sets the range of the Brightness to be from a min of 5%, to a max of 100%, in steps of 1% + + this->redChannel=redChannel; // save the channel number (from 0-15) + this->greenChannel=greenChannel; + this->blueChannel=blueChannel; + + this->redPin=new PwmPin(redChannel, redPin); // instantiate the PWM channel and attach the specified pin + this->greenPin=new PwmPin(greenChannel, greenPin); + this->bluePin=new PwmPin(blueChannel, bluePin); + + char cBuf[128]; + sprintf(cBuf,"Configuring RGB LED: Pins=(%d,%d,%d) Channels=(%d,%d,%d)\n",redPin,greenPin,bluePin,redChannel,greenChannel,blueChannel); + Serial.print(cBuf); + + } // end constructor + + StatusCode update(){ // update() method + + boolean p; + float v, h, s, r, g, b; + + h=H->getVal(); // get and store all current values. Note the use of the template to properly read the values + s=S->getVal(); + v=V->getVal(); // though H and S are defined as FLOAT in HAP, V (which is brightness) is defined as INT, but will be re-cast appropriately + p=power->getVal(); + + char cBuf[128]; + sprintf(cBuf,"Updating RGB LED on pins=(%d,%d,%d): ",redPin->getPin(),greenPin->getPin(),bluePin->getPin()); + LOG1(cBuf); + + if(power->updated()){ + p=power->getNewVal(); + sprintf(cBuf,"Power=%s->%s, ",power->getVal()?"true":"false",p?"true":"false"); + } else { + sprintf(cBuf,"Power=%s, ",p?"true":"false"); + } + LOG1(cBuf); + + if(H->updated()){ + h=H->getNewVal(); + sprintf(cBuf,"H=%.0f->%.0f, ",H->getVal(),h); + } else { + sprintf(cBuf,"H=%.0f, ",h); + } + LOG1(cBuf); + + if(S->updated()){ + s=S->getNewVal(); + sprintf(cBuf,"S=%.0f->%.0f, ",S->getVal(),s); + } else { + sprintf(cBuf,"S=%.0f, ",s); + } + LOG1(cBuf); + + if(V->updated()){ + v=V->getNewVal(); + sprintf(cBuf,"V=%.0f->%.0f ",V->getVal(),v); + } else { + sprintf(cBuf,"V=%.0f ",v); + } + LOG1(cBuf); + + // Here we call a static function of PwmPin that converts HSV to RGB. + // Parameters must all be floats in range of H[0,360], S[0,1], and V[0,1] + // R, G, B, returned [0,1] range as well + + PwmPin::HSVtoRGB(h,s/100.0,v/100.0,&r,&g,&b); // since HomeKit provides S and V in percent, scale down by 100 + + int R, G, B; + + R=p*r*100; // since PwmPin uses percent, scale back up by 100, and multiple by status fo power (either 0 or 1) + G=p*g*100; + B=p*b*100; + + sprintf(cBuf,"RGB=(%d,%d,%d)\n",R,G,B); + LOG1(cBuf); + + redPin->set(redChannel,R); // update the PWM channels with new values + greenPin->set(greenChannel,G); + bluePin->set(blueChannel,B); + + return(StatusCode::OK); // return OK status code + + } // update +}; + +////////////////////////////////// diff --git a/examples/Expert/13-EventNotifications/DEV_Temperature.h b/examples/Expert/13-EventNotifications/DEV_Temperature.h new file mode 100644 index 0000000..5b7c60b --- /dev/null +++ b/examples/Expert/13-EventNotifications/DEV_Temperature.h @@ -0,0 +1,34 @@ + +//////////////////////////////////// +// DEVICE-SPECIFIC LED SERVICES // +//////////////////////////////////// + +struct DEV_TempSensor : Service::TemperatureSensor { // A standalone temperature sensor + + SpanCharacteristic *temp; // reference to the Current Temperature Characteristic + float step=0.5; + + DEV_TempSensor(ServiceType sType=ServiceType::Regular) : Service::TemperatureSensor(sType){ // constructor() method + + temp=new Characteristic::CurrentTemperature(); + temp->value.FLOAT=22.0; + + Serial.print("Configuring Temperature Sensor"); // initialization message + Serial.print("\n"); + + } // end constructor + + SpanCharacteristic *event(){ + + temp->value.FLOAT+=step; + if(temp->value.FLOAT>28.0) + step=-step; + else if(temp->value.FLOAT<18.0) + step=-step; + + return(temp); + + } // event +}; + +////////////////////////////////// diff --git a/src/HAP.cpp b/src/HAP.cpp index 83224d0..f01149c 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -1090,7 +1090,33 @@ int HAPClient::putCharacteristicsURL(char *json){ ////////////////////////////////////// -void HAPClient::checkNotifications(){ +void HAPClient::checkEvents(){ + + unsigned long cTime=millis(); // current time + int nObj=0; + SpanBuf pObj[homeSpan.Events.size()]; // maximum number of objects needed if every Event is triggered and each requires a Notification + + for(int i=0;ihomeSpan.Events[i]->alarmTime){ // if alarm time has passed + homeSpan.Events[i]->alarmTime=cTime+homeSpan.Events[i]->period; // set new alarm time to current time plus alarm period + SpanCharacteristic *characteristic=homeSpan.Events[i]->service->event(); // check service for new EVENT + if(characteristic){ // if the service has responded with a characteristic to update + pObj[nObj].status=StatusCode::OK; // populate pObj + pObj[nObj].characteristic=characteristic; + pObj[nObj].val=""; // dummy object needed to ensure sprintfNotify knows to consider this "update" + nObj++; // increment number of characteristics found that need to be turned off + } + } + } + + if(nObj>0) + eventNotify(pObj,nObj); // transmit EVENT Notification for "n" pObj objects + +} + +////////////////////////////////////// + +void HAPClient::checkTimedResets(){ int n=0; SpanTimedReset *tReset; @@ -1107,7 +1133,7 @@ void HAPClient::checkNotifications(){ } else if(millis()>tReset->alarmTime){ // else characteristic is on, timer is started, and timer is expired tReset->trigger=true; // set trigger - n++; // increment number of Push Buttons found that need to be turned off + n++; // increment number of characteristics found that need to be turned off } } @@ -1132,7 +1158,7 @@ void HAPClient::checkNotifications(){ pObj[n].status=StatusCode::OK; // populate pObj pObj[n].characteristic=tReset->characteristic; pObj[n].val=""; // dummy object needed to ensure sprintfNotify knows to consider this "update" - n++; // increment number of Push Buttons found that need to be turned off + n++; // increment number of characteristics found that need to be turned off } } diff --git a/src/HAP.h b/src/HAP.h index 3ca94a0..f9de4df 100644 --- a/src/HAP.h +++ b/src/HAP.h @@ -111,7 +111,8 @@ struct HAPClient { static void removeControllers(); // removes all Controllers (sets allocated flags to false for all slots) static void removeController(uint8_t *id); // removes specific Controller. If no remaining admin Controllers, remove all others (if any) as per HAP requirements. static void printControllers(); // prints IDs of all allocated (paired) Controller - static void checkNotifications(); // checks for notifications and reports to controllers as needed (HAP Section 6.8) + static void checkTimedResets(); // checks for Timed Resets and reports to controllers as needed (HAP Section 6.8) + static void checkEvents(); // checks for Event Notifications and reports to controllers as needed (HAP Section 6.8) static void eventNotify(SpanBuf *pObj, int nObj, int ignoreClient=-1); // transmits EVENT Notifications for nObj SpanBuf objects, pObj, with optional flag to ignore a specific client }; diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index f3955ac..b8840f6 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -147,7 +147,8 @@ void Span::poll() { } // process HAP Client } // for-loop over connection slots - HAPClient::checkNotifications(); + HAPClient::checkTimedResets(); + HAPClient::checkEvents(); } // poll @@ -1075,6 +1076,23 @@ SpanTimedReset::SpanTimedReset(int waitTime){ } +/////////////////////////////// +// SpanEvent // +/////////////////////////////// + +SpanEvent::SpanEvent(int period){ + + if(homeSpan.Accessories.empty() || homeSpan.Accessories.back()->Services.empty() ){ + Serial.print("*** FATAL ERROR: Can't create new Timed Reset without a defined Service. Program halted!\n\n"); + while(1); + } + + this->service=homeSpan.Accessories.back()->Services.back(); + this->period=period; + homeSpan.Events.push_back(this); + +} + /////////////////////////////// // SpanRange // /////////////////////////////// diff --git a/src/HomeSpan.h b/src/HomeSpan.h index c1d1c2c..4a18f15 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -24,6 +24,7 @@ struct SpanCharacteristic; struct SpanRange; struct SpanBuf; struct SpanTimedReset; +struct SpanEvent; /////////////////////////////// @@ -46,6 +47,7 @@ struct Span{ SpanConfig hapConfig; // track configuration changes to the HAP Accessory database; used to increment the configuration number (c#) when changes found vector Accessories; // vector of pointers to all Accessories vector TimedResets; // vector of pointers to all TimedResets + vector Events; // vector of pointer to all Events void begin(Category catID, char *displayName="HomeSpan Server", @@ -99,7 +101,8 @@ struct SpanService{ SpanService(const char *type, ServiceType mod=ServiceType::Regular); int sprintfAttributes(char *cBuf); // prints Service JSON records into buf; return number of characters printed, excluding null terminator - virtual StatusCode update() {return(StatusCode::OK);} // update Service and return final statusCode based on updated Characteristics - should be overridden by DEVICE-SPECIFIC Services + virtual StatusCode update() {return(StatusCode::OK);} // update Service and return final statusCode based on updated Characteristics - should be overridden by DEVICE-SPECIFIC Services + virtual SpanCharacteristic* event(){return(NULL);} // event generation for Services that create their own events and need to notify HomeKit of a new Characteristic value }; /////////////////////////////// @@ -211,7 +214,7 @@ struct SpanRange{ /////////////////////////////// -struct SpanBuf{ // temporary storage buffer for use with putCharacteristicsURL() and checkNotifications() +struct SpanBuf{ // temporary storage buffer for use with putCharacteristicsURL() and checkTimedResets() int aid; // updated aid int iid; // updated iid char *val=NULL; // updated value (optional, though either at least 'val' or 'ev' must be specified) @@ -232,6 +235,16 @@ struct SpanTimedReset{ SpanTimedReset(int waitTime); }; +/////////////////////////////// + +struct SpanEvent{ + SpanService *service; // service to check for events + int period; // time period between checks (in milliseconds) + unsigned long alarmTime=0; // alarm time to trigger next check + + SpanEvent(int period); +}; + ///////////////////////////////////////////////// // Extern Variables diff --git a/src/Services.h b/src/Services.h index fa76803..5400e74 100644 --- a/src/Services.h +++ b/src/Services.h @@ -109,6 +109,8 @@ namespace Characteristic { struct SwingMode : SpanCharacteristic { SwingMode(uint8_t value=0) : SpanCharacteristic{"B6",PR+PW+EV,(uint8_t)value}{} }; + struct CurrentTemperature : SpanCharacteristic { CurrentTemperature(double value=0) : SpanCharacteristic{"11",PR+EV,(double)value}{} }; + struct Version : SpanCharacteristic { Version(char *value) : SpanCharacteristic{"37",PR,(char *)value}{} }; }