From f4698b8d59f0a6b5eacacfcaab006a4736d78a20 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 20 Aug 2022 07:37:56 -0500 Subject: [PATCH] Updated comments in Example 15 - RealPushButtons Added description of different triggerTypes and the use of custom functions --- .../15-RealPushButtons/15-RealPushButtons.ino | 133 ++++++++++++++++-- examples/15-RealPushButtons/DEV_LED.h | 3 +- 2 files changed, 126 insertions(+), 10 deletions(-) diff --git a/examples/15-RealPushButtons/15-RealPushButtons.ino b/examples/15-RealPushButtons/15-RealPushButtons.ino index c6219a8..491a97f 100644 --- a/examples/15-RealPushButtons/15-RealPushButtons.ino +++ b/examples/15-RealPushButtons/15-RealPushButtons.ino @@ -47,9 +47,9 @@ void setup() { // 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, + // One way to accomplish would be via custom code added to the loop() method of your derived Service that monitors a pushbutton, // 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. + // the setVal() method. Or you can simply 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. @@ -61,18 +61,21 @@ void setup() { // 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. - // The SpanButton() constructor takes 4 arguments, in the following order: + // The SpanButton() constructor takes 5 arguments, in the following order: // // pin - the pin number to which the PushButton is attached (required) // longTime - the minimum length of time (in milliseconds) the button needs to be pushed to be considered a LONG press (optional; default=2000 ms) // singleTime - the minimum length of time (in milliseconds) the button needs to be pushed to be considered a SINGLE press (optional; default=5 ms) // doubleTime - the maximum length of time (in milliseconds) between button presses to create a DOUBLE press (optional; default=200 ms) + // triggerType - the action that causes a trigger on the pin (optional; default=SpanButton::TRIGGER_ON_LOW). Built-in choices include: + // + // SpanButton::TRIGGER_ON_LOW: used for a button that connects pin to GROUND + // SpanButton::TRIGGER_ON_HIGH: used for a button that connects pin to VCC (typically +3.3V) + // SpanButton::TRIGGER_ON_TOUCH: used when a pin is connected to a touch pad/sensor - // When a 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. + // When a SpanButton() is first instantiated, HomeSpan configures the specified pin in accordance with the triggerType chosen. - // HomeSpan automatically polls all pins with associated SpanButton() objects and checks for LOW values, which indicates the button was + // Then, HomeSpan continuously polls all pins with associated SpanButton() objects and checks for triggers, which indicates the button was // pressed, but not yet released. It then starts a timer. If the button is released after being pressed for less than singleTime milliseconds, // nothing happens. If the button is released after being pressed for more than singleTime milliseconds, but for less than longTime milliseconds, // a SINGLE press is triggered, unless you press once again within doubleTime milliseconds to trigger a DOUBLE press. If the button is held for more @@ -90,7 +93,7 @@ void setup() { // Also in contrast with the loop method, the button() method takes two 'int' arguments, and should defined as follows: // - // void button(int pin, int pressType) + // void button(int pin, int pressType) // // where "pin" is the pin number of the PushButton that was triggered, and pressType is set to 0 for a SINGLE press, 1 for a DOUBLE press, // and 2 for a LONG press. You can also use the pre-defined constants SpanButton::SINGLE, SpanButton::DOUBLE, and SpanButton::LONG in place @@ -104,7 +107,7 @@ void setup() { // // 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, int pressType) override {...your code...} + // void button(int buttonPin, int pressType) 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 @@ -150,3 +153,115 @@ void loop(){ homeSpan.poll(); } // end of loop() + +//////////////// ADDITIONAL NOTES //////////////////////// + + // DEFAULT VALUES AND ALTERNATIVE CONSTRUCTORS + // -------------------------------------------- + + // As shown in this example, the following creates a SpanButton suitable for connecting pin 23 to GROUND via a pushbutton, and uses + // SpanButton's default values for longTime, singleTime, and doubleTime: + // + // new SpanButton(23); + // + // This is exactly the same as if you explicitly set each parameter to its default value: + // + // new SpanButton(23,2000,5,200,SpanButton::TRIGGER_ON_LOW); // equivalent to above + // + // If instead you want to create a SpanButton that connects pin 23 to VCC via a pushbutton using SpanButton::TRIGGER_ON-HIGH, + // you need to explictly set all the other parameters, even if you are satisfied with their default values, since triggerType + // is the last argument in the constructor: + // + // new SpanButton(23,2000,5,200,SpanButton::TRIGGER_ON_HIGH); + // + // Because this can be cumbersome, SpanButton includes an alternative constructor where triggerType is the second paramater, instead + // of the last. In this case triggerType is required, but longTime, singleTime, and doubleTime are still optional. + // + // For example, the following creates a SpanButton suitable for connecting pin 23 to a touch pad/sensor, and uses + // SpanButton's default values for longTime, singleTime, and doubleTime: + // + // new SpanButton(23,SpanButton::TRIGGER_ON_TOUCH); + // + // which is of course equivalent to: + // + // new SpanButton(23,SpanButton::TRIGGER_ON_TOUCH,2000,5,200); + + + // TOUCH PAD/SENSOR CALIBRATION + // ---------------------------- + + // SpanButton makes use of the ESP32's internal touch sensor peripheral to monitor pins for "touches". There are a number + // of paramaters that must be specified for touches to be accurately detected, depending on the exact size and shape of your + // touch pads. Upon instantiation of a SpanButton() with triggerType=SpanButton::TRIGGER_ON_TOUCH, SpanButton will conveniently + // perform an automatic calibration that sets an appropriate threshold level for detecting touches. + // + // However, if you need to, you can override this calibration process using the following two class-level functions: + // + // SpanButton::setTouchThreshold() - explicitly sets the threshold for detecting touches (i.e. overrides the auto-calibration) + // SpanButton::setTouchCycles() - explicitly sets the measurement and sleep times used by the ESP32's internal touch peripheral + // + // See the SpanButton secion of the Reference API for details on how to use these optional functions. + + + // THE triggerType FUNCTION + // ------------------------- + + // Though the three triggerType objects supported by SpanButton (SpanButton::TRIGGER_ON_LOW, etc.) may appear to be nothing more than + // constants, they are actually boolean functions that each accept a single integer argument. When SpanButton calls the triggerType function, + // it passes the pin number specified in the constructor as the integer argument, and the triggerType function returns TRUE if the + // "pushbutton" associated with the pin number is "pressed," or FALSE if it is not. + // + // For example, the definitions of SpanButton::TRIGGER_ON_LOW and SpanButton::TRIGGER_ON_HIGH are as follows: + // + // boolean TRIGGER_ON_LOW(int pinArg) { return( !digitalRead(pinArg) ); } + // boolean TRIGGER_ON_HIGH(int pinArg) { return( digitalRead(pinArg) ); } + // + // The definitions for SpanButton::TRIGGER_ON_TOUCH are more complicated since the ESP32 touch sensor library returns either a 2-byte + // or 4-byte numeric value when the state of pin configured as a touch sensor is read, rather than a simple 0 or 1. The triggerType + // function must therefore compare the value read from the touch sensor pin to some pre-computed "threshold" to determine whether or not + // the touch pad has in fact been touched. This is the threshold value that HomeSpan auto-calibrates for you as described above. + // + // Making things even more complex is that the ESP32 touch pins work in the reverse direction as touch pins on the ESP32-S2 and ESP32-S3. + // On the former, the values read from a touch sensor DECREASE when the touch pad is touched. On the latter, the values increase when the + // touch pad is touched. This means that for ESP32 devices, HomeSpan uses the following definition for SpanButton::TRIGGER_ON_TOUCH: + // + // boolean TRIGGER_ON_TOUCH(int pinArg) { return ( touchRead(pinArg) < threshold ); } + // + // whereas on ESP32-S2 and ESP32-S3 devices, HomeSpan uses a definition that flips the direction of the comparison: + // + // boolean TRIGGER_ON_TOUCH(int pinArg) { return ( touchRead(pinArg) > threshold ); } + // + // For ESP32-C3 devices, HomeSpan does not define TRIGGER_ON_TOUCH at all since there are no touch pins on an ESP32-C3 device! The compiler + // will throw an error if you try to create a SpanButton with triggerType=SpanButton::TRIGGER_ON_TOUCH, or if you call either of the + // calibration functions above. + // + + // CREATING YOUR OWN triggerType FUNCTION + // -------------------------------------- + + // You are not limited to choosing among HomeSpan's three built-in triggerType functions. You can instead create your own triggerType function + // and pass it to SpanButton as the triggerType parameter in the SpanButton constructor. Your function must be of the form `boolean func(int)`, + // and should return TRUE if the "pushbutton" associated with the pin number that HomeSpan passes to your function as the integer argument + // has been "pressed", or FALSE if it has not. This allows you to expand the used of SpanButton to work with pin multiplexers, pin extenders, + // or any device that may require custom handling via a third-party library. + // + // For example, if you were using an MCP I/O Port Expander with the Adafruit mcp library, you could create a triggerType function for a pin + // on the MCP device that is connected to ground through a pushbutton as such: + // + // boolean MCP_READ(int mcpPin) { return ( !mcp.digitalRead(mcpPin); ) } + // + // And then simply pass MCP_READ to SpanButton as the triggerType parameter using any of the SpanButton constuctors: + // + // new SpanButton(23,MCP_READ); // uses default longTime, singleTime, and doubleTime + // new SpanButton(23,MCP_READ,2000,5,200); // expliclty sets longTime, singleTime, and doubletime + // new SpanButton(23,2000,5,200,MCP_READ); // alternative constructor with arguments in a different order + // + // Alternatively, you can use a lambda function as the triggerType parameter, thus creating your function on the fly when instantiating a SpanButton: + // + // new SpanButton(23,[](int mcpPin)->boolean{ return ( !mcp.digitalRead(mcpPin); ) } + // + // Note: If you create your own triggerType function, don't forget to perform any initialization of the "pin", or setup/configuration of a + // pin extender, etc., prior to instantiating a SpanButton that uses your custom function. HomeSpan cannot do this for you. + // + + diff --git a/examples/15-RealPushButtons/DEV_LED.h b/examples/15-RealPushButtons/DEV_LED.h index ee2ca6f..1f03838 100644 --- a/examples/15-RealPushButtons/DEV_LED.h +++ b/examples/15-RealPushButtons/DEV_LED.h @@ -36,7 +36,8 @@ struct DEV_DimmableLED : Service::LightBulb { // Dimmable LED // 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), a SINGLE press (5 ms), and a DOUBLE press (200 ms). In the second and third we change the - // default LONG press time to 500 ms, which works well for repeatedly increasing or decreasing the brightness. + // default LONG press time to 500 ms, which works well for repeatedly increasing or decreasing the brightness. Since we do not specify + // a triggerType, SpanButton uses the default TRIGGER_ON_TOUCH, which is suitable for a pushbutton that connects pin to GROUND when pressed. // All of the logic for increasing/decreasing brightness, turning on/off power, and setting/resetting a favorite brightness level is found // in the button() method below.