diff --git a/examples/Advanced/10NEW-TimedResets/10NEW-TimedResets.ino b/examples/Advanced/10NEW-TimedResets/10NEW-TimedResets.ino new file mode 100644 index 0000000..5ee9057 --- /dev/null +++ b/examples/Advanced/10NEW-TimedResets/10NEW-TimedResets.ino @@ -0,0 +1,80 @@ + +//////////////////////////////////////////////////////////// +// // +// HomeSpan: A HomeKit implementation for the ESP32 // +// ------------------------------------------------ // +// // +// Example 10: Timed Resets - emulating a "pushbutton" // +// in HomeKit // +// // +//////////////////////////////////////////////////////////// + +#include "HomeSpan.h" +#include "DEV_Blinker.h" +#include "DEV_Identify.h" + +void setup() { + + // Though HomeKit and the HomeKit Accessory Protocol (HAP) Specification provide a very flexible framework + // for creating iOS- and MacOS-controlled devices, they does not contain every possible desired feature. + // + // One very common Characteristic HomeKit does not seem to contain is a simple pushbutton, like the type you + // would find on a remote control. Unlike switches that can be "on" or "off", a pushbutton has no state. + // Rather, a pushbutton performs some action when it's pushed, and that's all it does until it's pushed + // again. + // + // Though HomeKit does not contain such a Characteristic, it's easy to emulate in HomeSpan. To do so, simply + // define a Service with a boolen Characteristic (such as the On Characteristic), and create an update() + // method to peform the operations to be executed when the "pushbutton" is "pressed". The update() method + // should ignore the newValue requested by HomeKit, since the only thing that matters is that update() is called. + // + // You could stop there and have something in HomeKit that acts like a pushbutton, but it won't look like a + // pushbutton because every time you press the tile for your device in HomeKit, the Controller will toggle + // between showing it's on and showing it's off. Pressing a tile that shows the status is already on, in order + // to cause HomeKit to trigger the update() to perform a new action, is not very satisfying. + // + // Ideally, we'd like HomeKit to acknowledge you've pressed the tile for the device, maybe by lighting up for a + // second or so, and then it should reset to the "off" position. This would emulate a light-up pushbutton. + // + // Fortunately, HomeSpan includes a way of doing exactly this, using an object called SpanTimedReset(). Similar + // to SpanRange(), you create a new SpanTimedReset() object with a single argument representing the number of + // milliseconds HomeSpan should wait before telling HomeKit to reset, or "turn off", the device tile it just turned + // on when you pressed it. How does SpanTimedReset() know which Characteristic it should attach itself to? + // Similar to all other HomeSpan objects, SpanTimedReset() attaches to the last object you instantiated (and + // will throw an error message at start-up if you try to instantiate a new SpanTimedReset() object without having just + // instantiated a boolean Characteristic of some type). + // + // In Example 10 below we create a single pushbutton that blinks an LED three times. This is not very useful, but + // you can think about the LED as an IR LED that is transmitting a Volume-Up command to a TV, or an RF signal to + // some remote device, like a ceiling fan. + // + // All the functionality is wrapped up in a newly-defined "DEV_Blinker" Service, which can be found in DEV_Blinker.h. + // This new Service is a copy of the DEV_LED service we've been working so far, with modifications to make it into + // a generic blinking LED. As usual, changes and new lines are notably commented. + + Serial.begin(115200); + + homeSpan.begin(Category::Bridges,"HomeSpan Bridge"); + + // Defines the Bridge Accessory + + 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 *** defines an LED Blinker Accessory attached to pin 16 which blinks 3 times + + new SpanAccessory(); + new DEV_Identify("LED Blinker","HomeSpan","123-ABC","20mA LED","0.9",0); + new DEV_Blinker(16,3); // DEV_Blinker takes two arguments - pin, and number of times to blink + +} // end of setup() + +////////////////////////////////////// + +void loop(){ + + homeSpan.poll(); + +} // end of loop() diff --git a/examples/Advanced/10NEW-TimedResets/DEV_Blinker.h b/examples/Advanced/10NEW-TimedResets/DEV_Blinker.h new file mode 100644 index 0000000..8839c14 --- /dev/null +++ b/examples/Advanced/10NEW-TimedResets/DEV_Blinker.h @@ -0,0 +1,101 @@ + +//////////////////////////////////// +// DEVICE-SPECIFIC LED SERVICES // +//////////////////////////////////// + +// NOTE: This example is constructed only for the purpose of demonstrating how to +// use SpanTimedReset() to emulate a pushbutton in HomeSpan. The length of the blinking +// routine is much longer than HomeSpan should spend on an update(). To see how this +// effects HomeKit, try changing the number of blinks to 50, or keep it at 3 and +// increase the delay times in update() so that the blink routine takes 10 seconds or more. +// When activated, HomeKit will think the device has become non-responsive. +// +// In practice, pushbuton emulation is used for very short routines, such as driving +// an IR LED or an RF transmitter to send a code to a remote device. + +struct DEV_Blinker : Service::LightBulb { // LED Blinker + + int ledPin; // pin number defined for this LED + int nBlinks; // NEW! number of times to blink + + SpanCharacteristic *power; // reference to the On Characteristic + + DEV_Blinker(int ledPin, int nBlinks) : Service::LightBulb(){ // constructor() method + + power=new Characteristic::On(); + + // Here we create a new Timed Reset of 2000 milliseconds. Similar to SpanRange(), SpanTimedReset() automatically + // attaches to the last Characteristic instantiated, which in this case the the "power" Characteristic::On above. + // SpanTimedReset() will notify HomeKit that the Characteristic has been turned off by HomeSpan 2000 milliseconds + // after HomeKit requests it be turned on. This DOES NOT cause HomeKit to send an "off" request to HomeSpan (with + // one exception --- see * below). Rather, HomeSpan is notifying HomeKit that HomeSpan itself has turned "off" the + // Characteristic, and that HomeKit should reflect this new "off" status in the Tile shown for this device in the + // HomeKit Controller. + // + // Note that in practice you'll want to set the reset time to 500ms or less to better emulate a pushbutton. + // We've used a full 2 seconds in this example for illustrative purposes only. + + this->ledPin=ledPin; + this->nBlinks=nBlinks; // NEW! number of blinks + pinMode(ledPin,OUTPUT); + + Serial.print("Configuring LED Blinker: Pin="); // initialization message + Serial.print(ledPin); + Serial.print(" Blinks="); // NEW! add output message for number of blinks + Serial.print(nBlinks); + Serial.print("\n"); + + } // end constructor + + StatusCode update(){ // update() method + + // Instead of turning on or off the LED according to newValue, we blink it for + // the number of times specified, and leave it in the off position when finished. + // This line is deleted... + + // digitalWrite(ledPin,power->getNewVal()); + + // and is replaced by... + + if(power->getNewVal()){ // check to ensure HomeKit is requesting we "turn on" this device (else ignore) + + LOG1("Activating the LED Blinker on pin="); + LOG1(ledPin); + LOG1("\n"); + + for(int i=0;igetVal() && power->timeVal()>3000){ + LOG1("Resetting Blinking LED Control\n"); + power->setVal(false); + } + + } // loop + +}; + +////////////////////////////////// + +// * EXCEPTION: There is an apparent bug in HomeKit such that if you have an Accessory with three or more +// Services, and the Accessory receives a notification message from the device, AND the HomeKit interface is +// open to show the detailed control for Service in the Accessory, then for some reason HomeKit tries to +// update the device with the same status it just received from the device, even though this is contrary to +// the purpose of notification requests. This is why it's a good idea to check that newValue.BOOL==true. It +// avoids triggering the device if for some reason HomeKit should send a reqeust to update newValue to false. diff --git a/examples/Advanced/10NEW-TimedResets/DEV_Identify.h b/examples/Advanced/10NEW-TimedResets/DEV_Identify.h new file mode 100644 index 0000000..7e6826f --- /dev/null +++ b/examples/Advanced/10NEW-TimedResets/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;i