From a595fdf53fc1bbf1163e14fc776eb523078a602e Mon Sep 17 00:00:00 2001 From: Gregg Date: Thu, 30 Jul 2020 22:11:01 -0500 Subject: [PATCH] Completed Example 10 --- .../10-TimedResets/10-TimedResets.ino | 76 +++++----- .../Advanced/10-TimedResets/DEV_Blinker.h | 85 ++++++++++++ examples/Advanced/10-TimedResets/DEV_LED.h | 131 ------------------ 3 files changed, 127 insertions(+), 165 deletions(-) create mode 100644 examples/Advanced/10-TimedResets/DEV_Blinker.h delete mode 100644 examples/Advanced/10-TimedResets/DEV_LED.h diff --git a/examples/Advanced/10-TimedResets/10-TimedResets.ino b/examples/Advanced/10-TimedResets/10-TimedResets.ino index c6e1fc4..5ee9057 100644 --- a/examples/Advanced/10-TimedResets/10-TimedResets.ino +++ b/examples/Advanced/10-TimedResets/10-TimedResets.ino @@ -4,38 +4,53 @@ // HomeSpan: A HomeKit implementation for the ESP32 // // ------------------------------------------------ // // // -// Example 9: Logging messages to the Serial Monitor // -// // +// Example 10: Timed Resets - emulating a "pushbutton" // +// in HomeKit // // // //////////////////////////////////////////////////////////// #include "HomeSpan.h" -#include "DEV_LED.h" +#include "DEV_Blinker.h" #include "DEV_Identify.h" void setup() { - // HomeSpan sends a variety of messages to the Serial Monitor of the Arduino IDE whenever the device is connected - // to a computer. Message output is performed either by the usual Serial.print() function, or by one of two macros, - // LOG1() and LOG2(). These two macros are defined as Serial.print() or as no operation (), depending on the - // level of the VERBOSITY constant specified in the "Settings.h" file. Setting VERBOSITY to 0 sets both LOG1() and - // LOG2() to no-op, which means only messages explicitly sent with Serial.print() will be output by HomeSpan. Setting - // VERBOSITY to 1 means messages formed by the LOG1() macros will also be sent. And setting VERBOSITY to 2 causes - // both LOG1() and LOG2() messages to be sent. - // - // You can create your own log messages as needed through Serial.print() statements, but you can also create them with - // the LOG1() or LOG2() macros enabling you can turn them on or off by setting VERBOSITY to the appropriate level. - // Use LOG1() and LOG2() just as you would Serial.print(). - // - // Example 9 illustrates how to add such log messages. The code is identical to Example 8 (without comments), except - // that Serial.print() and LOG1() messages have been added to DEV_LED.h. The Serial.print() messages will always be - // output to the Arduino Serial Monitor. The LOG1() messages will only be output if VERBOSITY is set to 1 or 2. - // - // RECOMMENDATION: Since a HomeSpan ESP32 is meant to be physically connected to real-world devices, you may find - // yourself with numerous ESP32s each configured with a different set of Accessories. To aid in identification - // you may want to add Serial.print() statements containing some sort of initialization message to the constructors for - // each derived Service, such as DEV_LED. Doing so allows HomeSpan to "report" on its configuration upon start-up. See - // DEV_LED for examples. + // 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); @@ -48,18 +63,11 @@ void setup() { new Service::HAPProtocolInformation(); new Characteristic::Version("1.1.0"); - // Defines an ON/OFF LED Accessory attached to pin 16 + // *** NEW *** defines an LED Blinker Accessory attached to pin 16 which blinks 3 times new SpanAccessory(); - new DEV_Identify("LED #1","HomeSpan","123-ABC","20mA LED","0.9",0); - new DEV_LED(16); - new SpanTimedReset(2000); - - // Defines a Dimmable LED Accessory attached to pin 17 using PWM channel 0 - - new SpanAccessory(); - new DEV_Identify("LED #2","HomeSpan","123-ABC","20mA LED","0.9",0); - new DEV_DimmableLED(0,17); + 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() diff --git a/examples/Advanced/10-TimedResets/DEV_Blinker.h b/examples/Advanced/10-TimedResets/DEV_Blinker.h new file mode 100644 index 0000000..3a3de8b --- /dev/null +++ b/examples/Advanced/10-TimedResets/DEV_Blinker.h @@ -0,0 +1,85 @@ + +//////////////////////////////////// +// 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. + // 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. + + new SpanTimedReset(1000); // *** NEW!! instantiate SpanTimedRest with a delay of 2000 milliseconds + + 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 + + LOG1("Activating the LED Blinker on pin="); + LOG1(ledPin); + LOG1("\n"); + + // 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->newValue.BOOL); + + // and is replaced by... + + for(int i=0;iledPin=ledPin; - pinMode(ledPin,OUTPUT); - - // Here we output log messages when the constructor is initially called. - // We use Serial.print() since to ensure the message is always output - // regardless of the VERBOSITY setting. - - Serial.print("Configuring On/Off LED: Pin="); // initialization message - Serial.print(ledPin); - Serial.print("\n"); - - } // end constructor - - StatusCode update(){ // update() method - - // Here we output log messages whenever update() is called, - // which is helpful for debugging purposes if your physical device - // is not functioning as expected. Since it's just for debugging, - // we use LOG1() instead of Serial.print(). Note we can output - // both the current as well as the new power settings. - - LOG1("Updating On/Off LED on pin="); - LOG1(ledPin); - LOG1(": Current Power="); - LOG1(power->value.BOOL?"true":"false"); - LOG1(" New Power="); - LOG1(power->newValue.BOOL?"true":"false"); - LOG1("\n"); - - digitalWrite(ledPin,power->newValue.BOOL); - - 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 <- NEW!! - 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; // LED pin number <- NEW!! - this->pwmPin=new PwmPin(channel, ledPin); // configure the PWM channel and attach the specified ledPin. pinMode() does NOT need to be called. - - // Here we output log messages when the constructor is initially called. - // We use Serial.print() since to ensure the message is always output - // regardless of the VERBOSITY setting. - - 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 - - // Here we output log messages whenever update() is called, - // which is helpful for debugging purposes if your physical device - // is not functioning as expected. Since it's just for debugging, - // we use LOG1() instead of Serial.print(). - - // Note that in the prior example we did not save the ledPin number for - // DimmableLED since it was only needed by the constructor for initializing - // PwmPin(). For this example we add ledPin as a saved variable (see the two - // lines marketed NEW!! above) for the sole purpose of this log message. - - LOG1("Updating Dimmable LED on pin="); - LOG1(ledPin); - LOG1(": Current Power="); - LOG1(power->value.BOOL?"true":"false"); - LOG1(" Current Brightness="); - LOG1(level->value.INT); - - // Note that since Dimmable_LED has two updateable Characteristics, - // HomeKit may be requesting either or both to be updated. We can - // use the "isUpdated" flag of each Characteristic to output a message - // only if HomeKit actually requested an update for that Characteristic. - // Since update() is called whenever there is an update to at least - // one of the Characteristics in a Service, either power, level, or both - // will have its "isUpdated" flag set. - - if(power->isUpdated){ - LOG1(" New Power="); - LOG1(power->newValue.BOOL?"true":"false"); - } - - if(level->isUpdated){ - LOG1(" New Brightness="); - LOG1(level->newValue.INT); - } - - LOG1("\n"); - - pwmPin->set(channel,power->newValue.BOOL*level->newValue.INT); - - return(StatusCode::OK); // return OK status code - - } // update -}; - -//////////////////////////////////