From e298f32cbff43fa7093f00dd46ed87227add0677 Mon Sep 17 00:00:00 2001 From: Gregg Date: Fri, 21 Aug 2020 09:30:59 -0500 Subject: [PATCH] Completed Example 15 All commentary finsihed. --- .../15-RealPushButtons/15-RealPushButtons.ino | 71 ++++++++++++++++- .../15-RealPushButtons/DEV_Identify.h | 2 - .../D-Expert/15-RealPushButtons/DEV_LED.h | 76 ++++++++++++++----- 3 files changed, 123 insertions(+), 26 deletions(-) diff --git a/examples/Tutorials/D-Expert/15-RealPushButtons/15-RealPushButtons.ino b/examples/Tutorials/D-Expert/15-RealPushButtons/15-RealPushButtons.ino index 8e7062a..e9209c8 100644 --- a/examples/Tutorials/D-Expert/15-RealPushButtons/15-RealPushButtons.ino +++ b/examples/Tutorials/D-Expert/15-RealPushButtons/15-RealPushButtons.ino @@ -5,7 +5,7 @@ // ------------------------------------------------ // // // // Example 15: Real PushButtons // -// * manually controlling an LED // +// * manually controlling a Dimmable LED // // // // // //////////////////////////////////////////////////////////// @@ -15,6 +15,70 @@ #include "DEV_Identify.h" void setup() { + + // In Example 14 we saw how to emulate a PushButton tile within HomeKit by automatically resetting a Characteristic so that + // it "turns off" after a short period of time. However, sometimes we want to be able to physically control a device with actual + // PushButtons (or momentary switches) that trigger an action, such as turning on a light or fan, or opening a garage door. + // Additionally, we want HomeKit to reflect any changes in the device as a result of such manual actions - HomeKit should know + // when the light has been turned on or off manually. + + // One way to accomplish would be via custom code added to the loop() method of your derived Service that monitors the button, + // checks when it is pressed, debounces button noise, performs some actions when pressed, and informs HomeKit of the actions with + // the setVal() method. Or you can use HomeSpan's built-in SpanButton() object. + + // SpanButton() is a Service-level object, meaning it attaches itself to the last Service you define. Typically you would instantiate + // one of more SpanButton() objects directly inside the constructor for your derived Service. + + // SpanButton() supports two types of a triggers: a SHORT (momentary) button press, and a LONG (extended) button press. SpanButton() + // takes 3 arguments, in the following order: + // + // * the pin number to which the PushButton is attached (required) + // * the length of time (in milliseconds) the button needs to be pushed to be considered a LONG press (optional; default=2000 ms) + // * the length of time (in milliseconds) the button needs to be pushed to be considered a SHORT press (optional; default=5 ms) + + // When SpanButton() is instantiated, it sets the specified pin on the ESP32 to be an INPUT with PULL-UP, meaning that the pin will + // normally return a value of HIGH when read. Your actual PushButton should be connected so that this pin is GROUNDED when the button + // is pressed. + + // HomeSpan automatically polls all pins with associated SpanButton() objects and checks for LOW values, indicating the button was + // pressed, but not yet released. It then starts a timer and waits for the button to be released. + + // NOTE! TRIGGERS DO NOT OCCUR UNTIL THE BUTTON IS RELEASED - IF YOU HOLD DOWN A BUTTON INDEFINITELY, NOTHING HAPPENS. + + // The length of the press needed to trigger either a SHORT or LONG action is specified by the optional arguments. Since most buttons + // create spurious noise when pressed (and then again when released), the default time to trigger a SHORT press is 5ms. It's fine to change + // this to a longer value, but a shorter value is not recommended as this may allow spurious triggers unless you debounce your switch + // with hardware. + + // To use SpanButton() within a derived Service you need to implement a button() method. Similar to the loop() method, your button() + // method will typically contain some combination of getVal() functions and setVal() functions, along with code that performs some set + // of actions on the physical device (seting pins high or low, turning on fans, etc). However, in contrast to the loop() method, which + // is called by HomeSpan every polling cycle, HomeSpan only calls the button() method when a button attached to the Service registers a + // SHORT or LONG press. + + // Also in contrast with the loop method, the button() method takes two arguments, an int and a boolean, and should defined as follows: + // + // void button(int pin, boolean isLong) + // + // where "pin" is the pin number of the PushButton that was triggered, and "isLong" is a flag indicating whether SpanButton() detected a + // LONG press (isLong=true) or a SHORT press (isLong=false). Of course you can replace the variables "pin" and "isLong" with your own + // names. The only requirement is the defintiion conform to the "void button(int, boolean)" signature. When HomeSpan first starts up it checks + // all Services containing one or more SpanButton() instances to ensure you've implemented your own button(int, boolean) method. If not, + // HomeSpan will print a warning message on the Serial Monitor. Nothing bad happens if you instantiate a SpanButton() but forget to create + // the button() method, or you create it with the wrong parameters. Buy nothing good happens either - button presses are just ignored. + // + // C++ Note: For an extra check, you can also place the the contextual keyword "override" after your method definition as such: + // + // void button(int buttonPin, boolean longPress) override {...your code...} + // + // Doing so allows the compiler to check that you are indeed over-riding the base class button() method and not inadvertently creating a new + // button() method with an incorrect signature that will never be called by SpanButton(). In fact, you could add "override" to the definition + // of your update() and loop() methods as well, since these are always supposed to over-ride the base-class method. + + // To demonstrate how PushButtons works in practice, we will implement a Dimmable LED starting with the same LED code use in Example 11, + // but with 3 SpanButton() objects performing different functions that showcase the different parameters. As usual, all the code is implemented + // in DEV_LED.h, with NEW! comments highlighting changes from Example 11. You'll also notice that we've extended the constructor for this + // version of our derived Dimmable LED Service to include the pin numbers for each of our buttons. See DEV_LED.h for details. Serial.begin(115200); @@ -26,8 +90,9 @@ void setup() { new Characteristic::Version("1.1.0"); new SpanAccessory(); - new DEV_Identify("Switched LED","HomeSpan","123-ABC","20mA LED","0.9",0); - new DEV_DimmableLED(0,17,19,5,18); + new DEV_Identify("PushButton LED","HomeSpan","123-ABC","20mA LED","0.9",0); + + new DEV_DimmableLED(0,17,19,5,18); // NEW! added three extra arguments to specific the pin numbers for three SpanButtons() - see DEV_LED.h } // end of setup() diff --git a/examples/Tutorials/D-Expert/15-RealPushButtons/DEV_Identify.h b/examples/Tutorials/D-Expert/15-RealPushButtons/DEV_Identify.h index 95ba92a..17f7638 100644 --- a/examples/Tutorials/D-Expert/15-RealPushButtons/DEV_Identify.h +++ b/examples/Tutorials/D-Expert/15-RealPushButtons/DEV_Identify.h @@ -7,8 +7,6 @@ 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 - - // NEW! modified constructor() method to include optional ServiceType argument DEV_Identify(char *name, char *manu, char *sn, char *model, char *version, int nBlinks, ServiceType sType=ServiceType::Regular) : Service::AccessoryInformation(sType){ diff --git a/examples/Tutorials/D-Expert/15-RealPushButtons/DEV_LED.h b/examples/Tutorials/D-Expert/15-RealPushButtons/DEV_LED.h index 2029754..79f7466 100644 --- a/examples/Tutorials/D-Expert/15-RealPushButtons/DEV_LED.h +++ b/examples/Tutorials/D-Expert/15-RealPushButtons/DEV_LED.h @@ -9,6 +9,13 @@ struct DEV_DimmableLED : Service::LightBulb { // Dimmable LED + // This version of the Dimmable LED Service is similar to the one last used in Example 11, but now includes support for 3 physical PushButtons + // performing the following actions: + // + // power button: SHORT press toggles power on/off; LONG press sets brightness to 100% if power is on, or sets brightness to 5% if power is off + // raise button: SHORT press increases brightness by 1%. LONG press increases brightness by 10% + // lower button: SHORT press decreases brightness by 1%. LONG press decreases brightness by 10% + PwmPin *pwmPin; // reference to PWM Pin int ledPin; // pin number defined for this LED int powerPin; // NEW! pin with pushbutton to turn on/off LED @@ -17,6 +24,8 @@ struct DEV_DimmableLED : Service::LightBulb { // Dimmable 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 + + // NEW! Consructor includes 3 additionl arguments to specify pin numbers for power, raise, and lower buttons DEV_DimmableLED(int channel, int ledPin, int powerPin, int raisePin, int lowerPin, ServiceType sType=ServiceType::Regular) : Service::LightBulb(sType){ @@ -25,9 +34,18 @@ struct DEV_DimmableLED : Service::LightBulb { // Dimmable LED 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% - new SpanButton(powerPin); // NEW! create new SpanButton to control power on pin number "powerPin" - new SpanButton(raisePin,1000); // NEW! create new SpanButton to increase brightness on pin number "raisePin" - new SpanButton(lowerPin,3000,500); // NEW! create new SpanButton to decrease brightness on pin number "lowerPin" + // NEW! Below we create three SpanButton() objects. In the first we specify the pin number, as required, but allow SpanButton() to use + // its default values for a LONG press (2000 ms) and a SHORT press (5 ms). In the second we change the LONG press time to 1000 ms, which + // means we only have to hold the raise button for 1 second to trigger a LONG press that increases the brightness by 10%. In the the third, + // we change both the LONG press time to 3000 ms (which means holding the button for 3 full seconds before releasing to decrease the brightness + // by 10%), and the SHORT press time to 500 ms, which means holding down the button for at least half a second (but not longer than 3 seconds) + // to derease the brightness by 1%. The logic for increasing/decreasing brightness, as well as turning on/off power, is found in the button() + // method below. Note that in practice you likely would not use different combinations of parameters for buttons that perform similar types of + // functions. We've only done so here to illustrate how the parameters work. + + new SpanButton(powerPin); // NEW! create new SpanButton to control power using PushButton on pin number "powerPin" + new SpanButton(raisePin,1000); // NEW! create new SpanButton to increase brightness using PushButton on pin number "raisePin" + new SpanButton(lowerPin,3000,500); // NEW! create new SpanButton to decrease brightness using PushButton on pin number "lowerPin" this->channel=channel; // save the channel number (from 0-15) this->ledPin=ledPin; // save LED pin number @@ -71,40 +89,56 @@ struct DEV_DimmableLED : Service::LightBulb { // Dimmable LED } // update - void button(int pin, boolean isLong){ + // NEW! Here is the button() method where all the PushButton actions are defined. Take note of the signature, and use of the word "override" - LOG1("Found button press on pin: "); + 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"); - if(pin==powerPin && !isLong){ - power->setVal(1-power->getVal()); + if(pin==powerPin && !isLong){ // if a SHORT press of the power PushButton... + power->setVal(1-power->getVal()); // ...toggle the value of the power Characteristic } else - if(pin==powerPin && isLong){ - if(power->getVal()) - level->setVal(100); - else - level->setVal(5); + if(pin==powerPin && isLong){ // if a LONG press of the power PushButton... + if(power->getVal()) // ...and power Characteristic is true (LED is on) + level->setVal(100); // set brightness level Characteristic to 100% + else // ...else, power Characteristic is false (LED os off) + level->setVal(5); // so set brightness level Characteristic to 5% } else - if(pin==raisePin){ - int newLevel=level->getVal()+(isLong?10:1); - if(newLevel>100) + if(pin==raisePin){ // if raise PushButton has been pressed + int newLevel=level->getVal()+(isLong?10:1); // get current brightness level and increase by either 10% (LONG press) or 1% (SHORT press) + if(newLevel>100) // don't allow new level to exceed maximium of 100% newLevel=100; - level->setVal(newLevel); + level->setVal(newLevel); // set the value of the brightness Characteristic to this new level } else - if(pin==lowerPin){ - int newLevel=level->getVal()-(isLong?10:1); - if(newLevel<5) + if(pin==lowerPin){ // if lower PushButton has been pressed + int newLevel=level->getVal()-(isLong?10:1); // get current brightness level and decrease by either 10% (LONG press) or 1% (SHORT press) + if(newLevel<5) // don't allow new level to fall below minimum of 5% newLevel=5; - level->setVal(newLevel); + level->setVal(newLevel); // set the value of the brightness Characteristic to this new level } + + // Don't forget to set the new power and level for the actual LED - the above code by itself only changes the values of the Characteristics + // within HomeKit! We still need to take an action on the actual LED itself. - pwmPin->set(channel,power->getVal()*level->getVal()); + // Note the line below is similar to, but not the same as, the pwmPin->set function used in the update() method above. Within the + // update() method we used getNewVal() because we wanted to change the LED to match the NEW VALUES requested by the user via the + // HomeKit Controller. We did not need to (and must not) use setVal() to modify these values in the update() method since HomeSpan + // automatically does this for us, provided we return StatusCode::OK at the end of the update() method. + + // But in the button() method, getNewVal() means nothing, since the button() method is not called by HomeKit in response to a user request + // from a HomeKit Controller interface. Instead, we are manually changing the values of one or more Characteristic using setVal() in response + // to LONG and SHORT PushButton requests. These changes are instantaneous, so we can retreive the new values with a subsequent call to getVal(), + // as shown below. As usual, HomeSpan will send Event Notifications to all registered HomeKit Controllers letting them know about any changes + // we made using setVal(). + + pwmPin->set(channel,power->getVal()*level->getVal()); // update the physical LED to reflect the new values }