From fe6b542ed8032d64ab3ab3946c0677ebe44c8746 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 17 Apr 2022 17:38:15 -0500 Subject: [PATCH 01/98] Delete HomeKitPrimer.md Old, unused file. --- docs/HomeKitPrimer.md | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 docs/HomeKitPrimer.md diff --git a/docs/HomeKitPrimer.md b/docs/HomeKitPrimer.md deleted file mode 100644 index f7d82fa..0000000 --- a/docs/HomeKitPrimer.md +++ /dev/null @@ -1,10 +0,0 @@ -# HomeKit Primer - -*(editing in progress)* - -The standard reference for all HomeKit devices is [Apple's HomeKit Accessory Protocol Specification Release R2 (HAP-R2)](https://developer.apple.com/support/homekit-accessory-protocol/). HomeSpan rigorously implements HAP-R2, so if you are already familiar with HomeKit concepts and terminology, using HomeSpan to create your own HomeKit devices should be extremely easy. However, if this is your first time programming for HomeKit, some of the basic HomeKit terminology can be confusing, and unfortunately Apple's HAP-R2 guide does not provide much in the way of an introduction or overview to HomeKit itself. This page hopefully provides you with the missing "overview" you need to better understand the overall HomeKit ecosystem. - - ---- - -[↩️](README.md) Back to the Welcome page From c290c8637ee1aabd30e71f4dbc646c448f5e8ac8 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 23 Apr 2022 07:22:45 -0500 Subject: [PATCH 02/98] Added homeSpan.start() method as alternative to homeSpan.poll() Rather than call homeSpan.poll() in the main Arduino loop() function, you can instead call homeSpan.start() at the end of the set-up function. This keeps the main Arduino loop() function free for user-defined code that will not block, and does not get blocked by, homeSpan.poll(). If using a dual-core processor, polling now occurs on core 0, instead of the core 1 (where all other Arduino stuff normally runs). HomeSpan will throw a fatal error and halt processing if both homeSpan.poll() and homeSpan.start() are used in the same sketch. --- src/HomeSpan.cpp | 15 ++++++++++++++- src/HomeSpan.h | 6 +++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index a7c67b2..71b89c7 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -159,8 +159,21 @@ void Span::begin(Category catID, const char *displayName, const char *hostNameBa void Span::poll() { + if(pollTaskHandle){ + Serial.print("\n** FATAL ERROR: Do not call homeSpan.poll() directly if homeSpan.start() is used!\n** PROGRAM HALTED **\n\n"); + vTaskDelete(pollTaskHandle); + while(1); + } + + pollTask(); +} + +/////////////////////////////// + +void Span::pollTask() { + if(!strlen(category)){ - Serial.print("\n** FATAL ERROR: Cannot run homeSpan.poll() without an initial call to homeSpan.begin()!\n** PROGRAM HALTED **\n\n"); + Serial.print("\n** FATAL ERROR: Cannot start homeSpan polling without an initial call to homeSpan.begin()!\n** PROGRAM HALTED **\n\n"); while(1); } diff --git a/src/HomeSpan.h b/src/HomeSpan.h index c970652..bc93e76 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -192,6 +192,7 @@ struct Span{ PushButton controlButton; // controls HomeSpan configuration and resets Network network; // configures WiFi and Setup Code via either serial monitor or temporary Access Point SpanWebLog webLog; // optional web status/log + TaskHandle_t pollTaskHandle = NULL; // optional task handle to use for poll() function SpanOTA spanOTA; // manages OTA process SpanConfig hapConfig; // track configuration changes to the HAP Accessory database; used to increment the configuration number (c#) when changes found @@ -208,7 +209,8 @@ struct Span{ const char *hostNameBase=DEFAULT_HOST_NAME, const char *modelName=DEFAULT_MODEL_NAME); - void poll(); // poll HAP Clients and process any new HAP requests + void poll(); // calls pollTask() with some error checking + void pollTask(); // poll HAP Clients and process any new HAP requests int getFreeSlot(); // returns free HAPClient slot number. HAPClients slot keep track of each active HAPClient connection void checkConnect(); // check WiFi connection; connect if needed void commandMode(); // allows user to control and reset HomeSpan settings with the control button @@ -257,6 +259,8 @@ struct Span{ webLog.init(maxEntries, serv, tz, url); } + void start(){xTaskCreateUniversal([](void *parms){for(;;)homeSpan.pollTask();}, "pollTask", getArduinoLoopTaskStackSize(), NULL, 1, &pollTaskHandle, 0);} // start pollTask() + void setTimeServerTimeout(uint32_t tSec){webLog.waitTime=tSec*1000;} // sets wait time (in seconds) for optional web log time server to connect [[deprecated("Please use reserveSocketConnections(n) method instead.")]] From 54dbda94842782c82502cfbb366a8462823d4919 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 24 Apr 2022 08:42:18 -0500 Subject: [PATCH 03/98] Changed homeSpan.start() to homeSpan.autoPoll() --- src/HomeSpan.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/HomeSpan.h b/src/HomeSpan.h index bc93e76..f0dc4ea 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -259,7 +259,7 @@ struct Span{ webLog.init(maxEntries, serv, tz, url); } - void start(){xTaskCreateUniversal([](void *parms){for(;;)homeSpan.pollTask();}, "pollTask", getArduinoLoopTaskStackSize(), NULL, 1, &pollTaskHandle, 0);} // start pollTask() + void autoPoll(){xTaskCreateUniversal([](void *parms){for(;;)homeSpan.pollTask();}, "pollTask", getArduinoLoopTaskStackSize(), NULL, 1, &pollTaskHandle, 0);} // start pollTask() void setTimeServerTimeout(uint32_t tSec){webLog.waitTime=tSec*1000;} // sets wait time (in seconds) for optional web log time server to connect From 36ad80a91f63170b416c398b6e3797cd5127f35f Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 24 Apr 2022 09:01:38 -0500 Subject: [PATCH 04/98] Update Reference.md --- docs/Reference.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/Reference.md b/docs/Reference.md index ea01578..899aff6 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -22,8 +22,14 @@ At runtime HomeSpan will create a global **object** named `homeSpan` that suppor * `void poll()` * checks for HAP requests, local commands, and device activity - * **must** be called repeatedly in each sketch and is typically placed at the top of the Arduino `loop()` method - + * **must** be called repeatedly in each sketch and is typically placed at the top of the Arduino `loop()` method, *unless* `autoPoll()` is used instead + +* `void autoPoll()` + * an *optional* method to create a task that repeatedly calls `poll()` in the background, which frees up the Ardino `loop()` method for any user-defined code that would otherwise block, or be blocked by, calling `poll()` in the `loop()` method + * if used, **must** be placed in a sketch as the last line in the Arduino `setup()` method + * HomeSpan will throw and error + + --- The following **optional** `homeSpan` methods override various HomeSpan initialization parameters used in `begin()`, and therefore **should** be called before `begin()` to take effect. If a method is *not* called, HomeSpan uses the default parameter indicated below: From db2c8157993206076ecff777781feb6f3ec99e48 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 24 Apr 2022 10:00:41 -0500 Subject: [PATCH 05/98] Update Reference.md --- docs/Reference.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Reference.md b/docs/Reference.md index 899aff6..be3590f 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -27,8 +27,8 @@ At runtime HomeSpan will create a global **object** named `homeSpan` that suppor * `void autoPoll()` * an *optional* method to create a task that repeatedly calls `poll()` in the background, which frees up the Ardino `loop()` method for any user-defined code that would otherwise block, or be blocked by, calling `poll()` in the `loop()` method * if used, **must** be placed in a sketch as the last line in the Arduino `setup()` method - * HomeSpan will throw and error - + * HomeSpan will throw and error and halt if both `poll()`and `autoPoll()` are used in the same sketch - either place `poll()` in the Arduino `loop()` method **or** place `autoPoll()` at the the end of the Arduino `setup()` method + * can be used with both single-core and dual-core ESP32 boards. If used with a dual-core board, the polling task is created on the free processor that is typically not running other Arduino functions --- From b97775263d8dff8b04b199eff2af86dac970a6f5 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 24 Apr 2022 11:55:38 -0500 Subject: [PATCH 06/98] Update Overview.md --- docs/Overview.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/docs/Overview.md b/docs/Overview.md index 763aed0..eb63eb6 100644 --- a/docs/Overview.md +++ b/docs/Overview.md @@ -42,6 +42,30 @@ void loop(){ } // end of loop() ``` +Note that as an *alternative*, you can intruct HomeSpan to create separate task that repeatedly calls `homeSpan.poll()` in the background. To do so, **replace** the call to `homeSpan.poll()` in the main `loop()` with a call to `homeSpan.autoPoll()` at the end of the `setup()` function: + +```C++ +#include "HomeSpan.h" // include the HomeSpan library + +void setup() { + + Serial.begin(115200); // start the Serial interface + + homeSpan.begin(); // initialize HomeSpan + + /// DEFINITION OF HAP ACCESSORY ATTRIBUTE DATABASE GOES HERE /// + + homeSpan.autoPoll(); // start a task that repeatedly calls `homeSpan.poll()` in the background + +} // end of setup() + +void loop(){ + +} // end of loop() +``` + +This is particularly efficient when using dual-core processors since HomeSpan will run the polling task on the "free" processor that is otherwise not performing any other Arduino functions. + ## Creating the HAP Accessory Attribute Database The next step is to implement the code that defines the HAP Accessory Attribute Database, which is not really a database but simply a list of all HAP accessory objects, Service objects, and Characteristic objects implemented by this HomeSpan device. From 044fe22b1f2161c0b8a97fc391ee402c4e9a92da Mon Sep 17 00:00:00 2001 From: Gregg Date: Tue, 26 Apr 2022 21:45:29 -0500 Subject: [PATCH 07/98] Made FirmwareRevision Characteristic PR+EV instead of just PR Allows for updating FirmwareRevision, which HomeKit seems to poll every 60 seconds. --- src/Characteristics.h | 2 +- src/HomeSpan.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Characteristics.h b/src/Characteristics.h index 7caa9d9..e77e507 100644 --- a/src/Characteristics.h +++ b/src/Characteristics.h @@ -113,7 +113,7 @@ struct HapCharacteristics { HAPCHAR( CurrentVisibilityState, 135, PR+EV, UINT8, true ); HAPCHAR( FilterLifeLevel, AB, PR+EV, FLOAT, false ); HAPCHAR( FilterChangeIndication, AC, PR+EV, UINT8, true ); - HAPCHAR( FirmwareRevision, 52, PR, STRING, true ); + HAPCHAR( FirmwareRevision, 52, PR+EV, STRING, true ); HAPCHAR( HardwareRevision, 53, PR, STRING, true ); HAPCHAR( HeatingThresholdTemperature, 12, PR+PW+EV, FLOAT, false ); HAPCHAR( HoldPosition, 6F, PW, BOOL, true ); diff --git a/src/HomeSpan.h b/src/HomeSpan.h index f0dc4ea..e05df04 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -596,7 +596,7 @@ struct SpanCharacteristic{ void setString(const char *val){ if((perms & EV) == 0){ - Serial.printf("\n*** WARNING: Attempt to update Characteristic::%s with setVal() ignored. No NOTIFICATION permission on this characteristic\n\n",hapName); + Serial.printf("\n*** WARNING: Attempt to update Characteristic::%s with setString() ignored. No NOTIFICATION permission on this characteristic\n\n",hapName); return; } From 6dacbb0a8c637282f82987335219043cc8f0d65b Mon Sep 17 00:00:00 2001 From: Gregg Date: Wed, 27 Apr 2022 06:31:32 -0500 Subject: [PATCH 08/98] Update FeatherPins.h Changed mapping for S2 to Espressif ESP32-S2 (instead of UM Featherboard) --- src/FeatherPins.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FeatherPins.h b/src/FeatherPins.h index b4a35d6..9daa56e 100644 --- a/src/FeatherPins.h +++ b/src/FeatherPins.h @@ -35,7 +35,7 @@ #define DEVICE_SUFFIX "" #elif defined(CONFIG_IDF_TARGET_ESP32S2) - enum {F13=11,F12=10,F27=7,F33=3,F15=1,F32=38,F14=33,F22=9,F23=8,F26=17,F25=18,F34=14,F39=12,F36=6,F4=5,F5=36,F18=35,F19=37,F16=44,F17=43}; + enum {F13=1,F12=3,F27=7,F33=34,F15=10,F32=42,F14=11,F22=9,F23=8,F26=17,F25=14,F34=13,F39=12,F36=18,F4=19,F5=36,F18=35,F19=37,F16=20,F17=21,F21=16}; #define DEVICE_SUFFIX "-S2" #elif defined(CONFIG_IDF_TARGET_ESP32C3) From 4659e6f660b7fe34f9bf93f095e7d0ae160d0b60 Mon Sep 17 00:00:00 2001 From: Gregg Date: Wed, 27 Apr 2022 20:42:38 -0500 Subject: [PATCH 09/98] Enabled IPV6 Address Broadcast Added to see if this addresses the mdns-discovery issue in Arduino IDE 2.0.0 interface. --- src/HomeSpan.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 71b89c7..23cdfde 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -461,6 +461,8 @@ void Span::checkConnect(){ Serial.print(WiFi.localIP()); Serial.print("\n"); + WiFi.enableIpV6(); + char id[18]; // create string version of Accessory ID for MDNS broadcast memcpy(id,HAPClient::accessory.ID,17); // copy ID bytes id[17]='\0'; // add terminating null From 6a8b3653dacc27e9131625a6176f1e6b7267047e Mon Sep 17 00:00:00 2001 From: Gregg Date: Wed, 27 Apr 2022 21:11:44 -0500 Subject: [PATCH 10/98] Update FeatherPins.h Mappings are now based on specific boards, not just the architecture (e.g. S2 vs. C3). --- src/FeatherPins.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/FeatherPins.h b/src/FeatherPins.h index 9daa56e..458c5e2 100644 --- a/src/FeatherPins.h +++ b/src/FeatherPins.h @@ -25,20 +25,20 @@ * ********************************************************************************/ -// For developer use and testing only - provides a common set of pin numbers mapped to the Adafruit Feather Board +// For developer use and testing only - provides a common set of pin numbers mapped to the Adafruit Feather Board. // Facilitates the testing of identical code on an ESP32, ESP32-S2, and ESP32-C3 using a common jig without rewiring #pragma once -#if defined(CONFIG_IDF_TARGET_ESP32) +#if defined(ARDUINO_FEATHER_ESP32) enum {F13=13,F12=12,F27=27,F33=33,F15=15,F32=32,F14=14,F22=22,F23=23,F26=26,F25=25,F34=34,F39=39,F36=36,F4=4,F5=5,F18=18,F19=19,F16=16,F17=17,F21=21}; #define DEVICE_SUFFIX "" -#elif defined(CONFIG_IDF_TARGET_ESP32S2) +#elif defined(ARDUINO_ESP32S2_DEV) enum {F13=1,F12=3,F27=7,F33=34,F15=10,F32=42,F14=11,F22=9,F23=8,F26=17,F25=14,F34=13,F39=12,F36=18,F4=19,F5=36,F18=35,F19=37,F16=20,F17=21,F21=16}; #define DEVICE_SUFFIX "-S2" -#elif defined(CONFIG_IDF_TARGET_ESP32C3) +#elif defined(ARDUINO_ESP32C3_DEV) enum {F27=2,F33=7,F32=3,F14=10,F22=9,F23=8,F26=0,F25=1,F4=18,F5=4,F18=6,F19=5,F16=20,F17=21,F21=19}; #define DEVICE_SUFFIX "-C3" From 82035df0a74745e936dc78189529f5838cea5d02 Mon Sep 17 00:00:00 2001 From: Gregg Date: Fri, 29 Apr 2022 18:16:43 -0500 Subject: [PATCH 11/98] Removed enabling of IPv6 Not having an IPv6 address is NOT the cause of the Arduino 2.0 OTA network port problem. --- src/HomeSpan.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 23cdfde..71b89c7 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -461,8 +461,6 @@ void Span::checkConnect(){ Serial.print(WiFi.localIP()); Serial.print("\n"); - WiFi.enableIpV6(); - char id[18]; // create string version of Accessory ID for MDNS broadcast memcpy(id,HAPClient::accessory.ID,17); // copy ID bytes id[17]='\0'; // add terminating null From 212900f624a40c4e8e6f5654f3ff6a1995c52dfa Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 30 Apr 2022 07:57:01 -0500 Subject: [PATCH 12/98] Initial re-factoring of "i" command Instead of creating a static configLog for display upon start-up, an info log will be generated based on real-time data whenever the 'i' CLI command is requested. This provides for more streamlined error-checking as well. --- src/HomeSpan.cpp | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 71b89c7..57610fe 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -929,7 +929,26 @@ void Span::processSerialCommand(const char *c){ Serial.print("\n*** HomeSpan Info ***\n\n"); - Serial.print(configLog); + for(auto acc=Accessories.begin(); acc!=Accessories.end(); acc++){ + Serial.printf("\u27a4 Accessory: AID=%d\n",(*acc)->aid); + + for(auto svc=(*acc)->Services.begin(); svc!=(*acc)->Services.end(); svc++){ + Serial.printf(" \u279f Service %s: IID=%d, %sUUIS=\"%s\"",(*svc)->hapName,(*svc)->iid,(*svc)->isCustom?"Custom-":"",(*svc)->type); + Serial.printf("\n"); + + for(auto chr=(*svc)->Characteristics.begin(); chr!=(*svc)->Characteristics.end(); chr++){ + Serial.printf(" \u21e8 Characteristic %s(%s): IID=%d, %sUUID=\"%s\"",(*chr)->hapName,(*chr)->uvPrint((*chr)->value).c_str(),(*chr)->iid,(*chr)->isCustom?"Custom-":"",(*chr)->type); + if((*chr)->format!=FORMAT::STRING && (*chr)->format!=FORMAT::BOOL) + Serial.printf(", Range=[%s,%s]",(*chr)->uvPrint((*chr)->minValue).c_str(),(*chr)->uvPrint((*chr)->maxValue).c_str()); + Serial.printf("\n"); + } + } + } + + Serial.print("\n------------\n\n"); + + Serial.println(configLog); + Serial.print("\nConfigured as Bridge: "); Serial.print(homeSpan.isBridge?"YES":"NO"); Serial.print("\n\n"); From 88f2373a714b71030ed2a7060f1e446464eeb6be Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 30 Apr 2022 11:04:57 -0500 Subject: [PATCH 13/98] Continued refactoring of 'i' CLI command --- src/HomeSpan.cpp | 29 ++++++++++++++++++-- src/HomeSpan.h | 71 ++++++++++-------------------------------------- 2 files changed, 42 insertions(+), 58 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 57610fe..e382906 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -938,9 +938,34 @@ void Span::processSerialCommand(const char *c){ for(auto chr=(*svc)->Characteristics.begin(); chr!=(*svc)->Characteristics.end(); chr++){ Serial.printf(" \u21e8 Characteristic %s(%s): IID=%d, %sUUID=\"%s\"",(*chr)->hapName,(*chr)->uvPrint((*chr)->value).c_str(),(*chr)->iid,(*chr)->isCustom?"Custom-":"",(*chr)->type); - if((*chr)->format!=FORMAT::STRING && (*chr)->format!=FORMAT::BOOL) - Serial.printf(", Range=[%s,%s]",(*chr)->uvPrint((*chr)->minValue).c_str(),(*chr)->uvPrint((*chr)->maxValue).c_str()); + + if((*chr)->format!=FORMAT::STRING && (*chr)->format!=FORMAT::BOOL){ + if((*chr)->uvGet((*chr)->stepValue)>0) + Serial.printf(", %sRange=[%s,%s,%s]",(*chr)->customRange?"Custom-":"",(*chr)->uvPrint((*chr)->minValue).c_str(),(*chr)->uvPrint((*chr)->maxValue).c_str(),(*chr)->uvPrint((*chr)->stepValue).c_str()); + else + Serial.printf(", %sRange=[%s,%s]",(*chr)->customRange?"Custom-":"",(*chr)->uvPrint((*chr)->minValue).c_str(),(*chr)->uvPrint((*chr)->maxValue).c_str()); + } + + if((*chr)->nvsKey) + Serial.printf(" (nvs)"); Serial.printf("\n"); + + if(!(*chr)->isSupported) + Serial.printf(" *** WARNING! Service does not support this Characteristic. ***\n"); + else + if(invalidUUID((*chr)->type,(*chr)->isCustom)) + Serial.printf(" *** ERROR! Format of UUID is invalid. ***\n"); + else + if((*chr)->isRepeated) + Serial.printf(" *** ERROR! Characteristic already defined for this Service. ***\n"); + + if((*chr)->setRangeError) + Serial.printf(" *** WARNING! Attempt to set Custom Range for this Characteristic ignored. ***\n"); + + // 1. RANGE CHECK + // 2. MISSING REQUIRED CHARACTERISTICS + // 3. AID CHECK + } } } diff --git a/src/HomeSpan.h b/src/HomeSpan.h index e05df04..845527e 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -350,6 +350,9 @@ struct SpanCharacteristic{ boolean *ev; // Characteristic Event Notify Enable (per-connection) char *nvsKey=NULL; // key for NVS storage of Characteristic value boolean isCustom; // flag to indicate this is a Custom Characteristic + boolean isSupported; // flag to indicate this Characteristic is supported by the containing Service (it's either required or optional) + boolean isRepeated=false; // flag to indicate this Characteristic is defined repeated times within the same Service (reports an error) + boolean setRangeError=false; // flag to indicate attempt to set range on Characteristic that does not support changes to range uint32_t aid=0; // Accessory ID - passed through from Service containing this Characteristic boolean isUpdated=false; // set to true when new value has been requested by PUT /characteristic @@ -454,30 +457,14 @@ struct SpanCharacteristic{ template SpanCharacteristic *setRange(A min, B max, S step=0){ - char c[256]; - homeSpan.configLog+=String(" \u2b0c Set Range for ") + String(hapName) + " with AID=" + String(aid) + ", IID=" + String(iid); - - if(customRange){ - sprintf(c," *** ERROR! Range already set for this Characteristic! ***\n"); - homeSpan.nFatalErrors++; - } else - - if(staticRange){ - sprintf(c," *** ERROR! Can't change range for this Characteristic! ***\n"); - homeSpan.nFatalErrors++; - } else { - + if(!staticRange){ uvSet(minValue,min); uvSet(maxValue,max); uvSet(stepValue,step); customRange=true; + } else + setRangeError=true; - if(uvGet(stepValue)>0) - sprintf(c,": Min=%s, Max=%s, Step=%s\n",uvPrint(minValue),uvPrint(maxValue),uvPrint(stepValue)); - else - sprintf(c,": Min=%s, Max=%s\n",uvPrint(minValue),uvPrint(maxValue)); - } - homeSpan.configLog+=c; return(this); } // setRange() @@ -527,46 +514,18 @@ struct SpanCharacteristic{ uvSet(stepValue,0); } - homeSpan.configLog+="(" + uvPrint(value) + ")" + ": IID=" + String(iid) + ", " + (isCustom?"Custom-":"") + "UUID=\"" + String(type) + "\""; - if(format!=FORMAT::STRING && format!=FORMAT::BOOL) - homeSpan.configLog+= ", Range=[" + String(uvPrint(minValue)) + "," + String(uvPrint(maxValue)) + "]"; - - if(nvsFlag==2) - homeSpan.configLog+=" (restored)"; - else if(nvsFlag==1) - homeSpan.configLog+=" (storing)"; - - if(Span::invalidUUID(type,isCustom)){ - homeSpan.configLog+=" *** ERROR! Format of UUID is invalid. ***"; - homeSpan.nFatalErrors++; - } - - boolean valid=isCustom|service->isCustom; // automatically set valid if either Characteristic or containing Service is Custom + isSupported=isCustom|service->isCustom; // automatically set valid if either Characteristic or containing Service is Custom - for(int i=0; !valid && iServices.back()->req.size(); i++) - valid=!strcmp(type,homeSpan.Accessories.back()->Services.back()->req[i]->type); - - for(int i=0; !valid && iServices.back()->opt.size(); i++) - valid=!strcmp(type,homeSpan.Accessories.back()->Services.back()->opt[i]->type); - - if(!valid){ - homeSpan.configLog+=" *** WARNING! Service does not support this Characteristic. ***"; - homeSpan.nWarnings++; - } - - boolean repeated=false; - - for(int i=0; !repeated && iServices.back()->Characteristics.size(); i++) - repeated=!strcmp(type,homeSpan.Accessories.back()->Services.back()->Characteristics[i]->type); - - if(valid && repeated){ - homeSpan.configLog+=" *** ERROR! Characteristic already defined for this Service. ***"; - homeSpan.nFatalErrors++; - } + for(int i=0; !isSupported && iServices.back()->req.size(); i++) + isSupported=!strcmp(type,homeSpan.Accessories.back()->Services.back()->req[i]->type); + for(int i=0; !isSupported && iServices.back()->opt.size(); i++) + isSupported=!strcmp(type,homeSpan.Accessories.back()->Services.back()->opt[i]->type); + + for(int i=0; !isRepeated && iServices.back()->Characteristics.size(); i++) + isRepeated=!strcmp(type,homeSpan.Accessories.back()->Services.back()->Characteristics[i]->type); + homeSpan.Accessories.back()->Services.back()->Characteristics.push_back(this); - - homeSpan.configLog+="\n"; } // init() From 5f463b013ec81811e15e5197eb9fa005eb4a0063 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 30 Apr 2022 16:29:31 -0500 Subject: [PATCH 14/98] Continuing to refactor 'i' CLI command --- src/HomeSpan.cpp | 91 +++++++++++++----------------------------------- src/HomeSpan.h | 2 -- 2 files changed, 24 insertions(+), 69 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index e382906..cc1f9f0 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -184,11 +184,8 @@ void Span::pollTask() { if(!homeSpan.Accessories.back()->Services.empty()) homeSpan.Accessories.back()->Services.back()->validate(); - homeSpan.Accessories.back()->validate(); } - checkRanges(); - if(nWarnings>0){ configLog+="\n*** CAUTION: There " + String((nWarnings>1?"are ":"is ")) + String(nWarnings) + " WARNING" + (nWarnings>1?"S":"") + " associated with this configuration that may lead to the device becoming non-responsive, or operating in an unexpected manner. ***\n"; } @@ -931,10 +928,16 @@ void Span::processSerialCommand(const char *c){ for(auto acc=Accessories.begin(); acc!=Accessories.end(); acc++){ Serial.printf("\u27a4 Accessory: AID=%d\n",(*acc)->aid); + boolean foundInfo=false; for(auto svc=(*acc)->Services.begin(); svc!=(*acc)->Services.end(); svc++){ Serial.printf(" \u279f Service %s: IID=%d, %sUUIS=\"%s\"",(*svc)->hapName,(*svc)->iid,(*svc)->isCustom?"Custom-":"",(*svc)->type); - Serial.printf("\n"); + Serial.printf("\n"); + + if(!strcmp((*svc)->type,"3E")) + foundInfo=true; + else if((*acc)->aid==1) // this is an Accessory with aid=1, but it has more than just AccessoryInfo. So... + isBridge=false; // ...this is not a bridge device for(auto chr=(*svc)->Characteristics.begin(); chr!=(*svc)->Characteristics.end(); chr++){ Serial.printf(" \u21e8 Characteristic %s(%s): IID=%d, %sUUID=\"%s\"",(*chr)->hapName,(*chr)->uvPrint((*chr)->value).c_str(),(*chr)->iid,(*chr)->isCustom?"Custom-":"",(*chr)->type); @@ -951,31 +954,38 @@ void Span::processSerialCommand(const char *c){ Serial.printf("\n"); if(!(*chr)->isSupported) - Serial.printf(" *** WARNING! Service does not support this Characteristic. ***\n"); + Serial.printf(" *** WARNING! Service does not support this Characteristic ***\n"); else if(invalidUUID((*chr)->type,(*chr)->isCustom)) - Serial.printf(" *** ERROR! Format of UUID is invalid. ***\n"); + Serial.printf(" *** ERROR! Format of UUID is invalid ***\n"); else if((*chr)->isRepeated) - Serial.printf(" *** ERROR! Characteristic already defined for this Service. ***\n"); + Serial.printf(" *** ERROR! Characteristic already defined for this Service ***\n"); if((*chr)->setRangeError) - Serial.printf(" *** WARNING! Attempt to set Custom Range for this Characteristic ignored. ***\n"); + Serial.printf(" *** WARNING! Attempt to set Custom Range for this Characteristic ignored ***\n"); + + if((*chr)->format!=STRING && ((*chr)->uvGet((*chr)->value) < (*chr)->uvGet((*chr)->minValue) || (*chr)->uvGet((*chr)->value) > (*chr)->uvGet((*chr)->maxValue))) + Serial.printf(" *** WARNING! Value of %llg is out of range [%llg,%llg] ***\n",(*chr)->uvGet((*chr)->value),(*chr)->uvGet((*chr)->minValue),(*chr)->uvGet((*chr)->maxValue)); - // 1. RANGE CHECK // 2. MISSING REQUIRED CHARACTERISTICS // 3. AID CHECK - } - } - } + } // Characteristics + + } // Services + + if(!foundInfo) + Serial.printf(" *** ERROR! Required Service::AccessoryInformation() not found ***\n"); + + } // Accessories Serial.print("\n------------\n\n"); Serial.println(configLog); Serial.print("\nConfigured as Bridge: "); - Serial.print(homeSpan.isBridge?"YES":"NO"); + Serial.print(isBridge?"YES":"NO"); Serial.print("\n\n"); char d[]="------------------------------"; @@ -1419,37 +1429,6 @@ int Span::sprintfAttributes(char **ids, int numIDs, int flags, char *cBuf){ return(nChars); } -/////////////////////////////// - -void Span::checkRanges(){ - - boolean okay=true; - homeSpan.configLog+="\nRange Check:"; - - for(int i=0;iServices.size();j++){ - for(int k=0;kServices[j]->Characteristics.size();k++){ - SpanCharacteristic *chr=Accessories[i]->Services[j]->Characteristics[k]; - - if(chr->format!=STRING && (chr->uvGet(chr->value) < chr->uvGet(chr->minValue) || chr->uvGet(chr->value) > chr->uvGet(chr->maxValue))){ - char c[256]; - sprintf(c,"\n \u2718 Characteristic %s with AID=%d, IID=%d: Initial value of %lg is out of range [%llg,%llg]", - chr->hapName,chr->aid,chr->iid,chr->uvGet(chr->value),chr->uvGet(chr->minValue),chr->uvGet(chr->maxValue)); - if(okay) - homeSpan.configLog+="\n"; - homeSpan.configLog+=c; - homeSpan.nWarnings++; - okay=false; - } - } - } - } - - if(okay) - homeSpan.configLog+=" No Warnings"; - homeSpan.configLog+="\n\n"; -} - /////////////////////////////// // SpanAccessory // /////////////////////////////// @@ -1469,8 +1448,7 @@ SpanAccessory::SpanAccessory(uint32_t aid){ if(!homeSpan.Accessories.back()->Services.empty()) homeSpan.Accessories.back()->Services.back()->validate(); - - homeSpan.Accessories.back()->validate(); + } else { this->aid=1; } @@ -1502,27 +1480,6 @@ SpanAccessory::SpanAccessory(uint32_t aid){ /////////////////////////////// -void SpanAccessory::validate(){ - - boolean foundInfo=false; - - for(int i=0;itype,"3E")) - foundInfo=true; - else if(aid==1) // this is an Accessory with aid=1, but it has more than just AccessoryInfo. So... - homeSpan.isBridge=false; // ...this is not a bridge device - } - - if(!foundInfo){ - homeSpan.configLog+=" \u2718 Service AccessoryInformation"; - homeSpan.configLog+=" *** ERROR! Required Service for this Accessory not found. ***\n"; - homeSpan.nFatalErrors++; - } - -} - -/////////////////////////////// - int SpanAccessory::sprintfAttributes(char *cBuf){ int nBytes=0; diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 845527e..9855c42 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -215,7 +215,6 @@ struct Span{ void checkConnect(); // check WiFi connection; connect if needed void commandMode(); // allows user to control and reset HomeSpan settings with the control button void processSerialCommand(const char *c); // process command 'c' (typically from readSerial, though can be called with any 'c') - void checkRanges(); // checks values of all Characteristics to ensure they are each within range int sprintfAttributes(char *cBuf); // prints Attributes JSON database into buf, unless buf=NULL; return number of characters printed, excluding null terminator, even if buf=NULL void prettyPrint(char *buf, int nsp=2); // print arbitrary JSON from buf to serial monitor, formatted with indentions of 'nsp' spaces @@ -285,7 +284,6 @@ struct SpanAccessory{ SpanAccessory(uint32_t aid=0); int sprintfAttributes(char *cBuf); // prints Accessory JSON database into buf, unless buf=NULL; return number of characters printed, excluding null terminator, even if buf=NULL - void validate(); // error-checks Accessory }; /////////////////////////////// From 0e6f7d27f9edbb2dd60319a6c887556463cbf670 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 30 Apr 2022 18:18:15 -0500 Subject: [PATCH 15/98] Continuing with 'i' refactoring Next up: Re-do optional/required Characteristics logic --- src/HomeSpan.cpp | 54 +++++++++++++++--------------------------------- src/HomeSpan.h | 1 + 2 files changed, 18 insertions(+), 37 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index cc1f9f0..1549340 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -926,16 +926,29 @@ void Span::processSerialCommand(const char *c){ Serial.print("\n*** HomeSpan Info ***\n\n"); + std::set aidValues; + for(auto acc=Accessories.begin(); acc!=Accessories.end(); acc++){ Serial.printf("\u27a4 Accessory: AID=%d\n",(*acc)->aid); boolean foundInfo=false; + if(acc==Accessories.begin() && (*acc)->aid!=1) + Serial.printf(" *** ERROR! AID of first Accessory must always be 1 ***\n"); + + if(aidValues.find((*acc)->aid)!=aidValues.end()) + Serial.printf(" *** ERROR! AID already in use for another Accessory ***\n"); + + aidValues.insert((*acc)->aid); + for(auto svc=(*acc)->Services.begin(); svc!=(*acc)->Services.end(); svc++){ Serial.printf(" \u279f Service %s: IID=%d, %sUUIS=\"%s\"",(*svc)->hapName,(*svc)->iid,(*svc)->isCustom?"Custom-":"",(*svc)->type); Serial.printf("\n"); - if(!strcmp((*svc)->type,"3E")) + if(!strcmp((*svc)->type,"3E")){ foundInfo=true; + if((*svc)->iid!=1) + Serial.printf(" *** ERROR! The Accessory Information Service must be defined before any other Services in an Accessory ***\n"); + } else if((*acc)->aid==1) // this is an Accessory with aid=1, but it has more than just AccessoryInfo. So... isBridge=false; // ...this is not a bridge device @@ -976,7 +989,7 @@ void Span::processSerialCommand(const char *c){ } // Services if(!foundInfo) - Serial.printf(" *** ERROR! Required Service::AccessoryInformation() not found ***\n"); + Serial.printf(" *** ERROR! Required Accessory Information Service not found ***\n"); } // Accessories @@ -1459,23 +1472,6 @@ SpanAccessory::SpanAccessory(uint32_t aid){ this->aid=aid; } - homeSpan.configLog+="\u27a4 Accessory: AID=" + String(this->aid); - - for(int i=0;iaid==homeSpan.Accessories[i]->aid){ - homeSpan.configLog+=" *** ERROR! ID already in use for another Accessory. ***"; - homeSpan.nFatalErrors++; - break; - } - } - - if(homeSpan.Accessories.size()==1 && this->aid!=1){ - homeSpan.configLog+=" *** ERROR! ID of first Accessory must always be 1. ***"; - homeSpan.nFatalErrors++; - } - - homeSpan.configLog+="\n"; - } /////////////////////////////// @@ -1502,15 +1498,13 @@ int SpanAccessory::sprintfAttributes(char *cBuf){ SpanService::SpanService(const char *type, const char *hapName, boolean isCustom){ - if(!homeSpan.Accessories.empty() && !homeSpan.Accessories.back()->Services.empty()) // this is not the first Service to be defined for this Accessory - homeSpan.Accessories.back()->Services.back()->validate(); +// if(!homeSpan.Accessories.empty() && !homeSpan.Accessories.back()->Services.empty()) // this is not the first Service to be defined for this Accessory +// homeSpan.Accessories.back()->Services.back()->validate(); this->type=type; this->hapName=hapName; this->isCustom=isCustom; - homeSpan.configLog+=" \u279f Service " + String(hapName); - if(homeSpan.Accessories.empty()){ homeSpan.configLog+=" *** ERROR! Can't create new Service without a defined Accessory! ***\n"; homeSpan.nFatalErrors++; @@ -1520,20 +1514,6 @@ SpanService::SpanService(const char *type, const char *hapName, boolean isCustom homeSpan.Accessories.back()->Services.push_back(this); iid=++(homeSpan.Accessories.back()->iidCount); - homeSpan.configLog+=": IID=" + String(iid) + ", " + (isCustom?"Custom-":"") + "UUID=\"" + String(type) + "\""; - - if(Span::invalidUUID(type,isCustom)){ - homeSpan.configLog+=" *** ERROR! Format of UUID is invalid. ***"; - homeSpan.nFatalErrors++; - } - - if(!strcmp(this->type,"3E") && iid!=1){ - homeSpan.configLog+=" *** ERROR! The AccessoryInformation Service must be defined before any other Services in an Accessory. ***"; - homeSpan.nFatalErrors++; - } - - homeSpan.configLog+="\n"; - } /////////////////////////////// diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 9855c42..b538823 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -36,6 +36,7 @@ #include #include #include +#include #include #include From 9f71c67f14261cd7c6828e85b4189da0fd2f7736 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 30 Apr 2022 22:20:42 -0500 Subject: [PATCH 16/98] Updated logic for required/optional Characteristics Logic now embedded in 'i' CLI command. --- src/HomeSpan.cpp | 81 ++++++++++++++---------------------------------- src/HomeSpan.h | 24 ++++---------- src/Span.h | 4 +-- 3 files changed, 32 insertions(+), 77 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 1549340..53a4da3 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -179,17 +179,6 @@ void Span::pollTask() { if(!isInitialized){ - if(!homeSpan.Accessories.empty()){ - - if(!homeSpan.Accessories.back()->Services.empty()) - homeSpan.Accessories.back()->Services.back()->validate(); - - } - - if(nWarnings>0){ - configLog+="\n*** CAUTION: There " + String((nWarnings>1?"are ":"is ")) + String(nWarnings) + " WARNING" + (nWarnings>1?"S":"") + " associated with this configuration that may lead to the device becoming non-responsive, or operating in an unexpected manner. ***\n"; - } - processSerialCommand("i"); // print homeSpan configuration info if(nFatalErrors>0){ @@ -926,7 +915,7 @@ void Span::processSerialCommand(const char *c){ Serial.print("\n*** HomeSpan Info ***\n\n"); - std::set aidValues; + unordered_set aidValues; for(auto acc=Accessories.begin(); acc!=Accessories.end(); acc++){ Serial.printf("\u27a4 Accessory: AID=%d\n",(*acc)->aid); @@ -951,7 +940,9 @@ void Span::processSerialCommand(const char *c){ } else if((*acc)->aid==1) // this is an Accessory with aid=1, but it has more than just AccessoryInfo. So... isBridge=false; // ...this is not a bridge device - + + unordered_set hapChar; + for(auto chr=(*svc)->Characteristics.begin(); chr!=(*svc)->Characteristics.end(); chr++){ Serial.printf(" \u21e8 Characteristic %s(%s): IID=%d, %sUUID=\"%s\"",(*chr)->hapName,(*chr)->uvPrint((*chr)->value).c_str(),(*chr)->iid,(*chr)->isCustom?"Custom-":"",(*chr)->type); @@ -966,13 +957,13 @@ void Span::processSerialCommand(const char *c){ Serial.printf(" (nvs)"); Serial.printf("\n"); - if(!(*chr)->isSupported) + if(!(*chr)->isCustom && !(*svc)->isCustom && (*svc)->req.find((*chr)->hapChar)==(*svc)->req.end() && (*svc)->opt.find((*chr)->hapChar)==(*svc)->opt.end()) Serial.printf(" *** WARNING! Service does not support this Characteristic ***\n"); else if(invalidUUID((*chr)->type,(*chr)->isCustom)) Serial.printf(" *** ERROR! Format of UUID is invalid ***\n"); else - if((*chr)->isRepeated) + if(hapChar.find((*chr)->hapChar)!=hapChar.end()) Serial.printf(" *** ERROR! Characteristic already defined for this Service ***\n"); if((*chr)->setRangeError) @@ -981,15 +972,19 @@ void Span::processSerialCommand(const char *c){ if((*chr)->format!=STRING && ((*chr)->uvGet((*chr)->value) < (*chr)->uvGet((*chr)->minValue) || (*chr)->uvGet((*chr)->value) > (*chr)->uvGet((*chr)->maxValue))) Serial.printf(" *** WARNING! Value of %llg is out of range [%llg,%llg] ***\n",(*chr)->uvGet((*chr)->value),(*chr)->uvGet((*chr)->minValue),(*chr)->uvGet((*chr)->maxValue)); - // 2. MISSING REQUIRED CHARACTERISTICS - // 3. AID CHECK - + hapChar.insert((*chr)->hapChar); + } // Characteristics + + for(auto req=(*svc)->req.begin(); req!=(*svc)->req.end(); req++){ + if(hapChar.find(*req)==hapChar.end()) + Serial.printf(" *** WARNING! Required '%s' Characteristic for this Service not found ***\n",(*req)->hapName); + } } // Services if(!foundInfo) - Serial.printf(" *** ERROR! Required Accessory Information Service not found ***\n"); + Serial.printf(" *** ERROR! Required 'AccessoryInformation' Service not found ***\n"); } // Accessories @@ -1458,10 +1453,7 @@ SpanAccessory::SpanAccessory(uint32_t aid){ } this->aid=homeSpan.Accessories.back()->aid+1; - - if(!homeSpan.Accessories.back()->Services.empty()) - homeSpan.Accessories.back()->Services.back()->validate(); - + } else { this->aid=1; } @@ -1498,22 +1490,18 @@ int SpanAccessory::sprintfAttributes(char *cBuf){ SpanService::SpanService(const char *type, const char *hapName, boolean isCustom){ -// if(!homeSpan.Accessories.empty() && !homeSpan.Accessories.back()->Services.empty()) // this is not the first Service to be defined for this Accessory -// homeSpan.Accessories.back()->Services.back()->validate(); + if(homeSpan.Accessories.empty()){ + Serial.printf("\nFATAL ERROR! Can't create new Service '%s' without a defined Accessory ***\n",hapName); + Serial.printf("\n=== PROGRAM HALTED ==="); + while(1); + } this->type=type; this->hapName=hapName; this->isCustom=isCustom; - if(homeSpan.Accessories.empty()){ - homeSpan.configLog+=" *** ERROR! Can't create new Service without a defined Accessory! ***\n"; - homeSpan.nFatalErrors++; - return; - } - homeSpan.Accessories.back()->Services.push_back(this); iid=++(homeSpan.Accessories.back()->iidCount); - } /////////////////////////////// @@ -1573,26 +1561,6 @@ int SpanService::sprintfAttributes(char *cBuf){ return(nBytes); } -/////////////////////////////// - -void SpanService::validate(){ - - for(int i=0;itype,Characteristics[j]->type); - - if(!valid){ - homeSpan.configLog+=" \u2718 Characteristic " + String(req[i]->hapName); - homeSpan.configLog+=" *** WARNING! Required Characteristic for this Service not found. ***\n"; - homeSpan.nWarnings++; - } - } - - vector().swap(opt); - vector().swap(req); -} - /////////////////////////////// // SpanCharacteristic // /////////////////////////////// @@ -1604,13 +1572,12 @@ SpanCharacteristic::SpanCharacteristic(HapChar *hapChar, boolean isCustom){ format=hapChar->format; staticRange=hapChar->staticRange; this->isCustom=isCustom; - - homeSpan.configLog+=" \u21e8 Characteristic " + String(hapName); + this->hapChar=hapChar; if(homeSpan.Accessories.empty() || homeSpan.Accessories.back()->Services.empty()){ - homeSpan.configLog+=" *** ERROR! Can't create new Characteristic without a defined Service! ***\n"; - homeSpan.nFatalErrors++; - return; + Serial.printf("\nFATAL ERROR! Can't create new Characteristic '%s' without a defined Service ***\n",hapName); + Serial.printf("\n=== PROGRAM HALTED ==="); + while(1); } iid=++(homeSpan.Accessories.back()->iidCount); diff --git a/src/HomeSpan.h b/src/HomeSpan.h index b538823..ff76397 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -36,7 +36,7 @@ #include #include #include -#include +#include #include #include @@ -49,6 +49,7 @@ using std::vector; using std::unordered_map; +using std::unordered_set; enum { GET_AID=1, @@ -297,8 +298,8 @@ struct SpanService{ boolean hidden=false; // optional property indicating service is hidden boolean primary=false; // optional property indicating service is primary vector Characteristics; // vector of pointers to all Characteristics in this Service - vector req; // vector of pointers to all required HAP Characteristic Types for this Service - vector opt; // vector of pointers to all optional HAP Characteristic Types for this Service + unordered_set req; // unordered set of pointers to all required HAP Characteristic Types for this Service + unordered_set opt; // unordered set of pointers to all optional HAP Characteristic Types for this Service vector linkedServices; // vector of pointers to any optional linked Services boolean isCustom; // flag to indicate this is a Custom Service @@ -310,7 +311,6 @@ struct SpanService{ vector getLinks(){return(linkedServices);} // returns linkedServices vector for use as range in "for-each" loops int sprintfAttributes(char *cBuf); // prints Service JSON records into buf; return number of characters printed, excluding null terminator - void validate(); // error-checks Service virtual boolean update() {return(true);} // placeholder for code that is called when a Service is updated via a Controller. Must return true/false depending on success of update virtual void loop(){} // loops for each Service - called every cycle and can be over-ridden with user-defined code @@ -333,6 +333,7 @@ struct SpanCharacteristic{ }; int iid=0; // Instance ID (HAP Table 6-3) + HapChar *hapChar; // pointer to HAP Characteristic structure const char *type; // Characteristic Type const char *hapName; // HAP Name UVal value; // Characteristic Value @@ -349,8 +350,6 @@ struct SpanCharacteristic{ boolean *ev; // Characteristic Event Notify Enable (per-connection) char *nvsKey=NULL; // key for NVS storage of Characteristic value boolean isCustom; // flag to indicate this is a Custom Characteristic - boolean isSupported; // flag to indicate this Characteristic is supported by the containing Service (it's either required or optional) - boolean isRepeated=false; // flag to indicate this Characteristic is defined repeated times within the same Service (reports an error) boolean setRangeError=false; // flag to indicate attempt to set range on Characteristic that does not support changes to range uint32_t aid=0; // Accessory ID - passed through from Service containing this Characteristic @@ -512,18 +511,7 @@ struct SpanCharacteristic{ uvSet(maxValue,max); uvSet(stepValue,0); } - - isSupported=isCustom|service->isCustom; // automatically set valid if either Characteristic or containing Service is Custom - - for(int i=0; !isSupported && iServices.back()->req.size(); i++) - isSupported=!strcmp(type,homeSpan.Accessories.back()->Services.back()->req[i]->type); - - for(int i=0; !isSupported && iServices.back()->opt.size(); i++) - isSupported=!strcmp(type,homeSpan.Accessories.back()->Services.back()->opt[i]->type); - - for(int i=0; !isRepeated && iServices.back()->Characteristics.size(); i++) - isRepeated=!strcmp(type,homeSpan.Accessories.back()->Services.back()->Characteristics[i]->type); - + homeSpan.Accessories.back()->Services.back()->Characteristics.push_back(this); } // init() diff --git a/src/Span.h b/src/Span.h index 8b7e4b9..9d01787 100644 --- a/src/Span.h +++ b/src/Span.h @@ -31,8 +31,8 @@ // Macros to define vectors of required and optional characteristics for each Span Service structure -#define REQ(HAPCHAR) req.push_back(&hapChars.HAPCHAR) -#define OPT(HAPCHAR) opt.push_back(&hapChars.HAPCHAR) +#define REQ(HAPCHAR) req.insert(&hapChars.HAPCHAR) +#define OPT(HAPCHAR) opt.insert(&hapChars.HAPCHAR) namespace Service { From 7b30f971e2e55e04c03a1deb74248f64d9dcde6e Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 1 May 2022 09:24:45 -0500 Subject: [PATCH 17/98] Continued refactoring of 'i' CLI command --- src/HomeSpan.cpp | 73 ++++++++++++++++++++++-------------------------- src/HomeSpan.h | 5 ++-- 2 files changed, 36 insertions(+), 42 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 53a4da3..8b93d12 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -947,7 +947,9 @@ void Span::processSerialCommand(const char *c){ Serial.printf(" \u21e8 Characteristic %s(%s): IID=%d, %sUUID=\"%s\"",(*chr)->hapName,(*chr)->uvPrint((*chr)->value).c_str(),(*chr)->iid,(*chr)->isCustom?"Custom-":"",(*chr)->type); if((*chr)->format!=FORMAT::STRING && (*chr)->format!=FORMAT::BOOL){ - if((*chr)->uvGet((*chr)->stepValue)>0) + if((*chr)->validValues) + Serial.printf(", Valid Values=%s",(*chr)->validValues); + else if((*chr)->uvGet((*chr)->stepValue)>0) Serial.printf(", %sRange=[%s,%s,%s]",(*chr)->customRange?"Custom-":"",(*chr)->uvPrint((*chr)->minValue).c_str(),(*chr)->uvPrint((*chr)->maxValue).c_str(),(*chr)->uvPrint((*chr)->stepValue).c_str()); else Serial.printf(", %sRange=[%s,%s]",(*chr)->customRange?"Custom-":"",(*chr)->uvPrint((*chr)->minValue).c_str(),(*chr)->uvPrint((*chr)->maxValue).c_str()); @@ -969,6 +971,9 @@ void Span::processSerialCommand(const char *c){ if((*chr)->setRangeError) Serial.printf(" *** WARNING! Attempt to set Custom Range for this Characteristic ignored ***\n"); + if((*chr)->setValidValuesError) + Serial.printf(" *** WARNING! Attempt to set Custom Valid Values for this Characteristic ignored ***\n"); + if((*chr)->format!=STRING && ((*chr)->uvGet((*chr)->value) < (*chr)->uvGet((*chr)->minValue) || (*chr)->uvGet((*chr)->value) > (*chr)->uvGet((*chr)->maxValue))) Serial.printf(" *** WARNING! Value of %llg is out of range [%llg,%llg] ***\n",(*chr)->uvGet((*chr)->value),(*chr)->uvGet((*chr)->minValue),(*chr)->uvGet((*chr)->maxValue)); @@ -980,6 +985,15 @@ void Span::processSerialCommand(const char *c){ if(hapChar.find(*req)==hapChar.end()) Serial.printf(" *** WARNING! Required '%s' Characteristic for this Service not found ***\n",(*req)->hapName); } + + for(auto button=PushButtons.begin(); button!=PushButtons.end(); button++){ + if((*button)->service==(*svc)){ + Serial.printf(" \u25bc SpanButton: Pin=%d, Single=%ums, Double=%ums, Long=%ums\n",(*button)->pin,(*button)->singleTime,(*button)->doubleTime,(*button)->longTime); + + if((void(*)(int,int))((*svc)->*(&SpanService::button))==(void(*)(int,int))(&SpanService::button)) + Serial.printf(" *** WARNING! No button() method defined in this Service ***\n"); + } + } } // Services @@ -1775,35 +1789,26 @@ unsigned long SpanCharacteristic::timeVal(){ /////////////////////////////// SpanCharacteristic *SpanCharacteristic::setValidValues(int n, ...){ - char c[256]; - String *s = new String("["); + + if(format!=UINT8){ + setValidValuesError=true; + return(this); + } + + String s="["; va_list vl; va_start(vl,n); for(int i=0;ic_str(); - sprintf(c,": ValidValues=%s\n",validValues); - } - - homeSpan.configLog+=c; return(this); } @@ -1814,8 +1819,9 @@ SpanCharacteristic *SpanCharacteristic::setValidValues(int n, ...){ SpanRange::SpanRange(int min, int max, int step){ if(homeSpan.Accessories.empty() || homeSpan.Accessories.back()->Services.empty() || homeSpan.Accessories.back()->Services.back()->Characteristics.empty() ){ - homeSpan.configLog+=" \u2718 SpanRange: *** ERROR! Can't create new Range without a defined Characteristic! ***\n"; - homeSpan.nFatalErrors++; + Serial.printf("\nFATAL ERROR! Can't create new SpanRange(%d,%d,%d) without a defined Characteristic ***\n",min,max,step); + Serial.printf("\n=== PROGRAM HALTED ==="); + while(1); } else { homeSpan.Accessories.back()->Services.back()->Characteristics.back()->setRange(min,max,step); } @@ -1827,32 +1833,19 @@ SpanRange::SpanRange(int min, int max, int step){ SpanButton::SpanButton(int pin, uint16_t longTime, uint16_t singleTime, uint16_t doubleTime){ - homeSpan.configLog+=" \u25bc SpanButton: Pin=" + String(pin) + ", Single=" + String(singleTime) + "ms, Double=" + String(doubleTime) + "ms, Long=" + String(longTime) + "ms"; - if(homeSpan.Accessories.empty() || homeSpan.Accessories.back()->Services.empty()){ - homeSpan.configLog+=" *** ERROR! Can't create new PushButton without a defined Service! ***\n"; - homeSpan.nFatalErrors++; - return; + Serial.printf("\nFATAL ERROR! Can't create new SpanButton(%d,%u,%u,%u) without a defined Service ***\n",pin,longTime,singleTime,doubleTime); + Serial.printf("\n=== PROGRAM HALTED ==="); + while(1); } - Serial.print("Configuring PushButton: Pin="); // initialization message - Serial.print(pin); - Serial.print("\n"); - this->pin=pin; this->longTime=longTime; this->singleTime=singleTime; this->doubleTime=doubleTime; service=homeSpan.Accessories.back()->Services.back(); - if((void(*)(int,int))(service->*(&SpanService::button))==(void(*)(int,int))(&SpanService::button)){ - homeSpan.configLog+=" *** WARNING: No button() method defined for this PushButton! ***"; - homeSpan.nWarnings++; - } - pushButton=new PushButton(pin); // create underlying PushButton - - homeSpan.configLog+="\n"; homeSpan.PushButtons.push_back(this); } diff --git a/src/HomeSpan.h b/src/HomeSpan.h index ff76397..28abf49 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -346,11 +346,12 @@ struct SpanCharacteristic{ UVal stepValue; // Characteristic step size (not applicable for STRING) boolean staticRange; // Flag that indicates whether Range is static and cannot be changed with setRange() boolean customRange=false; // Flag for custom ranges - const char *validValues=NULL; // Optional JSON array of valid values. Applicable only to uint8 Characteristics + char *validValues=NULL; // Optional JSON array of valid values. Applicable only to uint8 Characteristics boolean *ev; // Characteristic Event Notify Enable (per-connection) char *nvsKey=NULL; // key for NVS storage of Characteristic value boolean isCustom; // flag to indicate this is a Custom Characteristic - boolean setRangeError=false; // flag to indicate attempt to set range on Characteristic that does not support changes to range + boolean setRangeError=false; // flag to indicate attempt to set Range on Characteristic that does not support changes to Range + boolean setValidValuesError=false; // flag to indicate attempt to set Valid Values on Characteristic that does not support changes to Valid Values uint32_t aid=0; // Accessory ID - passed through from Service containing this Characteristic boolean isUpdated=false; // set to true when new value has been requested by PUT /characteristic From 18414d885688925c4276e58bb31a063c3b10bc63 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 1 May 2022 12:47:08 -0500 Subject: [PATCH 18/98] Continuing refactor of 'i' CLI command --- src/HomeSpan.cpp | 46 ++++++++++++++++++++++++++-------------------- src/HomeSpan.h | 23 +++-------------------- 2 files changed, 29 insertions(+), 40 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 8b93d12..666399e 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -915,17 +915,21 @@ void Span::processSerialCommand(const char *c){ Serial.print("\n*** HomeSpan Info ***\n\n"); + int nErrors=0; + int nWarnings=0; + unordered_set aidValues; + char pNames[][7]={"PR","PW","EV","AA","TW","HD","WR"}; for(auto acc=Accessories.begin(); acc!=Accessories.end(); acc++){ Serial.printf("\u27a4 Accessory: AID=%d\n",(*acc)->aid); boolean foundInfo=false; if(acc==Accessories.begin() && (*acc)->aid!=1) - Serial.printf(" *** ERROR! AID of first Accessory must always be 1 ***\n"); + Serial.printf(" *** ERROR! AID of first Accessory must always be 1 ***\n",nErrors++); if(aidValues.find((*acc)->aid)!=aidValues.end()) - Serial.printf(" *** ERROR! AID already in use for another Accessory ***\n"); + Serial.printf(" *** ERROR! AID already in use for another Accessory ***\n",nErrors++); aidValues.insert((*acc)->aid); @@ -936,7 +940,7 @@ void Span::processSerialCommand(const char *c){ if(!strcmp((*svc)->type,"3E")){ foundInfo=true; if((*svc)->iid!=1) - Serial.printf(" *** ERROR! The Accessory Information Service must be defined before any other Services in an Accessory ***\n"); + Serial.printf(" *** ERROR! The Accessory Information Service must be defined before any other Services in an Accessory ***\n",nErrors++); } else if((*acc)->aid==1) // this is an Accessory with aid=1, but it has more than just AccessoryInfo. So... isBridge=false; // ...this is not a bridge device @@ -944,7 +948,14 @@ void Span::processSerialCommand(const char *c){ unordered_set hapChar; for(auto chr=(*svc)->Characteristics.begin(); chr!=(*svc)->Characteristics.end(); chr++){ - Serial.printf(" \u21e8 Characteristic %s(%s): IID=%d, %sUUID=\"%s\"",(*chr)->hapName,(*chr)->uvPrint((*chr)->value).c_str(),(*chr)->iid,(*chr)->isCustom?"Custom-":"",(*chr)->type); + Serial.printf(" \u21e8 Characteristic %s(%s): IID=%d, %sUUID=\"%s\", %sPerms=", + (*chr)->hapName,(*chr)->uvPrint((*chr)->value).c_str(),(*chr)->iid,(*chr)->isCustom?"Custom-":"",(*chr)->type,(*chr)->perms!=(*chr)->hapChar->perms?"Custom-":""); + + int foundPerms=0; + for(uint8_t i=0;i<7;i++){ + if((*chr)->perms & (1<format!=FORMAT::STRING && (*chr)->format!=FORMAT::BOOL){ if((*chr)->validValues) @@ -960,22 +971,22 @@ void Span::processSerialCommand(const char *c){ Serial.printf("\n"); if(!(*chr)->isCustom && !(*svc)->isCustom && (*svc)->req.find((*chr)->hapChar)==(*svc)->req.end() && (*svc)->opt.find((*chr)->hapChar)==(*svc)->opt.end()) - Serial.printf(" *** WARNING! Service does not support this Characteristic ***\n"); + Serial.printf(" *** WARNING! Service does not support this Characteristic ***\n",nWarnings++); else if(invalidUUID((*chr)->type,(*chr)->isCustom)) - Serial.printf(" *** ERROR! Format of UUID is invalid ***\n"); + Serial.printf(" *** ERROR! Format of UUID is invalid ***\n",nErrors++); else if(hapChar.find((*chr)->hapChar)!=hapChar.end()) - Serial.printf(" *** ERROR! Characteristic already defined for this Service ***\n"); + Serial.printf(" *** ERROR! Characteristic already defined for this Service ***\n",nErrors++); if((*chr)->setRangeError) - Serial.printf(" *** WARNING! Attempt to set Custom Range for this Characteristic ignored ***\n"); + Serial.printf(" *** WARNING! Attempt to set Custom Range for this Characteristic ignored ***\n",nWarnings++); if((*chr)->setValidValuesError) - Serial.printf(" *** WARNING! Attempt to set Custom Valid Values for this Characteristic ignored ***\n"); + Serial.printf(" *** WARNING! Attempt to set Custom Valid Values for this Characteristic ignored ***\n",nWarnings++); if((*chr)->format!=STRING && ((*chr)->uvGet((*chr)->value) < (*chr)->uvGet((*chr)->minValue) || (*chr)->uvGet((*chr)->value) > (*chr)->uvGet((*chr)->maxValue))) - Serial.printf(" *** WARNING! Value of %llg is out of range [%llg,%llg] ***\n",(*chr)->uvGet((*chr)->value),(*chr)->uvGet((*chr)->minValue),(*chr)->uvGet((*chr)->maxValue)); + Serial.printf(" *** WARNING! Value of %llg is out of range [%llg,%llg] ***\n",(*chr)->uvGet((*chr)->value),(*chr)->uvGet((*chr)->minValue),(*chr)->uvGet((*chr)->maxValue),nWarnings++); hapChar.insert((*chr)->hapChar); @@ -983,7 +994,7 @@ void Span::processSerialCommand(const char *c){ for(auto req=(*svc)->req.begin(); req!=(*svc)->req.end(); req++){ if(hapChar.find(*req)==hapChar.end()) - Serial.printf(" *** WARNING! Required '%s' Characteristic for this Service not found ***\n",(*req)->hapName); + Serial.printf(" *** WARNING! Required '%s' Characteristic for this Service not found ***\n",(*req)->hapName,nWarnings++); } for(auto button=PushButtons.begin(); button!=PushButtons.end(); button++){ @@ -991,24 +1002,19 @@ void Span::processSerialCommand(const char *c){ Serial.printf(" \u25bc SpanButton: Pin=%d, Single=%ums, Double=%ums, Long=%ums\n",(*button)->pin,(*button)->singleTime,(*button)->doubleTime,(*button)->longTime); if((void(*)(int,int))((*svc)->*(&SpanService::button))==(void(*)(int,int))(&SpanService::button)) - Serial.printf(" *** WARNING! No button() method defined in this Service ***\n"); + Serial.printf(" *** WARNING! No button() method defined in this Service ***\n",nWarnings++); } } } // Services if(!foundInfo) - Serial.printf(" *** ERROR! Required 'AccessoryInformation' Service not found ***\n"); + Serial.printf(" *** ERROR! Required 'AccessoryInformation' Service not found ***\n",nErrors++); } // Accessories - Serial.print("\n------------\n\n"); - - Serial.println(configLog); - - Serial.print("\nConfigured as Bridge: "); - Serial.print(isBridge?"YES":"NO"); - Serial.print("\n\n"); + Serial.printf("\nDatabase Validation: Warnings=%d, Errors=%d\n\n",nWarnings,nErrors); + Serial.printf("Configured as Bridge: %s\n\n",isBridge?"YES":"NO"); char d[]="------------------------------"; Serial.printf("%-30s %8s %10s %s %s %s %s %s\n","Service","UUID","AID","IID","Update","Loop","Button","Linked Services"); diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 28abf49..d5e70e2 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -159,7 +159,6 @@ struct Span{ boolean isInitialized=false; // flag indicating HomeSpan has been initialized int nFatalErrors=0; // number of fatal errors in user-defined configuration int nWarnings=0; // number of warnings errors in user-defined configuration - String configLog; // log of configuration process, including any errors boolean isBridge=true; // flag indicating whether device is configured as a bridge (i.e. first Accessory contains nothing but AccessoryInformation and HAPProtocolInformation) HapQR qrCode; // optional QR Code to use for pairing const char *sketchVersion="n/a"; // version of the sketch @@ -600,25 +599,9 @@ struct SpanCharacteristic{ } // setVal() SpanCharacteristic *setPerms(uint8_t perms){ - this->perms=perms; - homeSpan.configLog+=String(" \u2b0c Change Permissions for ") + String(hapName) + " with AID=" + String(aid) + ", IID=" + String(iid) + ":"; - - char pNames[][7]={"PR","PW","EV","AA","TW","HD","WR"}; - char sep=' '; - - for(uint8_t i=0;i<7;i++){ - if(perms & (1<0) + this->perms=perms; return(this); } From f48cea8afb18d62e8f17a72b9b5c969aecf6e916 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 1 May 2022 17:03:07 -0500 Subject: [PATCH 19/98] Completed refactoring of 'i' CLI command All error checking and database validation is now done dynamically every time the 'i' command is run. --- src/HomeSpan.cpp | 20 ++++---------------- src/HomeSpan.h | 2 -- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 666399e..5814f9f 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -180,20 +180,8 @@ void Span::pollTask() { if(!isInitialized){ processSerialCommand("i"); // print homeSpan configuration info - - if(nFatalErrors>0){ - Serial.print("\n*** PROGRAM HALTED DUE TO "); - Serial.print(nFatalErrors); - Serial.print(" FATAL ERROR"); - if(nFatalErrors>1) - Serial.print("S"); - Serial.print(" IN CONFIGURATION! ***\n\n"); - while(1); - } - - Serial.print("\n"); - - HAPClient::init(); // read NVS and load HAP settings + + HAPClient::init(); // read NVS and load HAP settings if(!strlen(network.wifiData.ssid)){ Serial.print("*** WIFI CREDENTIALS DATA NOT FOUND. "); @@ -1013,8 +1001,8 @@ void Span::processSerialCommand(const char *c){ } // Accessories - Serial.printf("\nDatabase Validation: Warnings=%d, Errors=%d\n\n",nWarnings,nErrors); - Serial.printf("Configured as Bridge: %s\n\n",isBridge?"YES":"NO"); + Serial.printf("\nConfigured as Bridge: %s\n\n",isBridge?"YES":"NO"); + Serial.printf("Database Validation: Warnings=%d, Errors=%d\n\n",nWarnings,nErrors); char d[]="------------------------------"; Serial.printf("%-30s %8s %10s %s %s %s %s %s\n","Service","UUID","AID","IID","Update","Loop","Button","Linked Services"); diff --git a/src/HomeSpan.h b/src/HomeSpan.h index d5e70e2..3e3ae37 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -157,8 +157,6 @@ struct Span{ char category[3]=""; // category ID of primary accessory - broadcast as Bonjour field "ci" (HAP Section 13) unsigned long snapTime; // current time (in millis) snapped before entering Service loops() or updates() boolean isInitialized=false; // flag indicating HomeSpan has been initialized - int nFatalErrors=0; // number of fatal errors in user-defined configuration - int nWarnings=0; // number of warnings errors in user-defined configuration boolean isBridge=true; // flag indicating whether device is configured as a bridge (i.e. first Accessory contains nothing but AccessoryInformation and HAPProtocolInformation) HapQR qrCode; // optional QR Code to use for pairing const char *sketchVersion="n/a"; // version of the sketch From 628c29c6b63f8f09d5ece994954e98cf39da643f Mon Sep 17 00:00:00 2001 From: Gregg Date: Fri, 6 May 2022 16:13:25 -0500 Subject: [PATCH 20/98] Updating logic that determined HAP Configuration Number (MDNS=c#) --- src/HAP.cpp | 6 +++--- src/HomeSpan.cpp | 34 ++++++++++++++++++++++++---------- src/HomeSpan.h | 9 +++++---- 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index bad8448..3001c64 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -149,8 +149,8 @@ void HAPClient::init(){ Serial.print("\n"); uint8_t tHash[48]; - TempBuffer tBuf(homeSpan.sprintfAttributes(NULL)+1); - homeSpan.sprintfAttributes(tBuf.buf); + TempBuffer tBuf(homeSpan.sprintfAttributes(NULL,GET_META|GET_PERMS|GET_TYPE|GET_DESC)+1); + homeSpan.sprintfAttributes(tBuf.buf,GET_META|GET_PERMS|GET_TYPE|GET_DESC); mbedtls_sha512_ret((uint8_t *)tBuf.buf,tBuf.len(),tHash,1); // create SHA-384 hash of JSON (can be any hash - just looking for a unique key) if(memcmp(tHash,homeSpan.hapConfig.hashCode,48)){ // if hash code of current HAP database does not match stored hash code @@ -1071,7 +1071,7 @@ int HAPClient::getCharacteristicsURL(char *urlBuf){ numIDs++; char *ids[numIDs]; // reserve space for number of IDs found - int flags=GET_AID; // flags indicating which characteristic fields to include in response (HAP Table 6-13) + int flags=GET_VALUE|GET_AID; // flags indicating which characteristic fields to include in response (HAP Table 6-13) numIDs=0; // reset number of IDs found char *lastSpace=strchr(urlBuf,' '); diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 5814f9f..0b611d4 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -899,6 +899,17 @@ void Span::processSerialCommand(const char *c){ } break; + + case 'C': { + + char cNum[16]; + sprintf(cNum,"%d",++hapConfig.configNumber); + mdns_service_txt_item_set("_hap","_tcp","c#",cNum); // Accessory Current Configuration Number (updated whenever config of HAP Accessory Attribute Database is updated) + + Serial.printf("\n*** Configuration number updated to: %d\n\n",hapConfig.configNumber); + } + break; + case 'i':{ Serial.print("\n*** HomeSpan Info ***\n\n"); @@ -1001,8 +1012,11 @@ void Span::processSerialCommand(const char *c){ } // Accessories - Serial.printf("\nConfigured as Bridge: %s\n\n",isBridge?"YES":"NO"); - Serial.printf("Database Validation: Warnings=%d, Errors=%d\n\n",nWarnings,nErrors); + Serial.printf("\nConfigured as Bridge: %s\n",isBridge?"YES":"NO"); + if(hapConfig.configNumber>0) + Serial.printf("Configuration Number: %d\n",hapConfig.configNumber); + Serial.printf("\nDatabase Validation: Warnings=%d, Errors=%d\n\n",nWarnings,nErrors); + char d[]="------------------------------"; Serial.printf("%-30s %8s %10s %s %s %s %s %s\n","Service","UUID","AID","IID","Update","Loop","Button","Linked Services"); @@ -1100,14 +1114,14 @@ void Span::setWifiCredentials(const char *ssid, const char *pwd){ /////////////////////////////// -int Span::sprintfAttributes(char *cBuf){ +int Span::sprintfAttributes(char *cBuf, int flags){ int nBytes=0; nBytes+=snprintf(cBuf,cBuf?64:0,"{\"accessories\":["); for(int i=0;isprintfAttributes(cBuf?(cBuf+nBytes):NULL); + nBytes+=Accessories[i]->sprintfAttributes(cBuf?(cBuf+nBytes):NULL,flags); if(i+1sprintfAttributes(cBuf?(cBuf+nChars):NULL,GET_AID+GET_NV); // get JSON attributes for characteristic + nChars+=pObj[i].characteristic->sprintfAttributes(cBuf?(cBuf+nChars):NULL,GET_VALUE|GET_AID|GET_NV); // get JSON attributes for characteristic notifyFlag=true; } // notification requested @@ -1476,13 +1490,13 @@ SpanAccessory::SpanAccessory(uint32_t aid){ /////////////////////////////// -int SpanAccessory::sprintfAttributes(char *cBuf){ +int SpanAccessory::sprintfAttributes(char *cBuf, int flags){ int nBytes=0; nBytes+=snprintf(cBuf,cBuf?64:0,"{\"aid\":%u,\"services\":[",aid); for(int i=0;isprintfAttributes(cBuf?(cBuf+nBytes):NULL); + nBytes+=Services[i]->sprintfAttributes(cBuf?(cBuf+nBytes):NULL,flags); if(i+1sprintfAttributes(cBuf?(cBuf+nBytes):NULL,GET_META|GET_PERMS|GET_TYPE|GET_DESC); + nBytes+=Characteristics[i]->sprintfAttributes(cBuf?(cBuf+nBytes):NULL,flags); if(i+1 getLinks(){return(linkedServices);} // returns linkedServices vector for use as range in "for-each" loops - int sprintfAttributes(char *cBuf); // prints Service JSON records into buf; return number of characters printed, excluding null terminator + int sprintfAttributes(char *cBuf, int flags); // prints Service JSON records into buf; return number of characters printed, excluding null terminator virtual boolean update() {return(true);} // placeholder for code that is called when a Service is updated via a Controller. Must return true/false depending on success of update virtual void loop(){} // loops for each Service - called every cycle and can be over-ridden with user-defined code From 7546350775d6c5bceae0b6aaa3429a420f968045 Mon Sep 17 00:00:00 2001 From: Gregg Date: Fri, 6 May 2022 17:04:15 -0500 Subject: [PATCH 21/98] Added "C" CLI command and updated/fixed config number logic The "C" command computes a hash of the current database config and updates the config number if needed. If config number has changed, the MDNS "c#" record is updated and the new config number is rebroadcast. This triggers HomeKit to immediately request an update of the HAP database so that the changes can be shortly reflected in the Home App without the need to close any Client connections or reboot the device. The config number logic has also been updated/fixed. Previously it would create a hash from the full HAP database, which includes the value of each characteristic. Thus, initial value changes of Characteristics, as a result of stored values in NVS, would cause an update to the config number upon reboot. This is not problematic, but also not intended, as a change in value is not really a change in the database config. The new logic computes a hash of the database that EXCLUDES all Characteristic values. --- src/HAP.cpp | 26 +++----------------------- src/HomeSpan.cpp | 46 +++++++++++++++++++++++++++++++++++++++------- src/HomeSpan.h | 1 + 3 files changed, 43 insertions(+), 30 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index 3001c64..9599503 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -142,34 +142,14 @@ void HAPClient::init(){ nvs_get_blob(hapNVS,"HAPHASH",&homeSpan.hapConfig,&len); // retrieve data } else { Serial.print("Resetting Accessory Configuration number...\n"); - nvs_set_blob(hapNVS,"HAPHASH",&homeSpan.hapConfig,sizeof(homeSpan.hapConfig)); // update data + nvs_set_blob(hapNVS,"HAPHASH",&homeSpan.hapConfig,sizeof(homeSpan.hapConfig)); // save data (will default to all zero values, which will then be updated below) nvs_commit(hapNVS); // commit to NVS } Serial.print("\n"); - uint8_t tHash[48]; - TempBuffer tBuf(homeSpan.sprintfAttributes(NULL,GET_META|GET_PERMS|GET_TYPE|GET_DESC)+1); - homeSpan.sprintfAttributes(tBuf.buf,GET_META|GET_PERMS|GET_TYPE|GET_DESC); - mbedtls_sha512_ret((uint8_t *)tBuf.buf,tBuf.len(),tHash,1); // create SHA-384 hash of JSON (can be any hash - just looking for a unique key) - - if(memcmp(tHash,homeSpan.hapConfig.hashCode,48)){ // if hash code of current HAP database does not match stored hash code - memcpy(homeSpan.hapConfig.hashCode,tHash,48); // update stored hash code - homeSpan.hapConfig.configNumber++; // increment configuration number - if(homeSpan.hapConfig.configNumber==65536) // reached max value - homeSpan.hapConfig.configNumber=1; // reset to 1 - - Serial.print("Accessory configuration has changed. Updating configuration number to "); - Serial.print(homeSpan.hapConfig.configNumber); - Serial.print("\n\n"); - nvs_set_blob(hapNVS,"HAPHASH",&homeSpan.hapConfig,sizeof(homeSpan.hapConfig)); // update data - nvs_commit(hapNVS); // commit to NVS - } else { - Serial.print("Accessory configuration number: "); - Serial.print(homeSpan.hapConfig.configNumber); - Serial.print("\n\n"); - } - + homeSpan.updateConfigNum(); + for(int i=0;iServices.size();j++){ SpanService *s=homeSpan.Accessories[i]->Services[j]; diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 0b611d4..2b16a58 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -899,14 +899,13 @@ void Span::processSerialCommand(const char *c){ } break; - case 'C': { - char cNum[16]; - sprintf(cNum,"%d",++hapConfig.configNumber); - mdns_service_txt_item_set("_hap","_tcp","c#",cNum); // Accessory Current Configuration Number (updated whenever config of HAP Accessory Attribute Database is updated) - - Serial.printf("\n*** Configuration number updated to: %d\n\n",hapConfig.configNumber); + if(updateConfigNum()){ // if config number changed, update MDNS record + char cNum[16]; + sprintf(cNum,"%d",hapConfig.configNumber); + mdns_service_txt_item_set("_hap","_tcp","c#",cNum); + } } break; @@ -1055,7 +1054,8 @@ void Span::processSerialCommand(const char *c){ Serial.print(" S - change the HomeKit Pairing Setup Code to \n"); Serial.print(" Q - change the HomeKit Setup ID for QR Codes to \n"); Serial.print(" O - change the OTA password\n"); - Serial.print(" A - start the HomeSpan Setup Access Point\n"); + Serial.print(" A - start the HomeSpan Setup Access Point\n"); + Serial.print(" C - update database configuration number\n"); Serial.print("\n"); Serial.print(" V - delete value settings for all saved Characteristics\n"); Serial.print(" U - unpair device by deleting all Controller data\n"); @@ -1459,6 +1459,38 @@ int Span::sprintfAttributes(char **ids, int numIDs, int flags, char *cBuf){ return(nChars); } +/////////////////////////////// + +boolean Span::updateConfigNum(){ + + uint8_t tHash[48]; + TempBuffer tBuf(sprintfAttributes(NULL,GET_META|GET_PERMS|GET_TYPE|GET_DESC)+1); + sprintfAttributes(tBuf.buf,GET_META|GET_PERMS|GET_TYPE|GET_DESC); + mbedtls_sha512_ret((uint8_t *)tBuf.buf,tBuf.len(),tHash,1); // create SHA-384 hash of JSON (can be any hash - just looking for a unique key) + + boolean changed=false; + + if(memcmp(tHash,hapConfig.hashCode,48)){ // if hash code of current HAP database does not match stored hash code + memcpy(hapConfig.hashCode,tHash,48); // update stored hash code + hapConfig.configNumber++; // increment configuration number + if(hapConfig.configNumber==65536) // reached max value + hapConfig.configNumber=1; // reset to 1 + + Serial.print("Accessory configuration has changed. Updating configuration number to "); + Serial.print(hapConfig.configNumber); + Serial.print("\n\n"); + nvs_set_blob(HAPClient::hapNVS,"HAPHASH",&hapConfig,sizeof(hapConfig)); // update data + nvs_commit(HAPClient::hapNVS); // commit to NVS + changed=true; + } else { + Serial.print("Accessory configuration number: "); + Serial.print(hapConfig.configNumber); + Serial.print("\n\n"); + } + + return(changed); +} + /////////////////////////////// // SpanAccessory // /////////////////////////////// diff --git a/src/HomeSpan.h b/src/HomeSpan.h index cc364ae..2a8ead2 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -214,6 +214,7 @@ struct Span{ void checkConnect(); // check WiFi connection; connect if needed void commandMode(); // allows user to control and reset HomeSpan settings with the control button void processSerialCommand(const char *c); // process command 'c' (typically from readSerial, though can be called with any 'c') + boolean updateConfigNum(); // updates HAP configuration number (MDNS 'c#' record) if hash of current config database is different from previously-stored hash; returns true if config number changed int sprintfAttributes(char *cBuf, int flags=GET_VALUE|GET_META|GET_PERMS|GET_TYPE|GET_DESC); // prints Attributes JSON database into buf, unless buf=NULL; return number of characters printed, excluding null terminator From bddfb4658ad43ddcb0f3dd9bba72e5e014e53ebd Mon Sep 17 00:00:00 2001 From: Gregg Date: Fri, 6 May 2022 22:19:37 -0500 Subject: [PATCH 22/98] Added 'm' CLI command to print free 8-bit heap memory --- src/HomeSpan.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 2b16a58..e1092ef 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -907,7 +907,12 @@ void Span::processSerialCommand(const char *c){ mdns_service_txt_item_set("_hap","_tcp","c#",cNum); } } - break; + break; + + case 'm': { + Serial.printf("Free Memory: %d bytes\n",heap_caps_get_free_size(MALLOC_CAP_DEFAULT)); + } + break; case 'i':{ @@ -1014,8 +1019,7 @@ void Span::processSerialCommand(const char *c){ Serial.printf("\nConfigured as Bridge: %s\n",isBridge?"YES":"NO"); if(hapConfig.configNumber>0) Serial.printf("Configuration Number: %d\n",hapConfig.configNumber); - Serial.printf("\nDatabase Validation: Warnings=%d, Errors=%d\n\n",nWarnings,nErrors); - + Serial.printf("\nDatabase Validation: Warnings=%d, Errors=%d\n\n",nWarnings,nErrors); char d[]="------------------------------"; Serial.printf("%-30s %8s %10s %s %s %s %s %s\n","Service","UUID","AID","IID","Update","Loop","Button","Linked Services"); From 554cefd3cefc8521941b2461d6def80032a7f9cc Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 8 May 2022 07:02:19 -0500 Subject: [PATCH 23/98] Initial work on adding destructors to Accessories, Services, and Characteristics Will allow for dynamic changes to database without rebooting. --- src/HomeSpan.cpp | 47 +++++++++++++++++++++++++++++++++++++++++------ src/HomeSpan.h | 7 ++++--- 2 files changed, 45 insertions(+), 9 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index e1092ef..d00be5d 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -1526,6 +1526,20 @@ SpanAccessory::SpanAccessory(uint32_t aid){ /////////////////////////////// +SpanAccessory::~SpanAccessory(){ + + while(Services.rbegin()!=Services.rend()) // delete all Services in this Accessory + delete *Services.rbegin(); + + auto acc=homeSpan.Accessories.begin(); // find Accessory in homeSpan vector and erase entry + while((*acc)!=this) + acc++; + homeSpan.Accessories.erase(acc); + Serial.printf("Deleted Accessory AID=%d\n",aid); +} + +/////////////////////////////// + int SpanAccessory::sprintfAttributes(char *cBuf, int flags){ int nBytes=0; @@ -1559,11 +1573,30 @@ SpanService::SpanService(const char *type, const char *hapName, boolean isCustom this->isCustom=isCustom; homeSpan.Accessories.back()->Services.push_back(this); + accessory=homeSpan.Accessories.back(); iid=++(homeSpan.Accessories.back()->iidCount); } /////////////////////////////// +SpanService::~SpanService(){ + + auto svc=accessory->Services.begin(); // find Service in containing Accessory vector and erase entry + while((*svc)!=this) + svc++; + accessory->Services.erase(svc); + + for(svc=homeSpan.Loops.begin(); (*svc)!=this && svc!=homeSpan.Loops.end(); svc++); // find and erase entry in Loop vector + if(svc!=homeSpan.Loops.end()){ + homeSpan.Loops.erase(svc); + Serial.printf("Deleted Loop Entry\n"); + } + + Serial.printf("Deleted Service AID=%d IID=%d\n",accessory->aid,iid); +} + +/////////////////////////////// + SpanService *SpanService::setPrimary(){ primary=true; return(this); @@ -1624,6 +1657,13 @@ int SpanService::sprintfAttributes(char *cBuf, int flags){ /////////////////////////////// SpanCharacteristic::SpanCharacteristic(HapChar *hapChar, boolean isCustom){ + + if(homeSpan.Accessories.empty() || homeSpan.Accessories.back()->Services.empty()){ + Serial.printf("\nFATAL ERROR! Can't create new Characteristic '%s' without a defined Service ***\n",hapName); + Serial.printf("\n=== PROGRAM HALTED ==="); + while(1); + } + type=hapChar->type; perms=hapChar->perms; hapName=hapChar->hapName; @@ -1632,12 +1672,7 @@ SpanCharacteristic::SpanCharacteristic(HapChar *hapChar, boolean isCustom){ this->isCustom=isCustom; this->hapChar=hapChar; - if(homeSpan.Accessories.empty() || homeSpan.Accessories.back()->Services.empty()){ - Serial.printf("\nFATAL ERROR! Can't create new Characteristic '%s' without a defined Service ***\n",hapName); - Serial.printf("\n=== PROGRAM HALTED ==="); - while(1); - } - + homeSpan.Accessories.back()->Services.back()->Characteristics.push_back(this); iid=++(homeSpan.Accessories.back()->iidCount); service=homeSpan.Accessories.back()->Services.back(); aid=homeSpan.Accessories.back()->aid; diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 2a8ead2..fe5f98d 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -283,6 +283,7 @@ struct SpanAccessory{ vector Services; // vector of pointers to all Services in this Accessory SpanAccessory(uint32_t aid=0); + ~SpanAccessory(); int sprintfAttributes(char *cBuf, int flags); // prints Accessory JSON database into buf, unless buf=NULL; return number of characters printed, excluding null terminator, even if buf=NULL }; @@ -301,8 +302,10 @@ struct SpanService{ unordered_set opt; // unordered set of pointers to all optional HAP Characteristic Types for this Service vector linkedServices; // vector of pointers to any optional linked Services boolean isCustom; // flag to indicate this is a Custom Service + SpanAccessory *accessory=NULL; // pointer to Accessory containing this Service SpanService(const char *type, const char *hapName, boolean isCustom=false); // constructor + ~SpanService(); SpanService *setPrimary(); // sets the Service Type to be primary and returns pointer to self SpanService *setHidden(); // sets the Service Type to be hidden and returns pointer to self @@ -511,9 +514,7 @@ struct SpanCharacteristic{ uvSet(maxValue,max); uvSet(stepValue,0); } - - homeSpan.Accessories.back()->Services.back()->Characteristics.push_back(this); - + } // init() From 05f1204b4fa30002f4faf0c9e31c0fdbf351c266 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 8 May 2022 11:56:26 -0500 Subject: [PATCH 24/98] Continuing destructor code... --- src/HomeSpan.cpp | 26 ++++++++++++++++++++++++-- src/HomeSpan.h | 3 ++- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index d00be5d..c2d2a01 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -1581,13 +1581,17 @@ SpanService::SpanService(const char *type, const char *hapName, boolean isCustom SpanService::~SpanService(){ + while(Characteristics.rbegin()!=Characteristics.rend()) // delete all Characteristics in this Service + delete *Characteristics.rbegin(); + auto svc=accessory->Services.begin(); // find Service in containing Accessory vector and erase entry while((*svc)!=this) svc++; accessory->Services.erase(svc); - for(svc=homeSpan.Loops.begin(); (*svc)!=this && svc!=homeSpan.Loops.end(); svc++); // find and erase entry in Loop vector - if(svc!=homeSpan.Loops.end()){ + for(svc=homeSpan.Loops.begin(); svc!=homeSpan.Loops.end() && (*svc)!=this; svc++); // search for entry in Loop vector... + + if(svc!=homeSpan.Loops.end()){ // ...if it exists, erase it homeSpan.Loops.erase(svc); Serial.printf("Deleted Loop Entry\n"); } @@ -1682,6 +1686,24 @@ SpanCharacteristic::SpanCharacteristic(HapChar *hapChar, boolean isCustom){ /////////////////////////////// +SpanCharacteristic::~SpanCharacteristic(){ + + auto chr=service->Characteristics.begin(); // find Characteristic in containing Service vector and erase entry + while((*chr)!=this) + chr++; + service->Characteristics.erase(chr); + + free(ev); + free(desc); + free(unit); + free(validValues); + free(nvsKey); + + Serial.printf("Deleted Characteristic AID=%d IID=%d\n",aid,iid); +} + +/////////////////////////////// + int SpanCharacteristic::sprintfAttributes(char *cBuf, int flags){ int nBytes=0; diff --git a/src/HomeSpan.h b/src/HomeSpan.h index fe5f98d..c6e9bbc 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -282,7 +282,7 @@ struct SpanAccessory{ int iidCount=0; // running count of iid to use for Services and Characteristics associated with this Accessory vector Services; // vector of pointers to all Services in this Accessory - SpanAccessory(uint32_t aid=0); + SpanAccessory(uint32_t aid=0); // constructor ~SpanAccessory(); int sprintfAttributes(char *cBuf, int flags); // prints Accessory JSON database into buf, unless buf=NULL; return number of characters printed, excluding null terminator, even if buf=NULL @@ -362,6 +362,7 @@ struct SpanCharacteristic{ SpanService *service=NULL; // pointer to Service containing this Characteristic SpanCharacteristic(HapChar *hapChar, boolean isCustom=false); // contructor + ~SpanCharacteristic(); int sprintfAttributes(char *cBuf, int flags); // prints Characteristic JSON records into buf, according to flags mask; return number of characters printed, excluding null terminator StatusCode loadUpdate(char *val, char *ev); // load updated val/ev from PUT /characteristic JSON request. Return intiial HAP status code (checks to see if characteristic is found, is writable, etc.) From 058d6b0c965d3636638bbf3495e2b4e3c9e3a0ca Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 8 May 2022 15:27:50 -0500 Subject: [PATCH 25/98] Completed destructors for SpanAccessory, SpanService, and SpanCharacteristics Tested to ensure any combination can be deleted. To do: must add logic to destruct any PushButtons that were added to a Service --- src/HAP.cpp | 13 ++----------- src/HomeSpan.cpp | 18 ++++++++++++++++-- src/HomeSpan.h | 2 +- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index 9599503..89fe7e7 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -141,23 +141,14 @@ void HAPClient::init(){ if(!nvs_get_blob(hapNVS,"HAPHASH",NULL,&len)){ // if found HAP HASH structure nvs_get_blob(hapNVS,"HAPHASH",&homeSpan.hapConfig,&len); // retrieve data } else { - Serial.print("Resetting Accessory Configuration number...\n"); + Serial.print("Resetting Database Hash...\n"); nvs_set_blob(hapNVS,"HAPHASH",&homeSpan.hapConfig,sizeof(homeSpan.hapConfig)); // save data (will default to all zero values, which will then be updated below) nvs_commit(hapNVS); // commit to NVS } Serial.print("\n"); - homeSpan.updateConfigNum(); - - for(int i=0;iServices.size();j++){ - SpanService *s=homeSpan.Accessories[i]->Services[j]; - if((void(*)())(s->*(&SpanService::loop)) != (void(*)())(&SpanService::loop)) // save pointers to services in Loops vector - homeSpan.Loops.push_back(s); - } - } - + homeSpan.updateConfigNum(); // create Configuration Cumber and Loop vector } ////////////////////////////////////// diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index c2d2a01..553142c 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -1492,6 +1492,15 @@ boolean Span::updateConfigNum(){ Serial.print("\n\n"); } + Loops.clear(); + + for(auto acc=Accessories.begin(); acc!=Accessories.end(); acc++){ // identify all services with over-ridden loop() methods + for(auto svc=(*acc)->Services.begin(); svc!=(*acc)->Services.end(); svc++){ + if((void(*)())((*svc)->*(&SpanService::loop)) != (void(*)())(&SpanService::loop)) // save pointers to services in Loops vector + homeSpan.Loops.push_back((*svc)); + } + } + return(changed); } @@ -1574,7 +1583,7 @@ SpanService::SpanService(const char *type, const char *hapName, boolean isCustom homeSpan.Accessories.back()->Services.push_back(this); accessory=homeSpan.Accessories.back(); - iid=++(homeSpan.Accessories.back()->iidCount); + iid=++(homeSpan.Accessories.back()->iidCount); } /////////////////////////////// @@ -1595,7 +1604,7 @@ SpanService::~SpanService(){ homeSpan.Loops.erase(svc); Serial.printf("Deleted Loop Entry\n"); } - + Serial.printf("Deleted Service AID=%d IID=%d\n",accessory->aid,iid); } @@ -1698,6 +1707,11 @@ SpanCharacteristic::~SpanCharacteristic(){ free(unit); free(validValues); free(nvsKey); + + if(format==FORMAT::STRING){ + free(value.STRING); + free(newValue.STRING); + } Serial.printf("Deleted Characteristic AID=%d IID=%d\n",aid,iid); } diff --git a/src/HomeSpan.h b/src/HomeSpan.h index c6e9bbc..2ebda74 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -315,7 +315,7 @@ struct SpanService{ int sprintfAttributes(char *cBuf, int flags); // prints Service JSON records into buf; return number of characters printed, excluding null terminator virtual boolean update() {return(true);} // placeholder for code that is called when a Service is updated via a Controller. Must return true/false depending on success of update - virtual void loop(){} // loops for each Service - called every cycle and can be over-ridden with user-defined code + virtual void loop(){} // loops for each Service - called every cycle if over-ridden with user-defined code virtual void button(int pin, int pressType){} // method called for a Service when a button attached to "pin" has a Single, Double, or Long Press, according to pressType }; From dc11c59a00c61f4fa6bcbebae09a2092e07274b2 Mon Sep 17 00:00:00 2001 From: Gregg Date: Mon, 9 May 2022 21:03:23 -0500 Subject: [PATCH 26/98] Deleted 'C' CLI command - replaced with homeSpan.updateDatabase() Method homeSpan.updateDatabase(boolean updateMDNS) recomputes database hash and compares with previous hash. If changed, config number is updated and saved in NVS along with new hash. If updateMDNS is true, a new "c#" MDNS record is also broadcast with the new config number. This triggers HomeKit to call for a database refresh. Method returns true if config number has changed, or false if it has not. --- src/HAP.cpp | 6 +++++- src/HomeSpan.cpp | 35 ++++++++++++----------------------- src/HomeSpan.h | 3 ++- 3 files changed, 19 insertions(+), 25 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index 89fe7e7..efe1d68 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -146,9 +146,13 @@ void HAPClient::init(){ nvs_commit(hapNVS); // commit to NVS } + if(homeSpan.updateDatabase(false)) // create Configuration Number and Loop vector + Serial.printf("\nAccessory configuration has changed. Updating configuration number to %d\n",homeSpan.hapConfig.configNumber); + else + Serial.printf("\nAccessory configuration number: %d\n",homeSpan.hapConfig.configNumber); + Serial.print("\n"); - homeSpan.updateConfigNum(); // create Configuration Cumber and Loop vector } ////////////////////////////////////// diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 553142c..5a181d3 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -899,16 +899,6 @@ void Span::processSerialCommand(const char *c){ } break; - case 'C': { - - if(updateConfigNum()){ // if config number changed, update MDNS record - char cNum[16]; - sprintf(cNum,"%d",hapConfig.configNumber); - mdns_service_txt_item_set("_hap","_tcp","c#",cNum); - } - } - break; - case 'm': { Serial.printf("Free Memory: %d bytes\n",heap_caps_get_free_size(MALLOC_CAP_DEFAULT)); } @@ -1052,6 +1042,7 @@ void Span::processSerialCommand(const char *c){ Serial.print(" s - print connection status\n"); Serial.print(" i - print summary information about the HAP Database\n"); Serial.print(" d - print the full HAP Accessory Attributes Database in JSON format\n"); + Serial.print(" m - print free heap memory\n"); Serial.print("\n"); Serial.print(" W - configure WiFi Credentials and restart\n"); Serial.print(" X - delete WiFi Credentials and restart\n"); @@ -1059,7 +1050,6 @@ void Span::processSerialCommand(const char *c){ Serial.print(" Q - change the HomeKit Setup ID for QR Codes to \n"); Serial.print(" O - change the OTA password\n"); Serial.print(" A - start the HomeSpan Setup Access Point\n"); - Serial.print(" C - update database configuration number\n"); Serial.print("\n"); Serial.print(" V - delete value settings for all saved Characteristics\n"); Serial.print(" U - unpair device by deleting all Controller data\n"); @@ -1465,7 +1455,7 @@ int Span::sprintfAttributes(char **ids, int numIDs, int flags, char *cBuf){ /////////////////////////////// -boolean Span::updateConfigNum(){ +boolean Span::updateDatabase(boolean updateMDNS){ uint8_t tHash[48]; TempBuffer tBuf(sprintfAttributes(NULL,GET_META|GET_PERMS|GET_TYPE|GET_DESC)+1); @@ -1480,16 +1470,15 @@ boolean Span::updateConfigNum(){ if(hapConfig.configNumber==65536) // reached max value hapConfig.configNumber=1; // reset to 1 - Serial.print("Accessory configuration has changed. Updating configuration number to "); - Serial.print(hapConfig.configNumber); - Serial.print("\n\n"); nvs_set_blob(HAPClient::hapNVS,"HAPHASH",&hapConfig,sizeof(hapConfig)); // update data nvs_commit(HAPClient::hapNVS); // commit to NVS changed=true; - } else { - Serial.print("Accessory configuration number: "); - Serial.print(hapConfig.configNumber); - Serial.print("\n\n"); + + if(updateMDNS){ + char cNum[16]; + sprintf(cNum,"%d",hapConfig.configNumber); + mdns_service_txt_item_set("_hap","_tcp","c#",cNum); + } } Loops.clear(); @@ -1544,7 +1533,7 @@ SpanAccessory::~SpanAccessory(){ while((*acc)!=this) acc++; homeSpan.Accessories.erase(acc); - Serial.printf("Deleted Accessory AID=%d\n",aid); + LOG1("Deleted Accessory AID=%d\n",aid); } /////////////////////////////// @@ -1602,10 +1591,10 @@ SpanService::~SpanService(){ if(svc!=homeSpan.Loops.end()){ // ...if it exists, erase it homeSpan.Loops.erase(svc); - Serial.printf("Deleted Loop Entry\n"); + LOG1("Deleted Loop Entry\n"); } - Serial.printf("Deleted Service AID=%d IID=%d\n",accessory->aid,iid); + LOG1("Deleted Service AID=%d IID=%d\n",accessory->aid,iid); } /////////////////////////////// @@ -1713,7 +1702,7 @@ SpanCharacteristic::~SpanCharacteristic(){ free(newValue.STRING); } - Serial.printf("Deleted Characteristic AID=%d IID=%d\n",aid,iid); + LOG1("Deleted Characteristic AID=%d IID=%d\n",aid,iid); } /////////////////////////////// diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 2ebda74..ac6f95d 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -214,7 +214,8 @@ struct Span{ void checkConnect(); // check WiFi connection; connect if needed void commandMode(); // allows user to control and reset HomeSpan settings with the control button void processSerialCommand(const char *c); // process command 'c' (typically from readSerial, though can be called with any 'c') - boolean updateConfigNum(); // updates HAP configuration number (MDNS 'c#' record) if hash of current config database is different from previously-stored hash; returns true if config number changed + + boolean updateDatabase(boolean updateMDNS=true); // updates HAP Configuration Number and Loop vector; iF updateMDNS=true and config number has changed, re-broadcasts MDNS 'c#' record; returns true if config number changed int sprintfAttributes(char *cBuf, int flags=GET_VALUE|GET_META|GET_PERMS|GET_TYPE|GET_DESC); // prints Attributes JSON database into buf, unless buf=NULL; return number of characters printed, excluding null terminator From d326ae1abfbe405084bd2a55c11fb51777ee00e2 Mon Sep 17 00:00:00 2001 From: Gregg Date: Mon, 9 May 2022 21:27:18 -0500 Subject: [PATCH 27/98] Added logic to delete PushButtons when Service is deleted This completes all code for the ~SpanService() destructor. To do: create tutorial example demonstrating a Dynamic Bridge --- src/HomeSpan.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 5a181d3..c0a1281 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -1588,12 +1588,22 @@ SpanService::~SpanService(){ accessory->Services.erase(svc); for(svc=homeSpan.Loops.begin(); svc!=homeSpan.Loops.end() && (*svc)!=this; svc++); // search for entry in Loop vector... - if(svc!=homeSpan.Loops.end()){ // ...if it exists, erase it homeSpan.Loops.erase(svc); LOG1("Deleted Loop Entry\n"); } - + + auto pb=homeSpan.PushButtons.begin(); // loop through PushButton vector and delete ALL PushButtons associated with this Service + while(pb!=homeSpan.PushButtons.end()){ + if((*pb)->service==this){ + pb=homeSpan.PushButtons.erase(pb); + LOG1("Deleted PushButton on Pin=%d\n",(*pb)->pin); + } + else { + pb++; + } + } + LOG1("Deleted Service AID=%d IID=%d\n",accessory->aid,iid); } From 615d37385007b053582320290812b0f638587501 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 14 May 2022 10:00:30 -0500 Subject: [PATCH 28/98] Added Example 20 - Dynamic Bridge. Also created homeSpan.getAccessory(aid) --- .../20-DynamicBridge/20-DynamicBridge.ino | 151 ++++++++++++++++++ src/HomeSpan.cpp | 16 +- src/HomeSpan.h | 3 +- 3 files changed, 168 insertions(+), 2 deletions(-) create mode 100644 examples/20-DynamicBridge/20-DynamicBridge.ino diff --git a/examples/20-DynamicBridge/20-DynamicBridge.ino b/examples/20-DynamicBridge/20-DynamicBridge.ino new file mode 100644 index 0000000..e25382a --- /dev/null +++ b/examples/20-DynamicBridge/20-DynamicBridge.ino @@ -0,0 +1,151 @@ +/********************************************************************************* + * MIT License + * + * Copyright (c) 2022 Gregg E. Berman + * + * https://github.com/HomeSpan/HomeSpan + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + ********************************************************************************/ + +#include "HomeSpan.h" + +#define MAX_LIGHTS 10 + +nvs_handle savedData; +int accNum[MAX_LIGHTS]={0}; + +////////////////////////////////////// + +void setup() { + + Serial.begin(115200); + + size_t len; + nvs_open("SAVED_DATA",NVS_READWRITE,&savedData); // open SAVED DATA + if(!nvs_get_blob(savedData,"ACC_NUM",NULL,&len)) // if ACCESSORY NUMBER data found + nvs_get_blob(savedData,"ACC_NUM",&accNum,&len); // retrieve data + + homeSpan.setLogLevel(1); + + homeSpan.begin(Category::Bridges,"HomeSpan Bridge"); + + new SpanAccessory(); + new Service::AccessoryInformation(); + new Characteristic::Identify(); + new Characteristic::Model("HomeSpan Dynamic Bridge"); + + for(int i=0;i0) + addLight(accNum[i]); + + new SpanUserCommand('a'," add a new light accessory with id=num",addAccessory); + new SpanUserCommand('d'," delete a light accessory with id=num",deleteAccessory); + new SpanUserCommand('u',"update configuration database",[](const char *buf){homeSpan.updateDatabase();}); + +} + +////////////////////////////////////// + +void loop(){ + + homeSpan.poll(); + +} + +/////////////////////////// + +void addLight(int n){ + + char name[32]; + sprintf(name,"Light-%d",n); + char sNum[32]; + sprintf(sNum,"%0.10d",n); + + Serial.printf("Adding Accessory: %s\n",name); + + new SpanAccessory(n+1); // add 1, since first Accessory is reserved for the bridge + new Service::AccessoryInformation(); + new Characteristic::Identify(); + new Characteristic::Name(name); + new Characteristic::SerialNumber(sNum); + new Service::LightBulb(); + new Characteristic::On(0,true); +} + +/////////////////////////// + +void addAccessory(const char *buf){ + + int n=atoi(buf+1); + + if(n<1){ + Serial.printf("Invalid Accessory number!\n"); + return; + } + + int i; + for(i=0;i0 && accNum[i]!=n;i++); + + if(i==MAX_LIGHTS){ + Serial.printf("Can't add any more lights - max is %d!\n",MAX_LIGHTS); + return; + } + + if(accNum[i]>0){ + Serial.printf("Accessory Light-%d already implemented!\n",n); + return; + } + + accNum[i]=n; + nvs_set_blob(savedData,"ACC_NUM",&accNum,sizeof(accNum)); // update data + nvs_commit(savedData); + + addLight(n); +} + +/////////////////////////// + +void deleteAccessory(const char *buf){ + + int n=atoi(buf+1); + + if(n<1){ + Serial.printf("Invalid Accessory number!\n"); + return; + } + + SpanAccessory *acc=homeSpan.getAccessory(n+1); + + if(!acc){ + Serial.printf("No such Accessory: Light-%d\n",n); + return; + } + + Serial.printf("Deleting Accessory: Light-%d\n",n); + delete acc; + + int i; // delete entry in accNum + for(i=0;accNum[i]!=n;i++); + accNum[i]=0; + + nvs_set_blob(savedData,"ACC_NUM",&accNum,sizeof(accNum)); // update data + nvs_commit(savedData); +} diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index c0a1281..757eddd 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -1032,7 +1032,7 @@ void Span::processSerialCommand(const char *c){ Serial.print("\n"); } } - Serial.print("\n*** End Info ***\n"); + Serial.print("\n*** End Info ***\n\n"); } break; @@ -1167,6 +1167,20 @@ void Span::prettyPrint(char *buf, int nsp){ Serial.print("\n"); } // prettyPrint + +/////////////////////////// + +SpanAccessory *Span::getAccessory(uint32_t n){ + + auto it=homeSpan.Accessories.begin(); + for(;it!=homeSpan.Accessories.end() && (*it)->aid!=n; it++); + + if(it==homeSpan.Accessories.end()) + return(NULL); + + return(*it); +} + /////////////////////////////// SpanCharacteristic *Span::find(uint32_t aid, int iid){ diff --git a/src/HomeSpan.h b/src/HomeSpan.h index ac6f95d..966de42 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -219,8 +219,9 @@ struct Span{ int sprintfAttributes(char *cBuf, int flags=GET_VALUE|GET_META|GET_PERMS|GET_TYPE|GET_DESC); // prints Attributes JSON database into buf, unless buf=NULL; return number of characters printed, excluding null terminator - void prettyPrint(char *buf, int nsp=2); // print arbitrary JSON from buf to serial monitor, formatted with indentions of 'nsp' spaces + void prettyPrint(char *buf, int nsp=2); // print arbitrary JSON from buf to serial monitor, formatted with indentions of 'nsp' spaces SpanCharacteristic *find(uint32_t aid, int iid); // return Characteristic with matching aid and iid (else NULL if not found) + SpanAccessory *getAccessory(uint32_t aid); // return Accessory with matching aid (else NULL if not found) int countCharacteristics(char *buf); // return number of characteristic objects referenced in PUT /characteristics JSON request int updateCharacteristics(char *buf, SpanBuf *pObj); // parses PUT /characteristics JSON request 'buf into 'pObj' and updates referenced characteristics; returns 1 on success, 0 on fail From f471e7377b53e3da099d897a8b4a38c703ec95fd Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 14 May 2022 11:21:59 -0500 Subject: [PATCH 29/98] Update 20-DynamicBridge.ino --- .../20-DynamicBridge/20-DynamicBridge.ino | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/examples/20-DynamicBridge/20-DynamicBridge.ino b/examples/20-DynamicBridge/20-DynamicBridge.ino index e25382a..1213926 100644 --- a/examples/20-DynamicBridge/20-DynamicBridge.ino +++ b/examples/20-DynamicBridge/20-DynamicBridge.ino @@ -30,7 +30,7 @@ #define MAX_LIGHTS 10 nvs_handle savedData; -int accNum[MAX_LIGHTS]={0}; +int accNum[MAX_LIGHTS+1]={0}; ////////////////////////////////////// @@ -56,9 +56,9 @@ void setup() { if(accNum[i]>0) addLight(accNum[i]); - new SpanUserCommand('a'," add a new light accessory with id=num",addAccessory); - new SpanUserCommand('d'," delete a light accessory with id=num",deleteAccessory); - new SpanUserCommand('u',"update configuration database",[](const char *buf){homeSpan.updateDatabase();}); + new SpanUserCommand('a'," - add a new light accessory with id=",addAccessory); + new SpanUserCommand('d'," - delete a light accessory with id=",deleteAccessory); + new SpanUserCommand('u',"update accessories database",updateAccessories); } @@ -142,10 +142,21 @@ void deleteAccessory(const char *buf){ Serial.printf("Deleting Accessory: Light-%d\n",n); delete acc; - int i; // delete entry in accNum + int i; // find entry in accNum for(i=0;accNum[i]!=n;i++); - accNum[i]=0; + for(;i Date: Wed, 18 May 2022 16:31:15 -0500 Subject: [PATCH 30/98] sort Accessories by AID to ensure same order all the time during dynamic changes --- src/HomeSpan.cpp | 3 +++ src/HomeSpan.h | 1 + 2 files changed, 4 insertions(+) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 757eddd..5479398 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include "HomeSpan.h" #include "HAP.h" @@ -1471,6 +1472,8 @@ int Span::sprintfAttributes(char **ids, int numIDs, int flags, char *cBuf){ boolean Span::updateDatabase(boolean updateMDNS){ + std::sort(Accessories.begin(),Accessories.end(),SpanAccessory::compare); + uint8_t tHash[48]; TempBuffer tBuf(sprintfAttributes(NULL,GET_META|GET_PERMS|GET_TYPE|GET_DESC)+1); sprintfAttributes(tBuf.buf,GET_META|GET_PERMS|GET_TYPE|GET_DESC); diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 966de42..5fe0739 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -288,6 +288,7 @@ struct SpanAccessory{ ~SpanAccessory(); int sprintfAttributes(char *cBuf, int flags); // prints Accessory JSON database into buf, unless buf=NULL; return number of characters printed, excluding null terminator, even if buf=NULL + static boolean compare(SpanAccessory *a, SpanAccessory *b){return(a->aidaid);} }; /////////////////////////////// From 52a977d2f11ddc9e18ffbc7e26d927c0e17faa82 Mon Sep 17 00:00:00 2001 From: Gregg Date: Thu, 19 May 2022 10:18:00 -0500 Subject: [PATCH 31/98] Revert "sort Accessories by AID to ensure same order all the time during dynamic changes" This reverts commit 2e98b163b20aef2fc1a405d8a935abee26645a3f. --- src/HomeSpan.cpp | 3 --- src/HomeSpan.h | 1 - 2 files changed, 4 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 5479398..757eddd 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -34,7 +34,6 @@ #include #include #include -#include #include "HomeSpan.h" #include "HAP.h" @@ -1472,8 +1471,6 @@ int Span::sprintfAttributes(char **ids, int numIDs, int flags, char *cBuf){ boolean Span::updateDatabase(boolean updateMDNS){ - std::sort(Accessories.begin(),Accessories.end(),SpanAccessory::compare); - uint8_t tHash[48]; TempBuffer tBuf(sprintfAttributes(NULL,GET_META|GET_PERMS|GET_TYPE|GET_DESC)+1); sprintfAttributes(tBuf.buf,GET_META|GET_PERMS|GET_TYPE|GET_DESC); diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 5fe0739..966de42 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -288,7 +288,6 @@ struct SpanAccessory{ ~SpanAccessory(); int sprintfAttributes(char *cBuf, int flags); // prints Accessory JSON database into buf, unless buf=NULL; return number of characters printed, excluding null terminator, even if buf=NULL - static boolean compare(SpanAccessory *a, SpanAccessory *b){return(a->aidaid);} }; /////////////////////////////// From 00ea4c6dd8dab1ed422696abdbac917c56c0d395 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 21 May 2022 12:08:03 -0500 Subject: [PATCH 32/98] Changes homeSpan.getAccessory(aid) to homeSpan.deleteAccessory() The only reason to have used getAccessory(aid) is to delete it, so the delete command is now invoked automatically. Function returns 0 on success (matching aid found) or -1 on fail (aid not found) --- examples/20-DynamicBridge/20-DynamicBridge.ino | 16 +++++++++++----- src/HomeSpan.cpp | 7 ++++--- src/HomeSpan.h | 2 +- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/examples/20-DynamicBridge/20-DynamicBridge.ino b/examples/20-DynamicBridge/20-DynamicBridge.ino index 1213926..97ca0ce 100644 --- a/examples/20-DynamicBridge/20-DynamicBridge.ino +++ b/examples/20-DynamicBridge/20-DynamicBridge.ino @@ -58,7 +58,8 @@ void setup() { new SpanUserCommand('a'," - add a new light accessory with id=",addAccessory); new SpanUserCommand('d'," - delete a light accessory with id=",deleteAccessory); - new SpanUserCommand('u',"update accessories database",updateAccessories); + new SpanUserCommand('D'," - delete ALL light accessories",deleteAllAccessories); + new SpanUserCommand('u',"- update accessories database",updateAccessories); } @@ -132,15 +133,12 @@ void deleteAccessory(const char *buf){ return; } - SpanAccessory *acc=homeSpan.getAccessory(n+1); - - if(!acc){ + if(homeSpan.deleteAccessory(n+1)){ Serial.printf("No such Accessory: Light-%d\n",n); return; } Serial.printf("Deleting Accessory: Light-%d\n",n); - delete acc; int i; // find entry in accNum for(i=0;accNum[i]!=n;i++); @@ -153,6 +151,14 @@ void deleteAccessory(const char *buf){ /////////////////////////// +void deleteAllAccessories(const char *buf){ + + nvs_erase_all(savedData); + nvs_commit(savedData); + } + +/////////////////////////// + void updateAccessories(const char *buf){ if(homeSpan.updateDatabase()) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 757eddd..d25f631 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -1170,15 +1170,16 @@ void Span::prettyPrint(char *buf, int nsp){ /////////////////////////// -SpanAccessory *Span::getAccessory(uint32_t n){ +int Span::deleteAccessory(uint32_t n){ auto it=homeSpan.Accessories.begin(); for(;it!=homeSpan.Accessories.end() && (*it)->aid!=n; it++); if(it==homeSpan.Accessories.end()) - return(NULL); + return(-1); - return(*it); + delete *it; + return(0); } /////////////////////////////// diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 966de42..40b592b 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -221,7 +221,7 @@ struct Span{ void prettyPrint(char *buf, int nsp=2); // print arbitrary JSON from buf to serial monitor, formatted with indentions of 'nsp' spaces SpanCharacteristic *find(uint32_t aid, int iid); // return Characteristic with matching aid and iid (else NULL if not found) - SpanAccessory *getAccessory(uint32_t aid); // return Accessory with matching aid (else NULL if not found) + int deleteAccessory(uint32_t aid); // deletes Accessory with matching aid, if found. Returns 0 on success, -1 on fail (aid not found) int countCharacteristics(char *buf); // return number of characteristic objects referenced in PUT /characteristics JSON request int updateCharacteristics(char *buf, SpanBuf *pObj); // parses PUT /characteristics JSON request 'buf into 'pObj' and updates referenced characteristics; returns 1 on success, 0 on fail From 7835665e60c6a80b19a7a71610abdfc753296d4b Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 21 May 2022 12:11:53 -0500 Subject: [PATCH 33/98] Update 20-DynamicBridge.ino --- examples/20-DynamicBridge/20-DynamicBridge.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/20-DynamicBridge/20-DynamicBridge.ino b/examples/20-DynamicBridge/20-DynamicBridge.ino index 97ca0ce..aa6d5bc 100644 --- a/examples/20-DynamicBridge/20-DynamicBridge.ino +++ b/examples/20-DynamicBridge/20-DynamicBridge.ino @@ -133,7 +133,7 @@ void deleteAccessory(const char *buf){ return; } - if(homeSpan.deleteAccessory(n+1)){ + if(homeSpan.deleteAccessory(n+1)!=0){ Serial.printf("No such Accessory: Light-%d\n",n); return; } From 1f89673fb72f9e5bec17ee19fa2092b31fee8276 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 21 May 2022 17:05:23 -0500 Subject: [PATCH 34/98] Updated 20-DynamicBridge to use std::array<> This allows the use of std::find(), std::remove, and iterator methods. Code is more intuitive. --- .../20-DynamicBridge/20-DynamicBridge.ino | 73 ++++++++++--------- 1 file changed, 40 insertions(+), 33 deletions(-) diff --git a/examples/20-DynamicBridge/20-DynamicBridge.ino b/examples/20-DynamicBridge/20-DynamicBridge.ino index aa6d5bc..4a31674 100644 --- a/examples/20-DynamicBridge/20-DynamicBridge.ino +++ b/examples/20-DynamicBridge/20-DynamicBridge.ino @@ -27,10 +27,10 @@ #include "HomeSpan.h" -#define MAX_LIGHTS 10 +#include nvs_handle savedData; -int accNum[MAX_LIGHTS+1]={0}; +std::array lights; ////////////////////////////////////// @@ -39,22 +39,21 @@ void setup() { Serial.begin(115200); size_t len; - nvs_open("SAVED_DATA",NVS_READWRITE,&savedData); // open SAVED DATA - if(!nvs_get_blob(savedData,"ACC_NUM",NULL,&len)) // if ACCESSORY NUMBER data found - nvs_get_blob(savedData,"ACC_NUM",&accNum,&len); // retrieve data + nvs_open("SAVED_DATA",NVS_READWRITE,&savedData); // open SAVED DATA + if(!nvs_get_blob(savedData,"LIGHTS",NULL,&len)) // if LIGHTS data found + nvs_get_blob(savedData,"LIGHTS",&lights,&len); // retrieve data homeSpan.setLogLevel(1); - homeSpan.begin(Category::Bridges,"HomeSpan Bridge"); + homeSpan.begin(Category::Lighting,"HomeSpan Lights"); new SpanAccessory(); new Service::AccessoryInformation(); new Characteristic::Identify(); new Characteristic::Model("HomeSpan Dynamic Bridge"); - for(int i=0;i0) - addLight(accNum[i]); + for(auto it=lights.begin(); it!=lights.end() && *it!=0; it++) + addLight(*it); new SpanUserCommand('a'," - add a new light accessory with id=",addAccessory); new SpanUserCommand('d'," - delete a light accessory with id=",deleteAccessory); @@ -102,24 +101,22 @@ void addAccessory(const char *buf){ return; } - int i; - for(i=0;i0 && accNum[i]!=n;i++); - - if(i==MAX_LIGHTS){ - Serial.printf("Can't add any more lights - max is %d!\n",MAX_LIGHTS); - return; - } - - if(accNum[i]>0){ + if(std::find(lights.begin(),lights.end(),n)!=lights.end()){ Serial.printf("Accessory Light-%d already implemented!\n",n); return; } + + auto it=std::find(lights.begin(),lights.end(),0); + + if(it==lights.end()){ + Serial.printf("Can't add any more lights - max is %d!\n",lights.size()); + return; + } - accNum[i]=n; - nvs_set_blob(savedData,"ACC_NUM",&accNum,sizeof(accNum)); // update data + *it=n; // save light number + nvs_set_blob(savedData,"LIGHTS",&lights,sizeof(lights)); // update data nvs_commit(savedData); - - addLight(n); + addLight(n); // add light accessory } /////////////////////////// @@ -140,22 +137,30 @@ void deleteAccessory(const char *buf){ Serial.printf("Deleting Accessory: Light-%d\n",n); - int i; // find entry in accNum - for(i=0;accNum[i]!=n;i++); - for(;i Date: Sat, 21 May 2022 17:26:27 -0500 Subject: [PATCH 35/98] Add weakly-define Arduino loop() function (defined as NO OP) Allows you to compile a sketch without defining the Arduino loop() function. Useful in cases where homeSpan.autoPoll() is used instead. --- src/HomeSpan.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index d25f631..05e7de0 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -2119,6 +2119,11 @@ void SpanOTA::error(ota_error_t err){ /////////////////////////////// +void __attribute__((weak)) loop(){ +} + +/////////////////////////////// + int SpanOTA::otaPercent; boolean SpanOTA::safeLoad; boolean SpanOTA::enabled=false; From 4d6f502708cf9ea13042812dd0cf33781e2f6655 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 21 May 2022 18:19:23 -0500 Subject: [PATCH 36/98] Create 20-AdvancedTechniques.ino --- .../20-AdvancedTechniques.ino | 175 ++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 examples/20-AdvancedTechniques/20-AdvancedTechniques.ino diff --git a/examples/20-AdvancedTechniques/20-AdvancedTechniques.ino b/examples/20-AdvancedTechniques/20-AdvancedTechniques.ino new file mode 100644 index 0000000..4a31674 --- /dev/null +++ b/examples/20-AdvancedTechniques/20-AdvancedTechniques.ino @@ -0,0 +1,175 @@ +/********************************************************************************* + * MIT License + * + * Copyright (c) 2022 Gregg E. Berman + * + * https://github.com/HomeSpan/HomeSpan + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + ********************************************************************************/ + +#include "HomeSpan.h" + +#include + +nvs_handle savedData; +std::array lights; + +////////////////////////////////////// + +void setup() { + + Serial.begin(115200); + + size_t len; + nvs_open("SAVED_DATA",NVS_READWRITE,&savedData); // open SAVED DATA + if(!nvs_get_blob(savedData,"LIGHTS",NULL,&len)) // if LIGHTS data found + nvs_get_blob(savedData,"LIGHTS",&lights,&len); // retrieve data + + homeSpan.setLogLevel(1); + + homeSpan.begin(Category::Lighting,"HomeSpan Lights"); + + new SpanAccessory(); + new Service::AccessoryInformation(); + new Characteristic::Identify(); + new Characteristic::Model("HomeSpan Dynamic Bridge"); + + for(auto it=lights.begin(); it!=lights.end() && *it!=0; it++) + addLight(*it); + + new SpanUserCommand('a'," - add a new light accessory with id=",addAccessory); + new SpanUserCommand('d'," - delete a light accessory with id=",deleteAccessory); + new SpanUserCommand('D'," - delete ALL light accessories",deleteAllAccessories); + new SpanUserCommand('u',"- update accessories database",updateAccessories); + +} + +////////////////////////////////////// + +void loop(){ + + homeSpan.poll(); + +} + +/////////////////////////// + +void addLight(int n){ + + char name[32]; + sprintf(name,"Light-%d",n); + char sNum[32]; + sprintf(sNum,"%0.10d",n); + + Serial.printf("Adding Accessory: %s\n",name); + + new SpanAccessory(n+1); // add 1, since first Accessory is reserved for the bridge + new Service::AccessoryInformation(); + new Characteristic::Identify(); + new Characteristic::Name(name); + new Characteristic::SerialNumber(sNum); + new Service::LightBulb(); + new Characteristic::On(0,true); +} + +/////////////////////////// + +void addAccessory(const char *buf){ + + int n=atoi(buf+1); + + if(n<1){ + Serial.printf("Invalid Accessory number!\n"); + return; + } + + if(std::find(lights.begin(),lights.end(),n)!=lights.end()){ + Serial.printf("Accessory Light-%d already implemented!\n",n); + return; + } + + auto it=std::find(lights.begin(),lights.end(),0); + + if(it==lights.end()){ + Serial.printf("Can't add any more lights - max is %d!\n",lights.size()); + return; + } + + *it=n; // save light number + nvs_set_blob(savedData,"LIGHTS",&lights,sizeof(lights)); // update data + nvs_commit(savedData); + addLight(n); // add light accessory +} + +/////////////////////////// + +void deleteAccessory(const char *buf){ + + int n=atoi(buf+1); + + if(n<1){ + Serial.printf("Invalid Accessory number!\n"); + return; + } + + if(homeSpan.deleteAccessory(n+1)!=0){ + Serial.printf("No such Accessory: Light-%d\n",n); + return; + } + + Serial.printf("Deleting Accessory: Light-%d\n",n); + + auto it=std::remove(lights.begin(),lights.end(),n); + *it=0; // overwrite end with a 0 + nvs_set_blob(savedData,"LIGHTS",&lights,sizeof(lights)); // update data + nvs_commit(savedData); +} + +/////////////////////////// + +void deleteAllAccessories(const char *buf){ + + if(lights[0]==0){ + Serial.printf("There are no Light Accessories to delete!\n"); + return; + } + + for(auto it=lights.begin(); it!=lights.end() && *it!=0; it++) + homeSpan.deleteAccessory(*it+1); + + std::fill(lights.begin(),lights.end(),0); + nvs_set_blob(savedData,"LIGHTS",&lights,sizeof(lights)); // update data + nvs_commit(savedData); + + Serial.printf("All Light Accessories deleted!\n"); +} + +/////////////////////////// + +void updateAccessories(const char *buf){ + + if(homeSpan.updateDatabase()) + Serial.printf("Accessories Database updated. New configuration number broadcasted...\n"); + else + Serial.printf("Nothing to update - no changes were made!\n"); +} + +/////////////////////////// From f9b8e2165a2efdf6517ce75381f3b10889eb1456 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 21 May 2022 18:24:35 -0500 Subject: [PATCH 37/98] Added homeSpan.autoPoll() to 20-AdvancedTechniques Deleted Arduino loop() function entirely since it no longer needed. --- examples/20-AdvancedTechniques/20-AdvancedTechniques.ino | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/examples/20-AdvancedTechniques/20-AdvancedTechniques.ino b/examples/20-AdvancedTechniques/20-AdvancedTechniques.ino index 4a31674..dd120f5 100644 --- a/examples/20-AdvancedTechniques/20-AdvancedTechniques.ino +++ b/examples/20-AdvancedTechniques/20-AdvancedTechniques.ino @@ -59,15 +59,8 @@ void setup() { new SpanUserCommand('d'," - delete a light accessory with id=",deleteAccessory); new SpanUserCommand('D'," - delete ALL light accessories",deleteAllAccessories); new SpanUserCommand('u',"- update accessories database",updateAccessories); - -} -////////////////////////////////////// - -void loop(){ - - homeSpan.poll(); - + homeSpan.autoPoll(); } /////////////////////////// From 6d409e9b89d5f2807eeec2a763539a8bc8bf7a37 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 22 May 2022 08:57:23 -0500 Subject: [PATCH 38/98] updated version check requires version 2.X of the Arduino-ESP32 board manager --- src/HomeSpan.h | 2 +- src/Settings.h | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 40b592b..1ca4046 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -261,7 +261,7 @@ struct Span{ webLog.init(maxEntries, serv, tz, url); } - void autoPoll(){xTaskCreateUniversal([](void *parms){for(;;)homeSpan.pollTask();}, "pollTask", getArduinoLoopTaskStackSize(), NULL, 1, &pollTaskHandle, 0);} // start pollTask() + void autoPoll(){xTaskCreateUniversal([](void *parms){for(;;)homeSpan.pollTask();}, "pollTask", CONFIG_ARDUINO_LOOP_STACK_SIZE, NULL, 1, &pollTaskHandle, 0);} // start pollTask() void setTimeServerTimeout(uint32_t tSec){webLog.waitTime=tSec*1000;} // sets wait time (in seconds) for optional web log time server to connect diff --git a/src/Settings.h b/src/Settings.h index 3da1fbb..a352ea7 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -55,6 +55,10 @@ #define ARDUINO_ESP_VERSION STRINGIFY(ARDUINO_ESP32_GIT_DESC) +#if ESP_ARDUINO_VERSION_MAJOR<2 + #error THIS SKETCH REQUIRES VERSION 2 OF THE ARDUINO ESP32 LIBRARY +#endif + ////////////////////////////////////////////////////// // DEFAULT SETTINGS // From 6018f37b60e49ef7cbbfe11e7df8d00356a922fb Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 22 May 2022 11:00:40 -0500 Subject: [PATCH 39/98] Added optional stackSize argument to homeSpan.autoPoll() If not specified, default is CONFIG_ARDUINO_LOOP_STACK_SIZE which is typically defined in the Arduino-ESP32 library as 8192. --- .../20-DynamicBridge/20-DynamicBridge.ino | 175 ------------------ src/HomeSpan.h | 2 +- 2 files changed, 1 insertion(+), 176 deletions(-) delete mode 100644 examples/20-DynamicBridge/20-DynamicBridge.ino diff --git a/examples/20-DynamicBridge/20-DynamicBridge.ino b/examples/20-DynamicBridge/20-DynamicBridge.ino deleted file mode 100644 index 4a31674..0000000 --- a/examples/20-DynamicBridge/20-DynamicBridge.ino +++ /dev/null @@ -1,175 +0,0 @@ -/********************************************************************************* - * MIT License - * - * Copyright (c) 2022 Gregg E. Berman - * - * https://github.com/HomeSpan/HomeSpan - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - ********************************************************************************/ - -#include "HomeSpan.h" - -#include - -nvs_handle savedData; -std::array lights; - -////////////////////////////////////// - -void setup() { - - Serial.begin(115200); - - size_t len; - nvs_open("SAVED_DATA",NVS_READWRITE,&savedData); // open SAVED DATA - if(!nvs_get_blob(savedData,"LIGHTS",NULL,&len)) // if LIGHTS data found - nvs_get_blob(savedData,"LIGHTS",&lights,&len); // retrieve data - - homeSpan.setLogLevel(1); - - homeSpan.begin(Category::Lighting,"HomeSpan Lights"); - - new SpanAccessory(); - new Service::AccessoryInformation(); - new Characteristic::Identify(); - new Characteristic::Model("HomeSpan Dynamic Bridge"); - - for(auto it=lights.begin(); it!=lights.end() && *it!=0; it++) - addLight(*it); - - new SpanUserCommand('a'," - add a new light accessory with id=",addAccessory); - new SpanUserCommand('d'," - delete a light accessory with id=",deleteAccessory); - new SpanUserCommand('D'," - delete ALL light accessories",deleteAllAccessories); - new SpanUserCommand('u',"- update accessories database",updateAccessories); - -} - -////////////////////////////////////// - -void loop(){ - - homeSpan.poll(); - -} - -/////////////////////////// - -void addLight(int n){ - - char name[32]; - sprintf(name,"Light-%d",n); - char sNum[32]; - sprintf(sNum,"%0.10d",n); - - Serial.printf("Adding Accessory: %s\n",name); - - new SpanAccessory(n+1); // add 1, since first Accessory is reserved for the bridge - new Service::AccessoryInformation(); - new Characteristic::Identify(); - new Characteristic::Name(name); - new Characteristic::SerialNumber(sNum); - new Service::LightBulb(); - new Characteristic::On(0,true); -} - -/////////////////////////// - -void addAccessory(const char *buf){ - - int n=atoi(buf+1); - - if(n<1){ - Serial.printf("Invalid Accessory number!\n"); - return; - } - - if(std::find(lights.begin(),lights.end(),n)!=lights.end()){ - Serial.printf("Accessory Light-%d already implemented!\n",n); - return; - } - - auto it=std::find(lights.begin(),lights.end(),0); - - if(it==lights.end()){ - Serial.printf("Can't add any more lights - max is %d!\n",lights.size()); - return; - } - - *it=n; // save light number - nvs_set_blob(savedData,"LIGHTS",&lights,sizeof(lights)); // update data - nvs_commit(savedData); - addLight(n); // add light accessory -} - -/////////////////////////// - -void deleteAccessory(const char *buf){ - - int n=atoi(buf+1); - - if(n<1){ - Serial.printf("Invalid Accessory number!\n"); - return; - } - - if(homeSpan.deleteAccessory(n+1)!=0){ - Serial.printf("No such Accessory: Light-%d\n",n); - return; - } - - Serial.printf("Deleting Accessory: Light-%d\n",n); - - auto it=std::remove(lights.begin(),lights.end(),n); - *it=0; // overwrite end with a 0 - nvs_set_blob(savedData,"LIGHTS",&lights,sizeof(lights)); // update data - nvs_commit(savedData); -} - -/////////////////////////// - -void deleteAllAccessories(const char *buf){ - - if(lights[0]==0){ - Serial.printf("There are no Light Accessories to delete!\n"); - return; - } - - for(auto it=lights.begin(); it!=lights.end() && *it!=0; it++) - homeSpan.deleteAccessory(*it+1); - - std::fill(lights.begin(),lights.end(),0); - nvs_set_blob(savedData,"LIGHTS",&lights,sizeof(lights)); // update data - nvs_commit(savedData); - - Serial.printf("All Light Accessories deleted!\n"); -} - -/////////////////////////// - -void updateAccessories(const char *buf){ - - if(homeSpan.updateDatabase()) - Serial.printf("Accessories Database updated. New configuration number broadcasted...\n"); - else - Serial.printf("Nothing to update - no changes were made!\n"); -} - -/////////////////////////// diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 1ca4046..4e936e9 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -261,7 +261,7 @@ struct Span{ webLog.init(maxEntries, serv, tz, url); } - void autoPoll(){xTaskCreateUniversal([](void *parms){for(;;)homeSpan.pollTask();}, "pollTask", CONFIG_ARDUINO_LOOP_STACK_SIZE, NULL, 1, &pollTaskHandle, 0);} // start pollTask() + void autoPoll(uint32_t stackSize=CONFIG_ARDUINO_LOOP_STACK_SIZE){xTaskCreateUniversal([](void *parms){for(;;)homeSpan.pollTask();}, "pollTask", stackSize, NULL, 1, &pollTaskHandle, 0);} // start pollTask() void setTimeServerTimeout(uint32_t tSec){webLog.waitTime=tSec*1000;} // sets wait time (in seconds) for optional web log time server to connect From 1b1123d4803cdf76e1c7859101cb0bb5c6120be5 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 22 May 2022 11:21:40 -0500 Subject: [PATCH 40/98] Update Reference.md --- docs/Reference.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/Reference.md b/docs/Reference.md index be3590f..6f41a58 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -24,11 +24,14 @@ At runtime HomeSpan will create a global **object** named `homeSpan` that suppor * checks for HAP requests, local commands, and device activity * **must** be called repeatedly in each sketch and is typically placed at the top of the Arduino `loop()` method, *unless* `autoPoll()` is used instead -* `void autoPoll()` - * an *optional* method to create a task that repeatedly calls `poll()` in the background, which frees up the Ardino `loop()` method for any user-defined code that would otherwise block, or be blocked by, calling `poll()` in the `loop()` method +* `void autoPoll(uint32_t stackSize)` + * an *optional* method to create a task with *stackSize* bytes of stack memory that repeatedly calls `poll()` in the background. This frees up the Ardino `loop()` method for any user-defined code that would otherwise block, or be blocked by, calling `poll()` in the `loop()` method * if used, **must** be placed in a sketch as the last line in the Arduino `setup()` method * HomeSpan will throw and error and halt if both `poll()`and `autoPoll()` are used in the same sketch - either place `poll()` in the Arduino `loop()` method **or** place `autoPoll()` at the the end of the Arduino `setup()` method * can be used with both single-core and dual-core ESP32 boards. If used with a dual-core board, the polling task is created on the free processor that is typically not running other Arduino functions + * if *stackSize* is not specified, defaults to the size used by the system for the normal Arduino `loop()` task (typically 8192 bytes) + * if this method is used, and you have no need to add your own code to the main Arduino `loop()`, you can safely skip defining a blank `void loop(){}` function in your sketch + * warning: this method should be considered *experimental* as different use cases are explored. For example, if the code you add to the Arduino `loop()` method tries to alter any HomeSpan settings, race conditions may yield undefined results --- From ae2a5f0a67e79f45cb41314598f72d2054bb2b94 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 22 May 2022 11:46:06 -0500 Subject: [PATCH 41/98] Update Reference.md --- docs/Reference.md | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/docs/Reference.md b/docs/Reference.md index 6f41a58..1465540 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -143,11 +143,6 @@ The following **optional** `homeSpan` methods enable additional features and pro * if *s* contains an invalid code, an error will be reported and the code will *not* be saved. Instead, the currently-stored Pairing Code (or the HomeSpan default Pairing Code if no code has been stored) will be used * :exclamation: SECURTY WARNING: Hardcoding a device's Pairing Code into your sketch is considered a security risk and is **not** recommended. Instead, use one of the more secure methods provided by HomeSpan, such as typing 'S \' from the CLI, or launching HomeSpan's Access Point, to set your Pairing Code without hardcoding it into your sketch -* `void deleteStoredValues()` - * deletes the value settings of all stored Characteristics from the NVS - * performs the same function as typing 'V' into the CLI - * can by called from anywhere in a sketch - * `void setSketchVersion(const char *sVer)` * sets the version of a HomeSpan sketch to *sVer*, which can be any arbitrary character string * if unspecified, HomeSpan uses "n/a" as the default version text @@ -171,6 +166,23 @@ The following **optional** `homeSpan` methods enable additional features and pro * `void setTimeServerTimeout(uint32_t tSec)` * changes the default 10-second timeout period HomeSpan uses when `enableWebLog()` tries set the device clock from an internet time server to *tSec* seconds +--- + +The following **optional** `homeSpan` methods provide additional run-time functionality for more advanced use cases: + +* `void deleteStoredValues()` + * deletes the value settings of all stored Characteristics from the NVS + * performs the same function as typing 'V' into the CLI + +* `int deleteAccessory(uint32_t aid)` + * deletes Accessory with Accessory ID of *aid*, if found + * returns 0 if sucessful (match found), or -1 if the specified *aid* does not match any current Accessories + * allows for dynamically changing the Accessory database during run-time + * deleting an Accessory automatically deletes all Services, Characteristics, and any other resources it contains + * produces level-1 log messages listing all deleted components + * though deletions take effect immediately, HomeKit Controllers, such as the Home App, will not be aware of these changes until the configuration number is updated and rebroadcast - see updateDatabase() below + + ## *SpanAccessory(uint32_t aid)* Creating an instance of this **class** adds a new HAP Accessory to the HomeSpan HAP Database. From f75596f3cd77cfe1e05f019fb6a41569817e984a Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 22 May 2022 13:13:34 -0500 Subject: [PATCH 42/98] Changed deleteAccessory from int to boolean Returns true on success, otherwise false --- .../20-AdvancedTechniques.ino | 16 +++++++++------- src/HomeSpan.cpp | 6 +++--- src/HomeSpan.h | 4 ++-- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/examples/20-AdvancedTechniques/20-AdvancedTechniques.ino b/examples/20-AdvancedTechniques/20-AdvancedTechniques.ino index dd120f5..4fa9647 100644 --- a/examples/20-AdvancedTechniques/20-AdvancedTechniques.ino +++ b/examples/20-AdvancedTechniques/20-AdvancedTechniques.ino @@ -123,17 +123,19 @@ void deleteAccessory(const char *buf){ return; } - if(homeSpan.deleteAccessory(n+1)!=0){ + if(homeSpan.deleteAccessory(n+1)){ + Serial.printf("Deleting Accessory: Light-%d\n",n); + + auto it=std::remove(lights.begin(),lights.end(),n); // remove entry from lights array + *it=0; // overwrite end with a 0 + nvs_set_blob(savedData,"LIGHTS",&lights,sizeof(lights)); // update data + nvs_commit(savedData); + + } else { Serial.printf("No such Accessory: Light-%d\n",n); - return; } - Serial.printf("Deleting Accessory: Light-%d\n",n); - auto it=std::remove(lights.begin(),lights.end(),n); - *it=0; // overwrite end with a 0 - nvs_set_blob(savedData,"LIGHTS",&lights,sizeof(lights)); // update data - nvs_commit(savedData); } /////////////////////////// diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 05e7de0..f881467 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -1170,16 +1170,16 @@ void Span::prettyPrint(char *buf, int nsp){ /////////////////////////// -int Span::deleteAccessory(uint32_t n){ +boolean Span::deleteAccessory(uint32_t n){ auto it=homeSpan.Accessories.begin(); for(;it!=homeSpan.Accessories.end() && (*it)->aid!=n; it++); if(it==homeSpan.Accessories.end()) - return(-1); + return(false); delete *it; - return(0); + return(true); } /////////////////////////////// diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 4e936e9..85bda61 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -215,13 +215,13 @@ struct Span{ void commandMode(); // allows user to control and reset HomeSpan settings with the control button void processSerialCommand(const char *c); // process command 'c' (typically from readSerial, though can be called with any 'c') - boolean updateDatabase(boolean updateMDNS=true); // updates HAP Configuration Number and Loop vector; iF updateMDNS=true and config number has changed, re-broadcasts MDNS 'c#' record; returns true if config number changed + boolean updateDatabase(boolean updateMDNS=true); // updates HAP Configuration Number and Loop vector; if updateMDNS=true and config number has changed, re-broadcasts MDNS 'c#' record; returns true if config number changed int sprintfAttributes(char *cBuf, int flags=GET_VALUE|GET_META|GET_PERMS|GET_TYPE|GET_DESC); // prints Attributes JSON database into buf, unless buf=NULL; return number of characters printed, excluding null terminator void prettyPrint(char *buf, int nsp=2); // print arbitrary JSON from buf to serial monitor, formatted with indentions of 'nsp' spaces SpanCharacteristic *find(uint32_t aid, int iid); // return Characteristic with matching aid and iid (else NULL if not found) - int deleteAccessory(uint32_t aid); // deletes Accessory with matching aid, if found. Returns 0 on success, -1 on fail (aid not found) + boolean deleteAccessory(uint32_t aid); // deletes Accessory with matching aid; returns true if found, else returns false int countCharacteristics(char *buf); // return number of characteristic objects referenced in PUT /characteristics JSON request int updateCharacteristics(char *buf, SpanBuf *pObj); // parses PUT /characteristics JSON request 'buf into 'pObj' and updates referenced characteristics; returns 1 on success, 0 on fail From 69789ec66c65f0f4e5d483950a44057a7f9d1e36 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 22 May 2022 16:33:14 -0500 Subject: [PATCH 43/98] Update Reference.md --- docs/Reference.md | 40 +++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/docs/Reference.md b/docs/Reference.md index 1465540..46b6e26 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -22,16 +22,7 @@ At runtime HomeSpan will create a global **object** named `homeSpan` that suppor * `void poll()` * checks for HAP requests, local commands, and device activity - * **must** be called repeatedly in each sketch and is typically placed at the top of the Arduino `loop()` method, *unless* `autoPoll()` is used instead - -* `void autoPoll(uint32_t stackSize)` - * an *optional* method to create a task with *stackSize* bytes of stack memory that repeatedly calls `poll()` in the background. This frees up the Ardino `loop()` method for any user-defined code that would otherwise block, or be blocked by, calling `poll()` in the `loop()` method - * if used, **must** be placed in a sketch as the last line in the Arduino `setup()` method - * HomeSpan will throw and error and halt if both `poll()`and `autoPoll()` are used in the same sketch - either place `poll()` in the Arduino `loop()` method **or** place `autoPoll()` at the the end of the Arduino `setup()` method - * can be used with both single-core and dual-core ESP32 boards. If used with a dual-core board, the polling task is created on the free processor that is typically not running other Arduino functions - * if *stackSize* is not specified, defaults to the size used by the system for the normal Arduino `loop()` task (typically 8192 bytes) - * if this method is used, and you have no need to add your own code to the main Arduino `loop()`, you can safely skip defining a blank `void loop(){}` function in your sketch - * warning: this method should be considered *experimental* as different use cases are explored. For example, if the code you add to the Arduino `loop()` method tries to alter any HomeSpan settings, race conditions may yield undefined results + * **must** be called repeatedly in each sketch and is typically placed at the top of the Arduino `loop()` method (*unless* `autoPoll()`, described further below, is used instead) --- @@ -174,14 +165,33 @@ The following **optional** `homeSpan` methods provide additional run-time functi * deletes the value settings of all stored Characteristics from the NVS * performs the same function as typing 'V' into the CLI -* `int deleteAccessory(uint32_t aid)` +* `boolean deleteAccessory(uint32_t aid)` * deletes Accessory with Accessory ID of *aid*, if found - * returns 0 if sucessful (match found), or -1 if the specified *aid* does not match any current Accessories - * allows for dynamically changing the Accessory database during run-time + * returns true if successful (match found), or false if the specified *aid* does not match any current Accessories + * allows for dynamically changing the Accessory database during run-time (i.e. changing the configuration *after* the Arduino `setup()` has finished) * deleting an Accessory automatically deletes all Services, Characteristics, and any other resources it contains - * produces level-1 log messages listing all deleted components - * though deletions take effect immediately, HomeKit Controllers, such as the Home App, will not be aware of these changes until the configuration number is updated and rebroadcast - see updateDatabase() below + * outputs Level-1 Log Messages listing all deleted components + * note: though deletions take effect immediately, HomeKit Controllers, such as the Home App, will not be aware of these changes until the database configuration number is updated and rebroadcast - see updateDatabase() below +* `boolean updateDatabase()` + * recomputes the database configuration number and, if changed, rebroadcasts the new number via MDNS so all connected HomeKit Controllers, such as the Home App, can request a full refresh to accurately reflect the new configuration + * returns true if configuration number has changed, false otherwise + * *only* needed if you want to make run-time (i.e. after the Arduino `setup()` function has completed) changes to the device's Accessory database + * use anytime after dynamically adding one or more Accessories (with `new SpanAccessory(aid)`) or deleting one or more Accessories (with `homeSpan.deleteAccessory(aid)`) + * note: this method is **not** needed if you have a static Accessory database that is fully defined in the Arduino `setup()` function of a sketch + +--- + +The following `homeSpan` methods are considered experimental, since not all use cases have been explored or debugged. Use with caution: + +* `void autoPoll(uint32_t stackSize)` + * an *optional* method to create a task with *stackSize* bytes of stack memory that repeatedly calls `poll()` in the background. This frees up the Ardino `loop()` method for any user-defined code to run in parallel that would otherwise block, or be blocked by, calling `poll()` in the `loop()` method + * if used, **must** be placed in a sketch as the last line in the Arduino `setup()` method + * HomeSpan will throw and error and halt if both `poll()`and `autoPoll()` are used in the same sketch - either place `poll()` in the Arduino `loop()` method **or** place `autoPoll()` at the the end of the Arduino `setup()` method + * can be used with both single-core and dual-core ESP32 boards. If used with a dual-core board, the polling task is created on the free processor that is typically not running other Arduino functions + * if *stackSize* is not specified, defaults to the size used by the system for the normal Arduino `loop()` task (typically 8192 bytes) + * if this method is used, and you have no need to add your own code to the main Arduino `loop()`, you can safely skip defining a blank `void loop(){}` function in your sketch + * warning: if any code you add to the Arduino `loop()` method tries to alter any HomeSpan settings or functions running in the background `poll()` task, race conditions may yield undefined results ## *SpanAccessory(uint32_t aid)* From e245822428164af1f3d9e6ef771a9aa63b71744d Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 22 May 2022 16:37:14 -0500 Subject: [PATCH 44/98] Update README.md --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 867c898..69fba0e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,7 +4,7 @@ Welcome to HomeSpan - a robust and extremely easy-to-use Arduino library for cre HomeSpan provides a microcontroller-focused implementation of [Apple's HomeKit Accessory Protocol Specification Release R2 (HAP-R2)](https://developer.apple.com/homekit/specification/) designed specifically for the Espressif ESP32 microcontroller running within the Arduino IDE. HomeSpan pairs directly to HomeKit via your home WiFi network without the need for any external bridges or components. With HomeSpan you can use the full power of the ESP32's I/O functionality to create custom control software and/or hardware to automatically operate external devices from the Home App on your iPhone, iPad, or Mac, or with Siri. -HomeSpan is fully compatible with both Versions 1 and 2 of the [Arduino-ESP32 Board Manager](https://github.com/espressif/arduino-esp32). Under Version 1, HomeSpan can be run only on the original ESP32. Under Version 2, HomeSpan can be run on the original ESP32 as well as Espressif's ESP32-S2 and ESP32-C3 chips. +HomeSpan requires Version 2 of the [Arduino-ESP32 Board Manager](https://github.com/espressif/arduino-esp32). HomeSpan can be run on the original ESP32 as well as Espressif's ESP32-S2 and ESP32-C3 chips. ### HomeSpan Highlights From 2520eed6d8b8b1aed46fd54c59184a478fc87b97 Mon Sep 17 00:00:00 2001 From: Gregg Date: Mon, 23 May 2022 21:26:57 -0500 Subject: [PATCH 45/98] Added detailed comments to 20-AdvancedTechniques To do: Add Tutorial to HomeSpan Documentation To do: test sketch on S2 and C3 devices To do: add homeSpan.autoPoll() to Unit Test as well --- .../20-AdvancedTechniques.ino | 145 ++++++++++++++---- 1 file changed, 113 insertions(+), 32 deletions(-) diff --git a/examples/20-AdvancedTechniques/20-AdvancedTechniques.ino b/examples/20-AdvancedTechniques/20-AdvancedTechniques.ino index 4fa9647..90c0c88 100644 --- a/examples/20-AdvancedTechniques/20-AdvancedTechniques.ino +++ b/examples/20-AdvancedTechniques/20-AdvancedTechniques.ino @@ -24,13 +24,42 @@ * SOFTWARE. * ********************************************************************************/ - + +////////////////////////////////////////////////////////////////// +// // +// HomeSpan: A HomeKit implementation for the ESP32 // +// ------------------------------------------------ // +// // +// Example 20: Demonstrates various advance HomeSpan functions // +// by implementing a Bridge in which one or more // +// Lightbulb Accessories can be added and deleted // +// *dynamically* without needing to restart the // +// device // +// // +////////////////////////////////////////////////////////////////// + #include "HomeSpan.h" -#include + // In Example 20 we will implement a bridge device supporting up to 10 Lightbulb Accessories. However, rather than pre-specifying the number of Lights, we + // will allow Light Accessories to be added and deleted dynamically by the user via the CLI. Changes are reflected in the Home App without the need to restart + // the device! Note this example uses a variety of advanced HomeSpan functions, as well as some detailed features of both the ESP32-IDF and C++ that have not been used + // in any of the previous examples. + + // We will use a C++ array with 10 elements containing integers representing the Light "ID" of each Lightbulb Accessory implemented. An ID of zero means there is no + // Light defined in that element. + +#include // include the C++ standard library array container + +std::array lights; // declare "lights" to be an array of 10 integers + +using std::fill; // place the std library function fill, remove, and find, into the global namespace so we can use them below without prefacing with "std::" +using std::remove; +using std::find; + + // We will use non-volatile storage (NVS) to store the lights array so that the device can restore the current configuration upon rebooting + +nvs_handle savedData; // declare savdData as a handle to be used with the NVS (see the ESP32-IDF for details on how to use NVS storage) -nvs_handle savedData; -std::array lights; ////////////////////////////////////// @@ -38,8 +67,10 @@ void setup() { Serial.begin(115200); + fill(lights.begin(),lights.end(),0); // initialize lights array with zeros in each of the 10 elements (no Light Accessories defined) + size_t len; - nvs_open("SAVED_DATA",NVS_READWRITE,&savedData); // open SAVED DATA + nvs_open("SAVED_DATA",NVS_READWRITE,&savedData); // open a new namespace called SAVED_DATA in the NVS if(!nvs_get_blob(savedData,"LIGHTS",NULL,&len)) // if LIGHTS data found nvs_get_blob(savedData,"LIGHTS",&lights,&len); // retrieve data @@ -47,34 +78,56 @@ void setup() { homeSpan.begin(Category::Lighting,"HomeSpan Lights"); - new SpanAccessory(); + // We begin by creating the Bridge Accessory + + new SpanAccessory(1); // here we specified the AID=1 for clarity (it would default to 1 anyway if left blank) new Service::AccessoryInformation(); new Characteristic::Identify(); - new Characteristic::Model("HomeSpan Dynamic Bridge"); + new Characteristic::Model("HomeSpan Dynamic Bridge"); // defining the Model is optional - for(auto it=lights.begin(); it!=lights.end() && *it!=0; it++) - addLight(*it); + // Now we create Light Accessories based on what is recorded in the lights array + // We'll use C++ iterators to loop over all elements until we reach the end of the array, or find an element with a value of zero + + for(auto it=lights.begin(); it!=lights.end() && *it!=0; it++) // loop over all elements (stopping when we get to the end, or hit an element with a value of zero) + addLight(*it); // call addLight (defined further below) with an argument equal to the integer stored in that element + + // Next we create four user-defined CLI commands so we can add and delete Light Accessories from the CLI. + // The functions for each command are defined further below. new SpanUserCommand('a'," - add a new light accessory with id=",addAccessory); new SpanUserCommand('d'," - delete a light accessory with id=",deleteAccessory); new SpanUserCommand('D'," - delete ALL light accessories",deleteAllAccessories); new SpanUserCommand('u',"- update accessories database",updateAccessories); + // Finally we call autoPoll to start polling the background. Note this is purely optional and only used here to illustrate how to + // use autoPoll - you could instead have called the usual homeSpan.poll() function by including it inside the Arduino loop() function + homeSpan.autoPoll(); -} + +} // end of setup() + +// Usually the Arduino loop() function would be defined somewhere here. But since we used autoPoll in the setup() function, +// we don't have to define the loop() function at all in this sketch! Why don't we get an error? Because HomeSpan includes +// a default loop() function, which prevents the compiler from complaining about loop() being undefined. /////////////////////////// +// This function creates a new Light Accessory with n as the "ID". +// It is called initially in setup() above to create Light Accessories based +// on what was stored in the lights array. It is also called in response to +// typing 'a' into the CLI (see below), which dynamically adds a new Light Accessory +// while the device is running. + void addLight(int n){ char name[32]; - sprintf(name,"Light-%d",n); + sprintf(name,"Light-%d",n); // create the name of the device using the specified "ID" char sNum[32]; - sprintf(sNum,"%0.10d",n); + sprintf(sNum,"%0.10d",n); // create serial number from the ID - this is helpful in case we rename the Light to something else using the Home App Serial.printf("Adding Accessory: %s\n",name); - new SpanAccessory(n+1); // add 1, since first Accessory is reserved for the bridge + new SpanAccessory(n+1); // IMPORTANT: add 1, since first Accessory with AID=1 is already used by the Bridge Accessory new Service::AccessoryInformation(); new Characteristic::Identify(); new Characteristic::Name(name); @@ -85,73 +138,86 @@ void addLight(int n){ /////////////////////////// +// This function is called in response to typing '@a ' into the CLI. +// It adds a new Light Accessory with ID=num, by calling addLight(num) above. + void addAccessory(const char *buf){ - int n=atoi(buf+1); + int n=atoi(buf+1); // read the value of specified - if(n<1){ + if(n<1){ // ensure is greater than 0 Serial.printf("Invalid Accessory number!\n"); return; } - if(std::find(lights.begin(),lights.end(),n)!=lights.end()){ + if(find(lights.begin(),lights.end(),n)!=lights.end()){ // search for this ID in the existing lights array - if found, report an error and return Serial.printf("Accessory Light-%d already implemented!\n",n); return; } - auto it=std::find(lights.begin(),lights.end(),0); + auto it=find(lights.begin(),lights.end(),0); // find the next "free" element in the light array (the first element with a value of zero) - if(it==lights.end()){ + if(it==lights.end()){ // if there were no elements with a zero, the array is full and no new Lights can be added Serial.printf("Can't add any more lights - max is %d!\n",lights.size()); return; } *it=n; // save light number - nvs_set_blob(savedData,"LIGHTS",&lights,sizeof(lights)); // update data + nvs_set_blob(savedData,"LIGHTS",&lights,sizeof(lights)); // update data in the NVS nvs_commit(savedData); - addLight(n); // add light accessory + addLight(n); // add light accessory by calling the function above! } /////////////////////////// +// This function deletes an existing Light Accessory and is called +// in response to typing '@d ' into the CLI. + void deleteAccessory(const char *buf){ - int n=atoi(buf+1); + int n=atoi(buf+1); // same as above, we read the specified and check that it is valid (i.e. greater than 0) if(n<1){ Serial.printf("Invalid Accessory number!\n"); return; } - if(homeSpan.deleteAccessory(n+1)){ + // Below we use the homeSpan method deleteAccessory(aid) to completely delete the Accessory with AID=n+1. + // We add 1 because the AID of the first Light Accessory is 2, since the Bridge Accessory has an AID of 1. + // The deleteAccessory() method returns true if an Accessory with matching AID is found, otherwise it returns false. + // When deleting an Accessory, HomeSpan will print a delete message for every Service, Characteristic, loop() method, + // button() method, and SpanButton, associated with that Accessory. These are Level-1 Log messages, so you'll need + // to have the Log Level in the sketch set to 1 or 2 to receive the output. + + if(homeSpan.deleteAccessory(n+1)){ // if deleteAccessory() is true, a match has been found Serial.printf("Deleting Accessory: Light-%d\n",n); - auto it=std::remove(lights.begin(),lights.end(),n); // remove entry from lights array - *it=0; // overwrite end with a 0 - nvs_set_blob(savedData,"LIGHTS",&lights,sizeof(lights)); // update data + fill(remove(lights.begin(),lights.end(),n),lights.end(),0); // remove entry from lights array and fill any undefined elements with zero + nvs_set_blob(savedData,"LIGHTS",&lights,sizeof(lights)); // update data in the NVS nvs_commit(savedData); } else { Serial.printf("No such Accessory: Light-%d\n",n); } - - } /////////////////////////// void deleteAllAccessories(const char *buf){ - if(lights[0]==0){ +// This function is called in response to typing '@D' into the CLI. +// It deletes all Light Accessories + + if(lights[0]==0){ // first check that there is at least one Light Accessory by checking for a non-zero ID in lights[0] Serial.printf("There are no Light Accessories to delete!\n"); return; } - for(auto it=lights.begin(); it!=lights.end() && *it!=0; it++) - homeSpan.deleteAccessory(*it+1); + for(auto it=lights.begin(); it!=lights.end() && *it!=0; it++) // use an iterator to loop over all non-zero elements in the lights array... + homeSpan.deleteAccessory(*it+1); // ... and delete the matching Light Accessory (don't forgot to add 1 to the Light ID to form the AID) - std::fill(lights.begin(),lights.end(),0); - nvs_set_blob(savedData,"LIGHTS",&lights,sizeof(lights)); // update data + fill(lights.begin(),lights.end(),0); // zero out all the elements in the lights array, since all Light Accessories have been deleted + nvs_set_blob(savedData,"LIGHTS",&lights,sizeof(lights)); // update data in the NVS nvs_commit(savedData); Serial.printf("All Light Accessories deleted!\n"); @@ -159,7 +225,22 @@ void deleteAllAccessories(const char *buf){ /////////////////////////// +// Lastly we have the all-important updateAccessories function. +// This is called in response to typing '@u' into the CLI. +// Though the above functions can be used to add and delete Light Accessories +// dyammically, Controllers such as the Home App that are already connected to +// the device don't yet know additional Light Accessories have been added to (or +// deleted from) the overall Accessories datase. To let them know, HomeSpan needs +// to increment the HAP Configuration Number and re-broadcast it via MDNS so all +// connected Controllers are aware that they need to request a refresh from the device. + +// When you type '@u' into the CLI, you should see a lot of activity between the device +// and any connected Controllers as they request a refresh. Be patient - it can take up to a +// minute for changes to be properly reflected in the Home App on your iPhone or Mac. + void updateAccessories(const char *buf){ + + // note the updateDatabase() method returns true if the database has indeed changed (e.g. one or more new Light Accessories were added), or false if nothing has changed if(homeSpan.updateDatabase()) Serial.printf("Accessories Database updated. New configuration number broadcasted...\n"); From 86a119be8f433c7900256864a26f5de0cbea07dd Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Fri, 27 May 2022 18:26:28 -0500 Subject: [PATCH 46/98] Update Tutorials.md --- docs/Tutorials.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/Tutorials.md b/docs/Tutorials.md index b04c2ff..a14fe55 100644 --- a/docs/Tutorials.md +++ b/docs/Tutorials.md @@ -99,6 +99,14 @@ Example 19 illustrates, through the implementation of two On/Off LEDs, how to ad * enabling the HomeSpan Web Log and specifying an optional NTP time server with the `homeSpan.enableWebLog()` method * using the `WEBLOG()` macro to create Web Log messages +### [Example 20 - AdvancedTechniques](../examples/19-AdvancedTechniques) +Example 20 illustrates a number of advanced techniques through the implementation of a "dynamic" bridge that allows Light Accessories to be *interactively* added and deleted at any time without the need to reboot the device. New HomeSpan API topics covered in this example include: + +* creating custom CLI commands using `SpanUserCommand()` +* dynamically deleting Accessories with `homeSpan.deleteAccessory()` +* refreshing the Accessory database (which automatically updates the Home App) using `homeSpan.updateDatabase()` +* using `homeSpan.autoPoll()` to implement HomeSpan Polling in the background (and on the second core, if available) + ## Other Examples The following examples showcase a variety of HomeSpan and HomeKit functionality as referenced in different sections of the HomeSpan documentation. The sketches can be found in the Arduino IDE under *File → Examples → HomeSpan → Other Examples* From 6cfbe3c209149a2b4768549004c1eb56b2a14201 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Fri, 27 May 2022 18:41:09 -0500 Subject: [PATCH 47/98] Update CLI.md --- docs/CLI.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/CLI.md b/docs/CLI.md index a670d2a..1572914 100644 --- a/docs/CLI.md +++ b/docs/CLI.md @@ -35,6 +35,9 @@ In addition to listening for incoming HAP requests, HomeSpan also continuously p * **d** - print the full HAP Accessory Attributes Database in JSON format * This outputs the full HAP Database in JSON format, exactly as it is transmitted to any HomeKit device that requests it (with the exception of the newlines and spaces that make it easier to read on the screen). Note that the value tag for each Characteristic will reflect the *current* value on the device for that Characteristic. +* **m** - print free heap memory (in bytes) + * This prints the amount of memory available for use when creating new objects or allocating memory. Useful for developers only. + * **W** - configure WiFi Credentials and restart * HomeSpan sketches *do not* contain WiFi network names or WiFi passwords. Rather, this information is separately stored in a dedicated Non-Volatile Storage (NVS) partition in the ESP32's flash memory, where it is permanently retained until updated (with this command) or erased (see below). When HomeSpan receives this command it first scans for any local WiFi networks. If your network is found, you can specify it by number when prompted for the WiFi SSID. Otherwise, you can directly type your WiFi network name. After you then type your WiFi Password, HomeSpan updates the NVS with these new WiFi Credentials, and restarts the device. From bb1d0050e205cb51ee34746358c9166e0cb9c126 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 28 May 2022 11:24:11 -0500 Subject: [PATCH 48/98] Changed SpanAccessory and SpanService to class from struct Split members into private/protected/public --- src/HomeSpan.h | 56 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 85bda61..29a99b5 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -278,21 +278,37 @@ struct Span{ /////////////////////////////// -struct SpanAccessory{ +class SpanAccessory{ + + friend class Span; + friend class SpanService; + friend class SpanCharacteristic; + friend class SpanButton; + friend class SpanRange; - uint32_t aid=0; // Accessory Instance ID (HAP Table 6-1) - int iidCount=0; // running count of iid to use for Services and Characteristics associated with this Accessory - vector Services; // vector of pointers to all Services in this Accessory + uint32_t aid=0; // Accessory Instance ID (HAP Table 6-1) + int iidCount=0; // running count of iid to use for Services and Characteristics associated with this Accessory + vector Services; // vector of pointers to all Services in this Accessory - SpanAccessory(uint32_t aid=0); // constructor - ~SpanAccessory(); + int sprintfAttributes(char *cBuf, int flags); // prints Accessory JSON database into buf, unless buf=NULL; return number of characters printed, excluding null terminator, even if buf=NULL - int sprintfAttributes(char *cBuf, int flags); // prints Accessory JSON database into buf, unless buf=NULL; return number of characters printed, excluding null terminator, even if buf=NULL + protected: + + ~SpanAccessory(); // destructor + + public: + + SpanAccessory(uint32_t aid=0); // constructor }; /////////////////////////////// -struct SpanService{ +class SpanService{ + + friend class Span; + friend class SpanAccessory; + friend class SpanCharacteristic; + friend class SpanRange; int iid=0; // Instance ID (HAP Table 6-2) const char *type; // Service Type @@ -300,22 +316,26 @@ struct SpanService{ boolean hidden=false; // optional property indicating service is hidden boolean primary=false; // optional property indicating service is primary vector Characteristics; // vector of pointers to all Characteristics in this Service - unordered_set req; // unordered set of pointers to all required HAP Characteristic Types for this Service - unordered_set opt; // unordered set of pointers to all optional HAP Characteristic Types for this Service vector linkedServices; // vector of pointers to any optional linked Services boolean isCustom; // flag to indicate this is a Custom Service SpanAccessory *accessory=NULL; // pointer to Accessory containing this Service - SpanService(const char *type, const char *hapName, boolean isCustom=false); // constructor - ~SpanService(); - - SpanService *setPrimary(); // sets the Service Type to be primary and returns pointer to self - SpanService *setHidden(); // sets the Service Type to be hidden and returns pointer to self - SpanService *addLink(SpanService *svc); // adds svc as a Linked Service and returns pointer to self - vector getLinks(){return(linkedServices);} // returns linkedServices vector for use as range in "for-each" loops - int sprintfAttributes(char *cBuf, int flags); // prints Service JSON records into buf; return number of characters printed, excluding null terminator + + protected: + ~SpanService(); // destructor + unordered_set req; // unordered set of pointers to all required HAP Characteristic Types for this Service + unordered_set opt; // unordered set of pointers to all optional HAP Characteristic Types for this Service + + public: + + SpanService(const char *type, const char *hapName, boolean isCustom=false); // constructor + SpanService *setPrimary(); // sets the Service Type to be primary and returns pointer to self + SpanService *setHidden(); // sets the Service Type to be hidden and returns pointer to self + SpanService *addLink(SpanService *svc); // adds svc as a Linked Service and returns pointer to self + vector getLinks(){return(linkedServices);} // returns linkedServices vector for use as range in "for-each" loops + virtual boolean update() {return(true);} // placeholder for code that is called when a Service is updated via a Controller. Must return true/false depending on success of update virtual void loop(){} // loops for each Service - called every cycle if over-ridden with user-defined code virtual void button(int pin, int pressType){} // method called for a Service when a button attached to "pin" has a Single, Double, or Long Press, according to pressType From 86a0c1cf754b09fb7f97e909f38e8516c4d60efc Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 28 May 2022 14:48:49 -0500 Subject: [PATCH 49/98] Changed SpanCharacteristic and SpanButton from struct to class Also moved SpanButton checking into Span instead of awkward reference to HAPClient function --- src/HAP.cpp | 16 +--------- src/HAP.h | 1 - src/HomeSpan.cpp | 12 +++++++- src/HomeSpan.h | 77 ++++++++++++++++++++++++++++-------------------- 4 files changed, 57 insertions(+), 49 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index efe1d68..4f7a09b 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -1321,20 +1321,6 @@ void HAPClient::callServiceLoops(){ homeSpan.Loops[i]->loop(); // call the loop() method } - -////////////////////////////////////// - -void HAPClient::checkPushButtons(){ - - for(int i=0;ipushButton->triggered(sb->singleTime,sb->longTime,sb->doubleTime)){ // if the underlying PushButton is triggered - sb->service->button(sb->pin,sb->pushButton->type()); // call the Service's button() routine with pin and type as parameters - } - } - -} - ////////////////////////////////////// void HAPClient::checkNotifications(){ @@ -1347,7 +1333,7 @@ void HAPClient::checkNotifications(){ ////////////////////////////////////// -void HAPClient::checkTimedWrites(){ +void HAPClient::checkTimedWrites(){ unsigned long cTime=millis(); // get current time diff --git a/src/HAP.h b/src/HAP.h index 707f438..b26d7f0 100644 --- a/src/HAP.h +++ b/src/HAP.h @@ -143,7 +143,6 @@ struct HAPClient { 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 callServiceLoops(); // call the loop() method for any Service with that over-rode the default method - static void checkPushButtons(); // checks for PushButton presses and calls button() method of attached Services when found static void checkNotifications(); // checks for Event Notifications and reports to controllers as needed (HAP Section 6.8) static void checkTimedWrites(); // checks for expired Timed Write PIDs, and clears any found (HAP Section 6.7.2.4) 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 f881467..8e050aa 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -277,7 +277,10 @@ void Span::pollTask() { } // for-loop over connection slots HAPClient::callServiceLoops(); - HAPClient::checkPushButtons(); + + for(auto it=PushButtons.begin();it!=PushButtons.end();it++) // check for SpanButton presses + (*it)->check(); + HAPClient::checkNotifications(); HAPClient::checkTimedWrites(); @@ -1978,6 +1981,13 @@ SpanButton::SpanButton(int pin, uint16_t longTime, uint16_t singleTime, uint16_t homeSpan.PushButtons.push_back(this); } +/////////////////////////////// + +void SpanButton::check(){ + + if(pushButton->triggered(singleTime,longTime,doubleTime)) // if the underlying PushButton is triggered + service->button(pin,pushButton->type()); // call the Service's button() routine with pin and type as parameters +} /////////////////////////////// // SpanUserCommand // diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 29a99b5..d14636a 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -343,7 +343,10 @@ class SpanService{ /////////////////////////////// -struct SpanCharacteristic{ +class SpanCharacteristic{ + + friend class Span; + friend class SpanService; union UVal { BOOL_t BOOL; @@ -382,17 +385,9 @@ struct SpanCharacteristic{ unsigned long updateTime=0; // last time value was updated (in millis) either by PUT /characteristic OR by setVal() UVal newValue; // the updated value requested by PUT /characteristic SpanService *service=NULL; // pointer to Service containing this Characteristic - - SpanCharacteristic(HapChar *hapChar, boolean isCustom=false); // contructor - ~SpanCharacteristic(); - + int sprintfAttributes(char *cBuf, int flags); // prints Characteristic JSON records into buf, according to flags mask; return number of characters printed, excluding null terminator - StatusCode loadUpdate(char *val, char *ev); // load updated val/ev from PUT /characteristic JSON request. Return intiial HAP status code (checks to see if characteristic is found, is writable, etc.) - - boolean updated(){return(isUpdated);} // returns isUpdated - unsigned long timeVal(); // returns time elapsed (in millis) since value was last updated - - SpanCharacteristic *setValidValues(int n, ...); // sets a list of 'n' valid values allowed for a Characteristic and returns pointer to self. Only applicable if format=uint8 + StatusCode loadUpdate(char *val, char *ev); // load updated val/ev from PUT /characteristic JSON request. Return intitial HAP status code (checks to see if characteristic is found, is writable, etc.) String uvPrint(UVal &u){ char c[64]; @@ -479,19 +474,9 @@ struct SpanCharacteristic{ return(0); // included to prevent compiler warnings } - template SpanCharacteristic *setRange(A min, B max, S step=0){ + protected: - if(!staticRange){ - uvSet(minValue,min); - uvSet(maxValue,max); - uvSet(stepValue,step); - customRange=true; - } else - setRangeError=true; - - return(this); - - } // setRange() + ~SpanCharacteristic(); // destructor template void init(T val, boolean nvsStore, A min=0, B max=1){ @@ -540,6 +525,9 @@ struct SpanCharacteristic{ } // init() + public: + + SpanCharacteristic(HapChar *hapChar, boolean isCustom=false); // constructor template T getVal(){ return(uvGet(value)); @@ -622,6 +610,25 @@ struct SpanCharacteristic{ } // setVal() + boolean updated(){return(isUpdated);} // returns isUpdated + unsigned long timeVal(); // returns time elapsed (in millis) since value was last updated + + SpanCharacteristic *setValidValues(int n, ...); // sets a list of 'n' valid values allowed for a Characteristic and returns pointer to self. Only applicable if format=uint8 + + template SpanCharacteristic *setRange(A min, B max, S step=0){ + + if(!staticRange){ + uvSet(minValue,min); + uvSet(maxValue,max); + uvSet(stepValue,step); + customRange=true; + } else + setRangeError=true; + + return(this); + + } // setRange() + SpanCharacteristic *setPerms(uint8_t perms){ perms&=0x7F; if(perms>0) @@ -659,7 +666,21 @@ struct [[deprecated("Please use Characteristic::setRange() method instead.")]] S /////////////////////////////// -struct SpanButton{ +class SpanButton{ + + friend class Span; + friend class SpanService; + + int pin; // pin number + uint16_t singleTime; // minimum time (in millis) required to register a single press + uint16_t longTime; // minimum time (in millis) required to register a long press + uint16_t doubleTime; // maximum time (in millis) between single presses to register a double press instead + SpanService *service; // Service to which this PushButton is attached + PushButton *pushButton; // PushButton associated with this SpanButton + + void check(); // check PushButton and call button() if pressed + + public: enum { SINGLE=0, @@ -667,14 +688,6 @@ struct SpanButton{ LONG=2 }; - int pin; // pin number - uint16_t singleTime; // minimum time (in millis) required to register a single press - uint16_t longTime; // minimum time (in millis) required to register a long press - uint16_t doubleTime; // maximum time (in millis) between single presses to register a double press instead - SpanService *service; // Service to which this PushButton is attached - - PushButton *pushButton; // PushButton associated with this SpanButton - SpanButton(int pin, uint16_t longTime=2000, uint16_t singleTime=5, uint16_t doubleTime=200); }; From 2b25acf2e114ba1c8650ef51bba9c2d2072adf55 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 29 May 2022 06:42:38 -0500 Subject: [PATCH 50/98] Converted SpanUserCommand to class from struct Also cleaned up some code by moving a few HAPClient routines into Span polling function. --- src/HAP.cpp | 10 ---------- src/HAP.h | 1 - src/HomeSpan.cpp | 5 ++++- src/HomeSpan.h | 15 ++++++++++++--- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index 4f7a09b..66f5893 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -1313,16 +1313,6 @@ int HAPClient::getStatusURL(){ ////////////////////////////////////// -void HAPClient::callServiceLoops(){ - - homeSpan.snapTime=millis(); // snap the current time for use in ALL loop routines - - for(int i=0;iloop(); // call the loop() method -} - -////////////////////////////////////// - void HAPClient::checkNotifications(){ if(!homeSpan.Notifications.empty()){ // if there are Notifications to process diff --git a/src/HAP.h b/src/HAP.h index b26d7f0..8ed6db1 100644 --- a/src/HAP.h +++ b/src/HAP.h @@ -142,7 +142,6 @@ 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 callServiceLoops(); // call the loop() method for any Service with that over-rode the default method static void checkNotifications(); // checks for Event Notifications and reports to controllers as needed (HAP Section 6.8) static void checkTimedWrites(); // checks for expired Timed Write PIDs, and clears any found (HAP Section 6.7.2.4) 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 8e050aa..bb2cb3c 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -276,7 +276,10 @@ void Span::pollTask() { } // process HAP Client } // for-loop over connection slots - HAPClient::callServiceLoops(); + snapTime=millis(); // snap the current time for use in ALL loop routines + + for(auto it=Loops.begin();it!=Loops.end();it++) // call loop() for all Services with over-ridden loop() methods + (*it)->loop(); for(auto it=PushButtons.begin();it!=PushButtons.end();it++) // check for SpanButton presses (*it)->check(); diff --git a/src/HomeSpan.h b/src/HomeSpan.h index d14636a..8fbebd7 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -77,7 +77,9 @@ struct SpanUserCommand; extern Span homeSpan; -/////////////////////////////// +//////////////////////////////////////////////////////// +// INTERNAL HOMESPAN STRUCTURES - NOT FOR USER ACCESS // +//////////////////////////////////////////////////////// struct SpanPartition{ char magicCookie[32]; @@ -145,7 +147,9 @@ struct SpanOTA{ // manages OTA process static void error(ota_error_t err); }; -/////////////////////////////// +////////////////////////////////////// +// USER API CLASSES BEGINS HERE // +////////////////////////////////////// struct Span{ @@ -693,12 +697,17 @@ class SpanButton{ /////////////////////////////// -struct SpanUserCommand { +class SpanUserCommand { + + friend class Span; + const char *s; // description of command void (*userFunction1)(const char *v)=NULL; // user-defined function to call void (*userFunction2)(const char *v, void *arg)=NULL; // user-defined function to call with user-defined arg void *userArg; + public: + SpanUserCommand(char c, const char *s, void (*f)(const char *)); SpanUserCommand(char c, const char *s, void (*f)(const char *, void *), void *arg); }; From c5ba453a951f25968c8b6f2b3cdd249a7c2c19ab Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 29 May 2022 10:27:34 -0500 Subject: [PATCH 51/98] Converted Span to class; added homeSpan.getLogLevel() and homeSpan.addWebLog(fmt,...) These functions are needed to provide public access to API now that Span is a class instead of a struct --- src/HAP.cpp | 1 - src/HomeSpan.cpp | 5 +--- src/HomeSpan.h | 75 +++++++++++++++++++++++++++++++----------------- src/Settings.h | 6 ++-- 4 files changed, 52 insertions(+), 35 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index 66f5893..a422988 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -30,7 +30,6 @@ #include #include "HAP.h" -#include "HomeSpan.h" ////////////////////////////////////// diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index bb2cb3c..d8b6aea 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -2047,7 +2047,7 @@ void SpanWebLog::initTime(){ /////////////////////////////// -void SpanWebLog::addLog(const char *fmt, ...){ +void SpanWebLog::vLog(const char *fmt, va_list ap){ if(maxEntries==0) return; @@ -2060,10 +2060,7 @@ void SpanWebLog::addLog(const char *fmt, ...){ log[index].clockTime.tm_year=0; free(log[index].message); - va_list ap; - va_start(ap,fmt); vasprintf(&log[index].message,fmt,ap); - va_end(ap); log[index].clientIP=homeSpan.lastClientIP; nEntries++; diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 8fbebd7..0971f4a 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -77,6 +77,8 @@ struct SpanUserCommand; extern Span homeSpan; +#include "HAP.h" + //////////////////////////////////////////////////////// // INTERNAL HOMESPAN STRUCTURES - NOT FOR USER ACCESS // //////////////////////////////////////////////////////// @@ -126,7 +128,7 @@ struct SpanWebLog{ // optional web status/log data void init(uint16_t maxEntries, const char *serv, const char *tz, const char *url); void initTime(); - void addLog(const char *fmr, ...); + void vLog(const char *fmr, va_list ap); }; /////////////////////////////// @@ -151,8 +153,19 @@ struct SpanOTA{ // manages OTA process // USER API CLASSES BEGINS HERE // ////////////////////////////////////// -struct Span{ +class Span{ + friend class SpanAccessory; + friend class SpanService; + friend class SpanCharacteristic; + friend class SpanUserCommand; + friend class SpanButton; + friend class SpanRange; + friend class SpanWebLog; + friend class SpanOTA; + friend class Network; + friend class HAPClient; + const char *displayName; // display name for this device - broadcast as part of Bonjour MDNS const char *hostNameBase; // base of hostName of this device - full host name broadcast by Bonjour MDNS will have 6-byte accessoryID as well as '.local' automatically appended const char *hostNameSuffix=NULL; // optional "suffix" of hostName of this device. If specified, will be used as the hostName suffix instead of the 6-byte accessoryID @@ -207,33 +220,40 @@ struct Span{ unordered_map UserCommands; // map of pointers to all UserCommands + void pollTask(); // poll HAP Clients and process any new HAP requests + int getFreeSlot(); // returns free HAPClient slot number. HAPClients slot keep track of each active HAPClient connection + void checkConnect(); // check WiFi connection; connect if needed + void commandMode(); // allows user to control and reset HomeSpan settings with the control button + + int sprintfAttributes(char *cBuf, int flags=GET_VALUE|GET_META|GET_PERMS|GET_TYPE|GET_DESC); // prints Attributes JSON database into buf, unless buf=NULL; return number of characters printed, excluding null terminator + + void prettyPrint(char *buf, int nsp=2); // print arbitrary JSON from buf to serial monitor, formatted with indentions of 'nsp' spaces + SpanCharacteristic *find(uint32_t aid, int iid); // return Characteristic with matching aid and iid (else NULL if not found) + int countCharacteristics(char *buf); // return number of characteristic objects referenced in PUT /characteristics JSON request + int updateCharacteristics(char *buf, SpanBuf *pObj); // parses PUT /characteristics JSON request 'buf into 'pObj' and updates referenced characteristics; returns 1 on success, 0 on fail + int sprintfAttributes(SpanBuf *pObj, int nObj, char *cBuf); // prints SpanBuf object into buf, unless buf=NULL; return number of characters printed, excluding null terminator, even if buf=NULL + int sprintfAttributes(char **ids, int numIDs, int flags, char *cBuf); // prints accessory.characteristic ids into buf, unless buf=NULL; return number of characters printed, excluding null terminator, even if buf=NULL + void clearNotify(int slotNum); // set ev notification flags for connection 'slotNum' to false across all characteristics + int sprintfNotify(SpanBuf *pObj, int nObj, char *cBuf, int conNum); // prints notification JSON into buf based on SpanBuf objects and specified connection number + + static boolean invalidUUID(const char *uuid, boolean isCustom){ + int x=0; + sscanf(uuid,"%*8[0-9a-fA-F]-%*4[0-9a-fA-F]-%*4[0-9a-fA-F]-%*4[0-9a-fA-F]-%*12[0-9a-fA-F]%n",&x); + return(isCustom && (strlen(uuid)!=36 || x!=36)); + } + + public: + void begin(Category catID=DEFAULT_CATEGORY, const char *displayName=DEFAULT_DISPLAY_NAME, const char *hostNameBase=DEFAULT_HOST_NAME, const char *modelName=DEFAULT_MODEL_NAME); void poll(); // calls pollTask() with some error checking - void pollTask(); // poll HAP Clients and process any new HAP requests - int getFreeSlot(); // returns free HAPClient slot number. HAPClients slot keep track of each active HAPClient connection - void checkConnect(); // check WiFi connection; connect if needed - void commandMode(); // allows user to control and reset HomeSpan settings with the control button void processSerialCommand(const char *c); // process command 'c' (typically from readSerial, though can be called with any 'c') boolean updateDatabase(boolean updateMDNS=true); // updates HAP Configuration Number and Loop vector; if updateMDNS=true and config number has changed, re-broadcasts MDNS 'c#' record; returns true if config number changed - - int sprintfAttributes(char *cBuf, int flags=GET_VALUE|GET_META|GET_PERMS|GET_TYPE|GET_DESC); // prints Attributes JSON database into buf, unless buf=NULL; return number of characters printed, excluding null terminator - - void prettyPrint(char *buf, int nsp=2); // print arbitrary JSON from buf to serial monitor, formatted with indentions of 'nsp' spaces - SpanCharacteristic *find(uint32_t aid, int iid); // return Characteristic with matching aid and iid (else NULL if not found) - boolean deleteAccessory(uint32_t aid); // deletes Accessory with matching aid; returns true if found, else returns false - - int countCharacteristics(char *buf); // return number of characteristic objects referenced in PUT /characteristics JSON request - int updateCharacteristics(char *buf, SpanBuf *pObj); // parses PUT /characteristics JSON request 'buf into 'pObj' and updates referenced characteristics; returns 1 on success, 0 on fail - int sprintfAttributes(SpanBuf *pObj, int nObj, char *cBuf); // prints SpanBuf object into buf, unless buf=NULL; return number of characters printed, excluding null terminator, even if buf=NULL - int sprintfAttributes(char **ids, int numIDs, int flags, char *cBuf); // prints accessory.characteristic ids into buf, unless buf=NULL; return number of characters printed, excluding null terminator, even if buf=NULL - - void clearNotify(int slotNum); // set ev notification flags for connection 'slotNum' to false across all characteristics - int sprintfNotify(SpanBuf *pObj, int nObj, char *cBuf, int conNum); // prints notification JSON into buf based on SpanBuf objects and specified connection number + boolean deleteAccessory(uint32_t aid); // deletes Accessory with matching aid; returns true if found, else returns false void setControlPin(uint8_t pin){controlPin=pin;} // sets Control Pin void setStatusPin(uint8_t pin){statusPin=pin;} // sets Status Pin @@ -244,6 +264,7 @@ struct Span{ void setApTimeout(uint16_t nSec){network.lifetime=nSec*1000;} // sets Access Point Timeout (seconds) void setCommandTimeout(uint16_t nSec){comModeLife=nSec*1000;} // sets Command Mode Timeout (seconds) void setLogLevel(uint8_t level){logLevel=level;} // sets Log Level for log messages (0=baseline, 1=intermediate, 2=all) + int getLogLevel(){return(logLevel);} // get Log Level void reserveSocketConnections(uint8_t n){maxConnections-=n;} // reserves n socket connections *not* to be used for HAP void setHostNameSuffix(const char *suffix){hostNameSuffix=suffix;} // sets the hostName suffix to be used instead of the 6-byte AccessoryID void setPortNum(uint16_t port){tcpPortNum=port;} // sets the TCP port number to use for communications between HomeKit and HomeSpan @@ -265,19 +286,19 @@ struct Span{ webLog.init(maxEntries, serv, tz, url); } + void addWebLog(const char *fmt, ...){ // add Web Log entry + va_list ap; + va_start(ap,fmt); + webLog.vLog(fmt,ap); + va_end(ap); + } + void autoPoll(uint32_t stackSize=CONFIG_ARDUINO_LOOP_STACK_SIZE){xTaskCreateUniversal([](void *parms){for(;;)homeSpan.pollTask();}, "pollTask", stackSize, NULL, 1, &pollTaskHandle, 0);} // start pollTask() void setTimeServerTimeout(uint32_t tSec){webLog.waitTime=tSec*1000;} // sets wait time (in seconds) for optional web log time server to connect [[deprecated("Please use reserveSocketConnections(n) method instead.")]] void setMaxConnections(uint8_t n){requestedMaxCon=n;} // sets maximum number of simultaneous HAP connections - - static boolean invalidUUID(const char *uuid, boolean isCustom){ - int x=0; - sscanf(uuid,"%*8[0-9a-fA-F]-%*4[0-9a-fA-F]-%*4[0-9a-fA-F]-%*4[0-9a-fA-F]-%*12[0-9a-fA-F]%n",&x); - return(isCustom && (strlen(uuid)!=36 || x!=36)); - } - }; /////////////////////////////// diff --git a/src/Settings.h b/src/Settings.h index a352ea7..8e5bf25 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -108,10 +108,10 @@ // 0=Minimal, 1=Informative, 2=All // #define LOG0(format,...) Serial.print ##__VA_OPT__(f)(format __VA_OPT__(,) __VA_ARGS__) -#define LOG1(format,...) if(homeSpan.logLevel>0)Serial.print ##__VA_OPT__(f)(format __VA_OPT__(,) __VA_ARGS__) -#define LOG2(format,...) if(homeSpan.logLevel>1)Serial.print ##__VA_OPT__(f)(format __VA_OPT__(,) __VA_ARGS__) +#define LOG1(format,...) if(homeSpan.getLogLevel()>0)Serial.print ##__VA_OPT__(f)(format __VA_OPT__(,) __VA_ARGS__) +#define LOG2(format,...) if(homeSpan.getLogLevel()>1)Serial.print ##__VA_OPT__(f)(format __VA_OPT__(,) __VA_ARGS__) -#define WEBLOG(format,...) homeSpan.webLog.addLog(format __VA_OPT__(,) __VA_ARGS__) +#define WEBLOG(format,...) homeSpan.addWebLog(format __VA_OPT__(,) __VA_ARGS__) ////////////////////////////////////////////////////// // Types of Accessory Categories // From af5839e5a950b7e3079080e4f85397f0400c4aa0 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 29 May 2022 10:32:57 -0500 Subject: [PATCH 52/98] Update Reference.md --- docs/Reference.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/Reference.md b/docs/Reference.md index 46b6e26..9dbd30f 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -40,7 +40,7 @@ The following **optional** `homeSpan` methods override various HomeSpan initiali * if *duration* is set to zero, auto-off is disabled (Status LED will remain on indefinitely) * `int getStatusPin()` -* returns the pin number of the Status LED as set by `setStatusPin(pin)`, or -1 if no pin has been set + * returns the pin number of the Status LED as set by `setStatusPin(pin)`, or -1 if no pin has been set * `void setApSSID(const char *ssid)` * sets the SSID (network name) of the HomeSpan Setup Access Point (default="HomeSpan-Setup") @@ -61,6 +61,9 @@ The following **optional** `homeSpan` methods override various HomeSpan initiali * 2 = all HomeSpan status messages plus all HAP communication packets to and from the HomeSpan device, as well as all `LOG1()` and `LOG2()` messages specified in the sketch by the user * note the log level can also be changed at runtime with the 'L' command via the [HomeSpan CLI](CLI.md) * see [Message Logging](Logging.md) for complete details + +* `int getLogLevel()` + * returns the current Log Level as set by `setLogLevel(level)` * `void reserveSocketConnections(uint8_t nSockets)` * reserves *nSockets* network sockets for uses **other than** by the HomeSpan HAP Server for HomeKit Controller Connections From 7d9dbd04b8ddea50ea22c4c528c5bf0ef8e2b618 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 29 May 2022 21:29:53 -0500 Subject: [PATCH 53/98] Update Reference.md --- docs/Reference.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/Reference.md b/docs/Reference.md index 9dbd30f..64184f7 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -118,8 +118,7 @@ The following **optional** `homeSpan` methods enable additional features and pro * sets the SSID (*ssid*) and password (*pwd*) of the WiFi network to which HomeSpan will connect * *ssid* and *pwd* are automatically saved in HomeSpan's non-volatile storage (NVS) for retrieval when the device restarts * note that the saved values are truncated if they exceed the maximum allowable characters (ssid=32; pwd=64) - -> :warning: SECURITY WARNING: The purpose of this function is to allow advanced users to *dynamically* set the device's WiFi Credentials using a customized Access Point function specified by `setApFunction(func)`. It it NOT recommended to use this function to hardcode your WiFi SSID and password directly into your sketch. Instead, use one of the more secure methods provided by HomeSpan, such as typing 'W' from the CLI, or launching HomeSpan's Access Point, to set your WiFi credentials without hardcoding them into your sketch + * :warning: SECURITY WARNING: The purpose of this function is to allow advanced users to *dynamically* set the device's WiFi Credentials using a customized Access Point function specified by `setApFunction(func)`. It it NOT recommended to use this function to hardcode your WiFi SSID and password directly into your sketch. Instead, use one of the more secure methods provided by HomeSpan, such as typing 'W' from the CLI, or launching HomeSpan's Access Point, to set your WiFi credentials without hardcoding them into your sketch * `void setWifiCallback(void (*func)())` * sets an optional user-defined callback function, *func*, to be called by HomeSpan upon start-up just after WiFi connectivity has been established. This one-time call to *func* is provided for users that are implementing other network-related services as part of their sketch, but that cannot be started until WiFi connectivity is established. The function *func* must be of type *void* and have no arguments @@ -135,7 +134,7 @@ The following **optional** `homeSpan` methods enable additional features and pro * example: `homeSpan.setPairingCode("46637726");` * a hashed version of the Pairing Code will be saved to the device's non-volatile storage, overwriting any currently-stored Pairing Code * if *s* contains an invalid code, an error will be reported and the code will *not* be saved. Instead, the currently-stored Pairing Code (or the HomeSpan default Pairing Code if no code has been stored) will be used - * :exclamation: SECURTY WARNING: Hardcoding a device's Pairing Code into your sketch is considered a security risk and is **not** recommended. Instead, use one of the more secure methods provided by HomeSpan, such as typing 'S \' from the CLI, or launching HomeSpan's Access Point, to set your Pairing Code without hardcoding it into your sketch + * :warning: SECURTY WARNING: Hardcoding a device's Pairing Code into your sketch is considered a security risk and is **not** recommended. Instead, use one of the more secure methods provided by HomeSpan, such as typing 'S \' from the CLI, or launching HomeSpan's Access Point, to set your Pairing Code without hardcoding it into your sketch * `void setSketchVersion(const char *sVer)` * sets the version of a HomeSpan sketch to *sVer*, which can be any arbitrary character string From bb4b0e3dea334d96fef565898c09f030dafb4aaf Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 11 Jun 2022 07:56:42 -0500 Subject: [PATCH 54/98] Updates for ESP32-S3 Compatability Added Feather.h mappings; and Changed RMT clock logic in RFControl to check for presence of RMT_SYS_CON_REG instead of simply looking for CONFIG_IDF_TARGET_ESP32C3; ** NOTE: Can ignore warnings about RMT_CH4...CH7+RX_LIM_REG redefine errors. This has been reported to Espressif IDF Github, been acknowledged as a bug, and will be fixed in a future release of the IDF. Since HomeSpan does NOT use the RMT peripheral for RECEIVING, these warning messages have no effect. --- src/FeatherPins.h | 30 +++++++++++++++++++++++++++--- src/extras/RFControl.cpp | 4 ++-- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/src/FeatherPins.h b/src/FeatherPins.h index 458c5e2..7072d86 100644 --- a/src/FeatherPins.h +++ b/src/FeatherPins.h @@ -31,15 +31,39 @@ #pragma once #if defined(ARDUINO_FEATHER_ESP32) - enum {F13=13,F12=12,F27=27,F33=33,F15=15,F32=32,F14=14,F22=22,F23=23,F26=26,F25=25,F34=34,F39=39,F36=36,F4=4,F5=5,F18=18,F19=19,F16=16,F17=17,F21=21}; + enum { + F13=13,F12=12,F27=27,F15=15,F32=32,F14=14,F16=16,F17=17,F21=21, // Digital Only (9 pins) + F26=26,F25=25,F34=34,F39=39,F36=36,F4=4, // A0-A5 + F22=22,F23=23, // I2C SCL/SDA + F5=5,F18=18,F19=19,F33=33 // SPI SCK/SDO/SDI/CS + }; #define DEVICE_SUFFIX "" #elif defined(ARDUINO_ESP32S2_DEV) - enum {F13=1,F12=3,F27=7,F33=34,F15=10,F32=42,F14=11,F22=9,F23=8,F26=17,F25=14,F34=13,F39=12,F36=18,F4=19,F5=36,F18=35,F19=37,F16=20,F17=21,F21=16}; + enum { + F13=1,F12=3,F27=7,F15=10,F32=42,F14=11,F16=20,F17=21,F21=16, // Digital Only (9 pins) + F26=17,F25=14,F34=13,F39=12,F36=18,F4=19, // A0-A5 + F22=9,F23=8, // I2C SCL/SDA + F5=36,F18=35,F19=37,F33=34 // SPI SCK/SDO/SDI/CS + }; #define DEVICE_SUFFIX "-S2" #elif defined(ARDUINO_ESP32C3_DEV) - enum {F27=2,F33=7,F32=3,F14=10,F22=9,F23=8,F26=0,F25=1,F4=18,F5=4,F18=6,F19=5,F16=20,F17=21,F21=19}; + enum { + F27=2,F32=3,F14=10,F16=20,F17=21,F21=19, // Digital Only (6 pins) + F26=0,F25=1,F4=18, // A0/A1/A5 + F22=9,F23=8, // I2C SCL/SDA + F5=4,F18=6,F19=5,F33=7 // SPI SCK/SDO/SDI/CS + }; #define DEVICE_SUFFIX "-C3" +#elif defined(ARDUINO_ESP32S3_DEV) + enum { + F13=5,F12=6,F27=7,F15=16,F32=17,F14=18,F16=37,F17=36,F21=35, // Digital Only (9 pins) + F26=1,F25=2,F34=20,F39=19,F36=15,F4=4, // A0-A5 + F22=9,F23=8, // I2C SCL/SDA + F5=12,F18=11,F19=13,F33=10 // SPI SCK/SDO/SDI/CS + }; + #define DEVICE_SUFFIX "-S3" + #endif diff --git a/src/extras/RFControl.cpp b/src/extras/RFControl.cpp index 6b6f1b6..66fea73 100644 --- a/src/extras/RFControl.cpp +++ b/src/extras/RFControl.cpp @@ -64,8 +64,8 @@ RFControl::RFControl(uint8_t pin, boolean refClock, boolean installDriver){ this->refClock=refClock; if(refClock) -#ifdef CONFIG_IDF_TARGET_ESP32C3 - REG_SET_FIELD(RMT_SYS_CONF_REG,RMT_SCLK_DIV_NUM,79); // ESP32-C3 does not have a 1 MHz REF Tick Clock, but allows the 80 MHz APB clock to be scaled by an additional RMT-specific divider +#ifdef RMT_SYS_CONF_REG + REG_SET_FIELD(RMT_SYS_CONF_REG,RMT_SCLK_DIV_NUM,79); // ESP32-C3 and ESP32-S3 do not have a 1 MHz REF Tick Clock, but allows the 80 MHz APB clock to be scaled by an additional RMT-specific divider #else rmt_set_source_clk(config->channel,RMT_BASECLK_REF); // use 1 MHz REF Tick Clock for ESP32 and ESP32-S2 #endif From 53a9705d6324023ba8dab20c8229edc3caacda05 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 11 Jun 2022 16:53:41 -0500 Subject: [PATCH 55/98] Update README.md --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 69fba0e..f708502 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,7 +4,7 @@ Welcome to HomeSpan - a robust and extremely easy-to-use Arduino library for cre HomeSpan provides a microcontroller-focused implementation of [Apple's HomeKit Accessory Protocol Specification Release R2 (HAP-R2)](https://developer.apple.com/homekit/specification/) designed specifically for the Espressif ESP32 microcontroller running within the Arduino IDE. HomeSpan pairs directly to HomeKit via your home WiFi network without the need for any external bridges or components. With HomeSpan you can use the full power of the ESP32's I/O functionality to create custom control software and/or hardware to automatically operate external devices from the Home App on your iPhone, iPad, or Mac, or with Siri. -HomeSpan requires Version 2 of the [Arduino-ESP32 Board Manager](https://github.com/espressif/arduino-esp32). HomeSpan can be run on the original ESP32 as well as Espressif's ESP32-S2 and ESP32-C3 chips. +HomeSpan requires Version 2 of the [Arduino-ESP32 Board Manager](https://github.com/espressif/arduino-esp32). HomeSpan can be run on the original ESP32 as well as Espressif's ESP32-S2, ESP32-C3, and ESP32-S3 chips. ### HomeSpan Highlights From 4f3df5d276142491a68a8ea2ed1812bd5689bffe Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 11 Jun 2022 16:55:46 -0500 Subject: [PATCH 56/98] Update PWM.md --- docs/PWM.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/PWM.md b/docs/PWM.md index 7743080..fdb238c 100644 --- a/docs/PWM.md +++ b/docs/PWM.md @@ -65,6 +65,7 @@ The following PWM resources are available: * ESP32: 16 Channels / 8 Timers (arranged in two distinct sets of 8 Channels and 4 Timers) * ESP32-S2: 8 Channels / 4 Timers * ESP32-C3: 6 Channels / 4 Timers +* ESP32-S3: 8 Channels / 4 Timers HomeSpan *automatically* allocates Channels and Timers to LedPin and ServoPin objects as they are instantiated. Every pin assigned consumes a single Channel; every *unique* frequency specified among all channels (within the same set, for the ESP32) consumes a single Timer. HomeSpan will conserve resources by re-using the same Timer for all Channels operating at the same frequency. *HomeSpan also automatically configures each Timer to support the maximum duty-resolution possible for the frequency specified.* From 281d5708ab9197a387c65fc63bce329401eb2451 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 11 Jun 2022 16:58:20 -0500 Subject: [PATCH 57/98] Update RMT.md --- docs/RMT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/RMT.md b/docs/RMT.md index 112585b..fe1c49b 100644 --- a/docs/RMT.md +++ b/docs/RMT.md @@ -6,7 +6,7 @@ The ESP32 has an on-chip signal-generator peripheral designed to drive an RF or ## *RFControl(int pin, boolean refClock=true)* -Creating an instance of this **class** initializes the RF/IR signal generator and specifies the ESP32 *pin* to output the signal. You may create more than one instance of this class if driving more than one RF/IR transmitter (each connected to different *pin*), subject to the following limitations: ESP32 - 8 instances; ESP32-S2 - 4 instances; ESP32-C3 - 2 instances. The optional parameter *refClock* is more fully described further below under the `start()` method. +Creating an instance of this **class** initializes the RF/IR signal generator and specifies the ESP32 *pin* to output the signal. You may create more than one instance of this class if driving more than one RF/IR transmitter (each connected to different *pin*), subject to the following limitations: ESP32 - 8 instances; ESP32-S2 and ESP32-S3 - 4 instances; ESP32-C3 - 2 instances. The optional parameter *refClock* is more fully described further below under the `start()` method. Signals are defined as a sequence of HIGH and LOW phases that together form a pulse train where you specify the duration, in *ticks*, of each HIGH and LOW phase, shown respectively as H1-H4 and L1-L4 in the following diagram: From 10789efe7e5677d9f14f991ff88c5fab927bc003 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Mon, 20 Jun 2022 16:44:50 -0500 Subject: [PATCH 58/98] Update Tutorials.md --- docs/Tutorials.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Tutorials.md b/docs/Tutorials.md index a14fe55..08feb0e 100644 --- a/docs/Tutorials.md +++ b/docs/Tutorials.md @@ -99,7 +99,7 @@ Example 19 illustrates, through the implementation of two On/Off LEDs, how to ad * enabling the HomeSpan Web Log and specifying an optional NTP time server with the `homeSpan.enableWebLog()` method * using the `WEBLOG()` macro to create Web Log messages -### [Example 20 - AdvancedTechniques](../examples/19-AdvancedTechniques) +### [Example 20 - AdvancedTechniques](../examples/20-AdvancedTechniques) Example 20 illustrates a number of advanced techniques through the implementation of a "dynamic" bridge that allows Light Accessories to be *interactively* added and deleted at any time without the need to reboot the device. New HomeSpan API topics covered in this example include: * creating custom CLI commands using `SpanUserCommand()` From 758b8880b16793512e40330de88da5e241421069 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Mon, 20 Jun 2022 16:53:27 -0500 Subject: [PATCH 59/98] Update Reference.md --- docs/Reference.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Reference.md b/docs/Reference.md index 64184f7..0d750f4 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -180,6 +180,7 @@ The following **optional** `homeSpan` methods provide additional run-time functi * returns true if configuration number has changed, false otherwise * *only* needed if you want to make run-time (i.e. after the Arduino `setup()` function has completed) changes to the device's Accessory database * use anytime after dynamically adding one or more Accessories (with `new SpanAccessory(aid)`) or deleting one or more Accessories (with `homeSpan.deleteAccessory(aid)`) + * **important**: once you delete an Accessory, you cannot re-use the same *aid* when adding a new Accessory (on the same device) unless the new Accessory is configured with the exact same Services and Characteristics as the deleted Accessory * note: this method is **not** needed if you have a static Accessory database that is fully defined in the Arduino `setup()` function of a sketch --- From 6c7bf03c3b29e2039d3c97ef8935e6819cc95bc6 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 25 Jun 2022 18:44:31 -0500 Subject: [PATCH 60/98] Added Other Examples -> ProgrammableHub --- Other Examples/ProgrammableHub/DEV_Identify.h | 38 +++ Other Examples/ProgrammableHub/DEV_LED.h | 69 ++++++ .../ProgrammableHub/ProgrammableHub.ino | 224 ++++++++++++++++++ src/HAP.cpp | 2 +- 4 files changed, 332 insertions(+), 1 deletion(-) create mode 100644 Other Examples/ProgrammableHub/DEV_Identify.h create mode 100644 Other Examples/ProgrammableHub/DEV_LED.h create mode 100644 Other Examples/ProgrammableHub/ProgrammableHub.ino diff --git a/Other Examples/ProgrammableHub/DEV_Identify.h b/Other Examples/ProgrammableHub/DEV_Identify.h new file mode 100644 index 0000000..b8d21d6 --- /dev/null +++ b/Other Examples/ProgrammableHub/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(const char *name, const char *manu, const char *sn, const char *model, const 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 LED + + pinMode(homeSpan.getStatusPin(),OUTPUT); // make sure LED is set for output + } + + boolean update(){ + + for(int i=0;isetRange(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->LED=new LedPin(ledPin); // configures a PWM LED for output to pin number "ledPin" + + Serial.printf("Configuring LED: Pin=%d %s\n",LED->getPin(),isDimmable?"(Dimmable)":""); // initialization message + + LED->set(power->getVal()*(isDimmable?(level->getVal()):100)); // set the LED to its initial state at startup. + + } // end constructor + + boolean update(){ // update() method + + LOG1("Updating LED on pin="); + LOG1(LED->getPin()); + LOG1(": Current Power="); + LOG1(power->getVal()?"true":"false"); + if(isDimmable){ + LOG1(" Current Brightness="); + LOG1(level->getVal()); + } + + if(power->updated()){ + LOG1(" New Power="); + LOG1(power->getNewVal()?"true":"false"); + } + + if(isDimmable && level->updated()){ + LOG1(" New Brightness="); + LOG1(level->getNewVal()); + } + + LOG1("\n"); + + LED->set(power->getNewVal()*(isDimmable?(level->getNewVal()):100)); // update the physical LED to reflect the new values + + return(true); // return true + + } // update + +}; + +////////////////////////////////// diff --git a/Other Examples/ProgrammableHub/ProgrammableHub.ino b/Other Examples/ProgrammableHub/ProgrammableHub.ino new file mode 100644 index 0000000..41034ea --- /dev/null +++ b/Other Examples/ProgrammableHub/ProgrammableHub.ino @@ -0,0 +1,224 @@ +/********************************************************************************* + * MIT License + * + * Copyright (c) 2022 Gregg E. Berman + * + * https://github.com/HomeSpan/HomeSpan + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + ********************************************************************************/ + +//////////////////////////////////////////////////////////// +// // +// HomeSpan: A HomeKit implementation for the ESP32 // +// ------------------------------------------------ // +// // +// Demonstrates how to implement a Web Server alongside // +// of HomeSpan to create a Programmable Hub serving up to // +// 16 Configurable Lights. // +// // +//////////////////////////////////////////////////////////// + +#include "HomeSpan.h" +#include "DEV_LED.h" +#include "DEV_Identify.h" + +#include // include WebServer library +WebServer webServer(80); // create WebServer on port 80 + +#define NLIGHTS 16 // maximum number of Lightbulb Accessories + +uint8_t pinList[]={0,4,5,12,14,15,16,17,18,19,22,23,25,26,27,32,33}; // list of allowed pins +char lightNames[NLIGHTS][9]; // storage for default light names + +nvs_handle lightNVS; // handle for NVS storage + +struct { // structure to store pin numbers and dimmable flag + uint8_t pin=0; + uint8_t dimmable=0; +} lightData[NLIGHTS]; + +//////////////////////////////////////////////////////////// + +void setup() { + + Serial.begin(115200); + + homeSpan.setLogLevel(1); + + homeSpan.setHostNameSuffix(""); // use null string for suffix (rather than the HomeSpan device ID) + homeSpan.setPortNum(1201); // change port number for HomeSpan so we can use port 80 for the Web Server + homeSpan.enableOTA(); // enable OTA updates + homeSpan.setMaxConnections(5); // reduce max connection to 5 (default is 8) since WebServer and a connecting client will need 2, and OTA needs 1 + homeSpan.setWifiCallback(setupWeb); // need to start Web Server after WiFi is established + + homeSpan.begin(Category::Bridges,"HomeSpan Light Hub","homespanhub"); + + for(int i=0;i0){ + new SpanAccessory(i+2); + new DEV_Identify(lightNames[i],"HomeSpan",lightNames[i],lightData[i].dimmable?"Dimmable":"Not Dimmable","1.0",0); + new DEV_GenericLED(lightData[i].pin,lightData[i].dimmable); + } + } + +} // end of setup() + +////////////////////////////////////// + +void loop(){ + + homeSpan.poll(); + webServer.handleClient(); // need to process webServer once each loop + +} // end of loop() + +////////////////////////////////////// + +void setupWeb(){ + Serial.print("Starting Light Server Hub...\n\n"); + webServer.begin(); + + // Create web routines inline + + webServer.on("/addForm", []() { + String content="

Add New Light Accessory

"; + + content+="
"; + content+=""; + content+="

"; + content+=""; + + content+="
"; + content+=""; + + content+="
"; + content+=""; + + webServer.send(200, "text/html", content); + + }); + + webServer.on("/", []() { + + String content = "
HomeSpan Light Server Hub Configuration

"; + content += "Select pins and check box if dimmable:

"; + + for(int i=0;i"; + + content += String(lightNames[i]) + ": "; + + content += " "; + content += "
"; + } + + content += "

"; + + webServer.send(200, "text/html", content); + + }); + + webServer.on("/configure", []() { + + for(int i=0;i"; + else + lightData[i].dimmable=0; + + content += "
"; + content += ""; + + nvs_set_blob(lightNVS,"LIGHTDATA",&lightData,sizeof(lightData)); // update data + nvs_commit(lightNVS); // commit to NVS + + webServer.send(200, "text/html", content); + + }); + + webServer.on("/reboot", []() { + + String content = "Rebooting! Will return to configuration page in 10 seconds.

"; + content += ""; + webServer.send(200, "text/html", content); + + for(int j=0;j\n"; response+="\n"; response+="\n"; From 989b315fc0540e4877a5f44232d2d63b82b7ade6 Mon Sep 17 00:00:00 2001 From: Gregg Date: Mon, 11 Jul 2022 21:23:20 -0500 Subject: [PATCH 61/98] Updated WiFi disconnect/re-connect logic Only initialize MDNS, WebLog, and OTA when first connecting to WiFi. Do not re-initialize upon re-connections after a disconnect. Also, add number of disconnects, and reset_reason() to WebLog --- .../ProgrammableHub/ProgrammableHub.ino | 50 ++- .../ProgrammableHub2/ProgrammableHub2.ino | 305 ++++++++++++++++++ src/HAP.cpp | 2 + src/HomeSpan.cpp | 18 +- src/HomeSpan.h | 4 +- 5 files changed, 367 insertions(+), 12 deletions(-) create mode 100644 Other Examples/ProgrammableHub2/ProgrammableHub2.ino diff --git a/Other Examples/ProgrammableHub/ProgrammableHub.ino b/Other Examples/ProgrammableHub/ProgrammableHub.ino index 41034ea..82e07e5 100644 --- a/Other Examples/ProgrammableHub/ProgrammableHub.ino +++ b/Other Examples/ProgrammableHub/ProgrammableHub.ino @@ -69,6 +69,8 @@ void setup() { homeSpan.setMaxConnections(5); // reduce max connection to 5 (default is 8) since WebServer and a connecting client will need 2, and OTA needs 1 homeSpan.setWifiCallback(setupWeb); // need to start Web Server after WiFi is established + homeSpan.enableWebLog(50,"pool.ntp.org","CST6CDT"); + homeSpan.begin(Category::Bridges,"HomeSpan Light Hub","homespanhub"); for(int i=0;iCancel"; - content+="
"; - content+=""; content+=""; content+=""; @@ -132,6 +145,35 @@ void setupWeb(){ webServer.send(200, "text/html", content); }); + + webServer.on("/addLight", []() { + + char lightName[32]; + uint8_t flags=0; + + String sColorControl("colorControl"); + String sIsDimmable("isDimmable"); + String sLightName("lightName"); + + for(int i=0;iAdd Another"; + content+=""; + content+=""; + + webServer.send(200, "text/html", content); + + }); webServer.on("/", []() { diff --git a/Other Examples/ProgrammableHub2/ProgrammableHub2.ino b/Other Examples/ProgrammableHub2/ProgrammableHub2.ino new file mode 100644 index 0000000..65436dd --- /dev/null +++ b/Other Examples/ProgrammableHub2/ProgrammableHub2.ino @@ -0,0 +1,305 @@ +/********************************************************************************* + * MIT License + * + * Copyright (c) 2022 Gregg E. Berman + * + * https://github.com/HomeSpan/HomeSpan + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + ********************************************************************************/ + +////////////////////////////////////////////////////////////////// +// // +// HomeSpan: A HomeKit implementation for the ESP32 // +// ------------------------------------------------ // +// // +// Example 20: Demonstrates various advance HomeSpan functions // +// by implementing a Bridge in which one or more // +// Lightbulb Accessories can be added and deleted // +// *dynamically* without needing to restart the // +// device // +// // +////////////////////////////////////////////////////////////////// + +#include "HomeSpan.h" + + // In Example 20 we will implement a bridge device supporting up to 10 Lightbulb Accessories. However, rather than pre-specifying the number of Lights, we + // will allow Light Accessories to be added and deleted dynamically by the user via the CLI. Changes are reflected in the Home App without the need to restart + // the device! Note this example uses a variety of advanced HomeSpan functions, as well as some detailed features of both the ESP32-IDF and C++ that have not been used + // in any of the previous examples. + + // We will use a C++ array with 10 elements containing integers representing the Light "ID" of each Lightbulb Accessory implemented. An ID of zero means there is no + // Light defined in that element. + +#include // include the C++ standard library array container + +#define MAX_LIGHTS 10 + +enum colorType_t : uint8_t{ + NO_COLOR, + TEMPERATURE_ONLY, + FULL_RGB +}; + +struct lightData_t { + char name[33]=""; + union { + struct { + boolean isConfigured:1; + boolean isdimmable:1; + colorType_t colorType:2; + }; + uint8_t val=0; + }; +}; + +std::array lightData; + +std::array lights; // declare "lights" to be an array of 10 integers + +using std::fill; // place the std library function fill, remove, and find, into the global namespace so we can use them below without prefacing with "std::" +using std::remove; +using std::find; + + // We will use non-volatile storage (NVS) to store the lights array so that the device can restore the current configuration upon rebooting + +nvs_handle savedData; // declare savdData as a handle to be used with the NVS (see the ESP32-IDF for details on how to use NVS storage) + +////////////////////////////////////// + +void setup() { + + Serial.begin(115200); + + fill(lights.begin(),lights.end(),0); // initialize lights array with zeros in each of the 10 elements (no Light Accessories defined) + + size_t len; + nvs_open("SAVED_DATA",NVS_READWRITE,&savedData); // open a new namespace called SAVED_DATA in the NVS + if(!nvs_get_blob(savedData,"LIGHTS",NULL,&len)) // if LIGHTS data found + nvs_get_blob(savedData,"LIGHTS",&lights,&len); // retrieve data + + homeSpan.setLogLevel(1); + + homeSpan.begin(Category::Lighting,"HomeSpan Lights"); + + // We begin by creating the Bridge Accessory + + new SpanAccessory(1); // here we specified the AID=1 for clarity (it would default to 1 anyway if left blank) + new Service::AccessoryInformation(); + new Characteristic::Identify(); + new Characteristic::Model("HomeSpan Dynamic Bridge"); // defining the Model is optional + + // Now we create Light Accessories based on what is recorded in the lights array + // We'll use C++ iterators to loop over all elements until we reach the end of the array, or find an element with a value of zero + + for(auto it=lights.begin(); it!=lights.end() && *it!=0; it++) // loop over all elements (stopping when we get to the end, or hit an element with a value of zero) + addLight(*it); // call addLight (defined further below) with an argument equal to the integer stored in that element + + for(auto it=lightData.begin(); it!=lightData.end(); it++){ // loop over all elements in lightData array + if((*it).isConfigured); + } + + + // Next we create user-defined CLI commands so we can add Light Accessories of different types from the CLI. + // We'll use inline lambda functions each calling addLight() with different parameters. + + new SpanUserCommand('a'," - add non-dimmable light accessory using name=",[](const char *c){addLight(c+1,false,NO_COLOR);}); + new SpanUserCommand('A'," - add dimmable light accessory using name=",[](const char *c){addLight(c+1,true,NO_COLOR);}); + new SpanUserCommand('t'," - add non-dimmable light accessory with color-temperature control using name=",[](const char *c){addLight(c+1,false,TEMPERATURE_ONLY);}); + new SpanUserCommand('T'," - add dimmable light accessory with color-temperature control using name=",[](const char *c){addLight(c+1,true,TEMPERATURE_ONLY);}); + new SpanUserCommand('r'," - add non-dimmable light accessory with full RGB color control using name=",[](const char *c){addLight(c+1,false,FULL_RGB);}); + new SpanUserCommand('R'," - add dimmable light accessory with full RGB color control using name=",[](const char *c){addLight(c+1,true,FULL_RGB);}); + + new SpanUserCommand('d'," - delete a light accessory with id=",deleteAccessory); + new SpanUserCommand('D'," - delete ALL light accessories",deleteAllAccessories); + new SpanUserCommand('u',"- update accessories database",updateAccessories); + + // Finally we call autoPoll to start polling the background. Note this is purely optional and only used here to illustrate how to + // use autoPoll - you could instead have called the usual homeSpan.poll() function by including it inside the Arduino loop() function + + homeSpan.autoPoll(); + +} // end of setup() + +// Usually the Arduino loop() function would be defined somewhere here. But since we used autoPoll in the setup() function, +// we don't have to define the loop() function at all in this sketch! Why don't we get an error? Because HomeSpan includes +// a default loop() function, which prevents the compiler from complaining about loop() being undefined. + +/////////////////////////// + +// This function creates a new Light Accessory. It is called initially in setup() above to create Light Accessories based +// on what was stored in the lights array. It is also called in response to adding lights via the CLI, which dynamically +// adds a new Light Accessory while the device is running. + +void addLight(const char *name, boolean isDimmable, colorType_t colorType){ + + while(*name==' ') // strip any leading spaces + name++; + + Serial.printf("Adding '%s' Light Accessory: %s with %s control\n",name,isDimmable?"dimmable":"non-dimmable",colorType==NO_COLOR?"no color":(colorType==TEMPERATURE_ONLY?"color-temperature":"full RGB color")); +// +// new SpanAccessory(n+1); // IMPORTANT: add 1, since first Accessory with AID=1 is already used by the Bridge Accessory +// new Service::AccessoryInformation(); +// new Characteristic::Identify(); +// new Characteristic::Name(name); +// new Characteristic::SerialNumber(sNum); +// new Service::LightBulb(); +// new Characteristic::On(0,true); +} + +/////////////////////////// + +// This function creates a new Light Accessory with n as the "ID". +// It is called initially in setup() above to create Light Accessories based +// on what was stored in the lights array. It is also called in response to +// typing 'a' into the CLI (see below), which dynamically adds a new Light Accessory +// while the device is running. + +void addLight(int n){ + + char name[32]; + sprintf(name,"Light-%d",n); // create the name of the device using the specified "ID" + char sNum[32]; + sprintf(sNum,"%0.10d",n); // create serial number from the ID - this is helpful in case we rename the Light to something else using the Home App + + Serial.printf("Adding Accessory: %s\n",name); + + new SpanAccessory(n+1); // IMPORTANT: add 1, since first Accessory with AID=1 is already used by the Bridge Accessory + new Service::AccessoryInformation(); + new Characteristic::Identify(); + new Characteristic::Name(name); + new Characteristic::SerialNumber(sNum); + new Service::LightBulb(); + new Characteristic::On(0,true); +} + +/////////////////////////// + +// This function is called in response to typing '@a ' into the CLI. +// It adds a new Light Accessory with ID=num, by calling addLight(num) above. + +void addAccessory(const char *buf){ + + int n=atoi(buf+1); // read the value of specified + + if(n<1){ // ensure is greater than 0 + Serial.printf("Invalid Accessory number!\n"); + return; + } + + if(find(lights.begin(),lights.end(),n)!=lights.end()){ // search for this ID in the existing lights array - if found, report an error and return + Serial.printf("Accessory Light-%d already implemented!\n",n); + return; + } + + auto it=find(lights.begin(),lights.end(),0); // find the next "free" element in the light array (the first element with a value of zero) + + if(it==lights.end()){ // if there were no elements with a zero, the array is full and no new Lights can be added + Serial.printf("Can't add any more lights - max is %d!\n",lights.size()); + return; + } + + *it=n; // save light number + nvs_set_blob(savedData,"LIGHTS",&lights,sizeof(lights)); // update data in the NVS + nvs_commit(savedData); + addLight(n); // add light accessory by calling the function above! +} + +/////////////////////////// + +// This function deletes an existing Light Accessory and is called +// in response to typing '@d ' into the CLI. + +void deleteAccessory(const char *buf){ + + int n=atoi(buf+1); // same as above, we read the specified and check that it is valid (i.e. greater than 0) + + if(n<1){ + Serial.printf("Invalid Accessory number!\n"); + return; + } + + // Below we use the homeSpan method deleteAccessory(aid) to completely delete the Accessory with AID=n+1. + // We add 1 because the AID of the first Light Accessory is 2, since the Bridge Accessory has an AID of 1. + // The deleteAccessory() method returns true if an Accessory with matching AID is found, otherwise it returns false. + // When deleting an Accessory, HomeSpan will print a delete message for every Service, Characteristic, loop() method, + // button() method, and SpanButton, associated with that Accessory. These are Level-1 Log messages, so you'll need + // to have the Log Level in the sketch set to 1 or 2 to receive the output. + + if(homeSpan.deleteAccessory(n+1)){ // if deleteAccessory() is true, a match has been found + Serial.printf("Deleting Accessory: Light-%d\n",n); + + fill(remove(lights.begin(),lights.end(),n),lights.end(),0); // remove entry from lights array and fill any undefined elements with zero + nvs_set_blob(savedData,"LIGHTS",&lights,sizeof(lights)); // update data in the NVS + nvs_commit(savedData); + + } else { + Serial.printf("No such Accessory: Light-%d\n",n); + } +} + +/////////////////////////// + +void deleteAllAccessories(const char *buf){ + +// This function is called in response to typing '@D' into the CLI. +// It deletes all Light Accessories + + if(lights[0]==0){ // first check that there is at least one Light Accessory by checking for a non-zero ID in lights[0] + Serial.printf("There are no Light Accessories to delete!\n"); + return; + } + + for(auto it=lights.begin(); it!=lights.end() && *it!=0; it++) // use an iterator to loop over all non-zero elements in the lights array... + homeSpan.deleteAccessory(*it+1); // ... and delete the matching Light Accessory (don't forgot to add 1 to the Light ID to form the AID) + + fill(lights.begin(),lights.end(),0); // zero out all the elements in the lights array, since all Light Accessories have been deleted + nvs_set_blob(savedData,"LIGHTS",&lights,sizeof(lights)); // update data in the NVS + nvs_commit(savedData); + + Serial.printf("All Light Accessories deleted!\n"); +} + +/////////////////////////// + +// Lastly we have the all-important updateAccessories function. +// This is called in response to typing '@u' into the CLI. +// Though the above functions can be used to add and delete Light Accessories +// dyammically, Controllers such as the Home App that are already connected to +// the device don't yet know additional Light Accessories have been added to (or +// deleted from) the overall Accessories datase. To let them know, HomeSpan needs +// to increment the HAP Configuration Number and re-broadcast it via MDNS so all +// connected Controllers are aware that they need to request a refresh from the device. + +// When you type '@u' into the CLI, you should see a lot of activity between the device +// and any connected Controllers as they request a refresh. Be patient - it can take up to a +// minute for changes to be properly reflected in the Home App on your iPhone or Mac. + +void updateAccessories(const char *buf){ + + // note the updateDatabase() method returns true if the database has indeed changed (e.g. one or more new Light Accessories were added), or false if nothing has changed + + if(homeSpan.updateDatabase()) + Serial.printf("Accessories Database updated. New configuration number broadcasted...\n"); + else + Serial.printf("Nothing to update - no changes were made!\n"); +} + +/////////////////////////// diff --git a/src/HAP.cpp b/src/HAP.cpp index 6c8d97f..28430de 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -1260,6 +1260,8 @@ int HAPClient::getStatusURL(){ response+="Up Time:" + String(uptime) + "\n"; response+="Current Time:" + String(clocktime) + "\n"; response+="Boot Time:" + String(homeSpan.webLog.bootTime) + "\n"; + response+="Reset Reason Code:" + String(esp_reset_reason()) + "\n"; + response+="WiFi Disconnects:" + String(homeSpan.connected/2) + "\n"; response+="ESP32 Board:" + String(ARDUINO_BOARD) + "\n"; response+="Arduino-ESP Version:" + String(ARDUINO_ESP_VERSION) + "\n"; response+="ESP-IDF Version:" + String(ESP_IDF_VERSION_MAJOR) + "." + String(ESP_IDF_VERSION_MINOR) + "." + String(ESP_IDF_VERSION_PATCH) + "\n"; diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index d8b6aea..acb1ac8 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -394,12 +394,12 @@ void Span::commandMode(){ void Span::checkConnect(){ - if(connected){ + if(connected%2){ if(WiFi.status()==WL_CONNECTED) return; - Serial.print("\n\n*** WiFi Connection Lost!\n"); // losing and re-establishing connection has not been tested - connected=false; + WEBLOG("*** WiFi Connection Lost!"); // losing and re-establishing connection has not been tested + connected++; waitTime=60000; alarmConnect=0; homeSpan.statusLED.start(LED_WIFI_CONNECTING); @@ -420,6 +420,7 @@ void Span::checkConnect(){ Serial.print(". You may type 'W ' to re-configure WiFi, or 'X ' to erase WiFi credentials. Will try connecting again in 60 seconds.\n\n"); waitTime=60000; } else { + WEBLOG("Trying to connect to %s. Waiting %d sec",network.wifiData.ssid,waitTime/1000); Serial.print("Trying to connect to "); Serial.print(network.wifiData.ssid); Serial.print(". Waiting "); @@ -433,14 +434,18 @@ void Span::checkConnect(){ return; } - connected=true; + connected++; + WEBLOG("WiFi Connected!"); Serial.print("Successfully connected to "); Serial.print(network.wifiData.ssid); Serial.print("! IP Address: "); Serial.print(WiFi.localIP()); Serial.print("\n"); + if(connected>1) // Do not initialize everything below if this is only a reconnect + return; + char id[18]; // create string version of Accessory ID for MDNS broadcast memcpy(id,HAPClient::accessory.ID,17); // copy ID bytes id[17]='\0'; // add terminating null @@ -533,7 +538,6 @@ void Span::checkConnect(){ ArduinoOTA.onStart(spanOTA.start).onEnd(spanOTA.end).onProgress(spanOTA.progress).onError(spanOTA.error); ArduinoOTA.begin(); - reserveSocketConnections(1); Serial.print("Starting OTA Server: "); Serial.print(displayName); Serial.print(" at "); @@ -2024,6 +2028,8 @@ void SpanWebLog::init(uint16_t maxEntries, const char *serv, const char *tz, con timeZone=tz; statusURL="GET /" + String(url) + " "; log = (log_t *)calloc(maxEntries,sizeof(log_t)); + if(timeServer) + homeSpan.reserveSocketConnections(1); } /////////////////////////////// @@ -2038,7 +2044,6 @@ void SpanWebLog::initTime(){ if(getLocalTime(&timeinfo,waitTime)){ strftime(bootTime,sizeof(bootTime),"%c",&timeinfo); Serial.printf("%s\n\n",bootTime); - homeSpan.reserveSocketConnections(1); timeInit=true; } else { Serial.printf("Can't access Time Server - time-keeping not initialized!\n\n"); @@ -2077,6 +2082,7 @@ void SpanOTA::init(boolean _auth, boolean _safeLoad){ enabled=true; safeLoad=_safeLoad; auth=_auth; + homeSpan.reserveSocketConnections(1); } /////////////////////////////// diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 0971f4a..e882978 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -110,7 +110,7 @@ struct SpanBuf{ // temporary storage buffer for us struct SpanWebLog{ // optional web status/log data boolean isEnabled=false; // flag to inidicate WebLog has been enabled - uint16_t maxEntries; // max number of log entries; + uint16_t maxEntries=0; // max number of log entries; int nEntries=0; // total cumulative number of log entries const char *timeServer; // optional time server to use for acquiring clock time const char *timeZone; // optional time-zone specification @@ -184,7 +184,7 @@ class Span{ String lastClientIP="0.0.0.0"; // IP address of last client accessing device through encrypted channel boolean newCode; // flag indicating new application code has been loaded (based on keeping track of app SHA256) - boolean connected=false; // WiFi connection status + int connected=0; // WiFi connection status (increments upon each connect and disconnect) unsigned long waitTime=60000; // time to wait (in milliseconds) between WiFi connection attempts unsigned long alarmConnect=0; // time after which WiFi connection attempt should be tried again From bb1874da96ca5f7c249c8e6add2e75177277ca09 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 16 Jul 2022 11:11:41 -0500 Subject: [PATCH 62/98] Update HAP.cpp --- src/HAP.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/HAP.cpp b/src/HAP.cpp index 28430de..aa161a9 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -1262,6 +1262,8 @@ int HAPClient::getStatusURL(){ response+="Boot Time:" + String(homeSpan.webLog.bootTime) + "\n"; response+="Reset Reason Code:" + String(esp_reset_reason()) + "\n"; response+="WiFi Disconnects:" + String(homeSpan.connected/2) + "\n"; + response+="WiFi Signal:" + String(WiFi.RSSI()) + " dDm\n"; + response+="WiFi Gateway:" + WiFi.gatewayIP().toString() + "\n"; response+="ESP32 Board:" + String(ARDUINO_BOARD) + "\n"; response+="Arduino-ESP Version:" + String(ARDUINO_ESP_VERSION) + "\n"; response+="ESP-IDF Version:" + String(ESP_IDF_VERSION_MAJOR) + "." + String(ESP_IDF_VERSION_MINOR) + "." + String(ESP_IDF_VERSION_PATCH) + "\n"; From c891548ffa1b7636031dda07b12193e9b73454ba Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 23 Jul 2022 08:19:25 -0500 Subject: [PATCH 63/98] Updated webLog logic to print system messages as well as user messaages User messages only printer is logLevel>0. Also, user messages are prefixed in CLI with the word "WEBLOG: " --- src/HAP.cpp | 2 +- src/HomeSpan.cpp | 68 +++++++++++++++++++++++------------------------- src/HomeSpan.h | 6 ++--- src/Settings.h | 2 +- 4 files changed, 38 insertions(+), 40 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index aa161a9..c4b2844 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -1262,7 +1262,7 @@ int HAPClient::getStatusURL(){ response+="Boot Time:" + String(homeSpan.webLog.bootTime) + "\n"; response+="Reset Reason Code:" + String(esp_reset_reason()) + "\n"; response+="WiFi Disconnects:" + String(homeSpan.connected/2) + "\n"; - response+="WiFi Signal:" + String(WiFi.RSSI()) + " dDm\n"; + response+="WiFi Signal:" + String(WiFi.RSSI()) + " dBm\n"; response+="WiFi Gateway:" + WiFi.gatewayIP().toString() + "\n"; response+="ESP32 Board:" + String(ARDUINO_BOARD) + "\n"; response+="Arduino-ESP Version:" + String(ARDUINO_ESP_VERSION) + "\n"; diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index acb1ac8..312fe0d 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -398,7 +398,7 @@ void Span::checkConnect(){ if(WiFi.status()==WL_CONNECTED) return; - WEBLOG("*** WiFi Connection Lost!"); // losing and re-establishing connection has not been tested + addWebLog(true,"*** WiFi Connection Lost!"); // losing and re-establishing connection has not been tested connected++; waitTime=60000; alarmConnect=0; @@ -420,12 +420,7 @@ void Span::checkConnect(){ Serial.print(". You may type 'W ' to re-configure WiFi, or 'X ' to erase WiFi credentials. Will try connecting again in 60 seconds.\n\n"); waitTime=60000; } else { - WEBLOG("Trying to connect to %s. Waiting %d sec",network.wifiData.ssid,waitTime/1000); - Serial.print("Trying to connect to "); - Serial.print(network.wifiData.ssid); - Serial.print(". Waiting "); - Serial.print(waitTime/1000); - Serial.print(" second(s) for response...\n"); + addWebLog(true,"Trying to connect to %s. Waiting %d sec...",network.wifiData.ssid,waitTime/1000); WiFi.begin(network.wifiData.ssid,network.wifiData.pwd); } @@ -434,14 +429,14 @@ void Span::checkConnect(){ return; } + if(!HAPClient::nAdminControllers()) + statusLED.start(LED_PAIRING_NEEDED); + else + statusLED.on(); + connected++; - WEBLOG("WiFi Connected!"); - Serial.print("Successfully connected to "); - Serial.print(network.wifiData.ssid); - Serial.print("! IP Address: "); - Serial.print(WiFi.localIP()); - Serial.print("\n"); + addWebLog(true,"WiFi Connected! IP Address = %s\n",WiFi.localIP().toString().c_str()); if(connected>1) // Do not initialize everything below if this is only a reconnect return; @@ -565,12 +560,8 @@ void Span::checkConnect(){ Serial.print("\n"); - if(!HAPClient::nAdminControllers()){ + if(!HAPClient::nAdminControllers()) Serial.print("DEVICE NOT YET PAIRED -- PLEASE PAIR WITH HOMEKIT APP\n\n"); - statusLED.start(LED_PAIRING_NEEDED); - } else { - statusLED.on(); - } if(wifiCallback) wifiCallback(); @@ -2052,26 +2043,33 @@ void SpanWebLog::initTime(){ /////////////////////////////// -void SpanWebLog::vLog(const char *fmt, va_list ap){ - if(maxEntries==0) - return; +void SpanWebLog::vLog(boolean sysMsg, const char *fmt, va_list ap){ - int index=nEntries%maxEntries; + char *buf; + vasprintf(&buf,fmt,ap); - log[index].upTime=esp_timer_get_time(); - if(timeInit) - getLocalTime(&log[index].clockTime,10); - else - log[index].clockTime.tm_year=0; + if(sysMsg) + Serial.printf("%s\n",buf); + else if(homeSpan.logLevel>0) + Serial.printf("WEBLOG: %s\n",buf); + + if(maxEntries>0){ + int index=nEntries%maxEntries; + + log[index].upTime=esp_timer_get_time(); + if(timeInit) + getLocalTime(&log[index].clockTime,10); + else + log[index].clockTime.tm_year=0; + + log[index].message=(char *)realloc(log[index].message, strlen(buf) + 1); + strcpy(log[index].message, buf); + + log[index].clientIP=homeSpan.lastClientIP; + nEntries++; + } - free(log[index].message); - vasprintf(&log[index].message,fmt,ap); - - log[index].clientIP=homeSpan.lastClientIP; - nEntries++; - - if(homeSpan.logLevel>0) - Serial.printf("WEBLOG: %s\n",log[index].message); + free(buf); } /////////////////////////////// diff --git a/src/HomeSpan.h b/src/HomeSpan.h index e882978..556f6a0 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -128,7 +128,7 @@ struct SpanWebLog{ // optional web status/log data void init(uint16_t maxEntries, const char *serv, const char *tz, const char *url); void initTime(); - void vLog(const char *fmr, va_list ap); + void vLog(boolean sysMsg, const char *fmr, va_list ap); }; /////////////////////////////// @@ -286,10 +286,10 @@ class Span{ webLog.init(maxEntries, serv, tz, url); } - void addWebLog(const char *fmt, ...){ // add Web Log entry + void addWebLog(boolean sysMsg, const char *fmt, ...){ // add Web Log entry va_list ap; va_start(ap,fmt); - webLog.vLog(fmt,ap); + webLog.vLog(sysMsg,fmt,ap); va_end(ap); } diff --git a/src/Settings.h b/src/Settings.h index 8e5bf25..ba7273d 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -111,7 +111,7 @@ #define LOG1(format,...) if(homeSpan.getLogLevel()>0)Serial.print ##__VA_OPT__(f)(format __VA_OPT__(,) __VA_ARGS__) #define LOG2(format,...) if(homeSpan.getLogLevel()>1)Serial.print ##__VA_OPT__(f)(format __VA_OPT__(,) __VA_ARGS__) -#define WEBLOG(format,...) homeSpan.addWebLog(format __VA_OPT__(,) __VA_ARGS__) +#define WEBLOG(format,...) homeSpan.addWebLog(false, format __VA_OPT__(,) __VA_ARGS__) ////////////////////////////////////////////////////// // Types of Accessory Categories // From d903fd7e974c1102e5c1696234014ba18dd3318c Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 23 Jul 2022 15:16:43 -0500 Subject: [PATCH 64/98] Updated RFControl for compatibility with Arduino-ESP32 v 2.0.4 Needed to set idle_level to LOW even though idle_output was already disabled. This is likely because 2.0.4 introduced an entirely new API for RMT, though version 2.0.3 IDF functions and structures seem to be accepted as well. --- src/extras/RFControl.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/extras/RFControl.cpp b/src/extras/RFControl.cpp index 66fea73..31e48c3 100644 --- a/src/extras/RFControl.cpp +++ b/src/extras/RFControl.cpp @@ -31,7 +31,7 @@ RFControl::RFControl(uint8_t pin, boolean refClock, boolean installDriver){ -#ifdef CONFIG_IDF_TARGET_ESP32C3 +#if defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3) if(nChannels==RMT_CHANNEL_MAX/2){ #else if(nChannels==RMT_CHANNEL_MAX){ @@ -52,6 +52,7 @@ RFControl::RFControl(uint8_t pin, boolean refClock, boolean installDriver){ config->mem_block_num=1; config->gpio_num=(gpio_num_t)pin; config->tx_config.idle_output_en=false; + config->tx_config.idle_level=RMT_IDLE_LEVEL_LOW; config->tx_config.loop_en=false; rmt_config(config); From be3af3b14e74f13732366f73810662be9c8b8e64 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 23 Jul 2022 21:05:08 -0500 Subject: [PATCH 65/98] Updated version number to 1.5.2 --- src/Settings.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Settings.h b/src/Settings.h index ba7273d..c838dac 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -36,7 +36,7 @@ #define HS_MAJOR 1 #define HS_MINOR 5 -#define HS_PATCH 1 +#define HS_PATCH 2 #define STRINGIFY(x) _STR(x) #define _STR(x) #x From 689ea86991ca74966ad02d0de0cd64471d5b2b52 Mon Sep 17 00:00:00 2001 From: Gregg Date: Thu, 28 Jul 2022 17:13:04 -0500 Subject: [PATCH 66/98] Updating ProgrammableHub2 Example --- .../ProgrammableHub2/ProgrammableHub2.ino | 267 +++++++----------- src/HomeSpan.cpp | 4 +- 2 files changed, 102 insertions(+), 169 deletions(-) diff --git a/Other Examples/ProgrammableHub2/ProgrammableHub2.ino b/Other Examples/ProgrammableHub2/ProgrammableHub2.ino index 65436dd..3489364 100644 --- a/Other Examples/ProgrammableHub2/ProgrammableHub2.ino +++ b/Other Examples/ProgrammableHub2/ProgrammableHub2.ino @@ -30,27 +30,13 @@ // HomeSpan: A HomeKit implementation for the ESP32 // // ------------------------------------------------ // // // -// Example 20: Demonstrates various advance HomeSpan functions // -// by implementing a Bridge in which one or more // -// Lightbulb Accessories can be added and deleted // -// *dynamically* without needing to restart the // -// device // // // ////////////////////////////////////////////////////////////////// #include "HomeSpan.h" - - // In Example 20 we will implement a bridge device supporting up to 10 Lightbulb Accessories. However, rather than pre-specifying the number of Lights, we - // will allow Light Accessories to be added and deleted dynamically by the user via the CLI. Changes are reflected in the Home App without the need to restart - // the device! Note this example uses a variety of advanced HomeSpan functions, as well as some detailed features of both the ESP32-IDF and C++ that have not been used - // in any of the previous examples. - - // We will use a C++ array with 10 elements containing integers representing the Light "ID" of each Lightbulb Accessory implemented. An ID of zero means there is no - // Light defined in that element. - -#include // include the C++ standard library array container - -#define MAX_LIGHTS 10 + +#define MAX_LIGHTS 10 +#define MAX_NAME_LENGTH 10 enum colorType_t : uint8_t{ NO_COLOR, @@ -59,27 +45,18 @@ enum colorType_t : uint8_t{ }; struct lightData_t { - char name[33]=""; + char name[MAX_NAME_LENGTH+1]=""; union { struct { boolean isConfigured:1; - boolean isdimmable:1; + boolean isDimmable:1; colorType_t colorType:2; }; uint8_t val=0; }; -}; +} lightData[MAX_LIGHTS]; -std::array lightData; - -std::array lights; // declare "lights" to be an array of 10 integers - -using std::fill; // place the std library function fill, remove, and find, into the global namespace so we can use them below without prefacing with "std::" -using std::remove; -using std::find; - - // We will use non-volatile storage (NVS) to store the lights array so that the device can restore the current configuration upon rebooting - + nvs_handle savedData; // declare savdData as a handle to be used with the NVS (see the ESP32-IDF for details on how to use NVS storage) ////////////////////////////////////// @@ -88,170 +65,130 @@ void setup() { Serial.begin(115200); - fill(lights.begin(),lights.end(),0); // initialize lights array with zeros in each of the 10 elements (no Light Accessories defined) - size_t len; nvs_open("SAVED_DATA",NVS_READWRITE,&savedData); // open a new namespace called SAVED_DATA in the NVS - if(!nvs_get_blob(savedData,"LIGHTS",NULL,&len)) // if LIGHTS data found - nvs_get_blob(savedData,"LIGHTS",&lights,&len); // retrieve data + + if(!nvs_get_blob(savedData,"LIGHTDATA",NULL,&len)) // if LIGHTDATA data found + nvs_get_blob(savedData,"LIGHTDATA",&lightData,&len); // retrieve data homeSpan.setLogLevel(1); - homeSpan.begin(Category::Lighting,"HomeSpan Lights"); - - // We begin by creating the Bridge Accessory + homeSpan.begin(Category::Lighting,"HomeSpan Light Hub"); new SpanAccessory(1); // here we specified the AID=1 for clarity (it would default to 1 anyway if left blank) new Service::AccessoryInformation(); new Characteristic::Identify(); - new Characteristic::Model("HomeSpan Dynamic Bridge"); // defining the Model is optional + new Characteristic::Model("HomeSpan Programmable Hub"); - // Now we create Light Accessories based on what is recorded in the lights array - // We'll use C++ iterators to loop over all elements until we reach the end of the array, or find an element with a value of zero - - for(auto it=lights.begin(); it!=lights.end() && *it!=0; it++) // loop over all elements (stopping when we get to the end, or hit an element with a value of zero) - addLight(*it); // call addLight (defined further below) with an argument equal to the integer stored in that element - - for(auto it=lightData.begin(); it!=lightData.end(); it++){ // loop over all elements in lightData array - if((*it).isConfigured); + for(int i=0;i - add non-dimmable light accessory using name=",[](const char *c){addLight(-1,c+1,false,NO_COLOR);}); + new SpanUserCommand('A'," - add dimmable light accessory using name=",[](const char *c){addLight(-1,c+1,true,NO_COLOR);}); + new SpanUserCommand('t'," - add non-dimmable light accessory with color-temperature control using name=",[](const char *c){addLight(-1,c+1,false,TEMPERATURE_ONLY);}); + new SpanUserCommand('T'," - add dimmable light accessory with color-temperature control using name=",[](const char *c){addLight(-1,c+1,true,TEMPERATURE_ONLY);}); + new SpanUserCommand('r'," - add non-dimmable light accessory with full RGB color control using name=",[](const char *c){addLight(-1,c+1,false,FULL_RGB);}); + new SpanUserCommand('R'," - add dimmable light accessory with full RGB color control using name=",[](const char *c){addLight(-1,c+1,true,FULL_RGB);}); - // Next we create user-defined CLI commands so we can add Light Accessories of different types from the CLI. - // We'll use inline lambda functions each calling addLight() with different parameters. - - new SpanUserCommand('a'," - add non-dimmable light accessory using name=",[](const char *c){addLight(c+1,false,NO_COLOR);}); - new SpanUserCommand('A'," - add dimmable light accessory using name=",[](const char *c){addLight(c+1,true,NO_COLOR);}); - new SpanUserCommand('t'," - add non-dimmable light accessory with color-temperature control using name=",[](const char *c){addLight(c+1,false,TEMPERATURE_ONLY);}); - new SpanUserCommand('T'," - add dimmable light accessory with color-temperature control using name=",[](const char *c){addLight(c+1,true,TEMPERATURE_ONLY);}); - new SpanUserCommand('r'," - add non-dimmable light accessory with full RGB color control using name=",[](const char *c){addLight(c+1,false,FULL_RGB);}); - new SpanUserCommand('R'," - add dimmable light accessory with full RGB color control using name=",[](const char *c){addLight(c+1,true,FULL_RGB);}); - - new SpanUserCommand('d'," - delete a light accessory with id=",deleteAccessory); + new SpanUserCommand('l'," - list all light accessories",listAccessories); + new SpanUserCommand('d'," - delete a light accessory with index=",deleteAccessory); new SpanUserCommand('D'," - delete ALL light accessories",deleteAllAccessories); new SpanUserCommand('u',"- update accessories database",updateAccessories); - // Finally we call autoPoll to start polling the background. Note this is purely optional and only used here to illustrate how to - // use autoPoll - you could instead have called the usual homeSpan.poll() function by including it inside the Arduino loop() function - homeSpan.autoPoll(); } // end of setup() -// Usually the Arduino loop() function would be defined somewhere here. But since we used autoPoll in the setup() function, -// we don't have to define the loop() function at all in this sketch! Why don't we get an error? Because HomeSpan includes -// a default loop() function, which prevents the compiler from complaining about loop() being undefined. - /////////////////////////// -// This function creates a new Light Accessory. It is called initially in setup() above to create Light Accessories based -// on what was stored in the lights array. It is also called in response to adding lights via the CLI, which dynamically -// adds a new Light Accessory while the device is running. +void addLight(int index, const char *name, boolean isDimmable, colorType_t colorType){ -void addLight(const char *name, boolean isDimmable, colorType_t colorType){ + if(index<0){ + for(index=0;indexsizeof(lightData[index].name)) + Serial.printf("Warning - name trimmed to max length of %d characters.\n",MAX_NAME_LENGTH); + + lightData[index].isDimmable=isDimmable; + lightData[index].colorType=colorType; + lightData[index].isConfigured=true; + name=lightData[index].name; -/////////////////////////// + nvs_set_blob(savedData,"LIGHTDATA",&lightData,sizeof(lightData)); // update data in the NVS + nvs_commit(savedData); + } -// This function creates a new Light Accessory with n as the "ID". -// It is called initially in setup() above to create Light Accessories based -// on what was stored in the lights array. It is also called in response to -// typing 'a' into the CLI (see below), which dynamically adds a new Light Accessory -// while the device is running. + Serial.printf("Adding Light Accessory: Name='%s' Dimmable=%s Color=%s\n",name,isDimmable?"YES":"NO",colorType==NO_COLOR?"NONE":(colorType==TEMPERATURE_ONLY?"TEMPERATURE_ONLY":"FULL_RGB")); -void addLight(int n){ - - char name[32]; - sprintf(name,"Light-%d",n); // create the name of the device using the specified "ID" - char sNum[32]; - sprintf(sNum,"%0.10d",n); // create serial number from the ID - this is helpful in case we rename the Light to something else using the Home App - - Serial.printf("Adding Accessory: %s\n",name); - - new SpanAccessory(n+1); // IMPORTANT: add 1, since first Accessory with AID=1 is already used by the Bridge Accessory + new SpanAccessory(index+2); // IMPORTANT: add 2, since first Accessory with AID=1 is already used by the Bridge Accessory new Service::AccessoryInformation(); new Characteristic::Identify(); new Characteristic::Name(name); - new Characteristic::SerialNumber(sNum); new Service::LightBulb(); - new Characteristic::On(0,true); + new Characteristic::On(0,true); + if(isDimmable) + new Characteristic::Brightness(100,true); + if(colorType==TEMPERATURE_ONLY) + new Characteristic::ColorTemperature(200,true); + if(colorType==FULL_RGB){ + new Characteristic::Hue(0,true); + new Characteristic::Saturation(0,true); + } + } /////////////////////////// -// This function is called in response to typing '@a ' into the CLI. -// It adds a new Light Accessory with ID=num, by calling addLight(num) above. +size_t strncpy_trim(char *dest, const char *src, size_t dSize){ -void addAccessory(const char *buf){ - - int n=atoi(buf+1); // read the value of specified + while(*src==' ') // skip over any leading spaces + src++; - if(n<1){ // ensure is greater than 0 - Serial.printf("Invalid Accessory number!\n"); - return; - } + size_t sLen=strlen(src); // string length of src after skipping over leading spaces + while(sLen>0 && src[sLen-1]==' ') // shorten length to remove trailing spaces + sLen--; - if(find(lights.begin(),lights.end(),n)!=lights.end()){ // search for this ID in the existing lights array - if found, report an error and return - Serial.printf("Accessory Light-%d already implemented!\n",n); - return; - } - - auto it=find(lights.begin(),lights.end(),0); // find the next "free" element in the light array (the first element with a value of zero) - - if(it==lights.end()){ // if there were no elements with a zero, the array is full and no new Lights can be added - Serial.printf("Can't add any more lights - max is %d!\n",lights.size()); - return; - } - - *it=n; // save light number - nvs_set_blob(savedData,"LIGHTS",&lights,sizeof(lights)); // update data in the NVS - nvs_commit(savedData); - addLight(n); // add light accessory by calling the function above! + size_t sSize=sLen+1; // add room for null terminator + + if(dest!=NULL) + *stpncpy(dest,src,(dSize' into the CLI. - void deleteAccessory(const char *buf){ - int n=atoi(buf+1); // same as above, we read the specified and check that it is valid (i.e. greater than 0) + int n=atoi(buf+1); - if(n<1){ - Serial.printf("Invalid Accessory number!\n"); + if(n<1 || n>MAX_LIGHTS){ + Serial.printf("Invalid Light Accessory index - must be between 1 and %d.\n",MAX_LIGHTS); return; } - // Below we use the homeSpan method deleteAccessory(aid) to completely delete the Accessory with AID=n+1. - // We add 1 because the AID of the first Light Accessory is 2, since the Bridge Accessory has an AID of 1. - // The deleteAccessory() method returns true if an Accessory with matching AID is found, otherwise it returns false. - // When deleting an Accessory, HomeSpan will print a delete message for every Service, Characteristic, loop() method, - // button() method, and SpanButton, associated with that Accessory. These are Level-1 Log messages, so you'll need - // to have the Log Level in the sketch set to 1 or 2 to receive the output. - if(homeSpan.deleteAccessory(n+1)){ // if deleteAccessory() is true, a match has been found Serial.printf("Deleting Accessory: Light-%d\n",n); - - fill(remove(lights.begin(),lights.end(),n),lights.end(),0); // remove entry from lights array and fill any undefined elements with zero - nvs_set_blob(savedData,"LIGHTS",&lights,sizeof(lights)); // update data in the NVS + + lightData[n].isConfigured=false; + nvs_set_blob(savedData,"LIGHTDATA",&lightData,sizeof(lightData)); // update data in the NVS nvs_commit(savedData); } else { - Serial.printf("No such Accessory: Light-%d\n",n); + Serial.printf("Nothing to delete - there is no Light Accessory at index=%d.\n",n); } } @@ -259,19 +196,13 @@ void deleteAccessory(const char *buf){ void deleteAllAccessories(const char *buf){ -// This function is called in response to typing '@D' into the CLI. -// It deletes all Light Accessories - - if(lights[0]==0){ // first check that there is at least one Light Accessory by checking for a non-zero ID in lights[0] - Serial.printf("There are no Light Accessories to delete!\n"); - return; + for(int i=0;i Date: Sun, 31 Jul 2022 17:42:18 -0500 Subject: [PATCH 67/98] Update ProgrammableHub2.ino --- .../ProgrammableHub2/ProgrammableHub2.ino | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/Other Examples/ProgrammableHub2/ProgrammableHub2.ino b/Other Examples/ProgrammableHub2/ProgrammableHub2.ino index 3489364..303f970 100644 --- a/Other Examples/ProgrammableHub2/ProgrammableHub2.ino +++ b/Other Examples/ProgrammableHub2/ProgrammableHub2.ino @@ -30,13 +30,17 @@ // HomeSpan: A HomeKit implementation for the ESP32 // // ------------------------------------------------ // // // +// Demonstrates how to implement a Web Server alongside // +// of HomeSpan to create a Programmable Hub serving up to // +// 24 Configurable Lights. Allows for dynamic changes // +// to Accessories without needing to reboot // // // ////////////////////////////////////////////////////////////////// #include "HomeSpan.h" -#define MAX_LIGHTS 10 -#define MAX_NAME_LENGTH 10 +#define MAX_LIGHTS 24 +#define MAX_NAME_LENGTH 32 enum colorType_t : uint8_t{ NO_COLOR, @@ -56,8 +60,7 @@ struct lightData_t { }; } lightData[MAX_LIGHTS]; - -nvs_handle savedData; // declare savdData as a handle to be used with the NVS (see the ESP32-IDF for details on how to use NVS storage) +nvs_handle savedData; ////////////////////////////////////// @@ -181,9 +184,9 @@ void deleteAccessory(const char *buf){ } if(homeSpan.deleteAccessory(n+1)){ // if deleteAccessory() is true, a match has been found - Serial.printf("Deleting Accessory: Light-%d\n",n); + Serial.printf("Deleting Light Accessory: Name='%s'\n",lightData[n-1].name); - lightData[n].isConfigured=false; + lightData[n-1].isConfigured=false; nvs_set_blob(savedData,"LIGHTDATA",&lightData,sizeof(lightData)); // update data in the NVS nvs_commit(savedData); @@ -197,9 +200,8 @@ void deleteAccessory(const char *buf){ void deleteAllAccessories(const char *buf){ for(int i=0;i Date: Thu, 4 Aug 2022 18:34:46 -0500 Subject: [PATCH 68/98] Update ProgrammableHub2.ino --- Other Examples/ProgrammableHub2/ProgrammableHub2.ino | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Other Examples/ProgrammableHub2/ProgrammableHub2.ino b/Other Examples/ProgrammableHub2/ProgrammableHub2.ino index 303f970..5753929 100644 --- a/Other Examples/ProgrammableHub2/ProgrammableHub2.ino +++ b/Other Examples/ProgrammableHub2/ProgrammableHub2.ino @@ -140,6 +140,10 @@ void addLight(int index, const char *name, boolean isDimmable, colorType_t color new Service::AccessoryInformation(); new Characteristic::Identify(); new Characteristic::Name(name); + char sNum[32]; + sprintf(sNum,"Light-%02d",index+1); + new Characteristic::SerialNumber(sNum); + new Service::LightBulb(); new Characteristic::On(0,true); if(isDimmable) From b29b43af0f100b31655bb50f409e7784b446ac7b Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 7 Aug 2022 09:47:22 -0500 Subject: [PATCH 69/98] Update ProgrammableHub2.ino --- .../ProgrammableHub2/ProgrammableHub2.ino | 178 +++++++++++------- 1 file changed, 115 insertions(+), 63 deletions(-) diff --git a/Other Examples/ProgrammableHub2/ProgrammableHub2.ino b/Other Examples/ProgrammableHub2/ProgrammableHub2.ino index 5753929..5d383fb 100644 --- a/Other Examples/ProgrammableHub2/ProgrammableHub2.ino +++ b/Other Examples/ProgrammableHub2/ProgrammableHub2.ino @@ -38,26 +38,27 @@ ////////////////////////////////////////////////////////////////// #include "HomeSpan.h" +#include // include WebServer library + +WebServer webServer(80); // create WebServer on port 80 #define MAX_LIGHTS 24 #define MAX_NAME_LENGTH 32 +#define HUB_NAME "lighthub" -enum colorType_t : uint8_t{ +enum colorType_t : uint8_t { NO_COLOR, TEMPERATURE_ONLY, FULL_RGB }; +uint32_t aidStore=2; // keep track of unique AID numbers - start with AID=2 + struct lightData_t { char name[MAX_NAME_LENGTH+1]=""; - union { - struct { - boolean isConfigured:1; - boolean isDimmable:1; - colorType_t colorType:2; - }; - uint8_t val=0; - }; + uint32_t aid=0; + boolean isDimmable:1; + colorType_t colorType:2; } lightData[MAX_LIGHTS]; nvs_handle savedData; @@ -74,9 +75,15 @@ void setup() { if(!nvs_get_blob(savedData,"LIGHTDATA",NULL,&len)) // if LIGHTDATA data found nvs_get_blob(savedData,"LIGHTDATA",&lightData,&len); // retrieve data + nvs_get_u32(savedData,"AID",&aidStore); // get AID, if it exists + homeSpan.setLogLevel(1); - homeSpan.begin(Category::Lighting,"HomeSpan Light Hub"); + homeSpan.setHostNameSuffix(""); // use null string for suffix (rather than the HomeSpan device ID) + homeSpan.setPortNum(1201); // change port number for HomeSpan so we can use port 80 for the Web Server + homeSpan.setWifiCallback(setupWeb); // need to start Web Server after WiFi is established + + homeSpan.begin(Category::Lighting,"HomeSpan Light Hub",HUB_NAME); new SpanAccessory(1); // here we specified the AID=1 for clarity (it would default to 1 anyway if left blank) new Service::AccessoryInformation(); @@ -84,16 +91,16 @@ void setup() { new Characteristic::Model("HomeSpan Programmable Hub"); for(int i=0;i - add non-dimmable light accessory using name=",[](const char *c){addLight(-1,c+1,false,NO_COLOR);}); - new SpanUserCommand('A'," - add dimmable light accessory using name=",[](const char *c){addLight(-1,c+1,true,NO_COLOR);}); - new SpanUserCommand('t'," - add non-dimmable light accessory with color-temperature control using name=",[](const char *c){addLight(-1,c+1,false,TEMPERATURE_ONLY);}); - new SpanUserCommand('T'," - add dimmable light accessory with color-temperature control using name=",[](const char *c){addLight(-1,c+1,true,TEMPERATURE_ONLY);}); - new SpanUserCommand('r'," - add non-dimmable light accessory with full RGB color control using name=",[](const char *c){addLight(-1,c+1,false,FULL_RGB);}); - new SpanUserCommand('R'," - add dimmable light accessory with full RGB color control using name=",[](const char *c){addLight(-1,c+1,true,FULL_RGB);}); + new SpanUserCommand('a'," - add non-dimmable light accessory using name=",[](const char *c){addLight(c+1,false,NO_COLOR);}); + new SpanUserCommand('A'," - add dimmable light accessory using name=",[](const char *c){addLight(c+1,true,NO_COLOR);}); + new SpanUserCommand('t'," - add non-dimmable light accessory with color-temperature control using name=",[](const char *c){addLight(c+1,false,TEMPERATURE_ONLY);}); + new SpanUserCommand('T'," - add dimmable light accessory with color-temperature control using name=",[](const char *c){addLight(c+1,true,TEMPERATURE_ONLY);}); + new SpanUserCommand('r'," - add non-dimmable light accessory with full RGB color control using name=",[](const char *c){addLight(c+1,false,FULL_RGB);}); + new SpanUserCommand('R'," - add dimmable light accessory with full RGB color control using name=",[](const char *c){addLight(c+1,true,FULL_RGB);}); new SpanUserCommand('l'," - list all light accessories",listAccessories); new SpanUserCommand('d'," - delete a light accessory with index=",deleteAccessory); @@ -106,55 +113,69 @@ void setup() { /////////////////////////// -void addLight(int index, const char *name, boolean isDimmable, colorType_t colorType){ +void loop(){ + webServer.handleClient(); // handle incoming web server traffic +} - if(index<0){ - for(index=0;indexsizeof(lightData[index].name)) - Serial.printf("Warning - name trimmed to max length of %d characters.\n",MAX_NAME_LENGTH); - - lightData[index].isDimmable=isDimmable; - lightData[index].colorType=colorType; - lightData[index].isConfigured=true; - name=lightData[index].name; - - nvs_set_blob(savedData,"LIGHTDATA",&lightData,sizeof(lightData)); // update data in the NVS - nvs_commit(savedData); - } - - Serial.printf("Adding Light Accessory: Name='%s' Dimmable=%s Color=%s\n",name,isDimmable?"YES":"NO",colorType==NO_COLOR?"NONE":(colorType==TEMPERATURE_ONLY?"TEMPERATURE_ONLY":"FULL_RGB")); - - new SpanAccessory(index+2); // IMPORTANT: add 2, since first Accessory with AID=1 is already used by the Bridge Accessory + new SpanAccessory(lightData[index].aid); new Service::AccessoryInformation(); new Characteristic::Identify(); - new Characteristic::Name(name); + new Characteristic::Name(lightData[index].name); char sNum[32]; - sprintf(sNum,"Light-%02d",index+1); + sprintf(sNum,"Light-%02d",index); new Characteristic::SerialNumber(sNum); new Service::LightBulb(); new Characteristic::On(0,true); - if(isDimmable) + if(lightData[index].isDimmable) new Characteristic::Brightness(100,true); - if(colorType==TEMPERATURE_ONLY) + if(lightData[index].colorType==TEMPERATURE_ONLY) new Characteristic::ColorTemperature(200,true); - if(colorType==FULL_RGB){ + if(lightData[index].colorType==FULL_RGB){ new Characteristic::Hue(0,true); new Characteristic::Saturation(0,true); } - + +} + +/////////////////////////// + +void addLight(const char *name, boolean isDimmable, colorType_t colorType){ + + int index=0; + for(index=0;indexsizeof(lightData[index].name)) + Serial.printf("Warning - name trimmed to max length of %d characters.\n",MAX_NAME_LENGTH); + + lightData[index].isDimmable=isDimmable; + lightData[index].colorType=colorType; + lightData[index].aid=aidStore++; + + nvs_set_blob(savedData,"LIGHTDATA",&lightData,sizeof(lightData)); // update data in the NVS + nvs_set_u32(savedData,"AID",aidStore); + nvs_commit(savedData); + + addLight(index); } /////////////////////////// @@ -180,22 +201,22 @@ size_t strncpy_trim(char *dest, const char *src, size_t dSize){ void deleteAccessory(const char *buf){ - int n=atoi(buf+1); + int index=atoi(buf+1); - if(n<1 || n>MAX_LIGHTS){ - Serial.printf("Invalid Light Accessory index - must be between 1 and %d.\n",MAX_LIGHTS); + if(index<0 || index>=MAX_LIGHTS){ + Serial.printf("Invalid Light Accessory index - must be between 0 and %d.\n",MAX_LIGHTS-1); return; } - if(homeSpan.deleteAccessory(n+1)){ // if deleteAccessory() is true, a match has been found - Serial.printf("Deleting Light Accessory: Name='%s'\n",lightData[n-1].name); + if(homeSpan.deleteAccessory(lightData[index].aid)){ // if deleteAccessory() is true, a match has been found + Serial.printf("Deleting Light Accessory: Name='%s'\n",lightData[index].name); - lightData[n-1].isConfigured=false; + lightData[index].aid=0; nvs_set_blob(savedData,"LIGHTDATA",&lightData,sizeof(lightData)); // update data in the NVS nvs_commit(savedData); } else { - Serial.printf("Nothing to delete - there is no Light Accessory at index=%d.\n",n); + Serial.printf("Nothing to delete - there is no Light Accessory at index=%d.\n",index); } } @@ -204,8 +225,8 @@ void deleteAccessory(const char *buf){ void deleteAllAccessories(const char *buf){ for(int i=0;i"; + response += "":">") + ""; + } + } + + response += ""; + + + response += ""; + webServer.send(200, "text/html", response); + + }); + +} + +/////////////////////////// From 546f4445e5f6d3f92e06be02725c55e967d35f8d Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 7 Aug 2022 19:22:01 -0500 Subject: [PATCH 70/98] Update ProgrammableHub2.ino --- .../ProgrammableHub2/ProgrammableHub2.ino | 93 ++++++++++++++++--- 1 file changed, 78 insertions(+), 15 deletions(-) diff --git a/Other Examples/ProgrammableHub2/ProgrammableHub2.ino b/Other Examples/ProgrammableHub2/ProgrammableHub2.ino index 5d383fb..b5d0e37 100644 --- a/Other Examples/ProgrammableHub2/ProgrammableHub2.ino +++ b/Other Examples/ProgrammableHub2/ProgrammableHub2.ino @@ -103,17 +103,16 @@ void setup() { new SpanUserCommand('R'," - add dimmable light accessory with full RGB color control using name=",[](const char *c){addLight(c+1,true,FULL_RGB);}); new SpanUserCommand('l'," - list all light accessories",listAccessories); - new SpanUserCommand('d'," - delete a light accessory with index=",deleteAccessory); + new SpanUserCommand('d'," - delete a light accessory with index=",[](const char *buf){deleteAccessory(atoi(buf+1));}); new SpanUserCommand('D'," - delete ALL light accessories",deleteAllAccessories); new SpanUserCommand('u',"- update accessories database",updateAccessories); - - homeSpan.autoPoll(); - + } // end of setup() /////////////////////////// void loop(){ + homeSpan.poll(); webServer.handleClient(); // handle incoming web server traffic } @@ -147,21 +146,21 @@ void addLight(int index){ /////////////////////////// -void addLight(const char *name, boolean isDimmable, colorType_t colorType){ +int addLight(const char *name, boolean isDimmable, colorType_t colorType){ int index=0; for(index=0;indexsizeof(lightData[index].name)) @@ -176,6 +175,7 @@ void addLight(const char *name, boolean isDimmable, colorType_t colorType){ nvs_commit(savedData); addLight(index); + return(index); } /////////////////////////// @@ -199,9 +199,7 @@ size_t strncpy_trim(char *dest, const char *src, size_t dSize){ /////////////////////////// -void deleteAccessory(const char *buf){ - - int index=atoi(buf+1); +void deleteAccessory(int index){ if(index<0 || index>=MAX_LIGHTS){ Serial.printf("Invalid Light Accessory index - must be between 0 and %d.\n",MAX_LIGHTS-1); @@ -266,31 +264,96 @@ void listAccessories(const char *buf){ /////////////////////////// void setupWeb(){ + Serial.printf("Starting Light Server Hub at %s.local\n\n",HUB_NAME); webServer.begin(); webServer.on("/", []() { String response = "HomeSpan Programmable Light Hub"; - response += "\n"; + response += "\n"; response += "

HomeSpan Lights

"; - response += ""; + response += ""; + response += "
AccessoryDim?Color Control
"; + + int openSlots=MAX_LIGHTS; for(int i=0;i"; - response += ""; + response += ""; + response += ""; + response += ""; + response += ""; + response += ""; + openSlots--; } } + response += ""; + response += ""; + response += ""; + response += ""; + response += ""; + response += "
AccessoryDim?Color ControlAction
":">") + "
" + String(lightData[i].name) + "":">") + "":">") + " NONE "; + response += "":">") + " TEMP ONLY "; + response += "":">") + " FULL COLOR
"; + response += ""; + response += "
"; - + response += ""; + + if(!openSlots) + response += "

Can't add any more Light Accessories. Max="+ String(MAX_LIGHTS) + "

"; response += ""; webServer.send(200, "text/html", response); }); + webServer.on("/deleteLight", []() { + + int index=atoi(webServer.arg(0).c_str()); + + String response = "HomeSpan Programmable Light Hub"; + response += "Deleted Light Accessory '" + String(lightData[index].name) + "'..."; + + deleteAccessory(index); + + webServer.send(200, "text/html", response); + + }); + + webServer.on("/addLight", []() { + + colorType_t colorType=NO_COLOR; + boolean isDimmable=false; + int iName=-1; + + for(int i=0;i Date: Sun, 7 Aug 2022 19:45:42 -0500 Subject: [PATCH 71/98] Update ProgrammableHub2.ino --- .../ProgrammableHub2/ProgrammableHub2.ino | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/Other Examples/ProgrammableHub2/ProgrammableHub2.ino b/Other Examples/ProgrammableHub2/ProgrammableHub2.ino index b5d0e37..08ab1eb 100644 --- a/Other Examples/ProgrammableHub2/ProgrammableHub2.ino +++ b/Other Examples/ProgrammableHub2/ProgrammableHub2.ino @@ -304,7 +304,9 @@ void setupWeb(){ response += ""; if(!openSlots) - response += "

Can't add any more Light Accessories. Max="+ String(MAX_LIGHTS) + "

"; + response += "

Can't add any more Light Accessories. Max="+ String(MAX_LIGHTS) + "

"; + + response += "

Press here to update the Home App when finished making changes:

"; response += ""; webServer.send(200, "text/html", response); @@ -316,7 +318,7 @@ void setupWeb(){ int index=atoi(webServer.arg(0).c_str()); String response = "HomeSpan Programmable Light Hub"; - response += "Deleted Light Accessory '" + String(lightData[index].name) + "'..."; + response += "Deleting Light Accessory '" + String(lightData[index].name) + "'..."; deleteAccessory(index); @@ -324,6 +326,21 @@ void setupWeb(){ }); + webServer.on("/update", []() { + + String response = "HomeSpan Programmable Light Hub"; + + if(homeSpan.updateDatabase()) + response += "Accessories Database updated. New configuration number broadcasted..."; + else + response += "Nothing to update - no changes were made..."; + + response += "..."; + + webServer.send(200, "text/html", response); + + }); + webServer.on("/addLight", []() { colorType_t colorType=NO_COLOR; From df2154d048898d25076aac1c10da2df8c8a644a4 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 7 Aug 2022 19:54:34 -0500 Subject: [PATCH 72/98] Update ProgrammableHub2.ino --- Other Examples/ProgrammableHub2/ProgrammableHub2.ino | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Other Examples/ProgrammableHub2/ProgrammableHub2.ino b/Other Examples/ProgrammableHub2/ProgrammableHub2.ino index 08ab1eb..1bb4154 100644 --- a/Other Examples/ProgrammableHub2/ProgrammableHub2.ino +++ b/Other Examples/ProgrammableHub2/ProgrammableHub2.ino @@ -306,6 +306,7 @@ void setupWeb(){ if(!openSlots) response += "

Can't add any more Light Accessories. Max="+ String(MAX_LIGHTS) + "

"; + response += "

Press here to delete ALL Light Accessories:

"; response += "

Press here to update the Home App when finished making changes:

"; response += ""; @@ -326,6 +327,16 @@ void setupWeb(){ }); + webServer.on("/deleteAll", []() { + + String response = "HomeSpan Programmable Light Hub"; + response += "Deleting All Light Accessories..."; + + webServer.send(200, "text/html", response); + deleteAllAccessories(""); + + }); + webServer.on("/update", []() { String response = "HomeSpan Programmable Light Hub"; From 999eb45e8a4f02d84569c95d4669484e65f973dc Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 7 Aug 2022 19:56:01 -0500 Subject: [PATCH 73/98] Created new version of ProgrammableHub with Dynamic Changes --- Other Examples/ProgrammableHub/DEV_Identify.h | 38 -- Other Examples/ProgrammableHub/DEV_LED.h | 69 --- .../ProgrammableHub/ProgrammableHub.ino | 497 +++++++++++------- 3 files changed, 309 insertions(+), 295 deletions(-) delete mode 100644 Other Examples/ProgrammableHub/DEV_Identify.h delete mode 100644 Other Examples/ProgrammableHub/DEV_LED.h diff --git a/Other Examples/ProgrammableHub/DEV_Identify.h b/Other Examples/ProgrammableHub/DEV_Identify.h deleted file mode 100644 index b8d21d6..0000000 --- a/Other Examples/ProgrammableHub/DEV_Identify.h +++ /dev/null @@ -1,38 +0,0 @@ - -////////////////////////////////// -// 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(const char *name, const char *manu, const char *sn, const char *model, const 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 LED - - pinMode(homeSpan.getStatusPin(),OUTPUT); // make sure LED is set for output - } - - boolean update(){ - - for(int i=0;isetRange(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->LED=new LedPin(ledPin); // configures a PWM LED for output to pin number "ledPin" - - Serial.printf("Configuring LED: Pin=%d %s\n",LED->getPin(),isDimmable?"(Dimmable)":""); // initialization message - - LED->set(power->getVal()*(isDimmable?(level->getVal()):100)); // set the LED to its initial state at startup. - - } // end constructor - - boolean update(){ // update() method - - LOG1("Updating LED on pin="); - LOG1(LED->getPin()); - LOG1(": Current Power="); - LOG1(power->getVal()?"true":"false"); - if(isDimmable){ - LOG1(" Current Brightness="); - LOG1(level->getVal()); - } - - if(power->updated()){ - LOG1(" New Power="); - LOG1(power->getNewVal()?"true":"false"); - } - - if(isDimmable && level->updated()){ - LOG1(" New Brightness="); - LOG1(level->getNewVal()); - } - - LOG1("\n"); - - LED->set(power->getNewVal()*(isDimmable?(level->getNewVal()):100)); // update the physical LED to reflect the new values - - return(true); // return true - - } // update - -}; - -////////////////////////////////// diff --git a/Other Examples/ProgrammableHub/ProgrammableHub.ino b/Other Examples/ProgrammableHub/ProgrammableHub.ino index 82e07e5..1bb4154 100644 --- a/Other Examples/ProgrammableHub/ProgrammableHub.ino +++ b/Other Examples/ProgrammableHub/ProgrammableHub.ino @@ -24,243 +24,364 @@ * SOFTWARE. * ********************************************************************************/ - -//////////////////////////////////////////////////////////// -// // -// HomeSpan: A HomeKit implementation for the ESP32 // -// ------------------------------------------------ // -// // -// Demonstrates how to implement a Web Server alongside // -// of HomeSpan to create a Programmable Hub serving up to // -// 16 Configurable Lights. // -// // -//////////////////////////////////////////////////////////// -#include "HomeSpan.h" -#include "DEV_LED.h" -#include "DEV_Identify.h" +////////////////////////////////////////////////////////////////// +// // +// HomeSpan: A HomeKit implementation for the ESP32 // +// ------------------------------------------------ // +// // +// Demonstrates how to implement a Web Server alongside // +// of HomeSpan to create a Programmable Hub serving up to // +// 24 Configurable Lights. Allows for dynamic changes // +// to Accessories without needing to reboot // +// // +////////////////////////////////////////////////////////////////// +#include "HomeSpan.h" #include // include WebServer library + WebServer webServer(80); // create WebServer on port 80 + +#define MAX_LIGHTS 24 +#define MAX_NAME_LENGTH 32 +#define HUB_NAME "lighthub" -#define NLIGHTS 16 // maximum number of Lightbulb Accessories +enum colorType_t : uint8_t { + NO_COLOR, + TEMPERATURE_ONLY, + FULL_RGB +}; -uint8_t pinList[]={0,4,5,12,14,15,16,17,18,19,22,23,25,26,27,32,33}; // list of allowed pins -char lightNames[NLIGHTS][9]; // storage for default light names +uint32_t aidStore=2; // keep track of unique AID numbers - start with AID=2 -nvs_handle lightNVS; // handle for NVS storage +struct lightData_t { + char name[MAX_NAME_LENGTH+1]=""; + uint32_t aid=0; + boolean isDimmable:1; + colorType_t colorType:2; +} lightData[MAX_LIGHTS]; -struct { // structure to store pin numbers and dimmable flag - uint8_t pin=0; - uint8_t dimmable=0; -} lightData[NLIGHTS]; +nvs_handle savedData; -//////////////////////////////////////////////////////////// +////////////////////////////////////// void setup() { - + Serial.begin(115200); + size_t len; + nvs_open("SAVED_DATA",NVS_READWRITE,&savedData); // open a new namespace called SAVED_DATA in the NVS + + if(!nvs_get_blob(savedData,"LIGHTDATA",NULL,&len)) // if LIGHTDATA data found + nvs_get_blob(savedData,"LIGHTDATA",&lightData,&len); // retrieve data + + nvs_get_u32(savedData,"AID",&aidStore); // get AID, if it exists + homeSpan.setLogLevel(1); homeSpan.setHostNameSuffix(""); // use null string for suffix (rather than the HomeSpan device ID) homeSpan.setPortNum(1201); // change port number for HomeSpan so we can use port 80 for the Web Server - homeSpan.enableOTA(); // enable OTA updates - homeSpan.setMaxConnections(5); // reduce max connection to 5 (default is 8) since WebServer and a connecting client will need 2, and OTA needs 1 - homeSpan.setWifiCallback(setupWeb); // need to start Web Server after WiFi is established - - homeSpan.enableWebLog(50,"pool.ntp.org","CST6CDT"); - - homeSpan.begin(Category::Bridges,"HomeSpan Light Hub","homespanhub"); + homeSpan.setWifiCallback(setupWeb); // need to start Web Server after WiFi is established - for(int i=0;i0){ - new SpanAccessory(i+2); - new DEV_Identify(lightNames[i],"HomeSpan",lightNames[i],lightData[i].dimmable?"Dimmable":"Not Dimmable","1.0",0); - new DEV_GenericLED(lightData[i].pin,lightData[i].dimmable); - } + for(int i=0;i - add non-dimmable light accessory using name=",[](const char *c){addLight(c+1,false,NO_COLOR);}); + new SpanUserCommand('A'," - add dimmable light accessory using name=",[](const char *c){addLight(c+1,true,NO_COLOR);}); + new SpanUserCommand('t'," - add non-dimmable light accessory with color-temperature control using name=",[](const char *c){addLight(c+1,false,TEMPERATURE_ONLY);}); + new SpanUserCommand('T'," - add dimmable light accessory with color-temperature control using name=",[](const char *c){addLight(c+1,true,TEMPERATURE_ONLY);}); + new SpanUserCommand('r'," - add non-dimmable light accessory with full RGB color control using name=",[](const char *c){addLight(c+1,false,FULL_RGB);}); + new SpanUserCommand('R'," - add dimmable light accessory with full RGB color control using name=",[](const char *c){addLight(c+1,true,FULL_RGB);}); + + new SpanUserCommand('l'," - list all light accessories",listAccessories); + new SpanUserCommand('d'," - delete a light accessory with index=",[](const char *buf){deleteAccessory(atoi(buf+1));}); + new SpanUserCommand('D'," - delete ALL light accessories",deleteAllAccessories); + new SpanUserCommand('u',"- update accessories database",updateAccessories); + } // end of setup() -////////////////////////////////////// +/////////////////////////// void loop(){ - homeSpan.poll(); - webServer.handleClient(); // need to process webServer once each loop - -} // end of loop() + webServer.handleClient(); // handle incoming web server traffic +} -////////////////////////////////////// +/////////////////////////// + +void addLight(int index){ + + Serial.printf("Adding Light Accessory: Name='%s' Dimmable=%s Color=%s\n", + lightData[index].name,lightData[index].isDimmable?"YES":"NO",lightData[index].colorType==NO_COLOR?"NONE":(lightData[index].colorType==TEMPERATURE_ONLY?"TEMPERATURE_ONLY":"FULL_RGB")); + + new SpanAccessory(lightData[index].aid); + new Service::AccessoryInformation(); + new Characteristic::Identify(); + new Characteristic::Name(lightData[index].name); + char sNum[32]; + sprintf(sNum,"Light-%02d",index); + new Characteristic::SerialNumber(sNum); + + new Service::LightBulb(); + new Characteristic::On(0,true); + if(lightData[index].isDimmable) + new Characteristic::Brightness(100,true); + if(lightData[index].colorType==TEMPERATURE_ONLY) + new Characteristic::ColorTemperature(200,true); + if(lightData[index].colorType==FULL_RGB){ + new Characteristic::Hue(0,true); + new Characteristic::Saturation(0,true); + } + +} + +/////////////////////////// + +int addLight(const char *name, boolean isDimmable, colorType_t colorType){ + + int index=0; + for(index=0;indexsizeof(lightData[index].name)) + Serial.printf("Warning - name trimmed to max length of %d characters.\n",MAX_NAME_LENGTH); + + lightData[index].isDimmable=isDimmable; + lightData[index].colorType=colorType; + lightData[index].aid=aidStore++; + + nvs_set_blob(savedData,"LIGHTDATA",&lightData,sizeof(lightData)); // update data in the NVS + nvs_set_u32(savedData,"AID",aidStore); + nvs_commit(savedData); + + addLight(index); + return(index); +} + +/////////////////////////// + +size_t strncpy_trim(char *dest, const char *src, size_t dSize){ + + while(*src==' ') // skip over any leading spaces + src++; + + size_t sLen=strlen(src); // string length of src after skipping over leading spaces + while(sLen>0 && src[sLen-1]==' ') // shorten length to remove trailing spaces + sLen--; + + size_t sSize=sLen+1; // add room for null terminator + + if(dest!=NULL) + *stpncpy(dest,src,(dSize=MAX_LIGHTS){ + Serial.printf("Invalid Light Accessory index - must be between 0 and %d.\n",MAX_LIGHTS-1); + return; + } + + if(homeSpan.deleteAccessory(lightData[index].aid)){ // if deleteAccessory() is true, a match has been found + Serial.printf("Deleting Light Accessory: Name='%s'\n",lightData[index].name); + + lightData[index].aid=0; + nvs_set_blob(savedData,"LIGHTDATA",&lightData,sizeof(lightData)); // update data in the NVS + nvs_commit(savedData); + + } else { + Serial.printf("Nothing to delete - there is no Light Accessory at index=%d.\n",index); + } +} + +/////////////////////////// + +void deleteAllAccessories(const char *buf){ + + for(int i=0;i"; + response += "":">") + ""; + response += "":">") + " NONE "; + response += "":">") + " TEMP ONLY "; + response += "":">") + " FULL COLOR "; + response += ""; + response += ""; + openSlots--; + } + } - content+="
"; - content+=""; - content+=""; - - content+="

Select Light Features:

"; - content+=""; - content+="
"; - content+=""; - content+="
"; - content+=""; - content+="

"; - - content+=""; - content+="

"; - - content+=""; - content+=""; + response += ""; + response += ""; + response += ""; + response += ""; + response += ""; + response += ""; + response += ""; + response += ""; + response += "
"; - content+=""; - content+=""; + if(!openSlots) + response += "

Can't add any more Light Accessories. Max="+ String(MAX_LIGHTS) + "

"; + + response += "

Press here to delete ALL Light Accessories:

"; + response += "

Press here to update the Home App when finished making changes:

"; - webServer.send(200, "text/html", content); + response += ""; + webServer.send(200, "text/html", response); + + }); + + webServer.on("/deleteLight", []() { + + int index=atoi(webServer.arg(0).c_str()); + + String response = "HomeSpan Programmable Light Hub"; + response += "Deleting Light Accessory '" + String(lightData[index].name) + "'..."; + deleteAccessory(index); + + webServer.send(200, "text/html", response); + + }); + + webServer.on("/deleteAll", []() { + + String response = "HomeSpan Programmable Light Hub"; + response += "Deleting All Light Accessories..."; + + webServer.send(200, "text/html", response); + deleteAllAccessories(""); + + }); + + webServer.on("/update", []() { + + String response = "HomeSpan Programmable Light Hub"; + + if(homeSpan.updateDatabase()) + response += "Accessories Database updated. New configuration number broadcasted..."; + else + response += "Nothing to update - no changes were made..."; + + response += "..."; + + webServer.send(200, "text/html", response); + }); webServer.on("/addLight", []() { - char lightName[32]; - uint8_t flags=0; - - String sColorControl("colorControl"); - String sIsDimmable("isDimmable"); - String sLightName("lightName"); - + colorType_t colorType=NO_COLOR; + boolean isDimmable=false; + int iName=-1; + for(int i=0;iAdd Another"; - content+=""; - content+=""; + if(iName!=-1){ + int index=addLight(webServer.arg(iName).c_str(),isDimmable,colorType); + response += "Adding Light Accessory '" + String(lightData[index].name) + "'"; + } else + response += "Error - bad URL request"; - webServer.send(200, "text/html", content); + response += "..."; - }); - - webServer.on("/", []() { - - String content = "
HomeSpan Light Server Hub Configuration

"; - content += "Select pins and check box if dimmable:

"; + webServer.send(200, "text/html", response); - for(int i=0;i"; - - content += String(lightNames[i]) + ": "; - - content += " "; - content += "
"; - } - - content += "

"; - - webServer.send(200, "text/html", content); - - }); - - webServer.on("/configure", []() { - - for(int i=0;i"; - else - lightData[i].dimmable=0; - - content += "
"; - content += ""; - - nvs_set_blob(lightNVS,"LIGHTDATA",&lightData,sizeof(lightData)); // update data - nvs_commit(lightNVS); // commit to NVS - - webServer.send(200, "text/html", content); - }); - webServer.on("/reboot", []() { - - String content = "Rebooting! Will return to configuration page in 10 seconds.

"; - content += ""; - webServer.send(200, "text/html", content); - for(int j=0;j Date: Sun, 7 Aug 2022 19:57:31 -0500 Subject: [PATCH 74/98] Delete ProgrammableHub2.ino --- .../ProgrammableHub2/ProgrammableHub2.ino | 387 ------------------ 1 file changed, 387 deletions(-) delete mode 100644 Other Examples/ProgrammableHub2/ProgrammableHub2.ino diff --git a/Other Examples/ProgrammableHub2/ProgrammableHub2.ino b/Other Examples/ProgrammableHub2/ProgrammableHub2.ino deleted file mode 100644 index 1bb4154..0000000 --- a/Other Examples/ProgrammableHub2/ProgrammableHub2.ino +++ /dev/null @@ -1,387 +0,0 @@ -/********************************************************************************* - * MIT License - * - * Copyright (c) 2022 Gregg E. Berman - * - * https://github.com/HomeSpan/HomeSpan - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - ********************************************************************************/ - -////////////////////////////////////////////////////////////////// -// // -// HomeSpan: A HomeKit implementation for the ESP32 // -// ------------------------------------------------ // -// // -// Demonstrates how to implement a Web Server alongside // -// of HomeSpan to create a Programmable Hub serving up to // -// 24 Configurable Lights. Allows for dynamic changes // -// to Accessories without needing to reboot // -// // -////////////////////////////////////////////////////////////////// - -#include "HomeSpan.h" -#include // include WebServer library - -WebServer webServer(80); // create WebServer on port 80 - -#define MAX_LIGHTS 24 -#define MAX_NAME_LENGTH 32 -#define HUB_NAME "lighthub" - -enum colorType_t : uint8_t { - NO_COLOR, - TEMPERATURE_ONLY, - FULL_RGB -}; - -uint32_t aidStore=2; // keep track of unique AID numbers - start with AID=2 - -struct lightData_t { - char name[MAX_NAME_LENGTH+1]=""; - uint32_t aid=0; - boolean isDimmable:1; - colorType_t colorType:2; -} lightData[MAX_LIGHTS]; - -nvs_handle savedData; - -////////////////////////////////////// - -void setup() { - - Serial.begin(115200); - - size_t len; - nvs_open("SAVED_DATA",NVS_READWRITE,&savedData); // open a new namespace called SAVED_DATA in the NVS - - if(!nvs_get_blob(savedData,"LIGHTDATA",NULL,&len)) // if LIGHTDATA data found - nvs_get_blob(savedData,"LIGHTDATA",&lightData,&len); // retrieve data - - nvs_get_u32(savedData,"AID",&aidStore); // get AID, if it exists - - homeSpan.setLogLevel(1); - - homeSpan.setHostNameSuffix(""); // use null string for suffix (rather than the HomeSpan device ID) - homeSpan.setPortNum(1201); // change port number for HomeSpan so we can use port 80 for the Web Server - homeSpan.setWifiCallback(setupWeb); // need to start Web Server after WiFi is established - - homeSpan.begin(Category::Lighting,"HomeSpan Light Hub",HUB_NAME); - - new SpanAccessory(1); // here we specified the AID=1 for clarity (it would default to 1 anyway if left blank) - new Service::AccessoryInformation(); - new Characteristic::Identify(); - new Characteristic::Model("HomeSpan Programmable Hub"); - - for(int i=0;i - add non-dimmable light accessory using name=",[](const char *c){addLight(c+1,false,NO_COLOR);}); - new SpanUserCommand('A'," - add dimmable light accessory using name=",[](const char *c){addLight(c+1,true,NO_COLOR);}); - new SpanUserCommand('t'," - add non-dimmable light accessory with color-temperature control using name=",[](const char *c){addLight(c+1,false,TEMPERATURE_ONLY);}); - new SpanUserCommand('T'," - add dimmable light accessory with color-temperature control using name=",[](const char *c){addLight(c+1,true,TEMPERATURE_ONLY);}); - new SpanUserCommand('r'," - add non-dimmable light accessory with full RGB color control using name=",[](const char *c){addLight(c+1,false,FULL_RGB);}); - new SpanUserCommand('R'," - add dimmable light accessory with full RGB color control using name=",[](const char *c){addLight(c+1,true,FULL_RGB);}); - - new SpanUserCommand('l'," - list all light accessories",listAccessories); - new SpanUserCommand('d'," - delete a light accessory with index=",[](const char *buf){deleteAccessory(atoi(buf+1));}); - new SpanUserCommand('D'," - delete ALL light accessories",deleteAllAccessories); - new SpanUserCommand('u',"- update accessories database",updateAccessories); - -} // end of setup() - -/////////////////////////// - -void loop(){ - homeSpan.poll(); - webServer.handleClient(); // handle incoming web server traffic -} - -/////////////////////////// - -void addLight(int index){ - - Serial.printf("Adding Light Accessory: Name='%s' Dimmable=%s Color=%s\n", - lightData[index].name,lightData[index].isDimmable?"YES":"NO",lightData[index].colorType==NO_COLOR?"NONE":(lightData[index].colorType==TEMPERATURE_ONLY?"TEMPERATURE_ONLY":"FULL_RGB")); - - new SpanAccessory(lightData[index].aid); - new Service::AccessoryInformation(); - new Characteristic::Identify(); - new Characteristic::Name(lightData[index].name); - char sNum[32]; - sprintf(sNum,"Light-%02d",index); - new Characteristic::SerialNumber(sNum); - - new Service::LightBulb(); - new Characteristic::On(0,true); - if(lightData[index].isDimmable) - new Characteristic::Brightness(100,true); - if(lightData[index].colorType==TEMPERATURE_ONLY) - new Characteristic::ColorTemperature(200,true); - if(lightData[index].colorType==FULL_RGB){ - new Characteristic::Hue(0,true); - new Characteristic::Saturation(0,true); - } - -} - -/////////////////////////// - -int addLight(const char *name, boolean isDimmable, colorType_t colorType){ - - int index=0; - for(index=0;indexsizeof(lightData[index].name)) - Serial.printf("Warning - name trimmed to max length of %d characters.\n",MAX_NAME_LENGTH); - - lightData[index].isDimmable=isDimmable; - lightData[index].colorType=colorType; - lightData[index].aid=aidStore++; - - nvs_set_blob(savedData,"LIGHTDATA",&lightData,sizeof(lightData)); // update data in the NVS - nvs_set_u32(savedData,"AID",aidStore); - nvs_commit(savedData); - - addLight(index); - return(index); -} - -/////////////////////////// - -size_t strncpy_trim(char *dest, const char *src, size_t dSize){ - - while(*src==' ') // skip over any leading spaces - src++; - - size_t sLen=strlen(src); // string length of src after skipping over leading spaces - while(sLen>0 && src[sLen-1]==' ') // shorten length to remove trailing spaces - sLen--; - - size_t sSize=sLen+1; // add room for null terminator - - if(dest!=NULL) - *stpncpy(dest,src,(dSize=MAX_LIGHTS){ - Serial.printf("Invalid Light Accessory index - must be between 0 and %d.\n",MAX_LIGHTS-1); - return; - } - - if(homeSpan.deleteAccessory(lightData[index].aid)){ // if deleteAccessory() is true, a match has been found - Serial.printf("Deleting Light Accessory: Name='%s'\n",lightData[index].name); - - lightData[index].aid=0; - nvs_set_blob(savedData,"LIGHTDATA",&lightData,sizeof(lightData)); // update data in the NVS - nvs_commit(savedData); - - } else { - Serial.printf("Nothing to delete - there is no Light Accessory at index=%d.\n",index); - } -} - -/////////////////////////// - -void deleteAllAccessories(const char *buf){ - - for(int i=0;i"; - response += "":">") + ""; - response += "":">") + " NONE "; - response += "":">") + " TEMP ONLY "; - response += "":">") + " FULL COLOR "; - response += ""; - response += ""; - openSlots--; - } - } - - response += ""; - response += ""; - response += ""; - response += ""; - response += ""; - response += ""; - response += ""; - - response += ""; - response += ""; - - if(!openSlots) - response += "

Can't add any more Light Accessories. Max="+ String(MAX_LIGHTS) + "

"; - - response += "

Press here to delete ALL Light Accessories:

"; - response += "

Press here to update the Home App when finished making changes:

"; - - response += ""; - webServer.send(200, "text/html", response); - - }); - - webServer.on("/deleteLight", []() { - - int index=atoi(webServer.arg(0).c_str()); - - String response = "HomeSpan Programmable Light Hub"; - response += "Deleting Light Accessory '" + String(lightData[index].name) + "'..."; - - deleteAccessory(index); - - webServer.send(200, "text/html", response); - - }); - - webServer.on("/deleteAll", []() { - - String response = "HomeSpan Programmable Light Hub"; - response += "Deleting All Light Accessories..."; - - webServer.send(200, "text/html", response); - deleteAllAccessories(""); - - }); - - webServer.on("/update", []() { - - String response = "HomeSpan Programmable Light Hub"; - - if(homeSpan.updateDatabase()) - response += "Accessories Database updated. New configuration number broadcasted..."; - else - response += "Nothing to update - no changes were made..."; - - response += "..."; - - webServer.send(200, "text/html", response); - - }); - - webServer.on("/addLight", []() { - - colorType_t colorType=NO_COLOR; - boolean isDimmable=false; - int iName=-1; - - for(int i=0;i Date: Sun, 7 Aug 2022 20:08:44 -0500 Subject: [PATCH 75/98] Update ProgrammableHub.ino --- Other Examples/ProgrammableHub/ProgrammableHub.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Other Examples/ProgrammableHub/ProgrammableHub.ino b/Other Examples/ProgrammableHub/ProgrammableHub.ino index 1bb4154..b7df732 100644 --- a/Other Examples/ProgrammableHub/ProgrammableHub.ino +++ b/Other Examples/ProgrammableHub/ProgrammableHub.ino @@ -42,7 +42,7 @@ WebServer webServer(80); // create WebServer on port 80 -#define MAX_LIGHTS 24 +#define MAX_LIGHTS 12 #define MAX_NAME_LENGTH 32 #define HUB_NAME "lighthub" From 3e88d699972d3808c73deb34783778eee473476a Mon Sep 17 00:00:00 2001 From: Gregg Date: Mon, 8 Aug 2022 05:56:11 -0500 Subject: [PATCH 76/98] Added optional AccessoryFlags Characteristic to AccessoryInformation Service This optional flag does not seem to have any affect on the Home App and does not produce any messages indicating that the device needs additional setup. Was hopeful this would have prevented the "Not Supported" message on a Home App Tile that is configured as a bridge, but without any other Accessories. --- Other Examples/ProgrammableHub/ProgrammableHub.ino | 3 ++- src/Characteristics.h | 1 + src/Span.h | 4 +++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Other Examples/ProgrammableHub/ProgrammableHub.ino b/Other Examples/ProgrammableHub/ProgrammableHub.ino index b7df732..fec8a48 100644 --- a/Other Examples/ProgrammableHub/ProgrammableHub.ino +++ b/Other Examples/ProgrammableHub/ProgrammableHub.ino @@ -32,7 +32,7 @@ // // // Demonstrates how to implement a Web Server alongside // // of HomeSpan to create a Programmable Hub serving up to // -// 24 Configurable Lights. Allows for dynamic changes // +// 12 Configurable Lights. Allows for dynamic changes // // to Accessories without needing to reboot // // // ////////////////////////////////////////////////////////////////// @@ -89,6 +89,7 @@ void setup() { new Service::AccessoryInformation(); new Characteristic::Identify(); new Characteristic::Model("HomeSpan Programmable Hub"); + new Characteristic::AccessoryFlags(); for(int i=0;i Date: Mon, 8 Aug 2022 06:24:32 -0500 Subject: [PATCH 77/98] Update Tutorials.md --- docs/Tutorials.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/Tutorials.md b/docs/Tutorials.md index 08feb0e..6ea8fd0 100644 --- a/docs/Tutorials.md +++ b/docs/Tutorials.md @@ -129,6 +129,9 @@ Demonstrates how to use HomeSpan's *Pixel* and *Dot* classes to control one- and ### [CustomService](../Other%20Examples/CustomService) Demonstrates how to create Custom Services and Custom Characteristics in HomeSpan to implement an Atmospheric Pressure Sensor recognized by the *Eve for HomeKit* app. See [Custom Characteristics and Custom Services Macros](Reference.md#custom-characteristics-and-custom-services-macros) for full details +### [ProgrammableHub](../Other%20Examples/ProgrammableHub) +Demonstrates how to implement a fully programmable Light Accessory Hub that allows the user to *dynamically* add/delete up to 12 Light Accessories directly through a device-hosted *web interface* or via HomeSpan's *command-line inteface*. Each light can be configured as dimmable/non-dimmable with either no color control, full RGB color control, or color-temperature control. Builds upon many of the techniques used in [Example 20](../examples/20-AdvancedTechniques) + --- [↩️](README.md) Back to the Welcome page From 57d791178c4ebf3906305508730ea7e4c2b769ce Mon Sep 17 00:00:00 2001 From: Gregg Date: Wed, 10 Aug 2022 06:20:50 -0500 Subject: [PATCH 78/98] starting update of ControlPin logic to make it more generic --- src/HomeSpan.cpp | 22 +++++++++++----------- src/HomeSpan.h | 7 ++++--- src/Network.cpp | 4 ++-- src/Settings.h | 2 +- src/Utils.h | 4 ++++ src/src.ino | 4 ++-- 6 files changed, 24 insertions(+), 19 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 502307f..cc47eb2 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -59,7 +59,6 @@ void Span::begin(Category catID, const char *displayName, const char *hostNameBa esp_task_wdt_delete(xTaskGetIdleTaskHandleForCPU(0)); // required to avoid watchdog timeout messages from ESP32-C3 - controlButton.init(controlPin); statusLED.init(statusPin,0,autoOffLED); if(requestedMaxCon=0) - Serial.print(controlPin); + if(getControlPin()>=0) + Serial.print(getControlPin()); else Serial.print("- *** WARNING: Device Control Pin is UNDEFINED"); Serial.print("\nSketch Version: "); @@ -196,7 +195,8 @@ void Span::pollTask() { homeSpan.statusLED.start(LED_WIFI_CONNECTING); } - controlButton.reset(); + if(controlButton) + controlButton->reset(); Serial.print(displayName); Serial.print(" is READY!\n\n"); @@ -290,14 +290,14 @@ void Span::pollTask() { if(spanOTA.enabled) ArduinoOTA.handle(); - if(controlButton.primed()){ + if(controlButton && controlButton->primed()){ statusLED.start(LED_ALERT); } - if(controlButton.triggered(3000,10000)){ + if(controlButton && controlButton->triggered(3000,10000)){ statusLED.off(); - if(controlButton.type()==PushButton::LONG){ - controlButton.wait(); + if(controlButton->type()==PushButton::LONG){ + controlButton->wait(); processSerialCommand("F"); // FACTORY RESET } else { commandMode(); // COMMAND MODE @@ -341,8 +341,8 @@ void Span::commandMode(){ statusLED.start(LED_ALERT); delay(2000); } else - if(controlButton.triggered(10,3000)){ - if(controlButton.type()==PushButton::SINGLE){ + if(controlButton->triggered(10,3000)){ + if(controlButton->type()==PushButton::SINGLE){ mode++; if(mode==6) mode=1; @@ -354,7 +354,7 @@ void Span::commandMode(){ } // while statusLED.start(LED_ALERT); - controlButton.wait(); + controlButton->wait(); switch(mode){ diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 556f6a0..88d29c3 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -191,7 +191,7 @@ class Span{ const char *defaultSetupCode=DEFAULT_SETUP_CODE; // Setup Code used for pairing int statusPin=DEFAULT_STATUS_PIN; // pin for Status LED uint16_t autoOffLED=0; // automatic turn-off duration (in seconds) for Status LED - int controlPin=DEFAULT_CONTROL_PIN; // pin for Control Pushbutton +// int controlPin=DEFAULT_CONTROL_PIN; // pin for Control Pushbutton uint8_t logLevel=DEFAULT_LOG_LEVEL; // level for writing out log messages to serial monitor uint8_t maxConnections=CONFIG_LWIP_MAX_SOCKETS-2; // maximum number of allowed simultaneous HAP connections uint8_t requestedMaxCon=CONFIG_LWIP_MAX_SOCKETS-2; // requested maximum number of simultaneous HAP connections @@ -205,7 +205,7 @@ class Span{ WiFiServer *hapServer; // pointer to the HAP Server connection Blinker statusLED; // indicates HomeSpan status - PushButton controlButton; // controls HomeSpan configuration and resets + PushButton *controlButton = NULL; // controls HomeSpan configuration and resets Network network; // configures WiFi and Setup Code via either serial monitor or temporary Access Point SpanWebLog webLog; // optional web status/log TaskHandle_t pollTaskHandle = NULL; // optional task handle to use for poll() function @@ -255,10 +255,11 @@ class Span{ boolean updateDatabase(boolean updateMDNS=true); // updates HAP Configuration Number and Loop vector; if updateMDNS=true and config number has changed, re-broadcasts MDNS 'c#' record; returns true if config number changed boolean deleteAccessory(uint32_t aid); // deletes Accessory with matching aid; returns true if found, else returns false - void setControlPin(uint8_t pin){controlPin=pin;} // sets Control Pin + void setControlPin(uint8_t pin){controlButton=new PushButton(pin);} // sets Control Pin void setStatusPin(uint8_t pin){statusPin=pin;} // sets Status Pin void setStatusAutoOff(uint16_t duration){autoOffLED=duration;} // sets Status LED auto off (seconds) int getStatusPin(){return(statusPin);} // get Status Pin + int getControlPin(){return(controlButton?controlButton->getPin():-1);} // get Control Pin (returns -1 if undefined) void setApSSID(const char *ssid){network.apSSID=ssid;} // sets Access Point SSID void setApPassword(const char *pwd){network.apPassword=pwd;} // sets Access Point Password void setApTimeout(uint16_t nSec){network.lifetime=nSec*1000;} // sets Access Point Timeout (seconds) diff --git a/src/Network.cpp b/src/Network.cpp index 8b2ad91..cc1c102 100644 --- a/src/Network.cpp +++ b/src/Network.cpp @@ -152,10 +152,10 @@ void Network::apConfigure(){ while(1){ // loop until we get timed out (which will be accelerated if save/cancel selected) - if(homeSpan.controlButton.triggered(9999,3000)){ + if(homeSpan.controlButton && homeSpan.controlButton->triggered(9999,3000)){ Serial.print("\n*** Access Point Terminated."); homeSpan.statusLED.start(LED_ALERT); - homeSpan.controlButton.wait(); + homeSpan.controlButton->wait(); Serial.print(" Restarting... \n\n"); homeSpan.statusLED.off(); ESP.restart(); diff --git a/src/Settings.h b/src/Settings.h index c838dac..e04fefa 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -71,7 +71,7 @@ #define DEFAULT_QR_ID "HSPN" // change with homeSpan.setQRID(qrID); -#define DEFAULT_CONTROL_PIN -1 // change with homeSpan.setControlPin(pin) +//#define DEFAULT_CONTROL_PIN -1 // change with homeSpan.setControlPin(pin) #define DEFAULT_STATUS_PIN -1 // change with homeSpan.setStatusPin(pin) #define DEFAULT_AP_SSID "HomeSpan-Setup" // change with homeSpan.setApSSID(ssid) diff --git a/src/Utils.h b/src/Utils.h index ff8784f..18bf612 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -148,6 +148,10 @@ class PushButton{ // Waits for button to be released. Use after Long Press if button release confirmation is desired + int getPin(){return(pin);} + +// Returns pin number + }; //////////////////////////////// diff --git a/src/src.ino b/src/src.ino index aea281e..19e4ae3 100644 --- a/src/src.ino +++ b/src/src.ino @@ -13,9 +13,9 @@ void setup() { Serial.begin(115200); - homeSpan.setLogLevel(2); +// homeSpan.setLogLevel(2); // homeSpan.setStatusPin(13); -// homeSpan.setControlPin(33); + homeSpan.setControlPin(33); homeSpan.setHostNameSuffix("-lamp1"); homeSpan.setPortNum(1201); From 54b83745730bbe5bff40918833a3f407403fcfc7 Mon Sep 17 00:00:00 2001 From: Gregg Date: Thu, 11 Aug 2022 06:12:41 -0500 Subject: [PATCH 79/98] begin update of PushButton to make it more generic Eliminated constructor that had no pin parameter. Pin parameter is now always required. Since PushButton was not part of HomeSpan API, these changes should not impact any users. --- src/HomeSpan.h | 1 - src/Settings.h | 1 - src/Utils.cpp | 27 ++------------------------- src/Utils.h | 19 ++----------------- 4 files changed, 4 insertions(+), 44 deletions(-) diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 88d29c3..5f29530 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -191,7 +191,6 @@ class Span{ const char *defaultSetupCode=DEFAULT_SETUP_CODE; // Setup Code used for pairing int statusPin=DEFAULT_STATUS_PIN; // pin for Status LED uint16_t autoOffLED=0; // automatic turn-off duration (in seconds) for Status LED -// int controlPin=DEFAULT_CONTROL_PIN; // pin for Control Pushbutton uint8_t logLevel=DEFAULT_LOG_LEVEL; // level for writing out log messages to serial monitor uint8_t maxConnections=CONFIG_LWIP_MAX_SOCKETS-2; // maximum number of allowed simultaneous HAP connections uint8_t requestedMaxCon=CONFIG_LWIP_MAX_SOCKETS-2; // requested maximum number of simultaneous HAP connections diff --git a/src/Settings.h b/src/Settings.h index e04fefa..b64e020 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -71,7 +71,6 @@ #define DEFAULT_QR_ID "HSPN" // change with homeSpan.setQRID(qrID); -//#define DEFAULT_CONTROL_PIN -1 // change with homeSpan.setControlPin(pin) #define DEFAULT_STATUS_PIN -1 // change with homeSpan.setStatusPin(pin) #define DEFAULT_AP_SSID "HomeSpan-Setup" // change with homeSpan.setApSSID(ssid) diff --git a/src/Utils.cpp b/src/Utils.cpp index 282d0e0..98f06c7 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -85,22 +85,9 @@ String Utils::mask(char *c, int n){ // PushButton // //////////////////////////////// -PushButton::PushButton(){} - -////////////////////////////////////// - PushButton::PushButton(int pin){ - init(pin); -} - -////////////////////////////////////// - -void PushButton::init(int pin){ this->pin=pin; - if(pin<0) - return; - status=0; doubleCheck=false; pinMode(pin, INPUT_PULLUP); @@ -110,9 +97,6 @@ void PushButton::init(int pin){ boolean PushButton::triggered(uint16_t singleTime, uint16_t longTime, uint16_t doubleTime){ - if(pin<0) - return(false); - unsigned long cTime=millis(); switch(status){ @@ -189,10 +173,7 @@ boolean PushButton::triggered(uint16_t singleTime, uint16_t longTime, uint16_t d ////////////////////////////////////// boolean PushButton::primed(){ - - if(pin<0) - return(false); - + if(millis()>singleAlarm && status==1){ status=2; return(true); @@ -209,11 +190,7 @@ int PushButton::type(){ ////////////////////////////////////// -void PushButton::wait(){ - - if(pin<0) - return; - +void PushButton::wait(){ while(!digitalRead(pin)); } diff --git a/src/Utils.h b/src/Utils.h index 18bf612..040ea44 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -89,27 +89,12 @@ class PushButton{ LONG=2 }; - PushButton(); PushButton(int pin); -// Creates generic pushbutton functionality on specified pin -// that is wired to connect to ground when the button is pressed. -// -// In the first form, a PushButton is instantiated without specifying -// the pin. In this case the pin must be specified in a subsequent call -// to init() before the PushButton can be used. -// -// In the second form, a PushButton is instantiated and initialized with -// the specified pin, obviating the need for a separate call to init(). +// Creates generic pushbutton functionality on specified pin. // // pin: Pin mumber to which pushbutton connects to ground when pressed - - void init(int pin); - -// Initializes PushButton, if not configured during instantiation. -// -// pin: Pin mumber to which pushbutton connects to ground when pressed - + void reset(); // Resets state of PushButton. Should be called once before any loops that will From 7841081fda192a92de2f68fcba1ed342194012c9 Mon Sep 17 00:00:00 2001 From: Gregg Date: Fri, 12 Aug 2022 21:56:45 -0500 Subject: [PATCH 80/98] Added option to set SpanButton type Types include: GROUNDED, POWERED, and TOUCH. Also added configureTouch() to customize parameters used for touch sensors. --- src/HomeSpan.cpp | 6 ++++-- src/HomeSpan.h | 5 ++++- src/Utils.cpp | 38 ++++++++++++++++++++++++++++++-------- src/Utils.h | 21 +++++++++++++++++++-- src/src.ino | 7 +++++++ 5 files changed, 64 insertions(+), 13 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index cc47eb2..935c8ff 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -61,6 +61,8 @@ void Span::begin(Category catID, const char *displayName, const char *hostNameBa statusLED.init(statusPin,0,autoOffLED); + PushButton::configureTouch(4000,1000,10); // set default parameters for any touch-style pushbuttons + if(requestedMaxConServices.empty()){ Serial.printf("\nFATAL ERROR! Can't create new SpanButton(%d,%u,%u,%u) without a defined Service ***\n",pin,longTime,singleTime,doubleTime); @@ -1975,7 +1977,7 @@ SpanButton::SpanButton(int pin, uint16_t longTime, uint16_t singleTime, uint16_t this->doubleTime=doubleTime; service=homeSpan.Accessories.back()->Services.back(); - pushButton=new PushButton(pin); // create underlying PushButton + pushButton=new PushButton(pin,buttonType); // create underlying PushButton homeSpan.PushButtons.push_back(this); } diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 5f29530..9c2262d 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -713,7 +713,10 @@ class SpanButton{ LONG=2 }; - SpanButton(int pin, uint16_t longTime=2000, uint16_t singleTime=5, uint16_t doubleTime=200); + SpanButton(int pin, uint16_t longTime=2000, uint16_t singleTime=5, uint16_t doubleTime=200, Button buttonType=Button::GROUNDED); + SpanButton(int pin, Button buttonType, uint16_t longTime=2000, uint16_t singleTime=5, uint16_t doubleTime=200) : SpanButton(pin,longTime,singleTime,doubleTime,buttonType){}; + + static void configureTouch(uint16_t measureTime, uint16_t sleepTime, uint16_t thresh){PushButton::configureTouch(measureTime,sleepTime,thresh);} }; /////////////////////////////// diff --git a/src/Utils.cpp b/src/Utils.cpp index 98f06c7..12cd87b 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -85,12 +85,30 @@ String Utils::mask(char *c, int n){ // PushButton // //////////////////////////////// -PushButton::PushButton(int pin){ +PushButton::PushButton(int pin, Button buttonType){ this->pin=pin; status=0; doubleCheck=false; - pinMode(pin, INPUT_PULLUP); + + switch(buttonType){ + + case Button::GROUNDED: + pinMode(pin, INPUT_PULLUP); + pressed=groundedButton; + break; + + case Button::POWERED: + pinMode(pin, INPUT_PULLDOWN); + pressed=poweredButton; + break; + + case Button::TOUCH: + pressed=touchButton; + break; + + }; + } ////////////////////////////////////// @@ -108,7 +126,7 @@ boolean PushButton::triggered(uint16_t singleTime, uint16_t longTime, uint16_t d return(true); } - if(!digitalRead(pin)){ // button is pressed + if(pressed(pin)){ // button is pressed singleAlarm=cTime+singleTime; if(!doubleCheck){ status=1; @@ -122,7 +140,7 @@ boolean PushButton::triggered(uint16_t singleTime, uint16_t longTime, uint16_t d case 1: case 2: - if(digitalRead(pin)){ // button is released + if(!pressed(pin)){ // button is released status=0; if(cTime>singleAlarm){ doubleCheck=true; @@ -138,7 +156,7 @@ boolean PushButton::triggered(uint16_t singleTime, uint16_t longTime, uint16_t d break; case 3: - if(digitalRead(pin)) // button has been released after a long press + if(!pressed(pin)) // button has been released after a long press status=0; else if(cTime>longAlarm){ longAlarm=cTime+longTime; @@ -148,7 +166,7 @@ boolean PushButton::triggered(uint16_t singleTime, uint16_t longTime, uint16_t d break; case 4: - if(digitalRead(pin)){ // button is released + if(!pressed(pin)){ // button is released status=0; } else @@ -161,7 +179,7 @@ boolean PushButton::triggered(uint16_t singleTime, uint16_t longTime, uint16_t d break; case 5: - if(digitalRead(pin)) // button has been released after double-click + if(!pressed(pin)) // button has been released after double-click status=0; break; @@ -191,7 +209,7 @@ int PushButton::type(){ ////////////////////////////////////// void PushButton::wait(){ - while(!digitalRead(pin)); + while(pressed(pin)); } ////////////////////////////////////// @@ -200,6 +218,10 @@ void PushButton::reset(){ status=0; } +////////////////////////////////////// + +uint16_t PushButton::touchThreshold; + //////////////////////////////// // Blinker // //////////////////////////////// diff --git a/src/Utils.h b/src/Utils.h index 040ea44..a9f288c 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -37,6 +37,12 @@ String mask(char *c, int n); // simply utility that creates a String fr } +enum class Button { + GROUNDED, + POWERED, + TOUCH +}; + ///////////////////////////////////////////////// // Creates a temporary buffer that is freed after // going out of scope @@ -80,7 +86,14 @@ class PushButton{ uint32_t doubleAlarm; uint32_t longAlarm; int pressType; + boolean (*pressed)(int pin); + + static uint16_t touchThreshold; + static boolean groundedButton(int pin){return(!digitalRead(pin));} + static boolean poweredButton(int pin){return(digitalRead(pin));} + static boolean touchButton(int pin){return(touchRead(pin) Date: Fri, 12 Aug 2022 23:15:02 -0500 Subject: [PATCH 81/98] Adding generic SpanButton function --- src/HomeSpan.cpp | 8 +++----- src/HomeSpan.h | 6 ++---- src/Utils.cpp | 13 +++++++++++++ src/Utils.h | 26 +++++++++++++++++++++----- 4 files changed, 39 insertions(+), 14 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 935c8ff..67343b3 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -1963,7 +1963,7 @@ SpanRange::SpanRange(int min, int max, int step){ // SpanButton // /////////////////////////////// -SpanButton::SpanButton(int pin, uint16_t longTime, uint16_t singleTime, uint16_t doubleTime, Button buttonType){ +SpanButton::SpanButton(int pin, uint16_t longTime, uint16_t singleTime, uint16_t doubleTime, Button buttonType) : PushButton(pin, buttonType){ if(homeSpan.Accessories.empty() || homeSpan.Accessories.back()->Services.empty()){ Serial.printf("\nFATAL ERROR! Can't create new SpanButton(%d,%u,%u,%u) without a defined Service ***\n",pin,longTime,singleTime,doubleTime); @@ -1971,13 +1971,11 @@ SpanButton::SpanButton(int pin, uint16_t longTime, uint16_t singleTime, uint16_t while(1); } - this->pin=pin; this->longTime=longTime; this->singleTime=singleTime; this->doubleTime=doubleTime; service=homeSpan.Accessories.back()->Services.back(); - pushButton=new PushButton(pin,buttonType); // create underlying PushButton homeSpan.PushButtons.push_back(this); } @@ -1985,8 +1983,8 @@ SpanButton::SpanButton(int pin, uint16_t longTime, uint16_t singleTime, uint16_t void SpanButton::check(){ - if(pushButton->triggered(singleTime,longTime,doubleTime)) // if the underlying PushButton is triggered - service->button(pin,pushButton->type()); // call the Service's button() routine with pin and type as parameters + if(triggered(singleTime,longTime,doubleTime)) // if the underlying PushButton is triggered + service->button(pin,type()); // call the Service's button() routine with pin and type as parameters } /////////////////////////////// diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 9c2262d..6f05a30 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -691,17 +691,15 @@ struct [[deprecated("Please use Characteristic::setRange() method instead.")]] S /////////////////////////////// -class SpanButton{ +class SpanButton : PushButton { friend class Span; friend class SpanService; - int pin; // pin number uint16_t singleTime; // minimum time (in millis) required to register a single press uint16_t longTime; // minimum time (in millis) required to register a long press uint16_t doubleTime; // maximum time (in millis) between single presses to register a double press instead SpanService *service; // Service to which this PushButton is attached - PushButton *pushButton; // PushButton associated with this SpanButton void check(); // check PushButton and call button() if pressed @@ -715,8 +713,8 @@ class SpanButton{ SpanButton(int pin, uint16_t longTime=2000, uint16_t singleTime=5, uint16_t doubleTime=200, Button buttonType=Button::GROUNDED); SpanButton(int pin, Button buttonType, uint16_t longTime=2000, uint16_t singleTime=5, uint16_t doubleTime=200) : SpanButton(pin,longTime,singleTime,doubleTime,buttonType){}; + SpanButton(int pin, boolean (*pressed)(int pin), uint16_t longTime=2000, uint16_t singleTime=5, uint16_t doubleTime=200) : SpanButton(pin,longTime,singleTime,doubleTime,Button::CUSTOM){this->pressed=pressed;}; - static void configureTouch(uint16_t measureTime, uint16_t sleepTime, uint16_t thresh){PushButton::configureTouch(measureTime,sleepTime,thresh);} }; /////////////////////////////// diff --git a/src/Utils.cpp b/src/Utils.cpp index 12cd87b..625aea9 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -113,6 +113,12 @@ PushButton::PushButton(int pin, Button buttonType){ ////////////////////////////////////// +PushButton::PushButton(int pin, boolean (*pressed)(int pin)) : PushButton(pin, Button::CUSTOM){ + this->pressed=pressed; +} + +////////////////////////////////////// + boolean PushButton::triggered(uint16_t singleTime, uint16_t longTime, uint16_t doubleTime){ unsigned long cTime=millis(); @@ -220,6 +226,13 @@ void PushButton::reset(){ ////////////////////////////////////// +void PushButton::configureTouch(uint16_t measureTime, uint16_t sleepTime, uint16_t thresh){ + touchSetCycles(measureTime,sleepTime); + touchThreshold=thresh; +} + +////////////////////////////////////// + uint16_t PushButton::touchThreshold; //////////////////////////////// diff --git a/src/Utils.h b/src/Utils.h index a9f288c..5f84f51 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -40,7 +40,8 @@ String mask(char *c, int n); // simply utility that creates a String fr enum class Button { GROUNDED, POWERED, - TOUCH + TOUCH, + CUSTOM }; ///////////////////////////////////////////////// @@ -80,13 +81,11 @@ struct TempBuffer { class PushButton{ int status; - int pin; boolean doubleCheck; uint32_t singleAlarm; uint32_t doubleAlarm; uint32_t longAlarm; int pressType; - boolean (*pressed)(int pin); static uint16_t touchThreshold; @@ -94,6 +93,11 @@ class PushButton{ static boolean poweredButton(int pin){return(digitalRead(pin));} static boolean touchButton(int pin){return(touchRead(pin) Date: Sat, 13 Aug 2022 07:52:32 -0500 Subject: [PATCH 82/98] Finalized new SpanButton and PushButton functionality Streamlined interface for both functions. To Do: Update SpanButton documentation --- src/HomeSpan.cpp | 2 +- src/HomeSpan.h | 5 ++--- src/Utils.cpp | 30 ++++++------------------------ src/Utils.h | 36 ++++++++++++++---------------------- 4 files changed, 23 insertions(+), 50 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 67343b3..0e021e9 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -1963,7 +1963,7 @@ SpanRange::SpanRange(int min, int max, int step){ // SpanButton // /////////////////////////////// -SpanButton::SpanButton(int pin, uint16_t longTime, uint16_t singleTime, uint16_t doubleTime, Button buttonType) : PushButton(pin, buttonType){ +SpanButton::SpanButton(int pin, uint16_t longTime, uint16_t singleTime, uint16_t doubleTime, pressTest_t pressed) : PushButton(pin, pressed){ if(homeSpan.Accessories.empty() || homeSpan.Accessories.back()->Services.empty()){ Serial.printf("\nFATAL ERROR! Can't create new SpanButton(%d,%u,%u,%u) without a defined Service ***\n",pin,longTime,singleTime,doubleTime); diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 6f05a30..ad1628a 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -711,9 +711,8 @@ class SpanButton : PushButton { LONG=2 }; - SpanButton(int pin, uint16_t longTime=2000, uint16_t singleTime=5, uint16_t doubleTime=200, Button buttonType=Button::GROUNDED); - SpanButton(int pin, Button buttonType, uint16_t longTime=2000, uint16_t singleTime=5, uint16_t doubleTime=200) : SpanButton(pin,longTime,singleTime,doubleTime,buttonType){}; - SpanButton(int pin, boolean (*pressed)(int pin), uint16_t longTime=2000, uint16_t singleTime=5, uint16_t doubleTime=200) : SpanButton(pin,longTime,singleTime,doubleTime,Button::CUSTOM){this->pressed=pressed;}; + SpanButton(int pin, uint16_t longTime=2000, uint16_t singleTime=5, uint16_t doubleTime=200, pressTest_t pressed=GROUNDED); + SpanButton(int pin, pressTest_t pressed=GROUNDED, uint16_t longTime=2000, uint16_t singleTime=5, uint16_t doubleTime=200) : SpanButton(pin,longTime,singleTime,doubleTime,pressed){}; }; diff --git a/src/Utils.cpp b/src/Utils.cpp index 625aea9..f75dbca 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -85,40 +85,22 @@ String Utils::mask(char *c, int n){ // PushButton // //////////////////////////////// -PushButton::PushButton(int pin, Button buttonType){ +PushButton::PushButton(int pin, pressTest_t pressed){ this->pin=pin; + this->pressed=pressed; status=0; doubleCheck=false; - switch(buttonType){ - - case Button::GROUNDED: - pinMode(pin, INPUT_PULLUP); - pressed=groundedButton; - break; - - case Button::POWERED: - pinMode(pin, INPUT_PULLDOWN); - pressed=poweredButton; - break; - - case Button::TOUCH: - pressed=touchButton; - break; - - }; + if(pressed==GROUNDED) + pinMode(pin, INPUT_PULLUP); + else if(pressed==POWERED) + pinMode(pin, INPUT_PULLDOWN); } ////////////////////////////////////// -PushButton::PushButton(int pin, boolean (*pressed)(int pin)) : PushButton(pin, Button::CUSTOM){ - this->pressed=pressed; -} - -////////////////////////////////////// - boolean PushButton::triggered(uint16_t singleTime, uint16_t longTime, uint16_t doubleTime){ unsigned long cTime=millis(); diff --git a/src/Utils.h b/src/Utils.h index 5f84f51..fbcdda3 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -37,13 +37,6 @@ String mask(char *c, int n); // simply utility that creates a String fr } -enum class Button { - GROUNDED, - POWERED, - TOUCH, - CUSTOM -}; - ///////////////////////////////////////////////// // Creates a temporary buffer that is freed after // going out of scope @@ -88,15 +81,13 @@ class PushButton{ int pressType; static uint16_t touchThreshold; - - static boolean groundedButton(int pin){return(!digitalRead(pin));} - static boolean poweredButton(int pin){return(digitalRead(pin));} - static boolean touchButton(int pin){return(touchRead(pin) Date: Sat, 13 Aug 2022 10:06:33 -0500 Subject: [PATCH 83/98] Added check to ignore PushButton::TOUCH is compiling on ESP32-C3 The C3 does not have touch capability. Compiler will throw an error is PushButton::TOUCH is referenced when compiling for C3. --- src/HomeSpan.h | 2 +- src/Utils.cpp | 2 ++ src/Utils.h | 3 +++ src/src.ino | 7 ------- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/HomeSpan.h b/src/HomeSpan.h index ad1628a..666d943 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -712,7 +712,7 @@ class SpanButton : PushButton { }; SpanButton(int pin, uint16_t longTime=2000, uint16_t singleTime=5, uint16_t doubleTime=200, pressTest_t pressed=GROUNDED); - SpanButton(int pin, pressTest_t pressed=GROUNDED, uint16_t longTime=2000, uint16_t singleTime=5, uint16_t doubleTime=200) : SpanButton(pin,longTime,singleTime,doubleTime,pressed){}; + SpanButton(int pin, pressTest_t pressed, uint16_t longTime=2000, uint16_t singleTime=5, uint16_t doubleTime=200) : SpanButton(pin,longTime,singleTime,doubleTime,pressed){}; }; diff --git a/src/Utils.cpp b/src/Utils.cpp index f75dbca..48ac668 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -209,8 +209,10 @@ void PushButton::reset(){ ////////////////////////////////////// void PushButton::configureTouch(uint16_t measureTime, uint16_t sleepTime, uint16_t thresh){ +#ifndef CONFIG_IDF_TARGET_ESP32C3 touchSetCycles(measureTime,sleepTime); touchThreshold=thresh; +#endif } ////////////////////////////////////// diff --git a/src/Utils.h b/src/Utils.h index fbcdda3..82e2a2b 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -99,7 +99,10 @@ class PushButton{ static boolean GROUNDED(int pin){return(!digitalRead(pin));} static boolean POWERED(int pin){return(digitalRead(pin));} + +#ifndef CONFIG_IDF_TARGET_ESP32C3 static boolean TOUCH(int pin){return(touchRead(pin) Date: Sun, 14 Aug 2022 18:03:59 -0500 Subject: [PATCH 84/98] Incorporated logic to auto-calibrate touch sensors Works with ESP32, ESP32-S2, and ESP32-S3 (ESP32-C3 does not support Touch Sensors). --- src/HomeSpan.cpp | 2 -- src/Utils.cpp | 23 ++++++++++++++++++++--- src/Utils.h | 13 +++++++++---- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 0e021e9..2a00d7c 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -61,8 +61,6 @@ void Span::begin(Category catID, const char *displayName, const char *hostNameBa statusLED.init(statusPin,0,autoOffLED); - PushButton::configureTouch(4000,1000,10); // set default parameters for any touch-style pushbuttons - if(requestedMaxCon 0 + else if (pressed==TOUCH && threshold==0){ + for(int i=0;i %d.\n",pin,threshold); +#endif + } +#endif } @@ -209,15 +224,17 @@ void PushButton::reset(){ ////////////////////////////////////// void PushButton::configureTouch(uint16_t measureTime, uint16_t sleepTime, uint16_t thresh){ -#ifndef CONFIG_IDF_TARGET_ESP32C3 +#if SOC_TOUCH_SENSOR_NUM > 0 touchSetCycles(measureTime,sleepTime); - touchThreshold=thresh; + threshold=thresh; #endif } ////////////////////////////////////// -uint16_t PushButton::touchThreshold; +#if SOC_TOUCH_SENSOR_NUM > 0 + touch_value_t PushButton::threshold=0; +#endif //////////////////////////////// // Blinker // diff --git a/src/Utils.h b/src/Utils.h index 82e2a2b..38ec7c0 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -79,8 +79,11 @@ class PushButton{ uint32_t doubleAlarm; uint32_t longAlarm; int pressType; - - static uint16_t touchThreshold; + +#if SOC_TOUCH_SENSOR_NUM > 0 + static touch_value_t threshold; + static const int calibCount=20; +#endif protected: @@ -100,8 +103,10 @@ class PushButton{ static boolean GROUNDED(int pin){return(!digitalRead(pin));} static boolean POWERED(int pin){return(digitalRead(pin));} -#ifndef CONFIG_IDF_TARGET_ESP32C3 - static boolean TOUCH(int pin){return(touchRead(pin)threshold);} #endif PushButton(int pin, pressTest_t pressed=GROUNDED); From 16b71af6f77531880cc2ceef043f412f44150e46 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 14 Aug 2022 18:14:41 -0500 Subject: [PATCH 85/98] Added button type to SpanButton HomeSpan Info --- src/HomeSpan.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 2a00d7c..95911b8 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -993,7 +993,18 @@ void Span::processSerialCommand(const char *c){ for(auto button=PushButtons.begin(); button!=PushButtons.end(); button++){ if((*button)->service==(*svc)){ - Serial.printf(" \u25bc SpanButton: Pin=%d, Single=%ums, Double=%ums, Long=%ums\n",(*button)->pin,(*button)->singleTime,(*button)->doubleTime,(*button)->longTime); + Serial.printf(" \u25bc SpanButton: Pin=%d, Single=%ums, Double=%ums, Long=%ums, Type=",(*button)->pin,(*button)->singleTime,(*button)->doubleTime,(*button)->longTime); + if((*button)->pressed==PushButton::GROUNDED) + Serial.printf("GROUNDED\n"); + else if((*button)->pressed==PushButton::POWERED) + Serial.printf("POWERED\n"); + +#if SOC_TOUCH_SENSOR_NUM > 0 + else if((*button)->pressed==PushButton::TOUCH) + Serial.printf("TOUCH\n"); +#endif + else + Serial.printf("USER-DEFINED\n"); if((void(*)(int,int))((*svc)->*(&SpanService::button))==(void(*)(int,int))(&SpanService::button)) Serial.printf(" *** WARNING! No button() method defined in this Service ***\n",nWarnings++); From 754ffc7f345b382c3606199eba84bd681aca74b8 Mon Sep 17 00:00:00 2001 From: Gregg Date: Mon, 15 Aug 2022 07:15:19 -0500 Subject: [PATCH 86/98] Added PushButton::setTouchThreshold() and PushButton::setTouchCycles() Not implemented for ESP32-C3 --- src/Utils.cpp | 9 --------- src/Utils.h | 16 +++++++++++++--- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/Utils.cpp b/src/Utils.cpp index 34e5c27..8ebd7cf 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -223,15 +223,6 @@ void PushButton::reset(){ ////////////////////////////////////// -void PushButton::configureTouch(uint16_t measureTime, uint16_t sleepTime, uint16_t thresh){ -#if SOC_TOUCH_SENSOR_NUM > 0 - touchSetCycles(measureTime,sleepTime); - threshold=thresh; -#endif -} - -////////////////////////////////////// - #if SOC_TOUCH_SENSOR_NUM > 0 touch_value_t PushButton::threshold=0; #endif diff --git a/src/Utils.h b/src/Utils.h index 38ec7c0..43e1d44 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -162,13 +162,23 @@ class PushButton{ // Returns pin number - static void configureTouch(uint16_t measureTime, uint16_t sleepTime, uint16_t thresh); +#if SOC_TOUCH_SENSOR_NUM > 0 -// Sets the measure time, sleep time, and lower threshold that triggers a touch - used only when buttonType=Button::TOUCH + static void setTouchCycles(uint16_t measureTime, uint16_t sleepTime){touchSetCycles(measureTime,sleepTime);} + +// Sets the measure time and sleep time touch cycles , and lower threshold that triggers a touch - used only when buttonType=Button::TOUCH // measureTime: duration of measurement time of all touch sensors in number of clock cycles // sleepTime: duration of sleep time (between measurements) of all touch sensors number of clock cycles -// touchThreshhold: the read value of touch sensors, below which sensors are considered touched (i.e. "pressed") + + static void setTouchThreshold(touch_value_t thresh){threshold=thresh;} + +// Sets the threshold that triggers a touch - used only when buttonType=Button::TOUCH + +// thresh: the read value of touch sensors, beyond which which sensors are considered touched (i.e. "pressed"). +// This is a class-level value applied to all touch sensor buttons. + +#endif }; From 7b93f6d33b536da59eda7d3220065e668ff2f918 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Mon, 15 Aug 2022 08:53:59 -0500 Subject: [PATCH 87/98] Update Reference.md --- docs/Reference.md | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/docs/Reference.md b/docs/Reference.md index 0d750f4..6f54d94 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -351,7 +351,7 @@ This is a **base class** from which all HomeSpan Characteristics are derived, an * returns a pointer to the Characteristic itself so that the method can be chained during instantiation * example: `(new Characteristic::RotationSpeed())->setUnit("percentage");` -## *SpanButton(int pin, uint16_t longTime, uint16_t singleTime, uint16_t doubleTime)* +### *SpanButton(int pin, uint16_t longTime, uint16_t singleTime, uint16_t doubleTime, buttonType)* Creating an instance of this **class** attaches a pushbutton handler to the ESP32 *pin* specified. @@ -359,11 +359,20 @@ Creating an instance of this **class** attaches a pushbutton handler to the ESP3 * instantiating a Button without first instantiating a Service throws an error during initialization The first argument is required; the rest are optional: -* *pin* - the ESP32 pin to which a one pole of a normally-open pushbutton will be connected; the other pole is connected to ground + +* *pin* - the ESP32 pin to which the button is connected * *longTime* - the minimum time (in millis) required for the button to be pressed and held to trigger a Long Press (default=2000 ms) * *singleTime* - the minimum time (in millis) required for the button to be pressed to trigger a Single Press (default=5 ms) * *doubleTime* - the maximum time (in millis) allowed between two Single Presses to qualify as a Double Press (default=200 ms) - +* *buttonType* - specifies the type of pushbutton. Choices are: + * `PushButton::GROUNDED` - a button that connects *pin* to GROUND when pushed (this is the default) + * `PushButton::POWERED` - a button that connects *pin* to +3.3V when pushed + * `PushButton::TOUCH`- a button that is connected to a touch sensor (not available on ESP32-C3) + +A second form of *SpanButton()* is provided for convenience: + * `SpanButton(int pin, buttonType, uint16_t longTime=2000, uint16_t singleTime=5, uint16_t doubleTime=200)` + * this allows you to set the *pin* and *buttonType* while keeping the other parameters optional + Trigger Rules: * If button is pressed and continuously held, a Long Press will be triggered every longTime ms until the button is released * If button is pressed for more than singleTime ms but less than longTime ms and then released, a Single Press will be triggered, UNLESS the button is pressed a second time within doubleTime ms AND held again for at least singleTime ms, in which case a DoublePress will be triggered; no further events will occur until the button is released @@ -376,6 +385,19 @@ HomeSpan automatically calls the `button(int pin, int pressType)` method of a Se * 2=long press (SpanButton::LONG) HomeSpan will report a warning, but not an error, during initialization if the user had not overridden the virtual button() method for a Service contaning one or more Buttons; triggers of those Buttons will simply ignored. + +When using one or more Touch Sensors, HomeSpan automatically calibrates the threshold at which they are triggered by polling the baseline sensor reading upon instantiation of first SpanButton of type `PushButton::TOUCH` found. For ESP32 devices, the threshold is set to 50% of the baseline value since triggers occur when a sensor value falls *below* the threhold level. For ESP32-S2 and ESP32-S3 devices, the threshold is set to 200% of the baseline value since triggers occur when a sensor value rises *above* the threhold level. Normally HomeSpan's auto calibration will result in accurate detection of SINGLE, DOUBLE, and LONG presses of touch sensors. However, if needed you can override the calibration and set your own threshold value using the following class-level method: + + * `void PushButton::setTouchThreshold(thresh)` + * sets the threshold value at which touch sensors are triggered to *thresh*, where *thresh* is either of type *uint16_t* (for ESP32 devices) or *uint32_t* (for ESP32-S2 and ESP32-S3 devices) + * the threshold specified is used for all SpanButton instances of type `PushButton::TOUCH` + * this method can be called either before or after SpanButtons are created + +In addition, you can also override the ESP32's touch sensor timing parameters using the following class-level method: + +* `void PushButton::setTouchCycles(uint16_t measureTime, uint16_t sleepTime)` + * changes the measurement time and sleep time clock cycles to *measureTime* and *sleepTime*, respectively. This is simply a pass-though call to the Arduino-ESP32 library `touchSetCycles()` function + * this method should be called *before* instantiating the first SpanButton() of type `PushButton::TOUCH` so that HomeSpan will calibrate the touch threshold based on the new timing parameters specified ### *SpanUserCommand(char c, const char \*desc, void (\*f)(const char \*buf [,void \*obj]) [,void \*userObject])* From 75e3df97f5c7271e00c2a54159a8cadd57a6d62d Mon Sep 17 00:00:00 2001 From: Gregg Date: Mon, 15 Aug 2022 18:43:04 -0500 Subject: [PATCH 88/98] Bumped version number to 1.6.0 --- src/Settings.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Settings.h b/src/Settings.h index b64e020..73225ad 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -35,8 +35,8 @@ // HomeSpan Version // #define HS_MAJOR 1 -#define HS_MINOR 5 -#define HS_PATCH 2 +#define HS_MINOR 6 +#define HS_PATCH 0 #define STRINGIFY(x) _STR(x) #define _STR(x) #x @@ -56,7 +56,7 @@ #define ARDUINO_ESP_VERSION STRINGIFY(ARDUINO_ESP32_GIT_DESC) #if ESP_ARDUINO_VERSION_MAJOR<2 - #error THIS SKETCH REQUIRES VERSION 2 OF THE ARDUINO ESP32 LIBRARY + #error HOMESPAN REQUIRES VERSION 2 OF THE ARDUINO ESP32 LIBRARY #endif ////////////////////////////////////////////////////// From 3b2c36f179f90747c44e39258d8d8d594dafa6da Mon Sep 17 00:00:00 2001 From: Gregg Date: Mon, 15 Aug 2022 21:21:21 -0500 Subject: [PATCH 89/98] Changed PushButton::pressType_t to triggerType_t and refreshed triggerType_t constants Pre-selects are no defined in both PushButton and SpanButton as: TRIGGER_ON_LOW, TRIGGER_ON_HIGH, and TRIGGER_ON_TOUCH --- src/HomeSpan.cpp | 14 +++++++------- src/HomeSpan.h | 11 +++++++++-- src/Utils.cpp | 22 +++++++++++----------- src/Utils.h | 30 +++++++++++++++--------------- 4 files changed, 42 insertions(+), 35 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 95911b8..bba3091 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -994,14 +994,14 @@ void Span::processSerialCommand(const char *c){ for(auto button=PushButtons.begin(); button!=PushButtons.end(); button++){ if((*button)->service==(*svc)){ Serial.printf(" \u25bc SpanButton: Pin=%d, Single=%ums, Double=%ums, Long=%ums, Type=",(*button)->pin,(*button)->singleTime,(*button)->doubleTime,(*button)->longTime); - if((*button)->pressed==PushButton::GROUNDED) - Serial.printf("GROUNDED\n"); - else if((*button)->pressed==PushButton::POWERED) - Serial.printf("POWERED\n"); + if((*button)->triggerType==PushButton::TRIGGER_ON_LOW) + Serial.printf("TRIGGER_ON_LOW\n"); + else if((*button)->triggerType==PushButton::TRIGGER_ON_HIGH) + Serial.printf("TRIGGER_ON_HIGH\n"); #if SOC_TOUCH_SENSOR_NUM > 0 - else if((*button)->pressed==PushButton::TOUCH) - Serial.printf("TOUCH\n"); + else if((*button)->triggerType==PushButton::TRIGGER_ON_TOUCH) + Serial.printf("TRIGGER_ON_TOUCH\n"); #endif else Serial.printf("USER-DEFINED\n"); @@ -1972,7 +1972,7 @@ SpanRange::SpanRange(int min, int max, int step){ // SpanButton // /////////////////////////////// -SpanButton::SpanButton(int pin, uint16_t longTime, uint16_t singleTime, uint16_t doubleTime, pressTest_t pressed) : PushButton(pin, pressed){ +SpanButton::SpanButton(int pin, uint16_t longTime, uint16_t singleTime, uint16_t doubleTime, triggerType_t triggerType) : PushButton(pin, triggerType){ if(homeSpan.Accessories.empty() || homeSpan.Accessories.back()->Services.empty()){ Serial.printf("\nFATAL ERROR! Can't create new SpanButton(%d,%u,%u,%u) without a defined Service ***\n",pin,longTime,singleTime,doubleTime); diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 666d943..9111fc0 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -710,9 +710,16 @@ class SpanButton : PushButton { DOUBLE=1, LONG=2 }; + + static constexpr triggerType_t TRIGGER_ON_LOW=PushButton::TRIGGER_ON_LOW; + static constexpr triggerType_t TRIGGER_ON_HIGH=PushButton::TRIGGER_ON_HIGH; + +#if SOC_TOUCH_SENSOR_NUM > 0 + static constexpr triggerType_t TRIGGER_ON_TOUCH=PushButton::TRIGGER_ON_TOUCH; +#endif - SpanButton(int pin, uint16_t longTime=2000, uint16_t singleTime=5, uint16_t doubleTime=200, pressTest_t pressed=GROUNDED); - SpanButton(int pin, pressTest_t pressed, uint16_t longTime=2000, uint16_t singleTime=5, uint16_t doubleTime=200) : SpanButton(pin,longTime,singleTime,doubleTime,pressed){}; + SpanButton(int pin, uint16_t longTime=2000, uint16_t singleTime=5, uint16_t doubleTime=200, triggerType_t triggerType=TRIGGER_ON_LOW); + SpanButton(int pin, triggerType_t triggerType, uint16_t longTime=2000, uint16_t singleTime=5, uint16_t doubleTime=200) : SpanButton(pin,longTime,singleTime,doubleTime,triggerType){}; }; diff --git a/src/Utils.cpp b/src/Utils.cpp index 8ebd7cf..5997e28 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -85,20 +85,20 @@ String Utils::mask(char *c, int n){ // PushButton // //////////////////////////////// -PushButton::PushButton(int pin, pressTest_t pressed){ +PushButton::PushButton(int pin, triggerType_t triggerType){ this->pin=pin; - this->pressed=pressed; + this->triggerType=triggerType; status=0; doubleCheck=false; - if(pressed==GROUNDED) + if(triggerType==TRIGGER_ON_LOW) pinMode(pin, INPUT_PULLUP); - else if(pressed==POWERED) + else if(triggerType==TRIGGER_ON_HIGH) pinMode(pin, INPUT_PULLDOWN); #if SOC_TOUCH_SENSOR_NUM > 0 - else if (pressed==TOUCH && threshold==0){ + else if (triggerType==TRIGGER_ON_TOUCH && threshold==0){ for(int i=0;isingleAlarm){ doubleCheck=true; @@ -159,7 +159,7 @@ boolean PushButton::triggered(uint16_t singleTime, uint16_t longTime, uint16_t d break; case 3: - if(!pressed(pin)) // button has been released after a long press + if(!triggerType(pin)) // button has been released after a long press status=0; else if(cTime>longAlarm){ longAlarm=cTime+longTime; @@ -169,7 +169,7 @@ boolean PushButton::triggered(uint16_t singleTime, uint16_t longTime, uint16_t d break; case 4: - if(!pressed(pin)){ // button is released + if(!triggerType(pin)){ // button is released status=0; } else @@ -182,7 +182,7 @@ boolean PushButton::triggered(uint16_t singleTime, uint16_t longTime, uint16_t d break; case 5: - if(!pressed(pin)) // button has been released after double-click + if(!triggerType(pin)) // button has been released after double-click status=0; break; @@ -212,7 +212,7 @@ int PushButton::type(){ ////////////////////////////////////// void PushButton::wait(){ - while(pressed(pin)); + while(triggerType(pin)); } ////////////////////////////////////// diff --git a/src/Utils.h b/src/Utils.h index 43e1d44..b32922c 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -87,10 +87,10 @@ class PushButton{ protected: - typedef boolean (*pressTest_t)(int pin); + typedef boolean (*triggerType_t)(int pin); int pin; - pressTest_t pressed; + triggerType_t triggerType; public: @@ -100,25 +100,25 @@ class PushButton{ LONG=2 }; - static boolean GROUNDED(int pin){return(!digitalRead(pin));} - static boolean POWERED(int pin){return(digitalRead(pin));} + static boolean TRIGGER_ON_LOW(int pin){return(!digitalRead(pin));} + static boolean TRIGGER_ON_HIGH(int pin){return(digitalRead(pin));} #if SOC_TOUCH_VERSION_1 // ESP32 - static boolean TOUCH(int pin){return(touchRead(pin)threshold);} + static boolean TRIGGER_ON_TOUCH(int pin){return(touchRead(pin)>threshold);} #endif - PushButton(int pin, pressTest_t pressed=GROUNDED); + PushButton(int pin, triggerType_t triggerType=TRIGGER_ON_LOW); // Creates pushbutton of specified type on specified pin // -// pin: pin number to which the button is connected -// pressed: a function of of the form 'boolean f(int)' that is passed -// the parameter *pin* and returns TRUE if the button associated -// with *pin* is pressed, or FALSE if not. Can choose from 3 pre-specifed -// pressTest_t functions (GROUNDED, POWERED, and TOUCH), or write your -// own custom handler +// pin: pin number to which the button is connected +// triggerType: a function of of the form 'boolean f(int)' that is passed +// the parameter *pin* and returns TRUE if the button associated +// with *pin* is pressed, or FALSE if not. Can choose from 3 pre-specifed +// triggerType_t functions (TRIGGER_ON_LOW, TRIGGER_ON_HIGH, and TRIGGER_ON_TOUCH), or write your +// own custom handler void reset(); @@ -166,14 +166,14 @@ class PushButton{ static void setTouchCycles(uint16_t measureTime, uint16_t sleepTime){touchSetCycles(measureTime,sleepTime);} -// Sets the measure time and sleep time touch cycles , and lower threshold that triggers a touch - used only when buttonType=Button::TOUCH +// Sets the measure time and sleep time touch cycles , and lower threshold that triggers a touch - used only when triggerType=PushButton::TRIGGER_ON_TOUCH // measureTime: duration of measurement time of all touch sensors in number of clock cycles // sleepTime: duration of sleep time (between measurements) of all touch sensors number of clock cycles static void setTouchThreshold(touch_value_t thresh){threshold=thresh;} -// Sets the threshold that triggers a touch - used only when buttonType=Button::TOUCH +// Sets the threshold that triggers a touch - used only when triggerType=TRIGGER_ON_TOUCH // thresh: the read value of touch sensors, beyond which which sensors are considered touched (i.e. "pressed"). // This is a class-level value applied to all touch sensor buttons. From 6109eabf14ab4f3d29d6ef3fec41ba3608b36164 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Mon, 15 Aug 2022 21:47:10 -0500 Subject: [PATCH 90/98] Update Reference.md --- docs/Reference.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/Reference.md b/docs/Reference.md index 6f54d94..10f50b9 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -351,7 +351,7 @@ This is a **base class** from which all HomeSpan Characteristics are derived, an * returns a pointer to the Characteristic itself so that the method can be chained during instantiation * example: `(new Characteristic::RotationSpeed())->setUnit("percentage");` -### *SpanButton(int pin, uint16_t longTime, uint16_t singleTime, uint16_t doubleTime, buttonType)* +### *SpanButton(int pin, uint16_t longTime, uint16_t singleTime, uint16_t doubleTime, boolean *triggerType(int))* Creating an instance of this **class** attaches a pushbutton handler to the ESP32 *pin* specified. @@ -364,14 +364,15 @@ The first argument is required; the rest are optional: * *longTime* - the minimum time (in millis) required for the button to be pressed and held to trigger a Long Press (default=2000 ms) * *singleTime* - the minimum time (in millis) required for the button to be pressed to trigger a Single Press (default=5 ms) * *doubleTime* - the maximum time (in millis) allowed between two Single Presses to qualify as a Double Press (default=200 ms) -* *buttonType* - specifies the type of pushbutton. Choices are: - * `PushButton::GROUNDED` - a button that connects *pin* to GROUND when pushed (this is the default) - * `PushButton::POWERED` - a button that connects *pin* to +3.3V when pushed - * `PushButton::TOUCH`- a button that is connected to a touch sensor (not available on ESP32-C3) +* *triggerType* - pointer to a boolean function that returns `true` or `false` depending on whether or not a "press" has been triggered on the specified *pin*. You may choose from the following built-in functions: + + * `SpanButton::TRIGGER_ON_LOW` - triggers when *pin* is driven LOW. Suitable for buttons that connect *pin* to GROUND (this is the default when triggerType is not specified) + * `SpanButton::TRIGGER_ON_HIGH` - triggers when *pin* is driven HIGH. Suitable for buttons that connect *pin* to VCC (typically 3.3V) + * `SpanButton::TRIGGER_ON_TOUCH`- uses the device's touch-sensor peripheral to trigger when a sensor attached to *pin* has been touched (not available on ESP32-C3) A second form of *SpanButton()* is provided for convenience: - * `SpanButton(int pin, buttonType, uint16_t longTime=2000, uint16_t singleTime=5, uint16_t doubleTime=200)` - * this allows you to set the *pin* and *buttonType* while keeping the other parameters optional + * `SpanButton(int pin, boolean *triggerType(int), uint16_t longTime=2000, uint16_t singleTime=5, uint16_t doubleTime=200)` + * this allows you to set the *pin* and *triggerType* while keeping the other parameters optional Trigger Rules: * If button is pressed and continuously held, a Long Press will be triggered every longTime ms until the button is released From b6973e1ac65f7bb48305f79e52648306a887735e Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Tue, 16 Aug 2022 06:36:45 -0500 Subject: [PATCH 91/98] Update Reference.md --- docs/Reference.md | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/docs/Reference.md b/docs/Reference.md index 10f50b9..b8c8f84 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -351,7 +351,7 @@ This is a **base class** from which all HomeSpan Characteristics are derived, an * returns a pointer to the Characteristic itself so that the method can be chained during instantiation * example: `(new Characteristic::RotationSpeed())->setUnit("percentage");` -### *SpanButton(int pin, uint16_t longTime, uint16_t singleTime, uint16_t doubleTime, boolean *triggerType(int))* +### *SpanButton(int pin, uint16_t longTime, uint16_t singleTime, uint16_t doubleTime, boolean (\*triggerType)(int))* Creating an instance of this **class** attaches a pushbutton handler to the ESP32 *pin* specified. @@ -364,23 +364,28 @@ The first argument is required; the rest are optional: * *longTime* - the minimum time (in millis) required for the button to be pressed and held to trigger a Long Press (default=2000 ms) * *singleTime* - the minimum time (in millis) required for the button to be pressed to trigger a Single Press (default=5 ms) * *doubleTime* - the maximum time (in millis) allowed between two Single Presses to qualify as a Double Press (default=200 ms) -* *triggerType* - pointer to a boolean function that returns `true` or `false` depending on whether or not a "press" has been triggered on the specified *pin*. You may choose from the following built-in functions: - - * `SpanButton::TRIGGER_ON_LOW` - triggers when *pin* is driven LOW. Suitable for buttons that connect *pin* to GROUND (this is the default when triggerType is not specified) - * `SpanButton::TRIGGER_ON_HIGH` - triggers when *pin* is driven HIGH. Suitable for buttons that connect *pin* to VCC (typically 3.3V) - * `SpanButton::TRIGGER_ON_TOUCH`- uses the device's touch-sensor peripheral to trigger when a sensor attached to *pin* has been touched (not available on ESP32-C3) - -A second form of *SpanButton()* is provided for convenience: - * `SpanButton(int pin, boolean *triggerType(int), uint16_t longTime=2000, uint16_t singleTime=5, uint16_t doubleTime=200)` - * this allows you to set the *pin* and *triggerType* while keeping the other parameters optional +* *triggerType* - pointer to a boolean function that accepts a single *int* argument and returns `true` or `false` depending on whether or not a "press" has been triggered on the *pin* number passed to the *int* argument. For ease of use, you may simply choose from the following built-in functions: + * `SpanButton::TRIGGER_ON_LOW` - triggers when *pin* is driven LOW. Suitable for buttons that connect *pin* to GROUND (this is the default when *triggerType* is not specified) -Trigger Rules: + * `SpanButton::TRIGGER_ON_HIGH` - triggers when *pin* is driven HIGH. Suitable for buttons that connect *pin* to VCC (typically 3.3V) + * `SpanButton::TRIGGER_ON_TOUCH`- uses the device's touch-sensor peripheral to trigger when a sensor attached to *pin* has been touched (not available on ESP32-C3 devices) + +When any of these built-in functions are selected (or *triggerType* is left unspecified and the default is used), SpanButton will automatically configure the *pin* as needed upon instantiation. + +Alternatively, you can set *triggerType* to any user-defined function of the form `boolean(int arg)` and provide your own logic for determining whether a trigger has occured on the specified *pin*, which is passed through to your function as *arg*. In this case *arg* can either represent an actual device pin, or simply be an arbitrary *int* your function utilizes, such as the virtual pin number on a multiplexer. Note: if you specify your own function for *triggerType* you also must include in your sketch any code needed to initialize the logic or configure whatever resource *triggerType* is utilizing (such as a pin multiplexer). + +For convenience, a second form of the *SpanButton()* constructor is also provided: + * `SpanButton(int pin, boolean (*triggerType)(int), uint16_t longTime=2000, uint16_t singleTime=5, uint16_t doubleTime=200)` + * this allows you to set just the *pin* and *triggerType* while leaving the remaining parameters at their default values + +#### Trigger Rules ### * If button is pressed and continuously held, a Long Press will be triggered every longTime ms until the button is released * If button is pressed for more than singleTime ms but less than longTime ms and then released, a Single Press will be triggered, UNLESS the button is pressed a second time within doubleTime ms AND held again for at least singleTime ms, in which case a DoublePress will be triggered; no further events will occur until the button is released * If singleTime>longTime, only Long Press triggers can occur * If doubleTime=0, Double Presses cannot occur - -HomeSpan automatically calls the `button(int pin, int pressType)` method of a Service upon a trigger event in any Button associated with that Service, where *pin* is the ESP32 pin to which the pushbutton is connected, and *pressType* is an integer that can also be represented by the enum constants indicated: + +#### Usage #### +HomeSpan automatically calls the `button(int pin, int pressType)` method of a Service upon a trigger event in any SpanButton associated with that Service, where *pin* is the ESP32 pin to which the pushbutton is connected, and *pressType* is an integer that can also be represented by the enum constants indicated: * 0=single press (SpanButton::SINGLE) * 1=double press (SpanButton::DOUBLE) * 2=long press (SpanButton::LONG) @@ -389,9 +394,10 @@ HomeSpan will report a warning, but not an error, during initialization if the u When using one or more Touch Sensors, HomeSpan automatically calibrates the threshold at which they are triggered by polling the baseline sensor reading upon instantiation of first SpanButton of type `PushButton::TOUCH` found. For ESP32 devices, the threshold is set to 50% of the baseline value since triggers occur when a sensor value falls *below* the threhold level. For ESP32-S2 and ESP32-S3 devices, the threshold is set to 200% of the baseline value since triggers occur when a sensor value rises *above* the threhold level. Normally HomeSpan's auto calibration will result in accurate detection of SINGLE, DOUBLE, and LONG presses of touch sensors. However, if needed you can override the calibration and set your own threshold value using the following class-level method: - * `void PushButton::setTouchThreshold(thresh)` - * sets the threshold value at which touch sensors are triggered to *thresh*, where *thresh* is either of type *uint16_t* (for ESP32 devices) or *uint32_t* (for ESP32-S2 and ESP32-S3 devices) - * the threshold specified is used for all SpanButton instances of type `PushButton::TOUCH` + * `void PushButton::setTouchThreshold(uintXX_t thresh)` + * sets the threshold value above (for ESP32 devices) or below (for ESP32-S2 and ESP32-S3 devices) which touch sensors are triggered to *thresh* + * *XX* is 16 (for ESP32 devices) or 32 (for ESP32-S2 and ESP32-S3 devices) + * the threshold specified is considered global and used for *all* SpanButton instances of type `PushButton::TOUCH` * this method can be called either before or after SpanButtons are created In addition, you can also override the ESP32's touch sensor timing parameters using the following class-level method: From fbc60a67eb385f0448f2328ad7d26cf5655b88af Mon Sep 17 00:00:00 2001 From: Gregg Date: Wed, 17 Aug 2022 06:29:26 -0500 Subject: [PATCH 92/98] Added setTouchCycles() and setTouchThreshold() to SpanButton as static functions --- src/HomeSpan.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 9111fc0..3bac4d5 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -716,6 +716,8 @@ class SpanButton : PushButton { #if SOC_TOUCH_SENSOR_NUM > 0 static constexpr triggerType_t TRIGGER_ON_TOUCH=PushButton::TRIGGER_ON_TOUCH; + static void setTouchCycles(uint16_t measureTime, uint16_t sleepTime){PushButton::setTouchCycles(measureTime,sleepTime);} + static void setTouchThreshold(touch_value_t thresh){PushButton::setTouchThreshold(thresh);} #endif SpanButton(int pin, uint16_t longTime=2000, uint16_t singleTime=5, uint16_t doubleTime=200, triggerType_t triggerType=TRIGGER_ON_LOW); From 30de6f84645d4c910d18c4a01acb45af71ed246e Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Wed, 17 Aug 2022 06:45:44 -0500 Subject: [PATCH 93/98] Update Reference.md --- docs/Reference.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/Reference.md b/docs/Reference.md index b8c8f84..8f7348c 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -386,25 +386,25 @@ For convenience, a second form of the *SpanButton()* constructor is also provide #### Usage #### HomeSpan automatically calls the `button(int pin, int pressType)` method of a Service upon a trigger event in any SpanButton associated with that Service, where *pin* is the ESP32 pin to which the pushbutton is connected, and *pressType* is an integer that can also be represented by the enum constants indicated: - * 0=single press (SpanButton::SINGLE) - * 1=double press (SpanButton::DOUBLE) - * 2=long press (SpanButton::LONG) + * 0=single press (`SpanButton::SINGLE`) + * 1=double press (`SpanButton::DOUBLE`) + * 2=long press (`SpanButton::LONG`) HomeSpan will report a warning, but not an error, during initialization if the user had not overridden the virtual button() method for a Service contaning one or more Buttons; triggers of those Buttons will simply ignored. -When using one or more Touch Sensors, HomeSpan automatically calibrates the threshold at which they are triggered by polling the baseline sensor reading upon instantiation of first SpanButton of type `PushButton::TOUCH` found. For ESP32 devices, the threshold is set to 50% of the baseline value since triggers occur when a sensor value falls *below* the threhold level. For ESP32-S2 and ESP32-S3 devices, the threshold is set to 200% of the baseline value since triggers occur when a sensor value rises *above* the threhold level. Normally HomeSpan's auto calibration will result in accurate detection of SINGLE, DOUBLE, and LONG presses of touch sensors. However, if needed you can override the calibration and set your own threshold value using the following class-level method: +When using one or more Touch Sensors, HomeSpan automatically calibrates the threshold at which they are triggered by polling the baseline sensor reading upon instantiation of first SpanButton of type `SpanButton::TRIGGER_ON_TOUCH`. For ESP32 devices, the threshold is set to 50% of the baseline value since triggers occur when a sensor value falls *below* the threhold level. For ESP32-S2 and ESP32-S3 devices, the threshold is set to 200% of the baseline value since triggers occur when a sensor value rises *above* the threhold level. Normally HomeSpan's auto calibration will result in accurate detection of SINGLE, DOUBLE, and LONG presses of touch sensors. However, if needed you can override the calibration and set your own threshold value using the following class-level method: - * `void PushButton::setTouchThreshold(uintXX_t thresh)` + * `void SpanButton::setTouchThreshold(uintXX_t thresh)` * sets the threshold value above (for ESP32 devices) or below (for ESP32-S2 and ESP32-S3 devices) which touch sensors are triggered to *thresh* * *XX* is 16 (for ESP32 devices) or 32 (for ESP32-S2 and ESP32-S3 devices) - * the threshold specified is considered global and used for *all* SpanButton instances of type `PushButton::TOUCH` + * the threshold specified is considered global and used for *all* SpanButton instances of type `SpanButton::TRIGGER_ON_TOUCH` * this method can be called either before or after SpanButtons are created In addition, you can also override the ESP32's touch sensor timing parameters using the following class-level method: -* `void PushButton::setTouchCycles(uint16_t measureTime, uint16_t sleepTime)` +* `void SpanButton::setTouchCycles(uint16_t measureTime, uint16_t sleepTime)` * changes the measurement time and sleep time clock cycles to *measureTime* and *sleepTime*, respectively. This is simply a pass-though call to the Arduino-ESP32 library `touchSetCycles()` function - * this method should be called *before* instantiating the first SpanButton() of type `PushButton::TOUCH` so that HomeSpan will calibrate the touch threshold based on the new timing parameters specified + * unless a specific threshold value has been set with `setTouchThreshold()`, `setTouchCycles()` must be called *before* instantiating the first SpanButton() of type `SpanButton::TRIGGER_ON_TOUCH` so that HomeSpan will calibrate the touch threshold based on the new timing parameters specified ### *SpanUserCommand(char c, const char \*desc, void (\*f)(const char \*buf [,void \*obj]) [,void \*userObject])* From a48746c2b3088c35974e45c66fe2a5c197129d47 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Wed, 17 Aug 2022 21:05:17 -0500 Subject: [PATCH 94/98] Update FAQ.md --- docs/FAQ.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/docs/FAQ.md b/docs/FAQ.md index fd02f79..1dd4234 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -28,10 +28,6 @@ * No, HomeSpan is coded specifically for the ESP32 and will not operate on an ESP8266 device. -#### Will HomeSpan work on an ESP32-S2 or ESP32-C3? - -* Yes! Starting with version 1.4.0, HomeSpan is fully compatible with Espressif's ESP32-S2 and ESP32-C3 chips, as well as the original ESP32 chip. Note that to select an ESP32-S2 or ESP32-C3 device from the Arduino IDE you'll need to install Version 2 of the [Arduino-ESP32 Board Manager](https://github.com/espressif/arduino-esp32). - #### How can I read HomeSpan's MDNS broadcast mentioned in the [OTA](OTA.md) documentation? * HomeSpan uses MDNS (multicast DNS) to broadcast a variety of HAP information used by Controllers wishing to pair with HomeSpan. Apple uses the name *Bonjour* to refer to MDNS, and originally included a Bonjour "Browser" in Safari that has since been discontinued. However, there are a number of alternative MDNS browsers available for free that operate on both the Mac and the iPhone, such as the [Discovery - DNS-SD Browser](https://apps.apple.com/us/app/discovery-dns-sd-browser/id1381004916?mt=12). You'll find all your HomeSpan devices, as well as any other HomeKit devices you may have, under the MDNS service named *_hap._tcp.* The fields broadcast by HomeSpan are a combination of all data elements requires by HAP (HAP-R2, Table 6-7) plus three additional HomeSpan fields: @@ -54,7 +50,7 @@ #### Can you add a Web Server to HomeSpan? -* Yes, provided you implement your Web Server using standard ESP32-Arduino libraries, such as `WebServer.h`. See [ProgrammableHub](https://github.com/HomeSpan/ProgrammableHub) for an illustrative example of how to easily integrate a Web Server into HomeSpan. This project also covers various other advanced topics, including TCP slot management, dynamic creation of Accessories, and saving arbitrary data in the ESP32's NVS. +* Yes, provided you implement your Web Server using standard ESP32-Arduino libraries, such as `WebServer.h`. See [ProgrammableHub](../Other%20Examples/ProgrammableHub) for an illustrative example of how to easily integrate a Web Server into HomeSpan. This project also covers various other advanced topics, including TCP slot management, dynamic creation of Accessories, and saving arbitrary data in the ESP32's NVS. #### Can you add *custom* Services and Characteristics to HomeSpan? From f4698b8d59f0a6b5eacacfcaab006a4736d78a20 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 20 Aug 2022 07:37:56 -0500 Subject: [PATCH 95/98] 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. From ed6d2d1d46d1f10b30f85eb0432548f046d9f714 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 20 Aug 2022 08:40:50 -0500 Subject: [PATCH 96/98] Update README.md --- docs/README.md | 50 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/docs/README.md b/docs/README.md index f708502..64f7ed4 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,7 +4,7 @@ Welcome to HomeSpan - a robust and extremely easy-to-use Arduino library for cre HomeSpan provides a microcontroller-focused implementation of [Apple's HomeKit Accessory Protocol Specification Release R2 (HAP-R2)](https://developer.apple.com/homekit/specification/) designed specifically for the Espressif ESP32 microcontroller running within the Arduino IDE. HomeSpan pairs directly to HomeKit via your home WiFi network without the need for any external bridges or components. With HomeSpan you can use the full power of the ESP32's I/O functionality to create custom control software and/or hardware to automatically operate external devices from the Home App on your iPhone, iPad, or Mac, or with Siri. -HomeSpan requires Version 2 of the [Arduino-ESP32 Board Manager](https://github.com/espressif/arduino-esp32). HomeSpan can be run on the original ESP32 as well as Espressif's ESP32-S2, ESP32-C3, and ESP32-S3 chips. +HomeSpan requires version 2.0.0 or later of the [Arduino-ESP32 Board Manager](https://github.com/espressif/arduino-esp32), and has been tested up through version 2.0.4 (recommended). HomeSpan can be run on the original ESP32 as well as Espressif's ESP32-S2, ESP32-C3, and ESP32-S3 chips. ### HomeSpan Highlights @@ -26,7 +26,9 @@ HomeSpan requires Version 2 of the [Arduino-ESP32 Board Manager](https://github. * Dedicated classes that utilize the ESP32's 16-channel PWM peripheral for easy control of: * LED Brightness * Servo Motors -* Integrated Push Button functionality supporting single, double, and long presses +* Integrated Push Button functionality supporting single, double, and long presses of: + * Physical pushbuttons that connect an ESP32 pin to either ground or VCC + * Touch pads/sensors connected to an ESP32 pin (for ESP32 devices that support touch pads) * Integrated access to the ESP32's on-chip Remote Control peripheral for easy generation of IR and RF signals * Dedicated classes to control one- and two-wire addressable RGB and RGBW LEDs and LED strips * Integrated Web Log for user-defined log messages @@ -45,7 +47,49 @@ HomeSpan requires Version 2 of the [Arduino-ESP32 Board Manager](https://github. * Launch the WiFi Access Point * A standalone, detailed End-User Guide -## ❗Latest Update - HomeSpan 1.5.1 (4/17/2022) +## ❗Latest Update - HomeSpan 1.6.0 (8/21/2022) + +* **Support for ESP32-S3 devices** + * Requires Arduino-ESP32 Board Manager version 2.0.4 or later + +* **New functionality to *dynamically* add/delete Accessories on the fly *without* rebooting the device** + * Adds `homeSpan.deleteAccessory()` and `homeSpan.updateDatabase()` methods + * Refactored the HomeSpan Info 'i' CLI command to reflect all dynamic database changes + * See [HomeSpan API Reference](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Reference.md) for details + +* **Expanded *SpanButton()* functionality** + * Now includes integrated support for pins connected to *touch pads*! + * Extends logic to include physical pushbuttons that connect pins to *VCC* as well as *ground* + * Allows users to easily add their own logic to use pin extenders, pin multiplexers, or any "pushbutton" device that requires calling additional methods + * See [HomeSpan API Reference](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Reference.md) for details + +* **Improved WiFi disconnect/reconnect handling + * Fixes code that may, under certain circumstances, have prevented HomeSpan from reconnecting to WiFi after signal is lost + * Adds WiFi diagnostics to Web Logs, including a record of each WiFi disconnect/reconnect, the total number of disconnects since booting, and WiFi signal strength + +* **New *experimental* multi-threading + * Adds new method `homeSpan.autoPoll()` that runs HomeSpan polling in a separate task thread + * Works with both single- and dual-core processors + * For dual-core processors, polling task will be created on the "free" core not being used for other Arduino functions + * Allows user to add time-sensitive code the the main Arduino `loop()` function without delaying, or being dalyed by, HomeSpan polling + * See [HomeSpan API Reference](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Reference.md) for details + +* **Created new [Example 20 - AdvancedTechniques](https://github.com/HomeSpan/HomeSpan/blob/master/examples/20-AdvancedTechniques)** + * Demonstrates how the `homeSpan.deleteAccessory()` and `homeSpan.updateDatabase()` methods can be used to create a bridge device that can be dynamically updated without the need to reboot HomeSpan + * Also included the `homeSpan.autoPoll()` method to show how it can be used in place calling `homeSpan.poll()` from the main Arduino `loop()` method + +* **Created new [Programmable Hub Example](https://github.com/HomeSpan/HomeSpan/blob/master/Other%20Examples/ProgrammableHub) + * Demonstrates how to implement a complete Web Interface to dynamically add/delete Light Accessories to a bridge accessory + * Expands upon many of the methods used in [Example 20](https://github.com/HomeSpan/HomeSpan/blob/master/examples/20-AdvancedTechniques) + +* **Other Updates:** + * New 'm' CLI command that prints free bytes remaining in heap memory - useful for monitoring memory usage when dynamically adding Accessories + * New `homeSpan.getLogLevel()` method that returns the current log level + * Converted all HomeSpan API objects (*Span, SpanAccessory, SpanService, SpanButton*...) from C++ *struct* to C++ *class* allowing proper separation of public, protected, and private members and methods + + + + * **New Web Logging functionality** * HomeSpan can now host a Web Log page for message logging From 61f3c9ba81f0203e1017aed34bfb2cceff4e0969 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 20 Aug 2022 10:58:51 -0500 Subject: [PATCH 97/98] Update README.md --- docs/README.md | 60 +++++++++----------------------------------------- 1 file changed, 11 insertions(+), 49 deletions(-) diff --git a/docs/README.md b/docs/README.md index 64f7ed4..40104ee 100644 --- a/docs/README.md +++ b/docs/README.md @@ -52,71 +52,33 @@ HomeSpan requires version 2.0.0 or later of the [Arduino-ESP32 Board Manager](ht * **Support for ESP32-S3 devices** * Requires Arduino-ESP32 Board Manager version 2.0.4 or later -* **New functionality to *dynamically* add/delete Accessories on the fly *without* rebooting the device** +* **New functionality to *dynamically* add/delete Accessories on the fly without rebooting the device** * Adds `homeSpan.deleteAccessory()` and `homeSpan.updateDatabase()` methods - * Refactored the HomeSpan Info 'i' CLI command to reflect all dynamic database changes + * Includes new [Example 20 - AdvancedTechniques](https://github.com/HomeSpan/HomeSpan/blob/master/examples/20-AdvancedTechniques) to demonstrate how these methods can be used to create a dynamic bridge * See [HomeSpan API Reference](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Reference.md) for details -* **Expanded *SpanButton()* functionality** - * Now includes integrated support for pins connected to *touch pads*! - * Extends logic to include physical pushbuttons that connect pins to *VCC* as well as *ground* - * Allows users to easily add their own logic to use pin extenders, pin multiplexers, or any "pushbutton" device that requires calling additional methods +* **New support for Touch Pads** + * `SpanButton()` now supports three pin trigger methods: *TRIGGER_ON_LOW*, *TRIGGER_ON_HIGH*, and *TRIGGER_ON_TOUCH* + * Also allows users to add their own trigger methods so `SpanButton()` can monitor pushbuttons attached to pin extenders, pin multiplexers, or any other device that requires calling third-party library functions * See [HomeSpan API Reference](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Reference.md) for details -* **Improved WiFi disconnect/reconnect handling +* **Improved WiFi disconnect/reconnect handling** * Fixes code that may, under certain circumstances, have prevented HomeSpan from reconnecting to WiFi after signal is lost - * Adds WiFi diagnostics to Web Logs, including a record of each WiFi disconnect/reconnect, the total number of disconnects since booting, and WiFi signal strength + * Adds WiFi diagnostics to Web Logs to monitor for disconnects and WiFi signal strength -* **New *experimental* multi-threading - * Adds new method `homeSpan.autoPoll()` that runs HomeSpan polling in a separate task thread +* **New option to run HomeSpan as a separate task in its own thread** * Works with both single- and dual-core processors * For dual-core processors, polling task will be created on the "free" core not being used for other Arduino functions * Allows user to add time-sensitive code the the main Arduino `loop()` function without delaying, or being dalyed by, HomeSpan polling * See [HomeSpan API Reference](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Reference.md) for details - -* **Created new [Example 20 - AdvancedTechniques](https://github.com/HomeSpan/HomeSpan/blob/master/examples/20-AdvancedTechniques)** - * Demonstrates how the `homeSpan.deleteAccessory()` and `homeSpan.updateDatabase()` methods can be used to create a bridge device that can be dynamically updated without the need to reboot HomeSpan - * Also included the `homeSpan.autoPoll()` method to show how it can be used in place calling `homeSpan.poll()` from the main Arduino `loop()` method -* **Created new [Programmable Hub Example](https://github.com/HomeSpan/HomeSpan/blob/master/Other%20Examples/ProgrammableHub) - * Demonstrates how to implement a complete Web Interface to dynamically add/delete Light Accessories to a bridge accessory +* **New [Programmable Hub](https://github.com/HomeSpan/HomeSpan/blob/master/Other%20Examples/ProgrammableHub) Example** + * Demonstrates how to implement a Web Interface that allows users to dynamically add, delete, and configure up to 12 separate Light Accessories on a bridge device * Expands upon many of the methods used in [Example 20](https://github.com/HomeSpan/HomeSpan/blob/master/examples/20-AdvancedTechniques) -* **Other Updates:** +* **Additional updates include:** * New 'm' CLI command that prints free bytes remaining in heap memory - useful for monitoring memory usage when dynamically adding Accessories * New `homeSpan.getLogLevel()` method that returns the current log level - * Converted all HomeSpan API objects (*Span, SpanAccessory, SpanService, SpanButton*...) from C++ *struct* to C++ *class* allowing proper separation of public, protected, and private members and methods - - - - - -* **New Web Logging functionality** - * HomeSpan can now host a Web Log page for message logging - * New WEBLOG() macro makes is easy to create user-defined log messages - * Provides for the optional use of an NTP Time Server to set the device clock so all messages can be properly timestamped - * See [HomeSpan Message Logging](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Logging.md) for full details - -* **New *printf*-style formatting for LOG() macros** - * Adds variadic forms of the LOG0(), LOG1(), and LOG2() macros so they can be used in the same manner as a standard C printf function - * Greatly simplifies the creation of log messages - * See [HomeSpan Message Logging](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Logging.md) for full details - -* **New CUSTOM_SERV() macro** - * Allows for the creation of Custom Services - * Can be used in conjunction with the existing CUSTOM_CHAR() macro to produce Services beyond those provided in HAP-R2 - * Includes a fully worked example of a custom [Pressure Sensor Accessory](https://github.com/HomeSpan/HomeSpan/blob/master/Other%20Examples/CustomService) that is recognized by *Eve for HomeKit* - * See [HomeSpan API Reference](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Reference.md) for details - -* **New "Safe-Load" mode for OTA updates** - * HomeSpan can check to make sure the new sketch being uploaded via OTA is another HomeSpan sketch. If not, the upload fails - * Upon rebooting after an OTA update, HomeSpan checks to ensure that OTA is enabled in the updated sketch. If not, HomeSpan rolls back to the previous version of the sketch - * See [HomeSpan OTA](https://github.com/HomeSpan/HomeSpan/blob/master/docs/OTA.md) for full details - -* **Additional updates include:** - * a new (optional) argument to `SpanUserCommand()` that allows for passing a pointer to any arbitrary data structure - * a new SPAN_ACCESSORY() macro that expands to a common snippet of code often used when creating Accessories - * refreshed and streamlined example Tutorials, and fully reworked Examples 7 and 11, to best conform with Home App behavior under iOS 15.4 See [Releases](https://github.com/HomeSpan/HomeSpan/releases) for details on all changes and bug fixes included in this update. From 1f98388115d11438a4036a94446da2a11d333218 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 20 Aug 2022 16:03:20 -0500 Subject: [PATCH 98/98] Update library.properties --- library.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library.properties b/library.properties index 55fa778..0a523e6 100644 --- a/library.properties +++ b/library.properties @@ -1,9 +1,9 @@ name=HomeSpan -version=1.5.1 +version=1.6.0 author=Gregg maintainer=Gregg sentence=A robust and extremely easy-to-use HomeKit implementation for the Espressif ESP32 running on the Arduino IDE. -paragraph=This library provides a microcontroller-focused implementation of Apple's HomeKit Accessory Protocol (HAP - Release R2) designed specifically for the ESP32 running on the Arduino IDE. HomeSpan pairs directly to iOS Home via WiFi without the need for any external bridges or components. Compatible with ESP32, ESP32-S2, and ESP32-C3. +paragraph=This library provides a microcontroller-focused implementation of Apple's HomeKit Accessory Protocol (HAP - Release R2) designed specifically for the ESP32 running on the Arduino IDE. HomeSpan pairs directly to iOS Home via WiFi without the need for any external bridges or components. Supports ESP32, ESP32-S2, ESP32-C3, and ESP32-S3. url=https://github.com/HomeSpan/HomeSpan architectures=esp32 includes=HomeSpan.h