From 4af2bdbbe3198a24fc4ccd780767ed53eb8157a4 Mon Sep 17 00:00:00 2001 From: Gregg Date: Mon, 29 Apr 2024 21:22:37 -0500 Subject: [PATCH 01/19] Add displayOrder to ServiceList.md Run shell-script 'tools/makeServiceList' --- docs/ServiceList.md | 1 + tools/makeServices | 2 ++ 2 files changed, 3 insertions(+) diff --git a/docs/ServiceList.md b/docs/ServiceList.md index abff6e9..fb3b681 100644 --- a/docs/ServiceList.md +++ b/docs/ServiceList.md @@ -428,6 +428,7 @@ The pre-defined constant expressions for enumerated Characteristics are in names CharacteristicFormatPermsMinMaxConstants/Defaults Active (B0) :small_blue_diamond:uint8PW+PR+EV01 ActiveIdentifier (E7) uint32PW+PR+EV02550 +DisplayOrder (136) tlv8PR+EV01"" RemoteKey (E1) uint8PW415 PowerModeSelection (DF) uint8PW00 ConfiguredName (E3) stringPW+PR+EV--"unnamed" diff --git a/tools/makeServices b/tools/makeServices index 279262b..8bc6c5d 100755 --- a/tools/makeServices +++ b/tools/makeServices @@ -54,6 +54,8 @@ BEGIN { uuid[char]=x[3] perms[char]=x[4] format[char]=tolower(x[5]) + if(format[char]=="tlv_enc") + format[char]="tlv8" static[char]=x[6] } From a52a2ad432452ed79047bf58a6d40d3d80eff3eb Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Mon, 29 Apr 2024 21:24:17 -0500 Subject: [PATCH 02/19] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6134b44..09d1c6a 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ HomeSpan requires version 2.0.0 or later of the [Arduino-ESP32 Board Manager](ht * a warning message is output on the Serial Monitor if `setVal()` is called to change the value of a Characteristic from within the `update()` method at the same time the Home App is sending an update request for that value * does not apply if `setVal()` is called from within `update()` to change the value of a Characteristic in response to a *write-response* request from the Home App -* **Fixed bug introduced in 1.9.0 that prevented `homeSpan.setPairingCode()` from saving (and subsequently using) the request Setup Pairing Code** (#786) +* **Fixed bug introduced in 1.9.0 that prevented `homeSpan.setPairingCode()` from saving (and subsequently using) the request Setup Pairing Code** * this method now operates silently, unless an invalid pairing code is provided, in which case an error is reported to the Serial Monitor and *the sketch is halted* * the process for setting the Pairing Code using the CLI 'S' command or via the Access Point are unchanged - confirmation messages are still output to the Serial Monitor and errors do *not* cause the sketch to halt From 638285f48f55d6bd340fa757f1afde4f72b058d0 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Mon, 29 Apr 2024 21:31:56 -0500 Subject: [PATCH 03/19] Update README.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 09d1c6a..a3be5cd 100644 --- a/README.md +++ b/README.md @@ -85,8 +85,9 @@ HomeSpan requires version 2.0.0 or later of the [Arduino-ESP32 Board Manager](ht * users should switch to the new constructor to avoid potential compatibility issues with future versions of HomeSpan * added new method `boolean isRGBW()` * returns *true* if Pixel was constructed as RGBW, else *false* if constructed as RGB only (i.e. no white LED) - * created new PixelTester sketch (found under Other-> Examples) to aid in determining the *pixelType* for any LED Strip - + * added new [PixelTester](examples/Other%20Examples/PixelTester) sketch (found under *Other Examples*) to aid in determining the *pixelType* for any LED Strip + * see the [Pixels](docs/Pixels.md) page for details + * **New ability to read and set the IIDs of Services and Characteristics** * adds new `SpanService` method `getIID()` that returns the IID of a Service From d523a27124665d70eb00822fb7ac0bee1362f75c Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Mon, 29 Apr 2024 21:36:06 -0500 Subject: [PATCH 04/19] Update Tutorials.md --- docs/Tutorials.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/Tutorials.md b/docs/Tutorials.md index 732c9ae..256cbd5 100644 --- a/docs/Tutorials.md +++ b/docs/Tutorials.md @@ -139,6 +139,9 @@ An example of HomeKit's *undocumented* Television Service showing how different ### [Pixel](../examples/Other%20Examples/Pixel) Demonstrates how to use HomeSpan's *Pixel* and *Dot* classes to control one- and two-wire Addressable RGB and RGBW LEDs. See the [Addressable RGB LEDs](Pixels.md) page for full details +### [PixelTester](../examples/Other%20Examples/PixelTester) +A sketch to aid in determining the *pixelType* for any RGB(W) LED Strip. See the [Addressable RGB LEDs](Pixels.md) page for full details + ### [CustomService](../examples/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 From 1863cc4309ab8201bb49efd259e23af4f2372d08 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Mon, 29 Apr 2024 21:37:54 -0500 Subject: [PATCH 05/19] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a3be5cd..db52f3d 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ HomeSpan requires version 2.0.0 or later of the [Arduino-ESP32 Board Manager](ht * added new method `boolean isRGBW()` * returns *true* if Pixel was constructed as RGBW, else *false* if constructed as RGB only (i.e. no white LED) * added new [PixelTester](examples/Other%20Examples/PixelTester) sketch (found under *Other Examples*) to aid in determining the *pixelType* for any LED Strip - * see the [Pixels](docs/Pixels.md) page for details + * see the [Adressable RGB LEDs](docs/Pixels.md) page for details * **New ability to read and set the IIDs of Services and Characteristics** From 8775a2df85d92ad8f105846322f75892a93077cb Mon Sep 17 00:00:00 2001 From: Gregg Date: Thu, 2 May 2024 20:45:30 -0500 Subject: [PATCH 06/19] Eliminated use of exceptions in PSRAM.h and added -fno-exceptions Added build_opt.h which include -fno-exceptions compiler instructions. Seems to save about 29K of flash in compiled code (compiling for ESP32) --- src/PSRAM.h | 11 +++++++---- src/build_opt.h | 1 + 2 files changed, 8 insertions(+), 4 deletions(-) create mode 100644 src/build_opt.h diff --git a/src/PSRAM.h b/src/PSRAM.h index dd9e03a..d5b0cb6 100644 --- a/src/PSRAM.h +++ b/src/PSRAM.h @@ -45,11 +45,14 @@ template struct Mallocator { typedef T value_type; Mallocator() = default; - template constexpr Mallocator(const Mallocator&) noexcept {} + template constexpr Mallocator(const Mallocator&) {} [[nodiscard]] T* allocate(std::size_t n) { - if(n > std::size_t(-1) / sizeof(T)) throw std::bad_alloc(); - if(auto p = static_cast(HS_MALLOC(n*sizeof(T)))) return p; - throw std::bad_alloc(); + auto p = static_cast(HS_MALLOC(n*sizeof(T))); + if(p==NULL){ + Serial.printf("\n\n*** FATAL ERROR: Requested allocation of %d bytes failed. Program Halting.\n\n",n*sizeof(T)); + while(1); + } + return p; } void deallocate(T* p, std::size_t) noexcept { std::free(p); } }; diff --git a/src/build_opt.h b/src/build_opt.h new file mode 100644 index 0000000..f7f5b39 --- /dev/null +++ b/src/build_opt.h @@ -0,0 +1 @@ +-fno-exceptions From 80a8935828f5d3a9b673d24b4e6d4b7ca3058f14 Mon Sep 17 00:00:00 2001 From: Gregg Date: Fri, 3 May 2024 06:36:25 -0500 Subject: [PATCH 07/19] Moved definitions of non-template SpanCharacteristic methods from HomeSpan.h to HomeSpan.cpp --- src/HomeSpan.cpp | 252 ++++++++++++++++++++++++++++++++++++++++++++++ src/HomeSpan.h | 257 +++++++---------------------------------------- 2 files changed, 286 insertions(+), 223 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index e815af0..871ef55 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -1886,6 +1886,207 @@ SpanCharacteristic::~SpanCharacteristic(){ /////////////////////////////// +String SpanCharacteristic::uvPrint(UVal &u){ + char c[64]; + switch(format){ + case FORMAT::BOOL: + return(String(u.BOOL)); + case FORMAT::INT: + return(String(u.INT)); + case FORMAT::UINT8: + return(String(u.UINT8)); + case FORMAT::UINT16: + return(String(u.UINT16)); + case FORMAT::UINT32: + return(String(u.UINT32)); + case FORMAT::UINT64: + sprintf(c,"%llu",u.UINT64); + return(String(c)); + case FORMAT::FLOAT: + sprintf(c,"%g",u.FLOAT); + return(String(c)); + case FORMAT::STRING: + case FORMAT::DATA: + case FORMAT::TLV_ENC: + return(String("\"") + String(u.STRING) + String("\"")); + } // switch + return(String()); // included to prevent compiler warnings +} + +/////////////////////////////// + +void SpanCharacteristic::uvSet(UVal &dest, UVal &src){ + if(format>=FORMAT::STRING) + uvSet(dest,(const char *)src.STRING); + else + dest=src; +} + +/////////////////////////////// + +void SpanCharacteristic::uvSet(UVal &u, const char *val){ + u.STRING = (char *)HS_REALLOC(u.STRING, strlen(val) + 1); + strcpy(u.STRING, val); +} + +/////////////////////////////// + +char *SpanCharacteristic::getStringGeneric(UVal &val){ + if(format>=FORMAT::STRING) + return val.STRING; + + return NULL; +} + +/////////////////////////////// + +void SpanCharacteristic::setString(const char *val, boolean notify){ + + setValCheck(); + uvSet(value,val); + setValFinish(notify); +} + +/////////////////////////////// + +size_t SpanCharacteristic::getDataGeneric(uint8_t *data, size_t len, UVal &val){ + if(format0){ + size_t olen; + mbedtls_base64_encode(NULL,0,&olen,NULL,len); // get length of string buffer needed (mbedtls includes the trailing null in this size) + value.STRING = (char *)HS_REALLOC(value.STRING,olen); // allocate sufficient size for storing value + mbedtls_base64_encode((uint8_t*)value.STRING,olen,&olen,data,len ); // encode data into string buf + } else { + value.STRING = (char *)HS_REALLOC(value.STRING,1); // allocate sufficient size for just trailing null character + *value.STRING ='\0'; + } + + setValFinish(notify); +} + +/////////////////////////////// + +size_t SpanCharacteristic::getTLVGeneric(TLV8 &tlv, UVal &val){ + + if(format tBuf(bufSize); // create fixed-size buffer to store decoded bytes + tlv.wipe(); // clear TLV completely + + size_t nChars=strlen(val.STRING); // total characters to decode + uint8_t *p=(uint8_t *)val.STRING; // set pointer to beginning of value + const size_t decodeSize=bufSize/3*4; // number of characters to decode in each pass + int status=0; + + while(nChars>0){ + size_t olen; + size_t n=nChars0){ + LOG0("\n*** WARNING: Can't unpack Characteristic::%s with getTLV(). TLV record is incomplete or corrupted!\n\n",hapName); + tlv.wipe(); + return(0); + } +return(tlv.pack_size()); +} + +/////////////////////////////// + +void SpanCharacteristic::setTLV(TLV8 &tlv, boolean notify){ + + setValCheck(); + + const size_t bufSize=36; // maximum size of buffer to store packed TLV bytes before encoding directly into value; must be multiple of 3 + size_t nBytes=tlv.pack_size(); // total size of packed TLV in bytes + + if(nBytes>0){ + size_t nChars; + mbedtls_base64_encode(NULL,0,&nChars,NULL,nBytes); // get length of string buffer needed (mbedtls includes the trailing null in this size) + value.STRING = (char *)HS_REALLOC(value.STRING,nChars); // allocate sufficient size for storing value + TempBuffer tBuf(bufSize); // create fixed-size buffer to store packed TLV bytes + tlv.pack_init(); // initialize TLV packing + uint8_t *p=(uint8_t *)value.STRING; // set pointer to beginning of value + while((nBytes=tlv.pack(tBuf,bufSize))>0){ // pack the next set of TLV bytes, up to a maximum of bufSize, into tBuf + size_t olen; // number of characters written (excludes null character) + mbedtls_base64_encode(p,nChars,&olen,tBuf,nBytes); // encode data directly into value + p+=olen; // advance pointer to null character + nChars-=olen; // subtract number of characters remaining + } + } else { + value.STRING = (char *)HS_REALLOC(value.STRING,1); // allocate sufficient size for just trailing null character + *value.STRING ='\0'; + } + + setValFinish(notify); +} + +/////////////////////////////// + +void SpanCharacteristic::setValCheck(){ + if(updateFlag==1) + LOG0("\n*** WARNING: Attempt to set value of Characteristic::%s within update() while it is being simultaneously updated by Home App. This may cause device to become non-responsive!\n\n",hapName); +} + +/////////////////////////////// + +void SpanCharacteristic::setValFinish(boolean notify){ + + uvSet(newValue,value); + updateTime=homeSpan.snapTime; + + if(notify){ + if((perms&EV) && (updateFlag!=2)){ // only broadcast notification if EV permission is set AND update is NOT being done in context of write-response + SpanBuf sb; // create SpanBuf object + sb.characteristic=this; // set characteristic + sb.status=StatusCode::OK; // set status + char dummy[]=""; + sb.val=dummy; // set dummy "val" so that printfNotify knows to consider this "update" + homeSpan.Notifications.push_back(sb); // store SpanBuf in Notifications vector + } + + if(nvsKey){ + nvs_set_str(homeSpan.charNVS,nvsKey,value.STRING); // store data + nvs_commit(homeSpan.charNVS); + } + } +} + +/////////////////////////////// + void SpanCharacteristic::printfAttributes(int flags){ const char permCodes[][7]={"pr","pw","ev","aa","tw","hd","wr"}; @@ -2066,6 +2267,57 @@ unsigned long SpanCharacteristic::timeVal(){ /////////////////////////////// +boolean SpanCharacteristic::updated(){ + + return(updateFlag>0); +} + +/////////////////////////////// + +uint32_t SpanCharacteristic::getIID(){ + + return(iid); +} + +/////////////////////////////// + +SpanCharacteristic *SpanCharacteristic::setPerms(uint8_t perms){ + perms&=0x7F; + if(perms>0) + this->perms=perms; + return(this); +} + +/////////////////////////////// + +SpanCharacteristic *SpanCharacteristic::addPerms(uint8_t dPerms){ + return(setPerms(perms|dPerms)); +} + +/////////////////////////////// + +SpanCharacteristic *SpanCharacteristic::removePerms(uint8_t dPerms){ + return(setPerms(perms&(~dPerms))); +} + +/////////////////////////////// + +SpanCharacteristic *SpanCharacteristic::setDescription(const char *c){ + desc = (char *)HS_REALLOC(desc, strlen(c) + 1); + strcpy(desc, c); + return(this); +} + +/////////////////////////////// + +SpanCharacteristic *SpanCharacteristic::setUnit(const char *c){ + unit = (char *)HS_REALLOC(unit, strlen(c) + 1); + strcpy(unit, c); + return(this); +} + +/////////////////////////////// + SpanCharacteristic *SpanCharacteristic::setValidValues(int n, ...){ String s="["; diff --git a/src/HomeSpan.h b/src/HomeSpan.h index d5b656b..0ce7fdb 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -549,47 +549,12 @@ class SpanCharacteristic{ void printfAttributes(int flags); // writes Characteristic JSON to hapOut stream StatusCode loadUpdate(char *val, char *ev, boolean wr); // 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]; - switch(format){ - case FORMAT::BOOL: - return(String(u.BOOL)); - case FORMAT::INT: - return(String(u.INT)); - case FORMAT::UINT8: - return(String(u.UINT8)); - case FORMAT::UINT16: - return(String(u.UINT16)); - case FORMAT::UINT32: - return(String(u.UINT32)); - case FORMAT::UINT64: - sprintf(c,"%llu",u.UINT64); - return(String(c)); - case FORMAT::FLOAT: - sprintf(c,"%g",u.FLOAT); - return(String(c)); - case FORMAT::STRING: - case FORMAT::DATA: - case FORMAT::TLV_ENC: - return(String("\"") + String(u.STRING) + String("\"")); - } // switch - return(String()); // included to prevent compiler warnings - } + String uvPrint(UVal &u); // returns "printable" String for any type of Characteristic + + void uvSet(UVal &dest, UVal &src); // copies UVal src into UVal dest + void uvSet(UVal &u, const char *val); // copies string val into UVal u - void uvSet(UVal &dest, UVal &src){ - if(format>=FORMAT::STRING) - uvSet(dest,(const char *)src.STRING); - else - dest=src; - } - - void uvSet(UVal &u, const char *val){ - u.STRING = (char *)HS_REALLOC(u.STRING, strlen(val) + 1); - strcpy(u.STRING, val); - } - - template void uvSet(UVal &u, T val){ + template void uvSet(UVal &u, T val){ // copies any other type of val into UVal u switch(format){ case FORMAT::BOOL: u.BOOL=(boolean)val; @@ -619,7 +584,7 @@ class SpanCharacteristic{ } // switch } - template T uvGet(UVal &u){ + template T uvGet(UVal &u){ // returns UVal u, cast into T switch(format){ case FORMAT::BOOL: @@ -646,7 +611,7 @@ class SpanCharacteristic{ protected: - ~SpanCharacteristic(); // destructor + ~SpanCharacteristic(); // destructor template void init(T val, boolean nvsStore, A min=0, B max=1){ @@ -688,10 +653,8 @@ class SpanCharacteristic{ public: - void *operator new(size_t size){return(HS_MALLOC(size));} // override new operator to use PSRAM when available SpanCharacteristic(HapChar *hapChar, boolean isCustom=false); // constructor - - uint32_t getIID(){return(iid);} // returns IID of Characteristic + void *operator new(size_t size){return(HS_MALLOC(size));} // override new operator to use PSRAM when available template T getVal(){ return(uvGet(value)); @@ -700,155 +663,6 @@ class SpanCharacteristic{ template T getNewVal(){ return(uvGet(newValue)); } - - char *getStringGeneric(UVal &val){ - if(format>=FORMAT::STRING) - return val.STRING; - - return NULL; - } - - char *getString(){return(getStringGeneric(value));} - char *getNewString(){return(getStringGeneric(newValue));} - - void setString(const char *val, boolean notify=true){ - - setValCheck(); - uvSet(value,val); - setValFinish(notify); - } - - size_t getDataGeneric(uint8_t *data, size_t len, UVal &val){ - if(format0){ - size_t olen; - mbedtls_base64_encode(NULL,0,&olen,NULL,len); // get length of string buffer needed (mbedtls includes the trailing null in this size) - value.STRING = (char *)HS_REALLOC(value.STRING,olen); // allocate sufficient size for storing value - mbedtls_base64_encode((uint8_t*)value.STRING,olen,&olen,data,len ); // encode data into string buf - } else { - value.STRING = (char *)HS_REALLOC(value.STRING,1); // allocate sufficient size for just trailing null character - *value.STRING ='\0'; - } - - setValFinish(notify); - } - - size_t getTLVGeneric(TLV8 &tlv, UVal &val){ - - if(format tBuf(bufSize); // create fixed-size buffer to store decoded bytes - tlv.wipe(); // clear TLV completely - - size_t nChars=strlen(val.STRING); // total characters to decode - uint8_t *p=(uint8_t *)val.STRING; // set pointer to beginning of value - const size_t decodeSize=bufSize/3*4; // number of characters to decode in each pass - int status=0; - - while(nChars>0){ - size_t olen; - size_t n=nChars0){ - LOG0("\n*** WARNING: Can't unpack Characteristic::%s with getTLV(). TLV record is incomplete or corrupted!\n\n",hapName); - tlv.wipe(); - return(0); - } - return(tlv.pack_size()); - } - - size_t getTLV(TLV8 &tlv){return(getTLVGeneric(tlv,value));} - size_t getNewTLV(TLV8 &tlv){return(getTLVGeneric(tlv,newValue));} - - void setTLV(TLV8 &tlv, boolean notify=true){ - - setValCheck(); - - const size_t bufSize=36; // maximum size of buffer to store packed TLV bytes before encoding directly into value; must be multiple of 3 - size_t nBytes=tlv.pack_size(); // total size of packed TLV in bytes - - if(nBytes>0){ - size_t nChars; - mbedtls_base64_encode(NULL,0,&nChars,NULL,nBytes); // get length of string buffer needed (mbedtls includes the trailing null in this size) - value.STRING = (char *)HS_REALLOC(value.STRING,nChars); // allocate sufficient size for storing value - TempBuffer tBuf(bufSize); // create fixed-size buffer to store packed TLV bytes - tlv.pack_init(); // initialize TLV packing - uint8_t *p=(uint8_t *)value.STRING; // set pointer to beginning of value - while((nBytes=tlv.pack(tBuf,bufSize))>0){ // pack the next set of TLV bytes, up to a maximum of bufSize, into tBuf - size_t olen; // number of characters written (excludes null character) - mbedtls_base64_encode(p,nChars,&olen,tBuf,nBytes); // encode data directly into value - p+=olen; // advance pointer to null character - nChars-=olen; // subtract number of characters remaining - } - } else { - value.STRING = (char *)HS_REALLOC(value.STRING,1); // allocate sufficient size for just trailing null character - *value.STRING ='\0'; - } - - setValFinish(notify); - } - - void setValCheck(){ - if(updateFlag==1) - LOG0("\n*** WARNING: Attempt to set value of Characteristic::%s within update() while it is being simultaneously updated by Home App. This may cause device to become non-responsive!\n\n",hapName); - } - - void setValFinish(boolean notify){ - - uvSet(newValue,value); - updateTime=homeSpan.snapTime; - - if(notify){ - if((perms&EV) && (updateFlag!=2)){ // only broadcast notification if EV permission is set AND update is NOT being done in context of write-response - SpanBuf sb; // create SpanBuf object - sb.characteristic=this; // set characteristic - sb.status=StatusCode::OK; // set status - char dummy[]=""; - sb.val=dummy; // set dummy "val" so that printfNotify knows to consider this "update" - homeSpan.Notifications.push_back(sb); // store SpanBuf in Notifications vector - } - - if(nvsKey){ - nvs_set_str(homeSpan.charNVS,nvsKey,value.STRING); // store data - nvs_commit(homeSpan.charNVS); - } - } - } template void setVal(T val, boolean notify=true){ @@ -880,10 +694,29 @@ class SpanCharacteristic{ } } - } // setVal() + } // setVal() + + char *getStringGeneric(UVal &val); // return the specified UVal for string-based Characteristics + char *getString(){return(getStringGeneric(value));} // return the value for string-based Characteristics + char *getNewString(){return(getStringGeneric(newValue));} // return the newValue for string-based Characteristics + void setString(const char *val, boolean notify=true); // set the value and newValue for string-based Characteristic - boolean updated(){return(updateFlag>0);} // returns true within update() if Characteristic was updated by Home App + size_t getDataGeneric(uint8_t *data, size_t len, UVal &val); // return the specified UVal for data-based Characteristics + size_t getData(uint8_t *data, size_t len){return(getDataGeneric(data,len,value));} // return the value for data-based Characteristics + size_t getNewData(uint8_t *data, size_t len){return(getDataGeneric(data,len,newValue));} // return the newValue for data-based Characteristics + void setData(uint8_t *data, size_t len, boolean notify=true); // set the value and newValue for data-based Characteristic + + size_t getTLVGeneric(TLV8 &tlv, UVal &val); // return the specified UVal for tlv8-based Characteristics + size_t getTLV(TLV8 &tlv){return(getTLVGeneric(tlv,value));} // return the value for tlv8-based Characteristics + size_t getNewTLV(TLV8 &tlv){return(getTLVGeneric(tlv,newValue));} // return the newValue for tlv8-based Characteristics + void setTLV(TLV8 &tlv, boolean notify=true); // set the value and newValue for tlv8-based Characteristic + + void setValCheck(); // initial check before setting value of any Characteristic + void setValFinish(boolean notify); // final processing after setting value of any Characteristic + + boolean updated(); // returns true within update() if Characteristic was updated by Home App unsigned long timeVal(); // returns time elapsed (in millis) since value was last updated, either by Home App or by using setVal() + uint32_t getIID(); // returns IID of Characteristic SpanCharacteristic *setValidValues(int n, ...); // sets a list of 'n' valid values allowed for a Characteristic and returns pointer to self. Only applicable if format=INT, UINT8, UINT16, or UINT32 @@ -901,33 +734,11 @@ class SpanCharacteristic{ } // setRange() - SpanCharacteristic *setPerms(uint8_t perms){ - perms&=0x7F; - if(perms>0) - this->perms=perms; - return(this); - } - - SpanCharacteristic *addPerms(uint8_t dPerms){ - return(setPerms(perms|dPerms)); - } - - SpanCharacteristic *removePerms(uint8_t dPerms){ - return(setPerms(perms&(~dPerms))); - } - - SpanCharacteristic *setDescription(const char *c){ - desc = (char *)HS_REALLOC(desc, strlen(c) + 1); - strcpy(desc, c); - return(this); - } - - SpanCharacteristic *setUnit(const char *c){ - unit = (char *)HS_REALLOC(unit, strlen(c) + 1); - strcpy(unit, c); - return(this); - } - + SpanCharacteristic *setPerms(uint8_t perms); // sets permissions of a Characteristic + SpanCharacteristic *addPerms(uint8_t dPerms); // add permissions of a Characteristic + SpanCharacteristic *removePerms(uint8_t dPerms); // removes permissions of a Characteristic + SpanCharacteristic *setDescription(const char *c); // sets description of a Characteristic + SpanCharacteristic *setUnit(const char *c); // set unit of a Characteristic }; /////////////////////////////// From 214286f4de2ed9a3de199370d2533282d2a64a50 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 4 May 2024 08:07:03 -0500 Subject: [PATCH 08/19] remove deprecated SpanRange structure Has been deprecated many versions ago. --- src/HomeSpan.cpp | 15 --------------- src/HomeSpan.h | 10 ---------- 2 files changed, 25 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 871ef55..67093a6 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -2353,21 +2353,6 @@ SpanCharacteristic *SpanCharacteristic::setValidValues(int n, ...){ return(this); } -/////////////////////////////// -// SpanRange // -/////////////////////////////// - -SpanRange::SpanRange(int min, int max, int step){ - - if(homeSpan.Accessories.empty() || homeSpan.Accessories.back()->Services.empty() || homeSpan.Accessories.back()->Services.back()->Characteristics.empty() ){ - LOG0("\nFATAL ERROR! Can't create new SpanRange(%d,%d,%d) without a defined Characteristic ***\n",min,max,step); - LOG0("\n=== PROGRAM HALTED ==="); - while(1); - } else { - homeSpan.Accessories.back()->Services.back()->Characteristics.back()->setRange(min,max,step); - } -} - /////////////////////////////// // SpanButton // /////////////////////////////// diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 0ce7fdb..374bfdd 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -109,7 +109,6 @@ struct Span; struct SpanAccessory; struct SpanService; struct SpanCharacteristic; -struct SpanRange; struct SpanBuf; struct SpanButton; struct SpanUserCommand; @@ -230,7 +229,6 @@ class Span{ friend class SpanCharacteristic; friend class SpanUserCommand; friend class SpanButton; - friend class SpanRange; friend class SpanWebLog; friend class SpanOTA; friend class Network; @@ -441,7 +439,6 @@ class SpanAccessory{ friend class SpanService; friend class SpanCharacteristic; friend class SpanButton; - friend class SpanRange; uint32_t aid=0; // Accessory Instance ID (HAP Table 6-1) uint32_t iidCount=0; // running count of iid to use for Services and Characteristics associated with this Accessory @@ -466,7 +463,6 @@ class SpanService{ friend class Span; friend class SpanAccessory; friend class SpanCharacteristic; - friend class SpanRange; uint32_t iid=0; // Instance ID (HAP Table 6-2) const char *type; // Service Type @@ -743,12 +739,6 @@ class SpanCharacteristic{ /////////////////////////////// -struct [[deprecated("Please use Characteristic::setRange() method instead.")]] SpanRange{ - SpanRange(int min, int max, int step); -}; - -/////////////////////////////// - class SpanButton : public PushButton { friend class Span; From be4825dacb6b7318b16f975f05dd6c4997fa3658 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 4 May 2024 08:31:34 -0500 Subject: [PATCH 09/19] Move "internal" SpanCharacteristic methods from public to private As well as additional clean-up of organization and format of SpanCharacteristic prototype declarations. --- src/HomeSpan.h | 76 +++++++++++++++++++++++--------------------------- 1 file changed, 35 insertions(+), 41 deletions(-) diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 374bfdd..e14f4d4 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -550,7 +550,7 @@ class SpanCharacteristic{ void uvSet(UVal &dest, UVal &src); // copies UVal src into UVal dest void uvSet(UVal &u, const char *val); // copies string val into UVal u - template void uvSet(UVal &u, T val){ // copies any other type of val into UVal u + template void uvSet(UVal &u, T val){ // copies numeric val into UVal u switch(format){ case FORMAT::BOOL: u.BOOL=(boolean)val; @@ -580,7 +580,11 @@ class SpanCharacteristic{ } // switch } - template T uvGet(UVal &u){ // returns UVal u, cast into T + char *getStringGeneric(UVal &val); // gets the specified UVal for string-based Characteristics + size_t getDataGeneric(uint8_t *data, size_t len, UVal &val); // gets the specified UVal for data-based Characteristics + size_t getTLVGeneric(TLV8 &tlv, UVal &val); // gets the specified UVal for tlv8-based Characteristics + + template T uvGet(UVal &u){ // gets the specified UVal for numeric-based Characteristics switch(format){ case FORMAT::BOOL: @@ -604,7 +608,10 @@ class SpanCharacteristic{ } return((T)0); // included to prevent compiler warnings } - + + void setValCheck(); // initial check before setting value of any Characteristic + void setValFinish(boolean notify); // final processing after setting value of any Characteristic + protected: ~SpanCharacteristic(); // destructor @@ -649,18 +656,24 @@ class SpanCharacteristic{ public: - SpanCharacteristic(HapChar *hapChar, boolean isCustom=false); // constructor - void *operator new(size_t size){return(HS_MALLOC(size));} // override new operator to use PSRAM when available + SpanCharacteristic(HapChar *hapChar, boolean isCustom=false); // SpanCharacteristic constructor + void *operator new(size_t size){return(HS_MALLOC(size));} // override new operator to use PSRAM when available - template T getVal(){ - return(uvGet(value)); - } + template T getVal(){return(uvGet(value));} // gets the value for numeric-based Characteristics + char *getString(){return(getStringGeneric(value));} // gets the value for string-based Characteristics + size_t getData(uint8_t *data, size_t len){return(getDataGeneric(data,len,value));} // gets the value for data-based Characteristics + size_t getTLV(TLV8 &tlv){return(getTLVGeneric(tlv,value));} // gets the value for tlv8-based Characteristics - template T getNewVal(){ - return(uvGet(newValue)); - } + template T getNewVal(){return(uvGet(newValue));} // gets the newValue for numeric-based Characteristics + char *getNewString(){return(getStringGeneric(newValue));} // gets the newValue for string-based Characteristics + size_t getNewData(uint8_t *data, size_t len){return(getDataGeneric(data,len,newValue));} // gets the newValue for data-based Characteristics + size_t getNewTLV(TLV8 &tlv){return(getTLVGeneric(tlv,newValue));} // gets the newValue for tlv8-based Characteristics - template void setVal(T val, boolean notify=true){ + void setString(const char *val, boolean notify=true); // sets the value and newValue for string-based Characteristic + void setData(uint8_t *data, size_t len, boolean notify=true); // sets the value and newValue for data-based Characteristic + void setTLV(TLV8 &tlv, boolean notify=true); // sets the value and newValue for tlv8-based Characteristic + + template void setVal(T val, boolean notify=true){ // sets the value and newValue for numeric-based Characteristics setValCheck(); @@ -692,31 +705,18 @@ class SpanCharacteristic{ } // setVal() - char *getStringGeneric(UVal &val); // return the specified UVal for string-based Characteristics - char *getString(){return(getStringGeneric(value));} // return the value for string-based Characteristics - char *getNewString(){return(getStringGeneric(newValue));} // return the newValue for string-based Characteristics - void setString(const char *val, boolean notify=true); // set the value and newValue for string-based Characteristic + boolean updated(); // returns true within update() if Characteristic was updated by Home App + unsigned long timeVal(); // returns time elapsed (in millis) since value was last updated, either by Home App or by using setVal() + uint32_t getIID(); // returns IID of Characteristic - size_t getDataGeneric(uint8_t *data, size_t len, UVal &val); // return the specified UVal for data-based Characteristics - size_t getData(uint8_t *data, size_t len){return(getDataGeneric(data,len,value));} // return the value for data-based Characteristics - size_t getNewData(uint8_t *data, size_t len){return(getDataGeneric(data,len,newValue));} // return the newValue for data-based Characteristics - void setData(uint8_t *data, size_t len, boolean notify=true); // set the value and newValue for data-based Characteristic + SpanCharacteristic *setPerms(uint8_t perms); // sets permissions of a Characteristic + SpanCharacteristic *addPerms(uint8_t dPerms); // add permissions of a Characteristic + SpanCharacteristic *removePerms(uint8_t dPerms); // removes permissions of a Characteristic + SpanCharacteristic *setDescription(const char *c); // sets description of a Characteristic + SpanCharacteristic *setUnit(const char *c); // set unit of a Characteristic + SpanCharacteristic *setValidValues(int n, ...); // sets a list of 'n' valid values allowed for a Characteristic - only applicable if format=INT, UINT8, UINT16, or UINT32 - size_t getTLVGeneric(TLV8 &tlv, UVal &val); // return the specified UVal for tlv8-based Characteristics - size_t getTLV(TLV8 &tlv){return(getTLVGeneric(tlv,value));} // return the value for tlv8-based Characteristics - size_t getNewTLV(TLV8 &tlv){return(getTLVGeneric(tlv,newValue));} // return the newValue for tlv8-based Characteristics - void setTLV(TLV8 &tlv, boolean notify=true); // set the value and newValue for tlv8-based Characteristic - - void setValCheck(); // initial check before setting value of any Characteristic - void setValFinish(boolean notify); // final processing after setting value of any Characteristic - - boolean updated(); // returns true within update() if Characteristic was updated by Home App - unsigned long timeVal(); // returns time elapsed (in millis) since value was last updated, either by Home App or by using setVal() - uint32_t getIID(); // returns IID of Characteristic - - SpanCharacteristic *setValidValues(int n, ...); // sets a list of 'n' valid values allowed for a Characteristic and returns pointer to self. Only applicable if format=INT, UINT8, UINT16, or UINT32 - - template SpanCharacteristic *setRange(A min, B max, S step=0){ + template SpanCharacteristic *setRange(A min, B max, S step=0){ // sets the allowed range of a Characteristic if(!staticRange){ uvSet(minValue,min); @@ -729,12 +729,6 @@ class SpanCharacteristic{ return(this); } // setRange() - - SpanCharacteristic *setPerms(uint8_t perms); // sets permissions of a Characteristic - SpanCharacteristic *addPerms(uint8_t dPerms); // add permissions of a Characteristic - SpanCharacteristic *removePerms(uint8_t dPerms); // removes permissions of a Characteristic - SpanCharacteristic *setDescription(const char *c); // sets description of a Characteristic - SpanCharacteristic *setUnit(const char *c); // set unit of a Characteristic }; /////////////////////////////// From 747b8c32441d3b50d00e68609b9334563ae3a5c9 Mon Sep 17 00:00:00 2001 From: Gregg Date: Wed, 8 May 2024 06:45:57 -0500 Subject: [PATCH 10/19] initial change in hap[i] array to reserve space according to socket number --- src/HAP.cpp | 8 +-- src/HAP.h | 1 + src/HomeSpan.cpp | 134 ++++++++++++++++++++--------------------------- src/build_opt.h | 1 - src/src.ino | 21 ++------ 5 files changed, 65 insertions(+), 100 deletions(-) delete mode 100644 src/build_opt.h diff --git a/src/HAP.cpp b/src/HAP.cpp index e5d6d07..fb92a05 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -1253,8 +1253,8 @@ void HAPClient::checkTimedWrites(){ void HAPClient::eventNotify(SpanBuf *pObj, int nObj, int ignoreClient){ - for(int cNum=0;cNumclient && cNum!=ignoreClient){ // if there is a client connected to this slot and it is NOT flagged to be ignored (in cases where it is the client making a PUT request) + for(int cNum=0;cNumclient && cNum!=ignoreClient){ // if there is a client connected to this slot and it is NOT flagged to be ignored (in cases where it is the client making a PUT request) homeSpan.printfNotify(pObj,nObj,cNum); // create JSON (which may be of zero length if there are no applicable notifications for this cNum) size_t nBytes=hapOut.getSize(); @@ -1470,8 +1470,8 @@ void HAPClient::removeController(uint8_t *id){ void HAPClient::tearDown(uint8_t *id){ - for(int i=0;iclient && (id==NULL || (hap[i]->cPair && !memcmp(id,hap[i]->cPair->ID,hap_controller_IDBYTES)))){ + for(int i=0;iclient && (id==NULL || (hap[i]->cPair && !memcmp(id,hap[i]->cPair->ID,hap_controller_IDBYTES)))){ LOG1("*** Terminating Client #%d\n",i); hap[i]->client.stop(); } diff --git a/src/HAP.h b/src/HAP.h index 8fd6f15..99f7551 100644 --- a/src/HAP.h +++ b/src/HAP.h @@ -93,6 +93,7 @@ struct HAPClient { WiFiClient client; // handle to client Controller *cPair=NULL; // pointer to info on current, session-verified Paired Controller (NULL=un-verified, and therefore un-encrypted, connection) + boolean isConnected=false; // flag to indicate client is connect // These temporary Curve25519 keys are generated in the first call to pair-verify and used in the second call to pair-verify so must persist for a short period diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 67093a6..0683012 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -89,14 +89,9 @@ 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 - if(requestedMaxConhasClient()){ // found new client + + WiFiClient newClient=hapServer->available(); // get new client - if(newClient=hapServer->available()){ // found a new HTTP client - int freeSlot=getFreeSlot(); // get next free slot + int socket=newClient.fd()-LWIP_SOCKET_OFFSET; // get socket number (starting at zero) + + if(hap[socket]==NULL) // create HAPClient at that socket if it does not alreay exist + hap[socket]=new HAPClient; - if(freeSlot==-1){ // no available free slots - freeSlot=randombytes_uniform(maxConnections); - LOG2("=======================================\n"); - LOG1("** Freeing Client #"); - LOG1(freeSlot); - LOG1(" ("); - LOG1(millis()/1000); - LOG1(" sec) "); - LOG1(hap[freeSlot]->client.remoteIP()); - LOG1("\n"); - hap[freeSlot]->client.stop(); // disconnect client from first slot and re-use - } - - hap[freeSlot]->client=newClient; // copy new client handle into free slot + hap[socket]->client=newClient; // copy new client handle + hap[socket]->isConnected=true; // set isConnected flag + hap[socket]->cPair=NULL; // reset pointer to verified ID + homeSpan.clearNotify(socket); // clear all notification requests for this connection + HAPClient::pairStatus=pairState_M1; // reset starting PAIR STATE (which may be needed if Accessory failed in middle of pair-setup) LOG2("=======================================\n"); - LOG1("** Client #"); - LOG1(freeSlot); - LOG1(" Connected: ("); - LOG1(millis()/1000); - LOG1(" sec) "); - LOG1(hap[freeSlot]->client.remoteIP()); - LOG1(" on Socket "); - LOG1(hap[freeSlot]->client.fd()-LWIP_SOCKET_OFFSET+1); - LOG1("/"); - LOG1(CONFIG_LWIP_MAX_SOCKETS); - LOG1("\n"); + LOG1("** Client #%d Connected (%lu sec): %s\n",socket,millis()/1000,newClient.remoteIP().toString().c_str()); LOG2("\n"); - - hap[freeSlot]->cPair=NULL; // reset pointer to verified ID - homeSpan.clearNotify(freeSlot); // clear all notification requests for this connection - HAPClient::pairStatus=pairState_M1; // reset starting PAIR STATE (which may be needed if Accessory failed in middle of pair-setup) } - - for(int i=0;iclient && hap[i]->client.available()){ // if connection exists and data is available + for(int i=0;iclient.remoteIP().toString(); // store IP Address for web logging - hap[i]->processRequest(); // process HAP request - homeSpan.lastClientIP="0.0.0.0"; // reset stored IP address to show "0.0.0.0" if homeSpan.getClientIP() is used in any other context - - if(!hap[i]->client){ // client disconnected by server - LOG1("** Disconnected Client #"); - LOG1(i); - LOG1(" ("); - LOG1(millis()/1000); - LOG1(" sec)\n"); + if(hap[i]){ // if this socket has a configured HAPClient + if(hap[i]->client){ // if the client is connected + if(hap[i]->client.available()){ // if client has data available + HAPClient::conNum=i; // set connection number + homeSpan.lastClientIP=hap[i]->client.remoteIP().toString(); // store IP Address for web logging + hap[i]->processRequest(); // PROCESS HAP REQUEST + homeSpan.lastClientIP="0.0.0.0"; // reset stored IP address to show "0.0.0.0" if homeSpan.getClientIP() is used in any other context + } + } + else if(hap[i]->isConnected){ // if client is not connected, but HAPClient thinks it is + LOG1("** Client #%d DISCONNECTED (%lu sec)\n",i,millis()/1000); + hap[i]->isConnected=false; } - - LOG2("\n"); - - } // process HAP Client - } // for-loop over connection slots - + } + } + 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 @@ -587,6 +559,13 @@ void Span::processSerialCommand(const char *c){ switch(c[0]){ + case 'Z': { + for(int i=0;iclient) + hap[i]->client.stop(); + } + break; + case 's': { LOG0("\n*** HomeSpan Status ***\n\n"); @@ -603,27 +582,26 @@ void Span::processSerialCommand(const char *c){ HAPClient::printControllers(); LOG0("\n"); - for(int i=0;iclient){ - - LOG0("%s on Socket %d/%d",hap[i]->client.remoteIP().toString().c_str(),hap[i]->client.fd()-LWIP_SOCKET_OFFSET+1,CONFIG_LWIP_MAX_SOCKETS); + for(int i=0;icPair){ - LOG0(" ID="); - HAPClient::charPrintRow(hap[i]->cPair->getID(),36); - LOG0(hap[i]->cPair->isAdmin()?" (admin)":" (regular)"); + if(hap[i]->client){ + LOG0(" %s",hap[i]->client.remoteIP().toString().c_str()); + + if(hap[i]->cPair){ + LOG0(" ID="); + HAPClient::charPrintRow(hap[i]->cPair->getID(),36); + LOG0(hap[i]->cPair->isAdmin()?" (admin)":" (regular)"); + } else { + LOG0(" (unverified)"); + } } else { - LOG0(" (unverified)"); + LOG0(" unconnected"); } - - } else { - LOG0("(unconnected)"); + LOG0("\n"); } - - LOG0("\n"); - } - + } LOG0("\n*** End Status ***\n\n"); } break; @@ -914,8 +892,8 @@ void Span::processSerialCommand(const char *c){ if(((*chr)->perms)&EV){ LOG0(", EV=("); boolean addComma=false; - for(int i=0;iev[i] && hap[i]->client){ + for(int i=0;iev[i] && hap[i] && hap[i]->client){ LOG0("%s%d",addComma?",":"",i); addComma=true; } diff --git a/src/build_opt.h b/src/build_opt.h deleted file mode 100644 index f7f5b39..0000000 --- a/src/build_opt.h +++ /dev/null @@ -1 +0,0 @@ --fno-exceptions diff --git a/src/src.ino b/src/src.ino index 13e2f91..c518e7d 100644 --- a/src/src.ino +++ b/src/src.ino @@ -31,7 +31,10 @@ void setup() { Serial.begin(115200); - homeSpan.begin(Category::Lighting,"HomeSpan Light"); + homeSpan.setLogLevel(2); + homeSpan.enableWebLog(); + + homeSpan.begin(Category::Lighting,"HomeSpan LightBulb"); new SpanAccessory(); new Service::AccessoryInformation(); @@ -39,8 +42,6 @@ void setup() { new Service::LightBulb(); new Characteristic::On(); -// new SpanUserCommand('k',"- list controllers",list_controllers); - homeSpan.setControllerCallback(list_controllers); } @@ -52,17 +53,3 @@ void loop(){ } ////////////////////////////////////// - - -void list_controllers(){ - Serial.printf("\nControllers\n"); - for(auto it=homeSpan.controllerListBegin(); it!=homeSpan.controllerListEnd(); ++it){ - Serial.printf("Admin=%d ID=",it->isAdmin()); - for(int i=0;i<36;i++) - Serial.printf("%02X",it->getID()[i]); - Serial.printf(" LTPK="); - for(int i=0;i<32;i++) - Serial.printf("%02X",it->getLTPK()[i]); - Serial.printf("\n"); - } -} From e0ec162938678c0ad66101bf25d24e4511df6bb7 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 11 May 2024 07:43:11 -0500 Subject: [PATCH 11/19] Added logic to check for hap[i] disconnect and issue stop() when found This cures memory leakage when using HomeSpan without a Home Hub. Sockets are now properly closed when the Home App abruptly disconnects. --- src/HAP.cpp | 1 + src/HomeSpan.cpp | 10 ++++++---- src/src.ino | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index fb92a05..e02ca74 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -1217,6 +1217,7 @@ void HAPClient::getStatusURL(HAPClient *hapClient, void (*callBack)(const char * if(hapClient){ hapClient->client.stop(); + delay(1); LOG2("------------ SENT! --------------\n"); } } diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 0683012..780c430 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -229,10 +229,10 @@ void Span::pollTask() { processSerialCommand(cBuf); } - if(hapServer->hasClient()){ // found new client + WiFiClient newClient; + + if(newClient=hapServer->available()){ // found new client - WiFiClient newClient=hapServer->available(); // get new client - int socket=newClient.fd()-LWIP_SOCKET_OFFSET; // get socket number (starting at zero) if(hap[socket]==NULL) // create HAPClient at that socket if it does not alreay exist @@ -262,7 +262,9 @@ void Span::pollTask() { } else if(hap[i]->isConnected){ // if client is not connected, but HAPClient thinks it is LOG1("** Client #%d DISCONNECTED (%lu sec)\n",i,millis()/1000); - hap[i]->isConnected=false; + hap[i]->isConnected=false; + hap[i]->client.stop(); + delay(1); } } } diff --git a/src/src.ino b/src/src.ino index c518e7d..e371724 100644 --- a/src/src.ino +++ b/src/src.ino @@ -30,7 +30,7 @@ void setup() { Serial.begin(115200); - + homeSpan.setLogLevel(2); homeSpan.enableWebLog(); From 3643506d89d06316f6dcf0f072387a3660728e11 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 11 May 2024 13:29:07 -0500 Subject: [PATCH 12/19] Move temporary keys for pairing/verification into separate structure Confirmed pairing and verification continue to works as expected. --- src/HAP.cpp | 40 +++++++++++++++------------------------- src/HAP.h | 12 +++++++----- 2 files changed, 22 insertions(+), 30 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index e02ca74..c63d95f 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -447,8 +447,7 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ // Note the SALT and INFO text fields used by HKDF to create this Session Key are NOT the same as those for creating iosDeviceX. // The iosDeviceX HKDF calculations are separate and will be performed further below with the SALT and INFO as specified in the HAP docs. - TempBuffer sessionKey(crypto_box_PUBLICKEYBYTES); // temporary space - used only in this block - HKDF::create(sessionKey,srp->K,64,"Pair-Setup-Encrypt-Salt","Pair-Setup-Encrypt-Info"); // create SessionKey + HKDF::create(temp.sessionKey,srp->K,64,"Pair-Setup-Encrypt-Salt","Pair-Setup-Encrypt-Info"); // create SessionKey LOG2("------- DECRYPTING SUB-TLVS -------\n"); @@ -456,7 +455,7 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ TempBuffer decrypted(itEncryptedData->getLen()-crypto_aead_chacha20poly1305_IETF_ABYTES); // temporary storage for decrypted data - if(crypto_aead_chacha20poly1305_ietf_decrypt(decrypted, NULL, NULL, *itEncryptedData, itEncryptedData->getLen(), NULL, 0, (unsigned char *)"\x00\x00\x00\x00PS-Msg05", sessionKey)==-1){ + if(crypto_aead_chacha20poly1305_ietf_decrypt(decrypted, NULL, NULL, *itEncryptedData, itEncryptedData->getLen(), NULL, 0, (unsigned char *)"\x00\x00\x00\x00PS-Msg05", temp.sessionKey)==-1){ LOG0("\n*** ERROR: Exchange-Request Authentication Failed\n\n"); responseTLV.add(kTLVType_Error,tagError_Authentication); // set Error=Authentication tlvRespond(responseTLV); // send response to client @@ -534,7 +533,7 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ itEncryptedData=responseTLV.add(kTLVType_EncryptedData,subPack.len()+crypto_aead_chacha20poly1305_IETF_ABYTES,NULL); //create blank EncryptedData TLV with space for subTLV + Authentication Tag - crypto_aead_chacha20poly1305_ietf_encrypt(*itEncryptedData,NULL,subPack,subPack.len(),NULL,0,NULL,(unsigned char *)"\x00\x00\x00\x00PS-Msg06",sessionKey); + crypto_aead_chacha20poly1305_ietf_encrypt(*itEncryptedData,NULL,subPack,subPack.len(),NULL,0,NULL,(unsigned char *)"\x00\x00\x00\x00PS-Msg06",temp.sessionKey); LOG2("---------- END SUB-TLVS! ----------\n"); @@ -611,16 +610,14 @@ int HAPClient::postPairVerifyURL(uint8_t *content, size_t len){ return(0); } - publicCurveKey=(uint8_t *)HS_MALLOC(crypto_box_PUBLICKEYBYTES); // temporary space - will be deleted at end of verification process TempBuffer secretCurveKey(crypto_box_SECRETKEYBYTES); // temporary space - used only in this block - crypto_box_keypair(publicCurveKey,secretCurveKey); // generate Accessory's random Curve25519 Public/Secret Key Pair + crypto_box_keypair(temp.publicCurveKey,secretCurveKey); // generate Accessory's random Curve25519 Public/Secret Key Pair - iosCurveKey=(uint8_t *)HS_MALLOC(crypto_box_PUBLICKEYBYTES); // temporary space - will be deleted at end of verification process - memcpy(iosCurveKey,*itPublicKey,crypto_box_PUBLICKEYBYTES); // save Controller's Curve25519 Public Key + memcpy(temp.iosCurveKey,*itPublicKey,crypto_box_PUBLICKEYBYTES); // save Controller's Curve25519 Public Key // concatenate Accessory's Curve25519 Public Key, Accessory's Pairing ID, and Controller's Curve25519 Public Key into accessoryInfo - TempBuffer accessoryInfo(publicCurveKey,crypto_box_PUBLICKEYBYTES,accessory.ID,hap_accessory_IDBYTES,iosCurveKey,crypto_box_PUBLICKEYBYTES,NULL); + TempBuffer accessoryInfo(temp.publicCurveKey,crypto_box_PUBLICKEYBYTES,accessory.ID,hap_accessory_IDBYTES,temp.iosCurveKey,crypto_box_PUBLICKEYBYTES,NULL); subTLV.add(kTLVType_Identifier,hap_accessory_IDBYTES,accessory.ID); // set Identifier subTLV record as Accessory's Pairing ID auto itSignature=subTLV.add(kTLVType_Signature,crypto_sign_BYTES,NULL); // create blank Signature subTLV @@ -634,19 +631,17 @@ int HAPClient::postPairVerifyURL(uint8_t *content, size_t len){ TempBuffer subPack(subTLV.pack_size()); // create sub-TLV by packing Identifier and Signature TLV records together subTLV.pack(subPack); - sharedCurveKey=(uint8_t *)HS_MALLOC(crypto_box_PUBLICKEYBYTES); // temporary space - will be deleted at end of verification process - crypto_scalarmult_curve25519(sharedCurveKey,secretCurveKey,iosCurveKey); // generate Shared-Secret Curve25519 Key from Accessory's Curve25519 Secret Key and Controller's Curve25519 Public Key + crypto_scalarmult_curve25519(temp.sharedCurveKey,secretCurveKey,temp.iosCurveKey); // generate Shared-Secret Curve25519 Key from Accessory's Curve25519 Secret Key and Controller's Curve25519 Public Key - sessionKey=(uint8_t *)HS_MALLOC(crypto_box_PUBLICKEYBYTES); // temporary space - will be deleted at end of verification process - HKDF::create(sessionKey,sharedCurveKey,crypto_box_PUBLICKEYBYTES,"Pair-Verify-Encrypt-Salt","Pair-Verify-Encrypt-Info"); // create Session Curve25519 Key from Shared-Secret Curve25519 Key using HKDF-SHA-512 + HKDF::create(temp.sessionKey,temp.sharedCurveKey,crypto_box_PUBLICKEYBYTES,"Pair-Verify-Encrypt-Salt","Pair-Verify-Encrypt-Info"); // create Session Curve25519 Key from Shared-Secret Curve25519 Key using HKDF-SHA-512 - auto itEncryptedData=responseTLV.add(kTLVType_EncryptedData,subPack.len()+crypto_aead_chacha20poly1305_IETF_ABYTES,NULL); // create blank EncryptedData subTLV - crypto_aead_chacha20poly1305_ietf_encrypt(*itEncryptedData,NULL,subPack,subPack.len(),NULL,0,NULL,(unsigned char *)"\x00\x00\x00\x00PV-Msg02",sessionKey); // encrypt data with Session Curve25519 Key and padded nonce="PV-Msg02" + auto itEncryptedData=responseTLV.add(kTLVType_EncryptedData,subPack.len()+crypto_aead_chacha20poly1305_IETF_ABYTES,NULL); // create blank EncryptedData subTLV + crypto_aead_chacha20poly1305_ietf_encrypt(*itEncryptedData,NULL,subPack,subPack.len(),NULL,0,NULL,(unsigned char *)"\x00\x00\x00\x00PV-Msg02",temp.sessionKey); // encrypt data with Session Curve25519 Key and padded nonce="PV-Msg02" LOG2("---------- END SUB-TLVS! ----------\n"); responseTLV.add(kTLVType_State,pairState_M2); // set State= - responseTLV.add(kTLVType_PublicKey,crypto_box_PUBLICKEYBYTES,publicCurveKey); // set PublicKey to Accessory's Curve25519 Public Key + responseTLV.add(kTLVType_PublicKey,crypto_box_PUBLICKEYBYTES,temp.publicCurveKey); // set PublicKey to Accessory's Curve25519 Public Key tlvRespond(responseTLV); // send response to client } @@ -670,7 +665,7 @@ int HAPClient::postPairVerifyURL(uint8_t *content, size_t len){ TempBuffer decrypted((*itEncryptedData).getLen()-crypto_aead_chacha20poly1305_IETF_ABYTES); // temporary storage for decrypted data - if(crypto_aead_chacha20poly1305_ietf_decrypt(decrypted, NULL, NULL, *itEncryptedData, itEncryptedData->getLen(), NULL, 0, (unsigned char *)"\x00\x00\x00\x00PV-Msg03", sessionKey)==-1){ + if(crypto_aead_chacha20poly1305_ietf_decrypt(decrypted, NULL, NULL, *itEncryptedData, itEncryptedData->getLen(), NULL, 0, (unsigned char *)"\x00\x00\x00\x00PV-Msg03", temp.sessionKey)==-1){ LOG0("\n*** ERROR: Verify Authentication Failed\n\n"); responseTLV.add(kTLVType_State,pairState_M4); // set State= responseTLV.add(kTLVType_Error,tagError_Authentication); // set Error=Authentication @@ -713,7 +708,7 @@ int HAPClient::postPairVerifyURL(uint8_t *content, size_t len){ // concatenate Controller's Curve25519 Public Key (from previous step), Controller's Pairing ID, and Accessory's Curve25519 Public Key (from previous step) into iosDeviceInfo - TempBuffer iosDeviceInfo(iosCurveKey,crypto_box_PUBLICKEYBYTES,tPair->ID,hap_controller_IDBYTES,publicCurveKey,crypto_box_PUBLICKEYBYTES,NULL); + TempBuffer iosDeviceInfo(temp.iosCurveKey,crypto_box_PUBLICKEYBYTES,tPair->ID,hap_controller_IDBYTES,temp.publicCurveKey,crypto_box_PUBLICKEYBYTES,NULL); if(crypto_sign_verify_detached(*itSignature, iosDeviceInfo, iosDeviceInfo.len(), tPair->LTPK) != 0){ // verify signature of iosDeviceInfo using Controller's LTPK LOG0("\n*** ERROR: LPTK Signature Verification Failed\n\n"); @@ -728,17 +723,12 @@ int HAPClient::postPairVerifyURL(uint8_t *content, size_t len){ cPair=tPair; // save Controller for this connection slot - connection is now verified and should be encrypted going forward - HKDF::create(a2cKey,sharedCurveKey,32,"Control-Salt","Control-Read-Encryption-Key"); // create AccessoryToControllerKey from (previously-saved) Shared-Secret Curve25519 Key (HAP Section 6.5.2) - HKDF::create(c2aKey,sharedCurveKey,32,"Control-Salt","Control-Write-Encryption-Key"); // create ControllerToAccessoryKey from (previously-saved) Shared-Secret Curve25519 Key (HAP Section 6.5.2) + HKDF::create(a2cKey,temp.sharedCurveKey,32,"Control-Salt","Control-Read-Encryption-Key"); // create AccessoryToControllerKey from (previously-saved) Shared-Secret Curve25519 Key (HAP Section 6.5.2) + HKDF::create(c2aKey,temp.sharedCurveKey,32,"Control-Salt","Control-Write-Encryption-Key"); // create ControllerToAccessoryKey from (previously-saved) Shared-Secret Curve25519 Key (HAP Section 6.5.2) a2cNonce.zero(); // reset Nonces for this session to zero c2aNonce.zero(); - free(publicCurveKey); // free storage of these temporary variables created in previous step - free(sharedCurveKey); - free(sessionKey); - free(iosCurveKey); - LOG2("\n*** SESSION VERIFICATION COMPLETE *** \n"); } break; diff --git a/src/HAP.h b/src/HAP.h index 99f7551..0a9e6fe 100644 --- a/src/HAP.h +++ b/src/HAP.h @@ -96,12 +96,14 @@ struct HAPClient { boolean isConnected=false; // flag to indicate client is connect // These temporary Curve25519 keys are generated in the first call to pair-verify and used in the second call to pair-verify so must persist for a short period - - uint8_t *publicCurveKey; // Accessory's Curve25519 Public Key - uint8_t *sharedCurveKey; // Shared-Secret Curve25519 Key derived from Accessory's Secret Key and Controller's Public Key - uint8_t *sessionKey; // Session Key Curve25519 (derived with various HKDF calls) - uint8_t *iosCurveKey; // Controller's Curve25519 Public Key + struct tempKeys_t { + uint8_t publicCurveKey[crypto_box_PUBLICKEYBYTES]; // Accessory's Curve25519 Public Key + uint8_t sharedCurveKey[crypto_box_PUBLICKEYBYTES]; // Shared-Secret Curve25519 Key derived from Accessory's Secret Key and Controller's Public Key + uint8_t sessionKey[crypto_box_PUBLICKEYBYTES]; // Session Key Curve25519 (derived with various HKDF calls) + uint8_t iosCurveKey[crypto_box_PUBLICKEYBYTES]; // Controller's Curve25519 Public Key + } temp; + // CurveKey and CurveKey Nonces are created once each new session is verified in /pair-verify. Keys persist for as long as connection is open uint8_t a2cKey[32]; // AccessoryToControllerKey derived from HKDF-SHA-512 of sharedCurveKey (HAP Section 6.5.2) From dbfad7d2225d3264b5ea5ea54d846f0640593f37 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 12 May 2024 07:17:21 -0500 Subject: [PATCH 13/19] initial change of hap[i] to linked-list from fixed array --- src/HAP.cpp | 19 +++++------ src/HAP.h | 1 - src/HomeSpan.cpp | 85 +++++++++++++++++++++--------------------------- 3 files changed, 46 insertions(+), 59 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index c63d95f..cf10f51 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -324,7 +324,7 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ iosTLV.print(); LOG2("------------ END TLVS! ------------\n"); - LOG1("In Pair Setup #%d (%s)...",conNum,client.remoteIP().toString().c_str()); + LOG1("In Pair Setup #%d (%s)...",client.fd()-LWIP_SOCKET_OFFSET,client.remoteIP().toString().c_str()); auto itState=iosTLV.find(kTLVType_State); @@ -344,7 +344,7 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ return(0); }; - LOG2("Found . Expected .\n",tlvState,pairStatus); + LOG1("Found . Expected .\n",tlvState,pairStatus); if(tlvState!=pairStatus){ // error: Device is not yet paired, but out-of-sequence pair-setup STATE was received LOG0("\n*** ERROR: Out-of-Sequence Pair-Setup request!\n\n"); @@ -574,7 +574,7 @@ int HAPClient::postPairVerifyURL(uint8_t *content, size_t len){ iosTLV.print(); LOG2("------------ END TLVS! ------------\n"); - LOG1("In Pair Verify #%d (%s)...",conNum,client.remoteIP().toString().c_str()); + LOG1("In Pair Verify #%d (%s)...",client.fd()-LWIP_SOCKET_OFFSET,client.remoteIP().toString().c_str()); auto itState=iosTLV.find(kTLVType_State); @@ -594,7 +594,7 @@ int HAPClient::postPairVerifyURL(uint8_t *content, size_t len){ return(0); }; - LOG2("Found \n",tlvState); // unlike pair-setup, out-of-sequencing can be handled gracefully for pair-verify (HAP requirement). No need to keep track of pairStatus + LOG1("Found \n",tlvState); // unlike pair-setup, out-of-sequencing can be handled gracefully for pair-verify (HAP requirement). No need to keep track of pairStatus switch(tlvState){ // Pair-Verify STATE received -- process request! (HAP Section 5.7) @@ -756,7 +756,7 @@ int HAPClient::postPairingsURL(uint8_t *content, size_t len){ iosTLV.print(); LOG2("------------ END TLVS! ------------\n"); - LOG1("In Post Pairings #%d (%s)...",conNum,client.remoteIP().toString().c_str()); + LOG1("In Post Pairings #%d (%s)...",client.fd()-LWIP_SOCKET_OFFSET,client.remoteIP().toString().c_str()); auto itState=iosTLV.find(kTLVType_State); auto itMethod=iosTLV.find(kTLVType_Method); @@ -882,7 +882,7 @@ int HAPClient::getAccessoriesURL(){ return(0); } - LOG1("In Get Accessories #%d (%s)...\n",conNum,client.remoteIP().toString().c_str()); + LOG1("In Get Accessories #%d (%s)...\n",client.fd()-LWIP_SOCKET_OFFSET,client.remoteIP().toString().c_str()); homeSpan.printfAttributes(); size_t nBytes=hapOut.getSize(); @@ -910,7 +910,7 @@ int HAPClient::getCharacteristicsURL(char *urlBuf){ return(0); } - LOG1("In Get Characteristics #%d (%s)...\n",conNum,client.remoteIP().toString().c_str()); + LOG1("In Get Characteristics #%d (%s)...\n",client.fd()-LWIP_SOCKET_OFFSET,client.remoteIP().toString().c_str()); int len=strlen(urlBuf); // determine number of IDs specified by counting commas in URL int numIDs=1; @@ -978,7 +978,7 @@ int HAPClient::putCharacteristicsURL(char *json){ return(0); } - LOG1("In Put Characteristics #%d (%s)...\n",conNum,client.remoteIP().toString().c_str()); + LOG1("In Put Characteristics #%d (%s)...\n",client.fd()-LWIP_SOCKET_OFFSET,client.remoteIP().toString().c_str()); int n=homeSpan.countCharacteristics(json); // count number of objects in JSON request if(n==0) // if no objects found, return @@ -1031,7 +1031,7 @@ int HAPClient::putPrepareURL(char *json){ return(0); } - LOG1("In Put Prepare #%d (%s)...\n",conNum,client.remoteIP().toString().c_str()); + LOG1("In Put Prepare #%d (%s)...\n",client.fd()-LWIP_SOCKET_OFFSET,client.remoteIP().toString().c_str()); char ttlToken[]="\"ttl\":"; char pidToken[]="\"pid\":"; @@ -1237,7 +1237,6 @@ void HAPClient::checkTimedWrites(){ else tw++; } - } ////////////////////////////////////// diff --git a/src/HAP.h b/src/HAP.h index 0a9e6fe..0aa42b8 100644 --- a/src/HAP.h +++ b/src/HAP.h @@ -93,7 +93,6 @@ struct HAPClient { WiFiClient client; // handle to client Controller *cPair=NULL; // pointer to info on current, session-verified Paired Controller (NULL=un-verified, and therefore un-encrypted, connection) - boolean isConnected=false; // flag to indicate client is connect // These temporary Curve25519 keys are generated in the first call to pair-verify and used in the second call to pair-verify so must persist for a short period diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 780c430..fd3a155 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -47,6 +47,8 @@ using namespace Utils; HapOut hapOut; // Specialized output stream that can both print to serial monitor and encrypt/transmit to HAP Clients with minimal memory usage (global-scoped variable) HAPClient **hap; // HAP Client structure containing HTTP client connections, parsing routines, and state variables (global-scoped variable) +list> hapList; // linked-list of HAP Client structures containing HTTP client connections, parsing routines, and state variables (global-scoped variable) + Span homeSpan; // HAP Attributes database and all related control functions for this Accessory (global-scoped variable) HapCharacteristics hapChars; // Instantiation of all HAP Characteristics used to create SpanCharacteristics (global-scoped variable) @@ -229,43 +231,36 @@ void Span::pollTask() { processSerialCommand(cBuf); } - WiFiClient newClient; - - if(newClient=hapServer->available()){ // found new client + if(hapServer->hasClient()){ + + auto it=hapList.emplace(hapList.begin()); + (*it).client=hapServer->available(); + +// homeSpan.clearNotify(socket); // clear all notification requests for this connection - int socket=newClient.fd()-LWIP_SOCKET_OFFSET; // get socket number (starting at zero) - - if(hap[socket]==NULL) // create HAPClient at that socket if it does not alreay exist - hap[socket]=new HAPClient; - - hap[socket]->client=newClient; // copy new client handle - hap[socket]->isConnected=true; // set isConnected flag - hap[socket]->cPair=NULL; // reset pointer to verified ID - homeSpan.clearNotify(socket); // clear all notification requests for this connection HAPClient::pairStatus=pairState_M1; // reset starting PAIR STATE (which may be needed if Accessory failed in middle of pair-setup) LOG2("=======================================\n"); - LOG1("** Client #%d Connected (%lu sec): %s\n",socket,millis()/1000,newClient.remoteIP().toString().c_str()); + LOG1("** Client #%d Connected (%lu sec): %s\n",(*it).client.fd()-LWIP_SOCKET_OFFSET,millis()/1000,(*it).client.remoteIP().toString().c_str()); LOG2("\n"); } - - for(int i=0;iclient){ // if the client is connected - if(hap[i]->client.available()){ // if client has data available - HAPClient::conNum=i; // set connection number - homeSpan.lastClientIP=hap[i]->client.remoteIP().toString(); // store IP Address for web logging - hap[i]->processRequest(); // PROCESS HAP REQUEST - homeSpan.lastClientIP="0.0.0.0"; // reset stored IP address to show "0.0.0.0" if homeSpan.getClientIP() is used in any other context - } - } - else if(hap[i]->isConnected){ // if client is not connected, but HAPClient thinks it is - LOG1("** Client #%d DISCONNECTED (%lu sec)\n",i,millis()/1000); - hap[i]->isConnected=false; - hap[i]->client.stop(); - delay(1); + auto it=hapList.begin(); + while(it!=hapList.end()){ + + if((*it).client.connected()){ // if the client is connected + if((*it).client.available()){ // if client has data available +// HAPClient::conNum=i; // set connection number + homeSpan.lastClientIP=(*it).client.remoteIP().toString(); // store IP Address for web logging + (*it).processRequest(); // PROCESS HAP REQUEST + homeSpan.lastClientIP="0.0.0.0"; // reset stored IP address to show "0.0.0.0" if homeSpan.getClientIP() is used in any other context } + it++; + } else { + LOG1("** Client #%d DISCONNECTED (%lu sec)\n",(*it).client.fd()-LWIP_SOCKET_OFFSET,millis()/1000); + (*it).client.stop(); + delay(5); + it=hapList.erase(it); } } @@ -277,7 +272,7 @@ void Span::pollTask() { for(auto it=PushButtons.begin();it!=PushButtons.end();it++) // check for SpanButton presses (*it)->check(); - HAPClient::checkNotifications(); +////// HAPClient::checkNotifications(); HAPClient::checkTimedWrites(); if(spanOTA.enabled) @@ -584,26 +579,20 @@ void Span::processSerialCommand(const char *c){ HAPClient::printControllers(); LOG0("\n"); - for(int i=0;iclient){ - LOG0(" %s",hap[i]->client.remoteIP().toString().c_str()); - - if(hap[i]->cPair){ - LOG0(" ID="); - HAPClient::charPrintRow(hap[i]->cPair->getID(),36); - LOG0(hap[i]->cPair->isAdmin()?" (admin)":" (regular)"); - } else { - LOG0(" (unverified)"); - } - } else { - LOG0(" unconnected"); - } - LOG0("\n"); + for(auto it=hapList.begin(); it!=hapList.end(); ++it){ + LOG0("Client #%d: %s",(*it).client.fd()-LWIP_SOCKET_OFFSET,(*it).client.remoteIP().toString().c_str()); + if((*it).cPair){ + LOG0(" ID="); + HAPClient::charPrintRow((*it).cPair->getID(),36); + LOG0((*it).cPair->isAdmin()?" (admin)":" (regular)\n"); + } else { + LOG0(" (unverified)\n"); } } + + if(hapList.empty()) + LOG0("No Client Connections!\n"); + LOG0("\n*** End Status ***\n\n"); } break; From 9d29b73dac0bfbce4e17229982068970bf1b45f8 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 2 Jun 2024 20:57:41 -0500 Subject: [PATCH 14/19] Fixed memory leak [needed to free `body` in tlvRespond()] This hopefully addresses memory leak that occurs when not using a Home Hub and connections constantly drop and re-establish. To do: must add back logic for notifications using new hap linked-list structure, and then delete static hap array code. --- src/HAP.cpp | 21 +++++++++++++-------- src/HAP.h | 1 + src/HomeSpan.cpp | 17 ++++++++++------- src/src.ino | 2 ++ 4 files changed, 26 insertions(+), 15 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index cf10f51..b7027d6 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -324,7 +324,7 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ iosTLV.print(); LOG2("------------ END TLVS! ------------\n"); - LOG1("In Pair Setup #%d (%s)...",client.fd()-LWIP_SOCKET_OFFSET,client.remoteIP().toString().c_str()); + LOG1("In Pair Setup #%d (%s)...",clientNumber,client.remoteIP().toString().c_str()); auto itState=iosTLV.find(kTLVType_State); @@ -574,7 +574,7 @@ int HAPClient::postPairVerifyURL(uint8_t *content, size_t len){ iosTLV.print(); LOG2("------------ END TLVS! ------------\n"); - LOG1("In Pair Verify #%d (%s)...",client.fd()-LWIP_SOCKET_OFFSET,client.remoteIP().toString().c_str()); + LOG1("In Pair Verify #%d (%s)...",clientNumber,client.remoteIP().toString().c_str()); auto itState=iosTLV.find(kTLVType_State); @@ -756,7 +756,7 @@ int HAPClient::postPairingsURL(uint8_t *content, size_t len){ iosTLV.print(); LOG2("------------ END TLVS! ------------\n"); - LOG1("In Post Pairings #%d (%s)...",client.fd()-LWIP_SOCKET_OFFSET,client.remoteIP().toString().c_str()); + LOG1("In Post Pairings #%d (%s)...",clientNumber,client.remoteIP().toString().c_str()); auto itState=iosTLV.find(kTLVType_State); auto itMethod=iosTLV.find(kTLVType_Method); @@ -882,7 +882,7 @@ int HAPClient::getAccessoriesURL(){ return(0); } - LOG1("In Get Accessories #%d (%s)...\n",client.fd()-LWIP_SOCKET_OFFSET,client.remoteIP().toString().c_str()); + LOG1("In Get Accessories #%d (%s)...\n",clientNumber,client.remoteIP().toString().c_str()); homeSpan.printfAttributes(); size_t nBytes=hapOut.getSize(); @@ -910,7 +910,7 @@ int HAPClient::getCharacteristicsURL(char *urlBuf){ return(0); } - LOG1("In Get Characteristics #%d (%s)...\n",client.fd()-LWIP_SOCKET_OFFSET,client.remoteIP().toString().c_str()); + LOG1("In Get Characteristics #%d (%s)...\n",clientNumber,client.remoteIP().toString().c_str()); int len=strlen(urlBuf); // determine number of IDs specified by counting commas in URL int numIDs=1; @@ -978,7 +978,7 @@ int HAPClient::putCharacteristicsURL(char *json){ return(0); } - LOG1("In Put Characteristics #%d (%s)...\n",client.fd()-LWIP_SOCKET_OFFSET,client.remoteIP().toString().c_str()); + LOG1("In Put Characteristics #%d (%s)...\n",clientNumber,client.remoteIP().toString().c_str()); int n=homeSpan.countCharacteristics(json); // count number of objects in JSON request if(n==0) // if no objects found, return @@ -1031,7 +1031,7 @@ int HAPClient::putPrepareURL(char *json){ return(0); } - LOG1("In Put Prepare #%d (%s)...\n",client.fd()-LWIP_SOCKET_OFFSET,client.remoteIP().toString().c_str()); + LOG1("In Put Prepare #%d (%s)...\n",clientNumber,client.remoteIP().toString().c_str()); char ttlToken[]="\"ttl\":"; char pidToken[]="\"pid\":"; @@ -1291,6 +1291,8 @@ void HAPClient::tlvRespond(TLV8 &tlv8){ LOG2("------------ SENT! --------------\n"); else LOG2("-------- SENT ENCRYPTED! --------\n"); + + free(body); } // tlvRespond @@ -1559,9 +1561,12 @@ HapOut::HapStreamBuffer::HapStreamBuffer(){ ////////////////////////////////////// HapOut::HapStreamBuffer::~HapStreamBuffer(){ - + sync(); free(buffer); + free(encBuf); + free(hash); + free(ctx); } ////////////////////////////////////// diff --git a/src/HAP.h b/src/HAP.h index 0aa42b8..c3c9d4a 100644 --- a/src/HAP.h +++ b/src/HAP.h @@ -92,6 +92,7 @@ struct HAPClient { // individual structures and data defined for each Hap Client connection WiFiClient client; // handle to client + int clientNumber; // client number Controller *cPair=NULL; // pointer to info on current, session-verified Paired Controller (NULL=un-verified, and therefore un-encrypted, connection) // These temporary Curve25519 keys are generated in the first call to pair-verify and used in the second call to pair-verify so must persist for a short period diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index fd3a155..7bc9e0a 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -235,13 +235,14 @@ void Span::pollTask() { auto it=hapList.emplace(hapList.begin()); (*it).client=hapServer->available(); + (*it).clientNumber=(*it).client.fd()-LWIP_SOCKET_OFFSET; // homeSpan.clearNotify(socket); // clear all notification requests for this connection HAPClient::pairStatus=pairState_M1; // reset starting PAIR STATE (which may be needed if Accessory failed in middle of pair-setup) LOG2("=======================================\n"); - LOG1("** Client #%d Connected (%lu sec): %s\n",(*it).client.fd()-LWIP_SOCKET_OFFSET,millis()/1000,(*it).client.remoteIP().toString().c_str()); + LOG1("** Client #%d Connected (%lu sec): %s\n",(*it).clientNumber,millis()/1000,(*it).client.remoteIP().toString().c_str()); LOG2("\n"); } @@ -257,7 +258,7 @@ void Span::pollTask() { } it++; } else { - LOG1("** Client #%d DISCONNECTED (%lu sec)\n",(*it).client.fd()-LWIP_SOCKET_OFFSET,millis()/1000); + LOG1("** Client #%d DISCONNECTED (%lu sec)\n",(*it).clientNumber,millis()/1000); (*it).client.stop(); delay(5); it=hapList.erase(it); @@ -557,9 +558,11 @@ void Span::processSerialCommand(const char *c){ switch(c[0]){ case 'Z': { - for(int i=0;iclient) - hap[i]->client.stop(); + for(auto it=hapList.begin(); it!=hapList.end(); ++it){ + (*it).client.stop(); + delay(5); + + } } break; @@ -580,11 +583,11 @@ void Span::processSerialCommand(const char *c){ LOG0("\n"); for(auto it=hapList.begin(); it!=hapList.end(); ++it){ - LOG0("Client #%d: %s",(*it).client.fd()-LWIP_SOCKET_OFFSET,(*it).client.remoteIP().toString().c_str()); + LOG0("Client #%d: %s",(*it).clientNumber,(*it).client.remoteIP().toString().c_str()); if((*it).cPair){ LOG0(" ID="); HAPClient::charPrintRow((*it).cPair->getID(),36); - LOG0((*it).cPair->isAdmin()?" (admin)":" (regular)\n"); + LOG0((*it).cPair->isAdmin()?" (admin)\n":" (regular)\n"); } else { LOG0(" (unverified)\n"); } diff --git a/src/src.ino b/src/src.ino index e371724..98d9733 100644 --- a/src/src.ino +++ b/src/src.ino @@ -35,6 +35,8 @@ void setup() { homeSpan.enableWebLog(); homeSpan.begin(Category::Lighting,"HomeSpan LightBulb"); + + new SpanUserCommand('D', " - disconnect WiFi", [](const char *buf){WiFi.disconnect();}); new SpanAccessory(); new Service::AccessoryInformation(); From 153ab451fd74d661aa0370c21e3d1a70192563b9 Mon Sep 17 00:00:00 2001 From: Gregg Date: Wed, 5 Jun 2024 13:32:15 -0500 Subject: [PATCH 15/19] Moved Controller Class definition back to HAP.h instead of HomeSpan.h As long as `class Controller` is forward-declared in HomeSpan.h, the definition can live in HAP.h --- src/HAP.h | 28 ++++++++++++++++++++++++++++ src/HomeSpan.h | 29 ----------------------------- 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/src/HAP.h b/src/HAP.h index c3c9d4a..ad19bb2 100644 --- a/src/HAP.h +++ b/src/HAP.h @@ -72,6 +72,34 @@ struct Accessory { uint8_t LTPK[crypto_sign_PUBLICKEYBYTES]; // Long Term Ed2519 Public Key }; +////////////////////////////////////////////////////////// +// Paired Controller Structure for Permanently-Stored Data + +class Controller { + friend class HAPClient; + + boolean allocated=false; // DEPRECATED (but needed for backwards compatability with original NVS storage of Controller info) + boolean admin; // Controller has admin privileges + uint8_t ID[36]; // Pairing ID + uint8_t LTPK[32]; // Long Term Ed2519 Public Key + + public: + + Controller(uint8_t *id, uint8_t *ltpk, boolean ad){ + allocated=true; + admin=ad; + memcpy(ID,id,36); + memcpy(LTPK,ltpk,32); + } + + Controller(){} + + const uint8_t *getID() const {return(ID);} + const uint8_t *getLTPK() const {return(LTPK);} + boolean isAdmin() const {return(admin);} + +}; + ///////////////////////////////////////////////// // HAPClient Structure // Reads and Writes from each HAP Client connection diff --git a/src/HomeSpan.h b/src/HomeSpan.h index e14f4d4..6798303 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -189,35 +189,6 @@ struct SpanOTA{ // manages OTA process static void error(ota_error_t err); }; - -////////////////////////////////////////////////////////// -// Paired Controller Structure for Permanently-Stored Data - -class Controller { - friend class HAPClient; - - boolean allocated=false; // DEPRECATED (but needed for backwards compatability with original NVS storage of Controller info) - boolean admin; // Controller has admin privileges - uint8_t ID[36]; // Pairing ID - uint8_t LTPK[32]; // Long Term Ed2519 Public Key - - public: - - Controller(uint8_t *id, uint8_t *ltpk, boolean ad){ - allocated=true; - admin=ad; - memcpy(ID,id,36); - memcpy(LTPK,ltpk,32); - } - - Controller(){} - - const uint8_t *getID() const {return(ID);} - const uint8_t *getLTPK() const {return(LTPK);} - boolean isAdmin() const {return(admin);} - -}; - ////////////////////////////////////// // USER API CLASSES BEGINS HERE // ////////////////////////////////////// From 983e159adf7f2eba8e03a54bbfd55e832740cd2d Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 8 Jun 2024 16:45:46 -0500 Subject: [PATCH 16/19] Created evList to store notifications Also updated all code to use hapList and deleted all references to **hap --- src/HAP.cpp | 29 ++++++------ src/HAP.h | 4 +- src/HomeSpan.cpp | 121 +++++++++++++++++++++++------------------------ src/HomeSpan.h | 32 ++++++++----- 4 files changed, 95 insertions(+), 91 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index b7027d6..ea031a3 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -1017,7 +1017,7 @@ int HAPClient::putCharacteristicsURL(char *json){ // Create and send Event Notifications if needed - eventNotify(pObj,n,HAPClient::conNum); // transmit EVENT Notification for "n" pObj objects, except DO NOT notify client making request + eventNotify(pObj,n,this); // transmit EVENT Notification for "n" pObj objects, except DO NOT notify client making request return(1); } @@ -1241,22 +1241,22 @@ void HAPClient::checkTimedWrites(){ ////////////////////////////////////// -void HAPClient::eventNotify(SpanBuf *pObj, int nObj, int ignoreClient){ - - for(int cNum=0;cNumclient && cNum!=ignoreClient){ // if there is a client connected to this slot and it is NOT flagged to be ignored (in cases where it is the client making a PUT request) +void HAPClient::eventNotify(SpanBuf *pObj, int nObj, HAPClient *ignore){ - homeSpan.printfNotify(pObj,nObj,cNum); // create JSON (which may be of zero length if there are no applicable notifications for this cNum) + for(auto it=homeSpan.hapList.begin(); it!=homeSpan.hapList.end(); ++it){ // loop over all connection slots + if(&(*it)!=ignore){ // if NOT flagged to be ignored (in cases where it is the client making a PUT request) + + homeSpan.printfNotify(pObj,nObj,&(*it)); // create JSON (which may be of zero length if there are no applicable notifications for this cNum) size_t nBytes=hapOut.getSize(); hapOut.flush(); if(nBytes>0){ // if there ARE notifications to send to client cNum - LOG2("\n>>>>>>>>>> %s >>>>>>>>>>\n",hap[cNum]->client.remoteIP().toString().c_str()); + LOG2("\n>>>>>>>>>> %s >>>>>>>>>>\n",it->client.remoteIP().toString().c_str()); - hapOut.setLogLevel(2).setHapClient(hap[cNum]); + hapOut.setLogLevel(2).setHapClient(&(*it)); hapOut << "EVENT/1.0 200 OK\r\nContent-Type: application/hap+json\r\nContent-Length: " << nBytes << "\r\n\r\n"; - homeSpan.printfNotify(pObj,nObj,cNum); + homeSpan.printfNotify(pObj,nObj,&(*it)); hapOut.flush(); LOG2("\n-------- SENT ENCRYPTED! --------\n"); @@ -1461,11 +1461,11 @@ void HAPClient::removeController(uint8_t *id){ ////////////////////////////////////// void HAPClient::tearDown(uint8_t *id){ - - for(int i=0;iclient && (id==NULL || (hap[i]->cPair && !memcmp(id,hap[i]->cPair->ID,hap_controller_IDBYTES)))){ - LOG1("*** Terminating Client #%d\n",i); - hap[i]->client.stop(); + + for(HAPClient &hc : homeSpan.hapList){ + if(id==NULL || (hc.cPair && !memcmp(id,hc.cPair->ID,hap_controller_IDBYTES))){ + LOG1("*** Terminating Client #%d\n",hc.clientNumber); + hc.client.stop(); } } } @@ -1691,5 +1691,4 @@ void HapOut::HapStreamBuffer::printFormatted(char *buf, size_t nChars, size_t ns pairState HAPClient::pairStatus; Accessory HAPClient::accessory; list> HAPClient::controllerList; -int HAPClient::conNum; diff --git a/src/HAP.h b/src/HAP.h index ad19bb2..9532741 100644 --- a/src/HAP.h +++ b/src/HAP.h @@ -115,7 +115,6 @@ struct HAPClient { static pairState pairStatus; // tracks pair-setup status static Accessory accessory; // Accessory ID and Ed25519 public and secret keys - permanently stored static list> controllerList; // linked-list of Paired Controller IDs and ED25519 long-term public keys - permanently stored - static int conNum; // connection number - used to keep track of per-connection EV notifications // individual structures and data defined for each Hap Client connection @@ -174,7 +173,7 @@ struct HAPClient { static void tearDown(uint8_t *id); // tears down connections using Controller with ID=id; tears down all connections if id=NULL 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 + static void eventNotify(SpanBuf *pObj, int nObj, HAPClient *ignore=NULL); // transmits EVENT Notifications for nObj SpanBuf objects, pObj, with optional flag to ignore a specific client static void getStatusURL(HAPClient *, void (*)(const char *, void *), void *); // GET / status (an optional, non-HAP feature) @@ -237,5 +236,4 @@ class HapOut : public std::ostream { ///////////////////////////////////////////////// // Extern Variables -extern HAPClient **hap; extern HapOut hapOut; diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 7bc9e0a..116ba7b 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -46,9 +46,6 @@ const __attribute__((section(".rodata_custom_desc"))) SpanPartition spanPartitio using namespace Utils; HapOut hapOut; // Specialized output stream that can both print to serial monitor and encrypt/transmit to HAP Clients with minimal memory usage (global-scoped variable) -HAPClient **hap; // HAP Client structure containing HTTP client connections, parsing routines, and state variables (global-scoped variable) -list> hapList; // linked-list of HAP Client structures containing HTTP client connections, parsing routines, and state variables (global-scoped variable) - Span homeSpan; // HAP Attributes database and all related control functions for this Accessory (global-scoped variable) HapCharacteristics hapChars; // Instantiation of all HAP Characteristics used to create SpanCharacteristics (global-scoped variable) @@ -71,7 +68,6 @@ Span::Span(){ rebootCount++; nvs_set_u8(wifiNVS,"REBOOTS",rebootCount); nvs_commit(wifiNVS); - } /////////////////////////////// @@ -91,8 +87,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 - hap=(HAPClient **)HS_CALLOC(CONFIG_LWIP_MAX_SOCKETS,sizeof(HAPClient *)); // create fixed array of pointers to HAPClient objects (initially set to NULL) - hapServer=new WiFiServer(tcpPortNum); // create HAP WIFI SERVER size_t len; @@ -233,35 +227,33 @@ void Span::pollTask() { if(hapServer->hasClient()){ - auto it=hapList.emplace(hapList.begin()); - (*it).client=hapServer->available(); - (*it).clientNumber=(*it).client.fd()-LWIP_SOCKET_OFFSET; - -// homeSpan.clearNotify(socket); // clear all notification requests for this connection - - HAPClient::pairStatus=pairState_M1; // reset starting PAIR STATE (which may be needed if Accessory failed in middle of pair-setup) + auto it=hapList.emplace(hapList.begin()); // create new HAPClient connection + it->client=hapServer->available(); + it->clientNumber=it->client.fd()-LWIP_SOCKET_OFFSET; + + HAPClient::pairStatus=pairState_M1; // reset starting PAIR STATE (which may be needed if Accessory failed in middle of pair-setup) LOG2("=======================================\n"); - LOG1("** Client #%d Connected (%lu sec): %s\n",(*it).clientNumber,millis()/1000,(*it).client.remoteIP().toString().c_str()); + LOG1("** Client #%d Connected (%lu sec): %s\n",it->clientNumber,millis()/1000,it->client.remoteIP().toString().c_str()); LOG2("\n"); } - auto it=hapList.begin(); - while(it!=hapList.end()){ + currentClient=hapList.begin(); + while(currentClient!=hapList.end()){ - if((*it).client.connected()){ // if the client is connected - if((*it).client.available()){ // if client has data available -// HAPClient::conNum=i; // set connection number - homeSpan.lastClientIP=(*it).client.remoteIP().toString(); // store IP Address for web logging - (*it).processRequest(); // PROCESS HAP REQUEST - homeSpan.lastClientIP="0.0.0.0"; // reset stored IP address to show "0.0.0.0" if homeSpan.getClientIP() is used in any other context + if(currentClient->client.connected()){ // if the client is connected + if(currentClient->client.available()){ // if client has data available + homeSpan.lastClientIP=currentClient->client.remoteIP().toString(); // store IP Address for web logging + currentClient->processRequest(); // PROCESS HAP REQUEST + homeSpan.lastClientIP="0.0.0.0"; // reset stored IP address to show "0.0.0.0" if homeSpan.getClientIP() is used in any other context } - it++; + currentClient++; } else { - LOG1("** Client #%d DISCONNECTED (%lu sec)\n",(*it).clientNumber,millis()/1000); - (*it).client.stop(); + LOG1("** Client #%d DISCONNECTED (%lu sec)\n",currentClient->clientNumber,millis()/1000); + currentClient->client.stop(); delay(5); - it=hapList.erase(it); + clearNotify(&*currentClient); // clear all notification requests for this connection + currentClient=hapList.erase(currentClient); // remove HAPClient connection } } @@ -273,7 +265,7 @@ void Span::pollTask() { for(auto it=PushButtons.begin();it!=PushButtons.end();it++) // check for SpanButton presses (*it)->check(); -////// HAPClient::checkNotifications(); + HAPClient::checkNotifications(); HAPClient::checkTimedWrites(); if(spanOTA.enabled) @@ -304,18 +296,6 @@ void Span::pollTask() { } // poll -/////////////////////////////// - -int Span::getFreeSlot(){ - - for(int i=0;iclient) - return(i); - } - - return(-1); -} - ////////////////////////////////////// void Span::commandMode(){ @@ -886,11 +866,9 @@ void Span::processSerialCommand(const char *c){ if(((*chr)->perms)&EV){ LOG0(", EV=("); boolean addComma=false; - for(int i=0;iev[i] && hap[i] && hap[i]->client){ - LOG0("%s%d",addComma?",":"",i); - addComma=true; - } + for(HAPClient *hc : (*chr)->evList){ + LOG0("%s%d",addComma?",":"",hc->clientNumber); + addComma=true; } LOG0(")"); } @@ -1463,29 +1441,25 @@ int Span::updateCharacteristics(char *buf, SpanBuf *pObj){ /////////////////////////////// -void Span::clearNotify(int slotNum){ - - for(int i=0;iServices.size();j++){ - for(int k=0;kServices[j]->Characteristics.size();k++){ - Accessories[i]->Services[j]->Characteristics[k]->ev[slotNum]=false; - } - } - } -} +void Span::clearNotify(HAPClient *hc){ + + for(auto const &acc : Accessories) + for(auto const &svc : acc->Services) + for(auto const &chr : svc->Characteristics) + chr->evList.remove(hc); +} /////////////////////////////// -void Span::printfNotify(SpanBuf *pObj, int nObj, int conNum){ +void Span::printfNotify(SpanBuf *pObj, int nObj, HAPClient *hc){ boolean notifyFlag=false; for(int i=0;ievList.has(hc)){ // if connection hc is subscribed to EV notifications for this characteristic - if(pObj[i].characteristic->ev[conNum]){ // if notifications requested for this characteristic by specified connection number - if(!notifyFlag) // this is first notification for any characteristic hapOut << "{\"characteristics\":["; // print start of JSON array else // else already printed at least one other characteristic @@ -1829,8 +1803,6 @@ SpanCharacteristic::SpanCharacteristic(HapChar *hapChar, boolean isCustom){ iid=++(homeSpan.Accessories.back()->iidCount); service=homeSpan.Accessories.back()->Services.back(); aid=homeSpan.Accessories.back()->aid; - - ev=(boolean *)HS_CALLOC(homeSpan.maxConnections,sizeof(boolean)); } /////////////////////////////// @@ -1842,7 +1814,6 @@ SpanCharacteristic::~SpanCharacteristic(){ chr++; service->Characteristics.erase(chr); - free(ev); free(desc); free(unit); free(validValues); @@ -2116,9 +2087,11 @@ void SpanCharacteristic::printfAttributes(int flags){ if(flags&GET_AID) hapOut << ",\"aid\":" << aid; + + HAPClient *hc=&(*(homeSpan.currentClient)); if(flags&GET_EV) - hapOut << ",\"ev\":" << (ev[HAPClient::conNum]?"true":"false"); + hapOut << ",\"ev\":" << (evList.has(hc)?"true":"false"); if(flags&GET_STATUS) hapOut << ",\"status\":0"; @@ -2144,7 +2117,12 @@ StatusCode SpanCharacteristic::loadUpdate(char *val, char *ev, boolean wr){ return(StatusCode::NotifyNotAllowed); LOG1("Notification Request for aid=%u iid=%u: %s\n",aid,iid,evFlag?"true":"false"); - this->ev[HAPClient::conNum]=evFlag; + HAPClient *hc=&(*(homeSpan.currentClient)); + + if(evFlag) + evList.add(hc); + else + evList.remove(hc); } if(!val) // no request to update value @@ -2325,6 +2303,25 @@ SpanCharacteristic *SpanCharacteristic::setValidValues(int n, ...){ return(this); } +/////////////////////////////// + +boolean SpanCharacteristic::EVLIST::has(HAPClient *hc){ + return(find_if(begin(), end(), [hc](const HAPClient *hcTemp){return(hc==hcTemp);}) != end()); +} + +/////////////////////////////// + +void SpanCharacteristic::EVLIST::add(HAPClient *hc){ + if(!has(hc)) + push_back(hc); +} + +/////////////////////////////// + +void SpanCharacteristic::EVLIST::remove(HAPClient *hc){ + remove_if(begin(), end(), [hc](const HAPClient *hcTemp){return(hc==hcTemp);}); +} + /////////////////////////////// // SpanButton // /////////////////////////////// diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 6798303..5cbb83e 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -112,6 +112,8 @@ struct SpanCharacteristic; struct SpanBuf; struct SpanButton; struct SpanUserCommand; + +struct HAPClient; class Controller; extern Span homeSpan; @@ -263,16 +265,17 @@ class Span{ 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 - vector> Accessories; // vector of pointers to all Accessories - vector> Loops; // vector of pointer to all Services that have over-ridden loop() methods + + list> hapList; // linked-list of HAPClient structures containing HTTP client connections, parsing routines, and state variables + list>::iterator currentClient; // iterator to current client + vector> Accessories; // vector of pointers to all Accessories + vector> Loops; // vector of pointer to all Services that have over-ridden loop() methods vector> Notifications; // vector of SpanBuf objects that store info for Characteristics that are updated with setVal() and require a Notification Event - vector> PushButtons; // vector of pointer to all PushButtons - unordered_map TimedWrites; // map of timed-write PIDs and Alarm Times (based on TTLs) - - unordered_map UserCommands; // map of pointers to all UserCommands + vector> PushButtons; // vector of pointer to all PushButtons + unordered_map TimedWrites; // map of timed-write PIDs and Alarm Times (based on TTLs) + 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 void resetStatus(); // resets statusLED and calls statusCallback based on current HomeSpan status @@ -285,8 +288,8 @@ class Span{ 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 void printfAttributes(SpanBuf *pObj, int nObj); // writes SpanBuf objects to hapOut stream boolean printfAttributes(char **ids, int numIDs, int flags); // writes accessory requested characteristic ids to hapOut stream - returns true if all characteristics are found and readable, else returns false - void clearNotify(int slotNum); // set ev notification flags for connection 'slotNum' to false across all characteristics - void printfNotify(SpanBuf *pObj, int nObj, int conNum); // writes notification JSON to hapOut stream based on SpanBuf objects and specified connection number + void clearNotify(HAPClient *hc); // clear all notifications related to specific client connection + void printfNotify(SpanBuf *pObj, int nObj, HAPClient *hc); // writes notification JSON to hapOut stream based on SpanBuf objects and specified connection static boolean invalidUUID(const char *uuid){ int x=0; @@ -487,6 +490,13 @@ class SpanCharacteristic{ STRING_t STRING = NULL; }; + class EVLIST : public vector>{ // vector of current connections that have subscribed to EV notifications for this Characteristic + public: + boolean has(HAPClient *hc); // returns true if pointer to connection hc is subscribed, else returns false + void add(HAPClient *hc); // adds connection hc as new subscriber, IF not already a subscriber + void remove(HAPClient *hc); // removes connection hc as a subscriber; okay to remove even if hc was not already a subscriber + }; + uint32_t iid=0; // Instance ID (HAP Table 6-3) HapChar *hapChar; // pointer to HAP Characteristic structure const char *type; // Characteristic Type @@ -502,7 +512,6 @@ class SpanCharacteristic{ boolean staticRange; // Flag that indicates whether Range is static and cannot be changed with setRange() boolean customRange=false; // Flag for custom ranges 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 @@ -513,7 +522,8 @@ class 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 - + EVLIST evList; // vector of current connections that have subscribed to EV notifications for this Characteristic + void printfAttributes(int flags); // writes Characteristic JSON to hapOut stream StatusCode loadUpdate(char *val, char *ev, boolean wr); // 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); // returns "printable" String for any type of Characteristic From 892c2247a3189d439ef0cb939d0e732e70b2eb97 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 8 Jun 2024 17:46:49 -0500 Subject: [PATCH 17/19] Finished testing new evList functionality --- src/HomeSpan.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 116ba7b..eef3bc3 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -866,7 +866,7 @@ void Span::processSerialCommand(const char *c){ if(((*chr)->perms)&EV){ LOG0(", EV=("); boolean addComma=false; - for(HAPClient *hc : (*chr)->evList){ + for(auto const &hc : (*chr)->evList){ LOG0("%s%d",addComma?",":"",hc->clientNumber); addComma=true; } @@ -2319,7 +2319,8 @@ void SpanCharacteristic::EVLIST::add(HAPClient *hc){ /////////////////////////////// void SpanCharacteristic::EVLIST::remove(HAPClient *hc){ - remove_if(begin(), end(), [hc](const HAPClient *hcTemp){return(hc==hcTemp);}); + auto it=remove_if(begin(), end(), [hc](const HAPClient *hcTemp){return(hc==hcTemp);}); + erase(it,end()); } /////////////////////////////// From 7ab8354ed7633f750287b99ce50a857e090385e0 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 8 Jun 2024 18:03:37 -0500 Subject: [PATCH 18/19] Deleted setMaxConnections() and DEPRECATED reserveSocketConnections setMaxConnections() was deprecated many version ago and is now deleted reserveSocketConnections() is no longer needed since new HomeKit architecture does not require more than a few connections - this function has been deprecated and if used will not do anything --- src/HomeSpan.cpp | 9 +++------ src/HomeSpan.h | 12 +++++------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index eef3bc3..25ee973 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -250,8 +250,8 @@ void Span::pollTask() { currentClient++; } else { LOG1("** Client #%d DISCONNECTED (%lu sec)\n",currentClient->clientNumber,millis()/1000); - currentClient->client.stop(); - delay(5); +// currentClient->client.stop(); +// delay(5); clearNotify(&*currentClient); // clear all notification requests for this connection currentClient=hapList.erase(currentClient); // remove HAPClient connection } @@ -500,7 +500,7 @@ void Span::checkConnect(){ if(webLog.timeServer) xTaskCreateUniversal(webLog.initTime, "timeSeverTaskHandle", 8096, &webLog, 1, NULL, 0); - LOG0("Starting HAP Server on port %d supporting %d simultaneous HomeKit Controller Connections...\n\n",tcpPortNum,maxConnections); + LOG0("Starting HAP Server on port %d...\n\n",tcpPortNum); hapServer->begin(); @@ -2390,8 +2390,6 @@ void SpanWebLog::init(uint16_t maxEntries, const char *serv, const char *tz, con isEnabled=true; } log = (log_t *)HS_CALLOC(maxEntries,sizeof(log_t)); - if(timeServer) - homeSpan.reserveSocketConnections(1); } /////////////////////////////// @@ -2458,7 +2456,6 @@ int SpanOTA::init(boolean _auth, boolean _safeLoad, const char *pwd){ enabled=true; safeLoad=_safeLoad; auth=_auth; - homeSpan.reserveSocketConnections(1); if(pwd==NULL) return(0); return(setPassword(pwd)); diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 5cbb83e..94ad261 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -238,8 +238,6 @@ class Span{ const char *defaultSetupCode=DEFAULT_SETUP_CODE; // Setup Code used for pairing uint16_t autoOffLED=0; // automatic turn-off duration (in seconds) for Status LED int 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 unsigned long comModeLife=DEFAULT_COMMAND_TIMEOUT*1000; // length of time (in milliseconds) to keep Command Mode alive before resuming normal operations uint16_t tcpPortNum=DEFAULT_TCP_PORT; // port for TCP communications between HomeKit and HomeSpan char qrID[5]=""; // Setup ID used for pairing with QR Code @@ -341,7 +339,6 @@ class Span{ int getLogLevel(){return(logLevel);} // get Log Level Span& setSerialInputDisable(boolean val){serialInputDisabled=val;return(*this);} // sets whether serial input is disabled (true) or enabled (false) boolean getSerialInputDisable(){return(serialInputDisabled);} // returns true if serial input is disabled, or false if serial input in enabled - Span& reserveSocketConnections(uint8_t n){maxConnections-=n;return(*this);} // reserves n socket connections *not* to be used for HAP Span& setHostNameSuffix(const char *suffix){hostNameSuffix=suffix;return(*this);} // sets the hostName suffix to be used instead of the 6-byte AccessoryID Span& setPortNum(uint16_t port){tcpPortNum=port;return(*this);} // sets the TCP port number to use for communications between HomeKit and HomeSpan Span& setQRID(const char *id); // sets the Setup ID for optional pairing with a QR Code @@ -399,10 +396,11 @@ class Span{ Span& setTimeServerTimeout(uint32_t tSec){webLog.waitTime=tSec*1000;return(*this);} // sets wait time (in seconds) for optional web log time server to connect list>::const_iterator controllerListBegin(); - list>::const_iterator controllerListEnd(); - - [[deprecated("Please use reserveSocketConnections(n) method instead.")]] - void setMaxConnections(uint8_t n){requestedMaxCon=n;} // sets maximum number of simultaneous HAP connections + list>::const_iterator controllerListEnd(); + + [[deprecated("This function has been deprecated (it is not needed) and no longer does anything. Please remove from sketch to ensure backwards compatilibilty with future versions.")]] + Span& reserveSocketConnections(uint8_t n){return(*this);} + }; /////////////////////////////// From e10025bd5428ea9ad0e6852fe1b93ce297debd89 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 8 Jun 2024 20:32:39 -0500 Subject: [PATCH 19/19] Update HomeSpan.cpp --- src/HomeSpan.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 25ee973..d5d7398 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -250,8 +250,6 @@ void Span::pollTask() { currentClient++; } else { LOG1("** Client #%d DISCONNECTED (%lu sec)\n",currentClient->clientNumber,millis()/1000); -// currentClient->client.stop(); -// delay(5); clearNotify(&*currentClient); // clear all notification requests for this connection currentClient=hapList.erase(currentClient); // remove HAPClient connection }