From 9b0b18310ec31ee9b12214944a7861e98aac2bda Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 24 Mar 2024 22:03:23 -0500 Subject: [PATCH 01/74] changed TLV8 from std::forward_list to std::list Though this increases space requirement for TLV8 (just a little), using std::list allows adding elements directly to end, which ensure the order is maintained when packing and unpacking. Will avoid potential confusion when TLV8 is used for Characteristics. --- src/HomeSpan.h | 2 +- src/TLV8.cpp | 6 +++--- src/TLV8.h | 8 ++++---- src/src.ino | 50 +++++++++++++++++++++++++++++--------------------- 4 files changed, 37 insertions(+), 29 deletions(-) diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 983289c..99865a7 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -537,7 +537,7 @@ class SpanCharacteristic{ } void uvSet(UVal &dest, UVal &src){ - if(format==FORMAT::STRING || format==FORMAT::DATA || format==FORMAT::TLV_ENC) + if(format>=FORMAT::STRING) uvSet(dest,(const char *)src.STRING); else dest=src; diff --git a/src/TLV8.cpp b/src/TLV8.cpp index 9fd6e16..2f19730 100644 --- a/src/TLV8.cpp +++ b/src/TLV8.cpp @@ -70,10 +70,10 @@ void tlv8_t::osprint(std::ostream& os){ TLV8_it TLV8::add(uint8_t tag, size_t len, const uint8_t* val){ - if(!empty() && front().tag==tag) - front().update(len,val); + if(!empty() && back().tag==tag) + back().update(len,val); else - emplace_front(tag,len,val); + emplace_back(tag,len,val); return(begin()); } diff --git a/src/TLV8.h b/src/TLV8.h index 4a83cc2..af752e8 100644 --- a/src/TLV8.h +++ b/src/TLV8.h @@ -29,7 +29,7 @@ #include #include -#include +#include #include #include "PSRAM.h" @@ -51,12 +51,12 @@ struct tlv8_t { ///////////////////////////////////// -typedef std::forward_list>::iterator TLV8_it; +typedef std::list>::iterator TLV8_it; typedef struct { const uint8_t tag; const char *name; } TLV8_names; ///////////////////////////////////// -class TLV8 : public std::forward_list> { +class TLV8 : public std::list> { TLV8_it currentPackIt; TLV8_it endPackIt; @@ -109,6 +109,6 @@ class TLV8 : public std::forward_list> { void unpack(uint8_t *buf, size_t bufSize); - void wipe(){std::forward_list>().swap(*this);} + void wipe(){std::list>().swap(*this);} }; diff --git a/src/src.ino b/src/src.ino index ea87240..7fda69a 100644 --- a/src/src.ino +++ b/src/src.ino @@ -26,18 +26,43 @@ ********************************************************************************/ #include "HomeSpan.h" +#include "TLV8.h" #define MAX_LIGHTS 1 void setup() { Serial.begin(115200); + delay(1000); + Serial.printf("\n\nREADY\n\n"); + + TLV8 tlv, tlv2; + + const size_t nMax=257; + uint8_t c[nMax]; + for(int i=0;i Date: Wed, 27 Mar 2024 20:41:29 -0500 Subject: [PATCH 02/74] Fixed return value in TLV8::add() --- src/TLV8.cpp | 2 +- src/src.ino | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/TLV8.cpp b/src/TLV8.cpp index 2f19730..3a52bd9 100644 --- a/src/TLV8.cpp +++ b/src/TLV8.cpp @@ -75,7 +75,7 @@ TLV8_it TLV8::add(uint8_t tag, size_t len, const uint8_t* val){ else emplace_back(tag,len,val); - return(begin()); + return(--end()); } ///////////////////////////////////// diff --git a/src/src.ino b/src/src.ino index 7fda69a..e92ce80 100644 --- a/src/src.ino +++ b/src/src.ino @@ -50,10 +50,14 @@ void setup() { tlv.add(7,33); tlv.add(7,34); tlv.add(15,nMax,c); - tlv.print(); +// tlv.print(); + + tlv.print(--tlv.end(),tlv.end()); Serial.printf("\nSize=%d\n\n",tlv.pack_size()); + + uint8_t bOut[tlv.pack_size()]; tlv.pack(bOut); From 1b74564baf9c9da504c7862a44ec735fb178c672 Mon Sep 17 00:00:00 2001 From: Gregg Date: Fri, 29 Mar 2024 09:19:06 -0500 Subject: [PATCH 03/74] More TLV updates --- src/HomeSpan.cpp | 10 ++++++---- src/HomeSpan.h | 6 +++--- src/src.ino | 37 +++++-------------------------------- 3 files changed, 14 insertions(+), 39 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 1364b59..73973fb 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -896,7 +896,7 @@ void Span::processSerialCommand(const char *c){ LOG0("%s%s",(foundPerms++)?"+":"",pNames[i]); } - if((*chr)->format!=FORMAT::STRING && (*chr)->format!=FORMAT::BOOL && (*chr)->format!=FORMAT::DATA){ + if((*chr)->formatformat!=FORMAT::BOOL){ if((*chr)->validValues) LOG0(", Valid Values=%s",(*chr)->validValues); else if((*chr)->uvGet((*chr)->stepValue)>0) @@ -937,7 +937,7 @@ void Span::processSerialCommand(const char *c){ if((*chr)->setValidValuesError) LOG0(" *** WARNING #%d! Attempt to set Custom Valid Values for this Characteristic ignored ***\n",++nWarnings); - if((*chr)->format!=STRING && (!(((*chr)->uvGet((*chr)->value) >= (*chr)->uvGet((*chr)->minValue)) && ((*chr)->uvGet((*chr)->value) <= (*chr)->uvGet((*chr)->maxValue))))) + if((*chr)->formatuvGet((*chr)->value) >= (*chr)->uvGet((*chr)->minValue)) && ((*chr)->uvGet((*chr)->value) <= (*chr)->uvGet((*chr)->maxValue))))) LOG0(" *** WARNING #%d! Value of %g is out of range [%g,%g] ***\n",++nWarnings,(*chr)->uvGet((*chr)->value),(*chr)->uvGet((*chr)->minValue),(*chr)->uvGet((*chr)->maxValue)); } // Characteristics @@ -1457,7 +1457,7 @@ int Span::updateCharacteristics(char *buf, SpanBuf *pObj){ if(status==StatusCode::OK){ // if status is okay pObj[j].characteristic->uvSet(pObj[j].characteristic->value,pObj[j].characteristic->newValue); // update characteristic value with new value if(pObj[j].characteristic->nvsKey){ // if storage key found - if(pObj[j].characteristic->format!=FORMAT::STRING && pObj[j].characteristic->format!=FORMAT::DATA) + if(pObj[j].characteristic->formatnvsKey,pObj[j].characteristic->value.UINT64); // store data as uint64_t regardless of actual type (it will be read correctly when access through uvGet()) else nvs_set_str(charNVS,pObj[j].characteristic->nvsKey,pObj[j].characteristic->value.STRING); // store data @@ -1833,7 +1833,7 @@ SpanCharacteristic::~SpanCharacteristic(){ free(validValues); free(nvsKey); - if(format==FORMAT::STRING || format==FORMAT::DATA){ + if(format>=FORMAT::STRING){ free(value.STRING); free(newValue.STRING); } @@ -2005,6 +2005,8 @@ StatusCode SpanCharacteristic::loadUpdate(char *val, char *ev, boolean wr){ break; case STRING: + case DATA: + case TLV_ENC: uvSet(newValue,(const char *)val); break; diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 99865a7..e10c32a 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -675,12 +675,12 @@ class SpanCharacteristic{ void setString(const char *val, boolean notify=true){ if(!((perms&EV) || (updateFlag==2))){ - LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setData() ignored. No EVENT NOTIFICATION (EV) permission on this characteristic\n\n",hapName); + LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setString() ignored. No EVENT NOTIFICATION (EV) permission on this characteristic\n\n",hapName); return; } if(updateFlag==1) - LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setVal() within update() while it is being updated by Home App. This may cause device to become non-responsive!\n\n",hapName); + LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setString() within update() while it is being updated by Home App. This may cause device to become non-responsive!\n\n",hapName); uvSet(value,val); uvSet(newValue,value); @@ -763,7 +763,7 @@ class SpanCharacteristic{ template void setVal(T val, boolean notify=true){ if(!((perms&EV) || (updateFlag==2))){ - LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setData() ignored. No EVENT NOTIFICATION (EV) permission on this characteristic\n\n",hapName); + LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setVal() ignored. No EVENT NOTIFICATION (EV) permission on this characteristic\n\n",hapName); return; } diff --git a/src/src.ino b/src/src.ino index e92ce80..19677e5 100644 --- a/src/src.ino +++ b/src/src.ino @@ -30,41 +30,11 @@ #define MAX_LIGHTS 1 +CUSTOM_CHAR_DATA(UserData, AAAAAAAA-BBBB-AAAA-AAAA-AAAAAAAAAAAA, PR+PW+EV); + void setup() { Serial.begin(115200); - delay(1000); - Serial.printf("\n\nREADY\n\n"); - - TLV8 tlv, tlv2; - - const size_t nMax=257; - uint8_t c[nMax]; - for(int i=0;isetDescription("Custom Data")->setData(x,5); char c[30]; sprintf(c,"Light-%d",i); new Characteristic::Name(c); @@ -93,6 +65,7 @@ void setup() { void loop(){ homeSpan.poll(); + delay(100000); } From 69fd86f2efec612750a685d98aaf4e7d5930cf73 Mon Sep 17 00:00:00 2001 From: Gregg Date: Fri, 29 Mar 2024 22:18:04 -0500 Subject: [PATCH 04/74] Testing TLV Characteristic using DisplayOrder (custom Characteristic) --- src/HomeSpan.h | 5 +- src/Span.h | 4 + src/src.ino | 219 +++++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 202 insertions(+), 26 deletions(-) diff --git a/src/HomeSpan.h b/src/HomeSpan.h index e10c32a..2938f4a 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -509,7 +509,7 @@ class SpanCharacteristic{ 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[67]; // space for 64 characters + surrounding quotes + terminating null + char c[64]; switch(format){ case FORMAT::BOOL: return(String(u.BOOL)); @@ -530,8 +530,7 @@ class SpanCharacteristic{ case FORMAT::STRING: case FORMAT::DATA: case FORMAT::TLV_ENC: - sprintf(c,"\"%.64s\"",u.STRING); // Truncating string to 64 chars - return(String(c)); + return(String("\"") + String(u.STRING) + String("\"")); } // switch return(String()); // included to prevent compiler warnings } diff --git a/src/Span.h b/src/Span.h index 1dc1395..23bba59 100644 --- a/src/Span.h +++ b/src/Span.h @@ -616,6 +616,10 @@ namespace Characteristic { HapChar _CUSTOM_##NAME {#UUID,#NAME,(PERMS)(PERMISISONS),DATA,true}; \ namespace Characteristic { struct NAME : SpanCharacteristic { NAME(const char * val="AA==", boolean nvsStore=false) : SpanCharacteristic {&_CUSTOM_##NAME,true} { init(val,nvsStore); } }; } +#define CUSTOM_CHAR_TLV(NAME,UUID,PERMISISONS) \ + HapChar _CUSTOM_##NAME {#UUID,#NAME,(PERMS)(PERMISISONS),TLV_ENC,true}; \ + namespace Characteristic { struct NAME : SpanCharacteristic { NAME(const char * val="", boolean nvsStore=false) : SpanCharacteristic {&_CUSTOM_##NAME,true} { init(val,nvsStore); } }; } + #else #define CUSTOM_CHAR(NAME,UUID,PERMISISONS,FORMAT,DEFVAL,MINVAL,MAXVAL,STATIC_RANGE) \ diff --git a/src/src.ino b/src/src.ino index 19677e5..01f20f2 100644 --- a/src/src.ino +++ b/src/src.ino @@ -28,45 +28,218 @@ #include "HomeSpan.h" #include "TLV8.h" -#define MAX_LIGHTS 1 +CUSTOM_CHAR_TLV(DisplayOrder,136,PR+EV); -CUSTOM_CHAR_DATA(UserData, AAAAAAAA-BBBB-AAAA-AAAA-AAAAAAAAAAAA, PR+PW+EV); +struct HomeSpanTV : Service::Television { + + SpanCharacteristic *active = new Characteristic::Active(0); // TV On/Off (set to Off at start-up) + SpanCharacteristic *activeID = new Characteristic::ActiveIdentifier(3); // Sets HDMI 3 on start-up + SpanCharacteristic *remoteKey = new Characteristic::RemoteKey(); // Used to receive button presses from the Remote Control widget + SpanCharacteristic *settingsKey = new Characteristic::PowerModeSelection(); // Adds "View TV Setting" option to Selection Screen + SpanCharacteristic *displayOrder = new Characteristic::DisplayOrder(); + + HomeSpanTV(const char *name) : Service::Television() { + new Characteristic::ConfiguredName(name); // Name of TV + Serial.printf("Configured TV: %s\n",name); + + TLV8 orderTLV; + uint32_t order[]={5,10,6,2,1,9,11,3,18,12}; + + for(int i=0;i0) + orderTLV.add(0); + orderTLV.add(1,sizeof(uint32_t),(uint8_t*)(order+i)); + } + + orderTLV.print(); + size_t n=orderTLV.pack_size(); + Serial.printf("Size=%d\n",n); + uint8_t c[n]; + orderTLV.pack(c); + displayOrder->setData(c,n); + + new SpanUserCommand('P', "- change order of inputs", changeOrder, this); + } + + boolean update() override { + + if(active->updated()){ + Serial.printf("Set TV Power to: %s\n",active->getNewVal()?"ON":"OFF"); + } + + if(activeID->updated()){ + Serial.printf("Set Input Source to HDMI-%d\n",activeID->getNewVal()); + } + + if(settingsKey->updated()){ + Serial.printf("Received request to \"View TV Settings\"\n"); + } + + if(remoteKey->updated()){ + Serial.printf("Remote Control key pressed: "); + switch(remoteKey->getNewVal()){ + case 4: + Serial.printf("UP ARROW\n"); + break; + case 5: + Serial.printf("DOWN ARROW\n"); + break; + case 6: + Serial.printf("LEFT ARROW\n"); + break; + case 7: + Serial.printf("RIGHT ARROW\n"); + break; + case 8: + Serial.printf("SELECT\n"); + break; + case 9: + Serial.printf("BACK\n"); + break; + case 11: + Serial.printf("PLAY/PAUSE\n"); + break; + case 15: + Serial.printf("INFO\n"); + break; + default: + Serial.print("UNKNOWN KEY\n"); + } + } + + return(true); + } + + static void changeOrder(const char *buf, void *arg){ + HomeSpanTV *hsTV=(HomeSpanTV *)arg; + + TLV8 orderTLV; + uint32_t order[]={12,10,6,2,1,9,11,3,18,5}; + + for(int i=0;i0) + orderTLV.add(0); + orderTLV.add(1,sizeof(uint32_t),(uint8_t*)(order+i)); + } + + orderTLV.print(); + size_t n=orderTLV.pack_size(); + Serial.printf("Size=%d\n",n); + uint8_t c[n]; + orderTLV.pack(c); + hsTV->displayOrder->setData(c,n); + } + +}; + +/////////////////////////////// void setup() { - + Serial.begin(115200); homeSpan.setLogLevel(2); + + homeSpan.begin(Category::Television,"HomeSpan Television"); - homeSpan.begin(Category::Lighting,"HomeSpan Max"); + SPAN_ACCESSORY(); + + SpanService *hdmi1 = new Service::InputSource(); // Source included in Selection List, but excluded from Settings Screen + new Characteristic::ConfiguredName("Alpha"); + new Characteristic::Identifier(5); + new Characteristic::IsConfigured(1); + new Characteristic::CurrentVisibilityState(0); + new Characteristic::TargetVisibilityState(0); - new SpanAccessory(); - new Service::AccessoryInformation(); - new Characteristic::Identify(); + SpanService *hdmi2 = new Service::InputSource(); + new Characteristic::ConfiguredName("Gamma"); + new Characteristic::Identifier(10); + new Characteristic::IsConfigured(1); + new Characteristic::CurrentVisibilityState(0); + new Characteristic::TargetVisibilityState(0); - for(int i=0;isetDescription("Custom Data")->setData(x,5); - char c[30]; - sprintf(c,"Light-%d",i); - new Characteristic::Name(c); - new Service::LightBulb(); - new Characteristic::On(0,false); - WEBLOG("Configuring %s",c); - } + SpanService *hdmi3 = new Service::InputSource(); + new Characteristic::ConfiguredName("Beta"); + new Characteristic::Identifier(6); + new Characteristic::IsConfigured(1); + new Characteristic::CurrentVisibilityState(0); + new Characteristic::TargetVisibilityState(0); + SpanService *hdmi4 = new Service::InputSource(); + new Characteristic::ConfiguredName("Zebra"); + new Characteristic::Identifier(2); + new Characteristic::IsConfigured(1); + new Characteristic::CurrentVisibilityState(0); + new Characteristic::TargetVisibilityState(0); + + SpanService *hdmi5 = new Service::InputSource(); + new Characteristic::ConfiguredName("Delta"); + new Characteristic::Identifier(1); + new Characteristic::IsConfigured(1); + new Characteristic::CurrentVisibilityState(0); + new Characteristic::TargetVisibilityState(0); + + SpanService *hdmi6 = new Service::InputSource(); + new Characteristic::ConfiguredName("Trident"); + new Characteristic::Identifier(9); + new Characteristic::IsConfigured(1); + new Characteristic::CurrentVisibilityState(0); + new Characteristic::TargetVisibilityState(0); + + SpanService *hdmi7 = new Service::InputSource(); + new Characteristic::ConfiguredName("Netflix"); + new Characteristic::Identifier(11); + new Characteristic::IsConfigured(1); + new Characteristic::CurrentVisibilityState(0); + new Characteristic::TargetVisibilityState(0); + + SpanService *hdmi8 = new Service::InputSource(); + new Characteristic::ConfiguredName("Alpha2"); + new Characteristic::Identifier(3); + new Characteristic::IsConfigured(1); + new Characteristic::CurrentVisibilityState(0); + new Characteristic::TargetVisibilityState(0); + + SpanService *hdmi9 = new Service::InputSource(); + new Characteristic::ConfiguredName("Moon"); + new Characteristic::Identifier(18); + new Characteristic::IsConfigured(1); + new Characteristic::CurrentVisibilityState(0); + new Characteristic::TargetVisibilityState(0); + + SpanService *hdmi10 = new Service::InputSource(); + new Characteristic::ConfiguredName("Gamba"); + new Characteristic::Identifier(12); + new Characteristic::IsConfigured(1); + new Characteristic::CurrentVisibilityState(0); + new Characteristic::TargetVisibilityState(0); + + SpanService *speaker = new Service::TelevisionSpeaker(); + new Characteristic::VolumeSelector(); + new Characteristic::VolumeControlType(3); + + (new HomeSpanTV("Test TV")) // Define a Television Service. Must link in InputSources! + ->addLink(hdmi1) + ->addLink(hdmi2) + ->addLink(hdmi3) + ->addLink(hdmi4) + ->addLink(hdmi5) + ->addLink(hdmi6) + ->addLink(hdmi7) + ->addLink(hdmi8) + ->addLink(hdmi9) + ->addLink(hdmi10) + ->addLink(speaker) + ; + } + ////////////////////////////////////// void loop(){ - homeSpan.poll(); - delay(100000); - + homeSpan.poll(); } ////////////////////////////////////// From e8f9ca8ac1ad214bd15d91268056b2b06f8b2261 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 30 Mar 2024 06:03:31 -0500 Subject: [PATCH 05/74] Modified UUID check to allow for Custom short-form UUID Also truncated string Characteristics printed via 'i' CLI Command to show only first 32 characters followed by "..." if there are more than 32. --- src/HomeSpan.cpp | 6 +++--- src/HomeSpan.h | 7 +++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 73973fb..6b0891d 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -887,8 +887,8 @@ void Span::processSerialCommand(const char *c){ isBridge=false; // ...this is not a bridge device for(auto chr=(*svc)->Characteristics.begin(); chr!=(*svc)->Characteristics.end(); chr++){ - LOG0(" \u21e8 Characteristic %s(%s): IID=%d, %sUUID=\"%s\", %sPerms=", - (*chr)->hapName,(*chr)->uvPrint((*chr)->value).c_str(),(*chr)->iid,(*chr)->isCustom?"Custom-":"",(*chr)->type,(*chr)->perms!=(*chr)->hapChar->perms?"Custom-":""); + LOG0(" \u21e8 Characteristic %s(%.32s%s): IID=%d, %sUUID=\"%s\", %sPerms=", + (*chr)->hapName,(*chr)->uvPrint((*chr)->value).c_str(),strlen((*chr)->uvPrint((*chr)->value).c_str())>32?"...":"",(*chr)->iid,(*chr)->isCustom?"Custom-":"",(*chr)->type,(*chr)->perms!=(*chr)->hapChar->perms?"Custom-":""); int foundPerms=0; for(uint8_t i=0;i<7;i++){ @@ -925,7 +925,7 @@ void Span::processSerialCommand(const char *c){ if(!(*chr)->isCustom && !(*svc)->isCustom && std::find((*svc)->req.begin(),(*svc)->req.end(),(*chr)->hapChar)==(*svc)->req.end() && std::find((*svc)->opt.begin(),(*svc)->opt.end(),(*chr)->hapChar)==(*svc)->opt.end()) LOG0(" *** WARNING #%d! Service does not support this Characteristic ***\n",++nWarnings); else - if(invalidUUID((*chr)->type,(*chr)->isCustom)) + if(invalidUUID((*chr)->type)) LOG0(" *** ERROR #%d! Format of UUID is invalid ***\n",++nErrors); else if(std::find_if((*svc)->Characteristics.begin(),chr,[chr](SpanCharacteristic *c)->boolean{return(c->hapChar==(*chr)->hapChar);})!=chr) diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 2938f4a..2d0fa22 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -287,10 +287,13 @@ class Span{ 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 - static boolean invalidUUID(const char *uuid, boolean isCustom){ + static boolean invalidUUID(const char *uuid){ int x=0; + sscanf(uuid,"%*8[0-9a-fA-F]%n",&x); // check for short-form of UUID + if(strlen(uuid)==x && uuid[0]!='0') + return(false); sscanf(uuid,"%*8[0-9a-fA-F]-%*4[0-9a-fA-F]-%*4[0-9a-fA-F]-%*4[0-9a-fA-F]-%*12[0-9a-fA-F]%n",&x); - return(isCustom && (strlen(uuid)!=36 || x!=36)); + return(strlen(uuid)!=36 || x!=36); } public: From f6d4d37ff7a1059ea4065b5eb6d1a1b7508f8cae Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 30 Mar 2024 17:51:37 -0500 Subject: [PATCH 06/74] Initial creation of setTLV() --- src/HAP.h | 1 - src/HomeSpan.h | 25 ++++++++++++++++++++++++- src/src.ino | 7 +------ 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/HAP.h b/src/HAP.h index 71779cb..c263277 100644 --- a/src/HAP.h +++ b/src/HAP.h @@ -34,7 +34,6 @@ #include "HAPConstants.h" #include "HKDF.h" #include "SRP.h" -#include "TLV8.h" const TLV8_names HAP_Names[] = { {kTLVType_Separator,"SEPARATOR"}, diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 2d0fa22..7298a7d 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -55,6 +55,7 @@ #include "HAPConstants.h" #include "HapQR.h" #include "Characteristics.h" +#include "TLV8.h" using std::vector; using std::unordered_map; @@ -756,12 +757,34 @@ class SpanCharacteristic{ } size_t olen; - mbedtls_base64_encode(NULL,0,&olen,data,len); // get length of string buffer needed (mbedtls includes the trailing null in this size) + mbedtls_base64_encode(NULL,0,&olen,NULL,len); // get length of string buffer needed (mbedtls includes the trailing null in this size) TempBuffer tBuf(olen); // create temporary string buffer, with room for trailing null mbedtls_base64_encode((uint8_t*)tBuf.get(),olen,&olen,data,len ); // encode data into string buf setString(tBuf,notify); // call setString to continue processing as if characteristic was a string } + + void setTLV(TLV8 &tlv, boolean notify=true){ + + if(updateFlag==1) + LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setVal() within update() while it is being updated by Home App. This may cause device to become non-responsive!\n\n",hapName); + + 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 + 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 + } + } + template void setVal(T val, boolean notify=true){ if(!((perms&EV) || (updateFlag==2))){ diff --git a/src/src.ino b/src/src.ino index 01f20f2..611143a 100644 --- a/src/src.ino +++ b/src/src.ino @@ -26,7 +26,6 @@ ********************************************************************************/ #include "HomeSpan.h" -#include "TLV8.h" CUSTOM_CHAR_TLV(DisplayOrder,136,PR+EV); @@ -52,11 +51,7 @@ struct HomeSpanTV : Service::Television { } orderTLV.print(); - size_t n=orderTLV.pack_size(); - Serial.printf("Size=%d\n",n); - uint8_t c[n]; - orderTLV.pack(c); - displayOrder->setData(c,n); + displayOrder->setTLV(orderTLV); new SpanUserCommand('P', "- change order of inputs", changeOrder, this); } From 11bd605a032bb15184b26757df72a1335f00c574 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 30 Mar 2024 22:13:52 -0500 Subject: [PATCH 07/74] updated setData and setTLV to use new common setValCheck() and setValFinish() --- src/HomeSpan.cpp | 4 +- src/HomeSpan.h | 100 +++++++++++++++++++++++++++++------------------ src/src.ino | 6 +++ 3 files changed, 69 insertions(+), 41 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 6b0891d..4afb96f 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -887,8 +887,8 @@ void Span::processSerialCommand(const char *c){ isBridge=false; // ...this is not a bridge device for(auto chr=(*svc)->Characteristics.begin(); chr!=(*svc)->Characteristics.end(); chr++){ - LOG0(" \u21e8 Characteristic %s(%.32s%s): IID=%d, %sUUID=\"%s\", %sPerms=", - (*chr)->hapName,(*chr)->uvPrint((*chr)->value).c_str(),strlen((*chr)->uvPrint((*chr)->value).c_str())>32?"...":"",(*chr)->iid,(*chr)->isCustom?"Custom-":"",(*chr)->type,(*chr)->perms!=(*chr)->hapChar->perms?"Custom-":""); + LOG0(" \u21e8 Characteristic %s(%.33s%s): IID=%d, %sUUID=\"%s\", %sPerms=", + (*chr)->hapName,(*chr)->uvPrint((*chr)->value).c_str(),strlen((*chr)->uvPrint((*chr)->value).c_str())>33?"...\"":"",(*chr)->iid,(*chr)->isCustom?"Custom-":"",(*chr)->type,(*chr)->perms!=(*chr)->hapChar->perms?"Custom-":""); int foundPerms=0; for(uint8_t i=0;i<7;i++){ diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 7298a7d..fb1ad7f 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -675,12 +675,7 @@ class SpanCharacteristic{ return NULL; } - void setString(const char *val, boolean notify=true){ - - if(!((perms&EV) || (updateFlag==2))){ - LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setString() ignored. No EVENT NOTIFICATION (EV) permission on this characteristic\n\n",hapName); - return; - } + void setString(const char *val, boolean notify=true){ if(updateFlag==1) LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setString() within update() while it is being updated by Home App. This may cause device to become non-responsive!\n\n",hapName); @@ -746,51 +741,78 @@ class SpanCharacteristic{ void setData(uint8_t *data, size_t len, boolean notify=true){ - if(!((perms&EV) || (updateFlag==2))){ - LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setData() ignored. No EVENT NOTIFICATION (EV) permission on this characteristic\n\n",hapName); - return; - } + setValCheck(); - if(len<1){ - LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setData() ignored. Size of data buffer must be greater than zero\n\n",hapName); - return; + if(len>0){ + 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'; } - 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) - TempBuffer tBuf(olen); // create temporary string buffer, with room for trailing null - mbedtls_base64_encode((uint8_t*)tBuf.get(),olen,&olen,data,len ); // encode data into string buf - setString(tBuf,notify); // call setString to continue processing as if characteristic was a string + setValFinish(notify); } void setTLV(TLV8 &tlv, boolean notify=true){ - if(updateFlag==1) - LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setVal() within update() while it is being updated by Home App. This may cause device to become non-responsive!\n\n",hapName); - + 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 - 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 - } - } - template void setVal(T val, boolean notify=true){ + 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'; + } - if(!((perms&EV) || (updateFlag==2))){ - LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setVal() ignored. No EVENT NOTIFICATION (EV) permission on this characteristic\n\n",hapName); - return; - } + 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){ if(updateFlag==1) LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setVal() within update() while it is being updated by Home App. This may cause device to become non-responsive!\n\n",hapName); diff --git a/src/src.ino b/src/src.ino index 611143a..375d88b 100644 --- a/src/src.ino +++ b/src/src.ino @@ -28,6 +28,7 @@ #include "HomeSpan.h" CUSTOM_CHAR_TLV(DisplayOrder,136,PR+EV); +CUSTOM_CHAR_DATA(TestData,333,PR+EV); struct HomeSpanTV : Service::Television { @@ -36,6 +37,7 @@ struct HomeSpanTV : Service::Television { SpanCharacteristic *remoteKey = new Characteristic::RemoteKey(); // Used to receive button presses from the Remote Control widget SpanCharacteristic *settingsKey = new Characteristic::PowerModeSelection(); // Adds "View TV Setting" option to Selection Screen SpanCharacteristic *displayOrder = new Characteristic::DisplayOrder(); + SpanCharacteristic *testData = new Characteristic::TestData(); HomeSpanTV(const char *name) : Service::Television() { new Characteristic::ConfiguredName(name); // Name of TV @@ -53,6 +55,10 @@ struct HomeSpanTV : Service::Television { orderTLV.print(); displayOrder->setTLV(orderTLV); + uint8_t blob[]={1,2,3,4,5,6,7,8,9,10,11,12}; +// testData->setData(blob,sizeof(blob)); + testData->setData(blob,1); + new SpanUserCommand('P', "- change order of inputs", changeOrder, this); } From cbe26c7c4142f6159c8ea33b56090835994755f5 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 31 Mar 2024 08:51:06 -0500 Subject: [PATCH 08/74] update setString() to use setValCheck() and setValFinish() --- src/HomeSpan.h | 27 +++------------------------ src/src.ino | 9 ++++++++- 2 files changed, 11 insertions(+), 25 deletions(-) diff --git a/src/HomeSpan.h b/src/HomeSpan.h index fb1ad7f..919a213 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -677,31 +677,10 @@ class SpanCharacteristic{ void setString(const char *val, boolean notify=true){ - if(updateFlag==1) - LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setString() within update() while it is being updated by Home App. This may cause device to become non-responsive!\n\n",hapName); - + setValCheck(); uvSet(value,val); - uvSet(newValue,value); - - updateTime=homeSpan.snapTime; - - if(notify){ - if(updateFlag!=2){ // do not broadcast EV if update is 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); - } - } - - } // setString() + setValFinish(notify); + } size_t getData(uint8_t *data, size_t len){ if(formatsetData(blob,1); new SpanUserCommand('P', "- change order of inputs", changeOrder, this); + new SpanUserCommand('C', "- change name of TV", setTVName, this); } boolean update() override { @@ -111,6 +113,11 @@ struct HomeSpanTV : Service::Television { return(true); } + static void setTVName(const char *buf, void *arg){ + HomeSpanTV *hsTV=(HomeSpanTV *)arg; + hsTV->tvname->setString("New Name"); + } + static void changeOrder(const char *buf, void *arg){ HomeSpanTV *hsTV=(HomeSpanTV *)arg; From eb821f002f1fad8b1a78cf991d7f99271a4a0f86 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 31 Mar 2024 09:23:23 -0500 Subject: [PATCH 09/74] created getTLV(TLV8 &tlv) Works, but is memory-inefficient since it decodes entire string before unpacking. Need to add new functionality to TLV8 so that unpacking can be done in chunks similar to how pack() works. Also need to create getNewTLV() OR make all get/getNew generic to reduce code size. --- src/HomeSpan.h | 24 +++++++++++++++++++++--- src/src.ino | 7 +++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 919a213..97a102e 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -736,6 +736,25 @@ class SpanCharacteristic{ } + size_t getTLV(TLV8 &tlv){ + if(format tBuf(olen); // create temporary buffer + + int ret=mbedtls_base64_decode(tBuf,olen,&olen,(uint8_t *)value.STRING,strlen(value.STRING)); + + if(ret==MBEDTLS_ERR_BASE64_INVALID_CHARACTER){ + LOG0("\n*** WARNING: Can't decode Characteristic::%s with getTLV(). Data is not in base-64 format\n\n",hapName); + return(0); + } + + tlv.unpack(tBuf,olen); + return(tlv.pack_size()); + } + void setTLV(TLV8 &tlv, boolean notify=true){ setValCheck(); @@ -793,9 +812,8 @@ class SpanCharacteristic{ template void setVal(T val, boolean notify=true){ - if(updateFlag==1) - LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setVal() within update() while it is being updated by Home App. This may cause device to become non-responsive!\n\n",hapName); - + setValCheck(); + if(!((val >= uvGet(minValue)) && (val <= uvGet(maxValue)))){ LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setVal(%g) is out of range [%g,%g]. This may cause device to become non-responsive!\n\n", hapName,(double)val,uvGet(minValue),uvGet(maxValue)); diff --git a/src/src.ino b/src/src.ino index ef3f4b1..d6ce93f 100644 --- a/src/src.ino +++ b/src/src.ino @@ -122,6 +122,12 @@ struct HomeSpanTV : Service::Television { HomeSpanTV *hsTV=(HomeSpanTV *)arg; TLV8 orderTLV; + + Serial.printf("BEFORE:\n"); + hsTV->displayOrder->getTLV(orderTLV); + orderTLV.print(); + orderTLV.wipe(); + uint32_t order[]={12,10,6,2,1,9,11,3,18,5}; for(int i=0;i Date: Sun, 31 Mar 2024 21:13:17 -0500 Subject: [PATCH 10/74] Updated getTLV() so it uses a fixed buffer as intermediate step This is much more memory efficient. Instead of decoding entire STRING from base64 to a temporary buffer or potentially very large size and then unpacking into TLV object, we decode a maximum of 48 characters at a time and unpack the resulting 36 (max) bytes until entire STRING is consumed. getTLV() returns pack_size of final TLV, unless there is a problem with conversion, in which cae TLV is wiped and return value=0 --- src/HomeSpan.h | 40 ++++++++++++++++++++++++++++------------ src/TLV8.cpp | 13 ++++++++----- src/TLV8.h | 2 +- src/src.ino | 3 +-- 4 files changed, 38 insertions(+), 20 deletions(-) diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 97a102e..0e90699 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -735,24 +735,40 @@ class SpanCharacteristic{ setValFinish(notify); } - - size_t getTLV(TLV8 &tlv){ + size_t getTLV(TLV8 &tlv){ + if(format tBuf(olen); // create temporary buffer + const size_t bufSize=36; // maximum size of buffer to store decoded bytes before unpacking into TLV; must be multiple of 3 + TempBuffer tBuf(bufSize); // create fixed-size buffer to store decoded bytes + tlv.wipe(); // clear TLV completely - int ret=mbedtls_base64_decode(tBuf,olen,&olen,(uint8_t *)value.STRING,strlen(value.STRING)); + size_t nChars=strlen(value.STRING); // total characters to decode + uint8_t *p=(uint8_t *)value.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; - if(ret==MBEDTLS_ERR_BASE64_INVALID_CHARACTER){ - LOG0("\n*** WARNING: Can't decode Characteristic::%s with getTLV(). Data is not in base-64 format\n\n",hapName); - return(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 setTLV(TLV8 &tlv, boolean notify=true){ diff --git a/src/TLV8.cpp b/src/TLV8.cpp index 3a52bd9..ff766f7 100644 --- a/src/TLV8.cpp +++ b/src/TLV8.cpp @@ -160,7 +160,10 @@ size_t TLV8::pack(uint8_t *buf, size_t bufSize){ ///////////////////////////////////// -void TLV8::unpack(uint8_t *buf, size_t bufSize){ +int TLV8::unpack(uint8_t *buf, size_t bufSize){ + + if(bufSize==0) + return(-1); if(empty()) unpackPhase=0; @@ -171,18 +174,17 @@ void TLV8::unpack(uint8_t *buf, size_t bufSize){ case 0: unpackTag=*buf++; bufSize--; + add(unpackTag); unpackPhase=1; break; case 1: unpackBytes=*buf++; bufSize--; - if(unpackBytes==0){ - add(unpackTag); + if(unpackBytes==0) unpackPhase=0; - } else { + else unpackPhase=2; - } break; case 2: @@ -196,6 +198,7 @@ void TLV8::unpack(uint8_t *buf, size_t bufSize){ break; } } + return(unpackPhase); } diff --git a/src/TLV8.h b/src/TLV8.h index af752e8..1ca1ea3 100644 --- a/src/TLV8.h +++ b/src/TLV8.h @@ -107,7 +107,7 @@ class TLV8 : public std::list> { void osprint(std::ostream& os, TLV8_it it1){osprint(os, it1, it1++);} void osprint(std::ostream& os){osprint(os, begin(), end());} - void unpack(uint8_t *buf, size_t bufSize); + int unpack(uint8_t *buf, size_t bufSize); void wipe(){std::list>().swap(*this);} diff --git a/src/src.ino b/src/src.ino index d6ce93f..aa236a0 100644 --- a/src/src.ino +++ b/src/src.ino @@ -122,8 +122,7 @@ struct HomeSpanTV : Service::Television { HomeSpanTV *hsTV=(HomeSpanTV *)arg; TLV8 orderTLV; - - Serial.printf("BEFORE:\n"); + hsTV->displayOrder->getTLV(orderTLV); orderTLV.print(); orderTLV.wipe(); From 1892a0a5a2d69b056fe0ad2d29cc42defdea9f13 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 31 Mar 2024 21:25:22 -0500 Subject: [PATCH 11/74] created getTLVGeneric() and used as base for getTLV() and getNewTLV() avoids unnecessary duplication of code where the only difference is whether you need the value or the newValue of a Characteristic. --- src/HomeSpan.h | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 0e90699..777b675 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -693,9 +693,9 @@ class SpanCharacteristic{ return(olen); if(ret==MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL) - LOG0("\n*** WARNING: Can't decode Characteristic::%s with getData(). Destination buffer is too small (%d out of %d bytes needed)\n\n",hapName,len,olen); + LOG0("\n*** WARNING: Can't decode Characteristic::%s with getData(). Destination buffer is too small (%d out of %d bytes needed)!\n\n",hapName,len,olen); else if(ret==MBEDTLS_ERR_BASE64_INVALID_CHARACTER) - LOG0("\n*** WARNING: Can't decode Characteristic::%s with getData(). Data is not in base-64 format\n\n",hapName); + LOG0("\n*** WARNING: Can't decode Characteristic::%s with getData(). Data is not in base-64 format!\n\n",hapName); return(olen); } @@ -711,9 +711,9 @@ class SpanCharacteristic{ return(olen); if(ret==MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL) - LOG0("\n*** WARNING: Can't decode Characteristic::%s with getData(). Destination buffer is too small (%d out of %d bytes needed)\n\n",hapName,len,olen); + LOG0("\n*** WARNING: Can't decode Characteristic::%s with getData(). Destination buffer is too small (%d out of %d bytes needed)!\n\n",hapName,len,olen); else if(ret==MBEDTLS_ERR_BASE64_INVALID_CHARACTER) - LOG0("\n*** WARNING: Can't decode Characteristic::%s with getData(). Data is not in base-64 format\n\n",hapName); + LOG0("\n*** WARNING: Can't decode Characteristic::%s with getData(). Data is not in base-64 format!\n\n",hapName); return(olen); } @@ -735,7 +735,7 @@ class SpanCharacteristic{ setValFinish(notify); } - size_t getTLV(TLV8 &tlv){ + 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(value.STRING); // total characters to decode - uint8_t *p=(uint8_t *)value.STRING; // set pointer to beginning of value - const size_t decodeSize=bufSize/3*4; // number of characters to decode in each pass + 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){ @@ -755,7 +755,7 @@ class SpanCharacteristic{ int ret=mbedtls_base64_decode(tBuf,tBuf.len(),&olen,p,n); if(ret==MBEDTLS_ERR_BASE64_INVALID_CHARACTER){ - LOG0("\n*** WARNING: Can't decode Characteristic::%s with getTLV(). Data is not in base-64 format\n\n",hapName); + LOG0("\n*** WARNING: Can't decode Characteristic::%s with getTLV(). Data is not in base-64 format!\n\n",hapName); tlv.wipe(); return(0); } @@ -764,13 +764,16 @@ class SpanCharacteristic{ nChars-=n; } if(status>0){ - LOG0("\n*** WARNING: Can't unpack Characteristic::%s with getTLV(). TLV record is incomplete or corrupted\n\n",hapName); + 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(); From 3d4b02e492406dc237bfe99a326b8d0415a53249 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 31 Mar 2024 21:40:00 -0500 Subject: [PATCH 12/74] created getDataGeneric() and getStringGeneric() Though this simplifies the code, the code size is still the same - compiler must have already optimized these functions. --- src/HomeSpan.h | 35 ++++++++--------------------------- src/src.ino | 1 + 2 files changed, 9 insertions(+), 27 deletions(-) diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 777b675..e0563a8 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -661,19 +661,15 @@ class SpanCharacteristic{ return(uvGet(newValue)); } - char *getString(){ + char *getStringGeneric(UVal &val){ if(format>=FORMAT::STRING) - return value.STRING; + return val.STRING; return NULL; } - char *getNewString(){ - if(format>=FORMAT::STRING) - return newValue.STRING; - - return NULL; - } + char *getString(){return(getStringGeneric(value));} + char *getNewString(){return(getStringGeneric(newValue));} void setString(const char *val, boolean notify=true){ @@ -682,12 +678,12 @@ class SpanCharacteristic{ setValFinish(notify); } - size_t getData(uint8_t *data, size_t len){ + size_t getDataGeneric(uint8_t *data, size_t len, UVal &val){ if(formattvname->setString("New Name"); + Serial.printf("Reset TV Name to '%s'\n",hsTV->tvname->getString()); } static void changeOrder(const char *buf, void *arg){ From 75cbf9715f8445b33dd7fb6f582bff2c754e30aa Mon Sep 17 00:00:00 2001 From: Gregg Date: Thu, 4 Apr 2024 21:12:13 -0500 Subject: [PATCH 13/74] added mechanism to strip unnecessary backslashes from Home App JSON Apple "escapes" forward slashes in JSON output, replacing '/' with '\/', which destroys base64 strings. --- src/HomeSpan.cpp | 2 +- src/Utils.cpp | 14 ++++++++++++++ src/Utils.h | 2 +- src/src.ino | 9 +++++---- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 4afb96f..e8153bc 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -2007,7 +2007,7 @@ StatusCode SpanCharacteristic::loadUpdate(char *val, char *ev, boolean wr){ case STRING: case DATA: case TLV_ENC: - uvSet(newValue,(const char *)val); + uvSet(newValue,(const char *)stripBackslash(val)); break; default: diff --git a/src/Utils.cpp b/src/Utils.cpp index 0b2e46c..d3711f0 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -73,6 +73,20 @@ char *Utils::readSerial(char *c, int max){ ////////////////////////////////////// +char *Utils::stripBackslash(char *c){ + + size_t n=strlen(c); + char *p=c; + for(int i=0;i<=n;i++){ + *p=c[i]; + if(*p!='\\') + p++; + } + return(c); +} + +////////////////////////////////////// + String Utils::mask(char *c, int n){ String s=""; int len=strlen(c); diff --git a/src/Utils.h b/src/Utils.h index 255d602..26e3db9 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -35,7 +35,7 @@ namespace Utils { char *readSerial(char *c, int max); // read serial port into 'c' until , but storing only first 'max' characters (the rest are discarded) String mask(char *c, int n); // simply utility that creates a String from 'c' with all except the first and last 'n' characters replaced by '*' - +char *stripBackslash(char *c); // strips backslashes out of c (Apple unecessesarily "escapes" forward slashes in JSON) } ///////////////////////////////////////////////// diff --git a/src/src.ino b/src/src.ino index a2a4477..4f57226 100644 --- a/src/src.ino +++ b/src/src.ino @@ -49,7 +49,7 @@ struct HomeSpanTV : Service::Television { for(int i=0;i0) - orderTLV.add(0); + orderTLV.add(6); orderTLV.add(1,sizeof(uint32_t),(uint8_t*)(order+i)); } @@ -117,6 +117,7 @@ struct HomeSpanTV : Service::Television { HomeSpanTV *hsTV=(HomeSpanTV *)arg; hsTV->tvname->setString("New Name"); Serial.printf("Reset TV Name to '%s'\n",hsTV->tvname->getString()); + Serial.printf("Showing displayOrder '%s'\n",hsTV->displayOrder->getString()); } static void changeOrder(const char *buf, void *arg){ @@ -128,12 +129,12 @@ struct HomeSpanTV : Service::Television { orderTLV.print(); orderTLV.wipe(); - uint32_t order[]={12,10,6,2,1,9,11,3,18,5}; + uint8_t order[]={12,10,6,2,1,9,11,3,18,5}; - for(int i=0;i0) orderTLV.add(0); - orderTLV.add(1,sizeof(uint32_t),(uint8_t*)(order+i)); + orderTLV.add(1,sizeof(uint8_t),(uint8_t*)(order+i)); } Serial.printf("AFTER:\n"); From 7e2625034cb289d4263737ee4509340e2019f90e Mon Sep 17 00:00:00 2001 From: Gregg Date: Thu, 4 Apr 2024 21:31:28 -0500 Subject: [PATCH 14/74] Added TLV8_it add(uint8_t tag, TLV8 &subTLV) Allows easy-add of a sub TLV to an existing TLV8 --- src/TLV8.cpp | 8 ++++++++ src/TLV8.h | 1 + 2 files changed, 9 insertions(+) diff --git a/src/TLV8.cpp b/src/TLV8.cpp index ff766f7..a486136 100644 --- a/src/TLV8.cpp +++ b/src/TLV8.cpp @@ -80,6 +80,14 @@ TLV8_it TLV8::add(uint8_t tag, size_t len, const uint8_t* val){ ///////////////////////////////////// +TLV8_it TLV8::add(uint8_t tag, TLV8 &subTLV){ + auto it=add(tag,subTLV.pack_size(),NULL); // create space for inserting sub TLV and store iterator to new element + subTLV.pack(*it); // pack subTLV into new element + return(--end()); +} + +///////////////////////////////////// + TLV8_it TLV8::find(uint8_t tag, TLV8_it it1, TLV8_it it2){ auto it=it1; diff --git a/src/TLV8.h b/src/TLV8.h index 1ca1ea3..5985e4d 100644 --- a/src/TLV8.h +++ b/src/TLV8.h @@ -80,6 +80,7 @@ class TLV8 : public std::list> { TLV8_it add(uint8_t tag, size_t len, const uint8_t *val); TLV8_it add(uint8_t tag, uint8_t val){return(add(tag, 1, &val));} TLV8_it add(uint8_t tag){return(add(tag, 0, NULL));} + TLV8_it add(uint8_t tag, TLV8 &subTLV); TLV8_it find(uint8_t tag, TLV8_it it1, TLV8_it it2); TLV8_it find(uint8_t tag, TLV8_it it1){return(find(tag, it1, end()));} From acebaf6caab6f88a6fc03638a99043ca1cb1d5e5 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 6 Apr 2024 21:45:49 -0500 Subject: [PATCH 15/74] Add ability to unpack TLV8 by iterator Also fixed bug using increment operator (++) by replacing with std::next() --- src/TLV8.cpp | 9 +++++++++ src/TLV8.h | 9 +++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/TLV8.cpp b/src/TLV8.cpp index a486136..2766490 100644 --- a/src/TLV8.cpp +++ b/src/TLV8.cpp @@ -209,6 +209,15 @@ int TLV8::unpack(uint8_t *buf, size_t bufSize){ return(unpackPhase); } +///////////////////////////////////// + +int TLV8::unpack(TLV8_it it){ + + if(it==end()) + return(0); + + return(unpack(*it,(*it).len)); +} ///////////////////////////////////// diff --git a/src/TLV8.h b/src/TLV8.h index 5985e4d..5240ee6 100644 --- a/src/TLV8.h +++ b/src/TLV8.h @@ -92,7 +92,7 @@ class TLV8 : public std::list> { size_t pack_size(){return(pack_size(begin(), end()));} void pack_init(TLV8_it it1, TLV8_it it2){currentPackIt=it1; endPackIt=it2; currentPackPhase=0;} - void pack_init(TLV8_it it1){pack_init(it1, it1++);} + void pack_init(TLV8_it it1){pack_init(it1, std::next(it1));} void pack_init(){pack_init(begin(),end());} size_t pack(uint8_t *buf, size_t bufSize); @@ -101,15 +101,16 @@ class TLV8 : public std::list> { const char *getName(uint8_t tag); void print(TLV8_it it1, TLV8_it it2); - void print(TLV8_it it1){print(it1, it1++);} + void print(TLV8_it it1){print(it1, std::next(it1));} void print(){print(begin(), end());} void osprint(std::ostream& os, TLV8_it it1, TLV8_it it2); - void osprint(std::ostream& os, TLV8_it it1){osprint(os, it1, it1++);} + void osprint(std::ostream& os, TLV8_it it1){osprint(os, it1, std::next(it1));} void osprint(std::ostream& os){osprint(os, begin(), end());} int unpack(uint8_t *buf, size_t bufSize); - + int unpack(TLV8_it it); + void wipe(){std::list>().swap(*this);} }; From d40d70964319a4a58cd3323efbdcf0b145054cc4 Mon Sep 17 00:00:00 2001 From: Gregg Date: Mon, 8 Apr 2024 21:33:27 -0500 Subject: [PATCH 16/74] Add TLV8 add() methods for uint64_t and char*, as well as new getVal() template for returning an integer --- src/TLV8.cpp | 18 ++++++++++++++++++ src/TLV8.h | 14 +++++++++++--- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/TLV8.cpp b/src/TLV8.cpp index 2766490..ffbe10b 100644 --- a/src/TLV8.cpp +++ b/src/TLV8.cpp @@ -81,6 +81,7 @@ TLV8_it TLV8::add(uint8_t tag, size_t len, const uint8_t* val){ ///////////////////////////////////// TLV8_it TLV8::add(uint8_t tag, TLV8 &subTLV){ + auto it=add(tag,subTLV.pack_size(),NULL); // create space for inserting sub TLV and store iterator to new element subTLV.pack(*it); // pack subTLV into new element return(--end()); @@ -88,6 +89,17 @@ TLV8_it TLV8::add(uint8_t tag, TLV8 &subTLV){ ///////////////////////////////////// +TLV8_it TLV8::add(uint8_t tag, uint64_t val){ + + uint8_t *p=reinterpret_cast(&val); + size_t nBytes=sizeof(uint64_t); + while(nBytes>1 && p[nBytes-1]==0) // TLV requires little endian without any trailing zero bytes (i.e. only use what is needed to fully represent the value) + nBytes--; + return(add(tag, nBytes, p)); +} + +///////////////////////////////////// + TLV8_it TLV8::find(uint8_t tag, TLV8_it it1, TLV8_it it2){ auto it=it1; @@ -247,6 +259,12 @@ void TLV8::print(TLV8_it it1, TLV8_it it2){ Serial.printf("(%d) ",(*it1).len); for(int i=0;i<(*it1).len;i++) Serial.printf("%02X",(*it1).val.get()[i]); + if((*it1).len==0) + Serial.printf(" (null)"); + else if((*it1).len<=4) + Serial.printf(" (%u)",(*it1).getVal()); + else if((*it1).len<=8) + Serial.printf(" (%llu)",(*it1).getVal()); Serial.printf("\n"); it1++; } diff --git a/src/TLV8.h b/src/TLV8.h index 5240ee6..aca36c4 100644 --- a/src/TLV8.h +++ b/src/TLV8.h @@ -42,10 +42,17 @@ struct tlv8_t { tlv8_t(uint8_t tag, size_t len, const uint8_t* val); void update(size_t addLen, const uint8_t *addVal); void osprint(std::ostream& os); - + operator uint8_t*() const{ return(val.get()); } + + template T getVal(){ + T iVal=0; + for(int i=0;i(val.get()[i])<<(i*8); + return(iVal); + } }; @@ -78,9 +85,10 @@ class TLV8 : public std::list> { TLV8(const TLV8_names *names, int nNames) : names{names}, nNames{nNames} {}; TLV8_it add(uint8_t tag, size_t len, const uint8_t *val); - TLV8_it add(uint8_t tag, uint8_t val){return(add(tag, 1, &val));} - TLV8_it add(uint8_t tag){return(add(tag, 0, NULL));} + TLV8_it add(uint8_t tag, uint64_t val); TLV8_it add(uint8_t tag, TLV8 &subTLV); + TLV8_it add(uint8_t tag){return(add(tag, 0, NULL));} + TLV8_it add(uint8_t tag, const char *val){return(add(tag, strlen(val), reinterpret_cast(val)));} TLV8_it find(uint8_t tag, TLV8_it it1, TLV8_it it2); TLV8_it find(uint8_t tag, TLV8_it it1){return(find(tag, it1, end()));} From 5ded77d6d98a2c6fef5f60072ae4bbd4018e3103 Mon Sep 17 00:00:00 2001 From: Gregg Date: Mon, 8 Apr 2024 21:52:51 -0500 Subject: [PATCH 17/74] Replace TLV8 (*it)[0] with new TLV8 getVal() method in HAP.cpp --- src/HAP.cpp | 12 ++++++------ src/TLV8.cpp | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index a036ba8..69720de 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -334,7 +334,7 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ return(0); } - int tlvState=(*itState)[0]; + int tlvState=(*itState).getVal(); if(nAdminControllers()){ // error: Device already paired (i.e. there is at least one admin Controller). We should not be receiving any requests for Pair-Setup! LOG0("\n*** ERROR: Device already paired!\n\n"); @@ -363,7 +363,7 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ auto itMethod=iosTLV.find(kTLVType_Method); - if(iosTLV.len(itMethod)!=1 || (*itMethod)[0]!=0){ // error: "Pair Setup" method must always be 0 to indicate setup without MiFi Authentification (HAP Table 5-3) + if(iosTLV.len(itMethod)!=1 || (*itMethod).getVal()!=0){ // error: "Pair Setup" method must always be 0 to indicate setup without MiFi Authentification (HAP Table 5-3) LOG0("\n*** ERROR: Pair 'Method' missing or not set to 0\n\n"); responseTLV.add(kTLVType_Error,tagError_Unavailable); // set Error=Unavailable tlvRespond(responseTLV); // send response to client @@ -585,7 +585,7 @@ int HAPClient::postPairVerifyURL(uint8_t *content, size_t len){ return(0); } - int tlvState=(*itState)[0]; + int tlvState=(*itState).getVal(); if(!nAdminControllers()){ // error: Device not yet paired - we should not be receiving any requests for Pair-Verify! LOG0("\n*** ERROR: Device not yet paired!\n\n"); @@ -771,7 +771,7 @@ int HAPClient::postPairingsURL(uint8_t *content, size_t len){ auto itState=iosTLV.find(kTLVType_State); auto itMethod=iosTLV.find(kTLVType_Method); - if(iosTLV.len(itState)!=1 || (*itState)[0]!=1){ // missing STATE TLV + if(iosTLV.len(itState)!=1 || (*itState).getVal()!=1){ // missing STATE TLV LOG0("\n*** ERROR: Parirings 'State' is either missing or not set to \n\n"); badRequestError(); // return with 400 error, which closes connection return(0); @@ -783,7 +783,7 @@ int HAPClient::postPairingsURL(uint8_t *content, size_t len){ return(0); } - int tlvMethod=(*itMethod)[0]; + int tlvMethod=(*itMethod).getVal(); responseTLV.add(kTLVType_State,pairState_M2); // all responses include State=M2 @@ -810,7 +810,7 @@ int HAPClient::postPairingsURL(uint8_t *content, size_t len){ return(0); } - tagError err=addController(*itIdentifier,*itPublicKey,(*itPermissions)[0]); + tagError err=addController(*itIdentifier,*itPublicKey,(*itPermissions).getVal()); if(err!=tagError_None) responseTLV.add(kTLVType_Error,err); diff --git a/src/TLV8.cpp b/src/TLV8.cpp index ffbe10b..51689f8 100644 --- a/src/TLV8.cpp +++ b/src/TLV8.cpp @@ -260,11 +260,11 @@ void TLV8::print(TLV8_it it1, TLV8_it it2){ for(int i=0;i<(*it1).len;i++) Serial.printf("%02X",(*it1).val.get()[i]); if((*it1).len==0) - Serial.printf(" (null)"); + Serial.printf(" [null]"); else if((*it1).len<=4) - Serial.printf(" (%u)",(*it1).getVal()); + Serial.printf(" [%u]",(*it1).getVal()); else if((*it1).len<=8) - Serial.printf(" (%llu)",(*it1).getVal()); + Serial.printf(" [%llu]",(*it1).getVal()); Serial.printf("\n"); it1++; } From 7a50479bacd97483f22799b06c6206fcd0bba05b Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 13 Apr 2024 19:12:26 -0500 Subject: [PATCH 18/74] Changed TLV:add() so it returns TLV8 instead of iterator. Makes it easier to chain .add() functions as well as dynamically create sub TLVs. Changing TLV8::add() in this fashion also required updating various use cases of TLV::add() in HAP.cpp where add() was used to reserve space for a blank TLV element. All changes have been tested. --- src/HAP.cpp | 32 +++++++++++++++----------------- src/TLV8.cpp | 15 +++++++-------- src/TLV8.h | 10 +++++----- 3 files changed, 27 insertions(+), 30 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index 69720de..a4aa4c0 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -370,7 +370,6 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ return(0); }; - auto itPublicKey=responseTLV.add(kTLVType_PublicKey,384,NULL); // create blank PublicKey TLV with space for 384 bytes if(srp==NULL) // create instance of SRP (if not already created) to persist until Pairing-Setup M5 completes srp=new SRP6A; @@ -381,7 +380,8 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ responseTLV.add(kTLVType_Salt,16,verifyData.get()->salt); // write Salt from verification data into TLV - srp->createPublicKey(verifyData,*itPublicKey); // create accessory Public Key from stored verification data and write result into PublicKey TLV + responseTLV.add(kTLVType_PublicKey,384,NULL); // create blank PublicKey TLV with space for 384 bytes + srp->createPublicKey(verifyData,responseTLV.back()); // create accessory Public Key from stored verification data and write result into PublicKey TLV tlvRespond(responseTLV); // send response to client pairStatus=pairState_M3; // set next expected pair-state request from client @@ -414,9 +414,8 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ return(0); }; - auto itAccProof=responseTLV.add(kTLVType_Proof,64,NULL); // create blank accessory Proof TLV with space for 64 bytes - - srp->createAccProof(*itAccProof); // M1 has been successully verified; now create accessory Proof M2 + responseTLV.add(kTLVType_Proof,64,NULL); // create blank accessory Proof TLV with space for 64 bytes + srp->createAccProof(responseTLV.back()); // M1 has been successully verified; now create accessory Proof M2 tlvRespond(responseTLV); // send response to client pairStatus=pairState_M5; // set next expected pair-state request from client @@ -513,14 +512,13 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ TempBuffer accessoryInfo(accessoryX,accessoryX.len(),accessory.ID,hap_accessory_IDBYTES,accessory.LTPK,crypto_sign_PUBLICKEYBYTES,NULL); - subTLV.clear(); // clear existing SUBTLV records + subTLV.clear(); // clear existing SUBTLV records - itSignature=subTLV.add(kTLVType_Signature,64,NULL); // create blank Signature TLV with space for 64 bytes + subTLV.add(kTLVType_Signature,64,NULL); // create blank Signature TLV with space for 64 bytes + crypto_sign_detached(subTLV.back(),NULL,accessoryInfo,accessoryInfo.len(),accessory.LTSK); // produce signature of accessoryInfo using AccessoryLTSK (Ed25519 long-term secret key) - crypto_sign_detached(*itSignature,NULL,accessoryInfo,accessoryInfo.len(),accessory.LTSK); // produce signature of accessoryInfo using AccessoryLTSK (Ed25519 long-term secret key) - - subTLV.add(kTLVType_Identifier,hap_accessory_IDBYTES,accessory.ID); // set Identifier TLV record as accessoryPairingID - subTLV.add(kTLVType_PublicKey,crypto_sign_PUBLICKEYBYTES,accessory.LTPK); // set PublicKey TLV record as accessoryLTPK + subTLV.add(kTLVType_Identifier,hap_accessory_IDBYTES,accessory.ID); // set Identifier TLV record as accessoryPairingID + subTLV.add(kTLVType_PublicKey,crypto_sign_PUBLICKEYBYTES,accessory.LTPK); // set PublicKey TLV record as accessoryLTPK LOG2("------- ENCRYPTING SUB-TLVS -------\n"); @@ -532,9 +530,9 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ // Encrypt the subTLV data using the same SRP Session Key as above with ChaCha20-Poly1305 - itEncryptedData=responseTLV.add(kTLVType_EncryptedData,subPack.len()+crypto_aead_chacha20poly1305_IETF_ABYTES,NULL); //create blank EncryptedData TLV with space for subTLV + Authentication Tag + 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(responseTLV.back(),NULL,subPack,subPack.len(),NULL,0,NULL,(unsigned char *)"\x00\x00\x00\x00PS-Msg06",sessionKey); LOG2("---------- END SUB-TLVS! ----------\n"); @@ -623,8 +621,8 @@ int HAPClient::postPairVerifyURL(uint8_t *content, size_t len){ TempBuffer accessoryInfo(publicCurveKey,crypto_box_PUBLICKEYBYTES,accessory.ID,hap_accessory_IDBYTES,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 - crypto_sign_detached(*itSignature,NULL,accessoryInfo,accessoryInfo.len(),accessory.LTSK); // produce Signature of accessoryInfo using Accessory's LTSK + subTLV.add(kTLVType_Signature,crypto_sign_BYTES,NULL); // create blank Signature subTLV + crypto_sign_detached(subTLV.back(),NULL,accessoryInfo,accessoryInfo.len(),accessory.LTSK); // produce Signature of accessoryInfo using Accessory's LTSK LOG2("------- ENCRYPTING SUB-TLVS -------\n"); @@ -640,8 +638,8 @@ int HAPClient::postPairVerifyURL(uint8_t *content, size_t len){ 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 - 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" + responseTLV.add(kTLVType_EncryptedData,subPack.len()+crypto_aead_chacha20poly1305_IETF_ABYTES,NULL); // create blank EncryptedData subTLV + crypto_aead_chacha20poly1305_ietf_encrypt(responseTLV.back(),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" LOG2("---------- END SUB-TLVS! ----------\n"); diff --git a/src/TLV8.cpp b/src/TLV8.cpp index 51689f8..7020af0 100644 --- a/src/TLV8.cpp +++ b/src/TLV8.cpp @@ -68,28 +68,27 @@ void tlv8_t::osprint(std::ostream& os){ ///////////////////////////////////// -TLV8_it TLV8::add(uint8_t tag, size_t len, const uint8_t* val){ +TLV8 &TLV8::add(uint8_t tag, size_t len, const uint8_t* val){ if(!empty() && back().tag==tag) back().update(len,val); else emplace_back(tag,len,val); - return(--end()); + return(*this); } ///////////////////////////////////// -TLV8_it TLV8::add(uint8_t tag, TLV8 &subTLV){ +TLV8 &TLV8::add(uint8_t tag, TLV8 &subTLV){ - auto it=add(tag,subTLV.pack_size(),NULL); // create space for inserting sub TLV and store iterator to new element - subTLV.pack(*it); // pack subTLV into new element - return(--end()); + subTLV.pack(add(tag,subTLV.pack_size(),NULL).back()); // add new blank element of sufficient size and pack subTLV into this new element + return(*this); } ///////////////////////////////////// -TLV8_it TLV8::add(uint8_t tag, uint64_t val){ +TLV8 &TLV8::add(uint8_t tag, uint64_t val){ uint8_t *p=reinterpret_cast(&val); size_t nBytes=sizeof(uint64_t); @@ -105,7 +104,7 @@ TLV8_it TLV8::find(uint8_t tag, TLV8_it it1, TLV8_it it2){ auto it=it1; while(it!=it2 && (*it).tag!=tag) it++; - return(it==it2?end():it); + return(it); } ///////////////////////////////////// diff --git a/src/TLV8.h b/src/TLV8.h index aca36c4..93b3e34 100644 --- a/src/TLV8.h +++ b/src/TLV8.h @@ -84,11 +84,11 @@ class TLV8 : public std::list> { TLV8(){}; TLV8(const TLV8_names *names, int nNames) : names{names}, nNames{nNames} {}; - TLV8_it add(uint8_t tag, size_t len, const uint8_t *val); - TLV8_it add(uint8_t tag, uint64_t val); - TLV8_it add(uint8_t tag, TLV8 &subTLV); - TLV8_it add(uint8_t tag){return(add(tag, 0, NULL));} - TLV8_it add(uint8_t tag, const char *val){return(add(tag, strlen(val), reinterpret_cast(val)));} + TLV8 & add(uint8_t tag, size_t len, const uint8_t *val); + TLV8 & add(uint8_t tag, uint64_t val); + TLV8 & add(uint8_t tag, TLV8 &subTLV); + TLV8 & add(uint8_t tag){return(add(tag, 0, NULL));} + TLV8 & add(uint8_t tag, const char *val){return(add(tag, strlen(val), reinterpret_cast(val)));} TLV8_it find(uint8_t tag, TLV8_it it1, TLV8_it it2); TLV8_it find(uint8_t tag, TLV8_it it1){return(find(tag, it1, end()));} From 28990d6ed674c32f5bf2dc878eb64056912105b7 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 13 Apr 2024 22:00:37 -0500 Subject: [PATCH 19/74] Revert "Changed TLV:add() so it returns TLV8 instead of iterator." This reverts commit 7a50479bacd97483f22799b06c6206fcd0bba05b. --- src/HAP.cpp | 32 +++++++++++++++++--------------- src/TLV8.cpp | 15 ++++++++------- src/TLV8.h | 10 +++++----- 3 files changed, 30 insertions(+), 27 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index a4aa4c0..69720de 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -370,6 +370,7 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ return(0); }; + auto itPublicKey=responseTLV.add(kTLVType_PublicKey,384,NULL); // create blank PublicKey TLV with space for 384 bytes if(srp==NULL) // create instance of SRP (if not already created) to persist until Pairing-Setup M5 completes srp=new SRP6A; @@ -380,8 +381,7 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ responseTLV.add(kTLVType_Salt,16,verifyData.get()->salt); // write Salt from verification data into TLV - responseTLV.add(kTLVType_PublicKey,384,NULL); // create blank PublicKey TLV with space for 384 bytes - srp->createPublicKey(verifyData,responseTLV.back()); // create accessory Public Key from stored verification data and write result into PublicKey TLV + srp->createPublicKey(verifyData,*itPublicKey); // create accessory Public Key from stored verification data and write result into PublicKey TLV tlvRespond(responseTLV); // send response to client pairStatus=pairState_M3; // set next expected pair-state request from client @@ -414,8 +414,9 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ return(0); }; - responseTLV.add(kTLVType_Proof,64,NULL); // create blank accessory Proof TLV with space for 64 bytes - srp->createAccProof(responseTLV.back()); // M1 has been successully verified; now create accessory Proof M2 + auto itAccProof=responseTLV.add(kTLVType_Proof,64,NULL); // create blank accessory Proof TLV with space for 64 bytes + + srp->createAccProof(*itAccProof); // M1 has been successully verified; now create accessory Proof M2 tlvRespond(responseTLV); // send response to client pairStatus=pairState_M5; // set next expected pair-state request from client @@ -512,13 +513,14 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ TempBuffer accessoryInfo(accessoryX,accessoryX.len(),accessory.ID,hap_accessory_IDBYTES,accessory.LTPK,crypto_sign_PUBLICKEYBYTES,NULL); - subTLV.clear(); // clear existing SUBTLV records + subTLV.clear(); // clear existing SUBTLV records - subTLV.add(kTLVType_Signature,64,NULL); // create blank Signature TLV with space for 64 bytes - crypto_sign_detached(subTLV.back(),NULL,accessoryInfo,accessoryInfo.len(),accessory.LTSK); // produce signature of accessoryInfo using AccessoryLTSK (Ed25519 long-term secret key) + itSignature=subTLV.add(kTLVType_Signature,64,NULL); // create blank Signature TLV with space for 64 bytes - subTLV.add(kTLVType_Identifier,hap_accessory_IDBYTES,accessory.ID); // set Identifier TLV record as accessoryPairingID - subTLV.add(kTLVType_PublicKey,crypto_sign_PUBLICKEYBYTES,accessory.LTPK); // set PublicKey TLV record as accessoryLTPK + crypto_sign_detached(*itSignature,NULL,accessoryInfo,accessoryInfo.len(),accessory.LTSK); // produce signature of accessoryInfo using AccessoryLTSK (Ed25519 long-term secret key) + + subTLV.add(kTLVType_Identifier,hap_accessory_IDBYTES,accessory.ID); // set Identifier TLV record as accessoryPairingID + subTLV.add(kTLVType_PublicKey,crypto_sign_PUBLICKEYBYTES,accessory.LTPK); // set PublicKey TLV record as accessoryLTPK LOG2("------- ENCRYPTING SUB-TLVS -------\n"); @@ -530,9 +532,9 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ // Encrypt the subTLV data using the same SRP Session Key as above with ChaCha20-Poly1305 - responseTLV.add(kTLVType_EncryptedData,subPack.len()+crypto_aead_chacha20poly1305_IETF_ABYTES,NULL); //create blank EncryptedData TLV with space for subTLV + Authentication Tag + 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(responseTLV.back(),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",sessionKey); LOG2("---------- END SUB-TLVS! ----------\n"); @@ -621,8 +623,8 @@ int HAPClient::postPairVerifyURL(uint8_t *content, size_t len){ TempBuffer accessoryInfo(publicCurveKey,crypto_box_PUBLICKEYBYTES,accessory.ID,hap_accessory_IDBYTES,iosCurveKey,crypto_box_PUBLICKEYBYTES,NULL); subTLV.add(kTLVType_Identifier,hap_accessory_IDBYTES,accessory.ID); // set Identifier subTLV record as Accessory's Pairing ID - subTLV.add(kTLVType_Signature,crypto_sign_BYTES,NULL); // create blank Signature subTLV - crypto_sign_detached(subTLV.back(),NULL,accessoryInfo,accessoryInfo.len(),accessory.LTSK); // produce Signature of accessoryInfo using Accessory's LTSK + auto itSignature=subTLV.add(kTLVType_Signature,crypto_sign_BYTES,NULL); // create blank Signature subTLV + crypto_sign_detached(*itSignature,NULL,accessoryInfo,accessoryInfo.len(),accessory.LTSK); // produce Signature of accessoryInfo using Accessory's LTSK LOG2("------- ENCRYPTING SUB-TLVS -------\n"); @@ -638,8 +640,8 @@ int HAPClient::postPairVerifyURL(uint8_t *content, size_t len){ 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 - responseTLV.add(kTLVType_EncryptedData,subPack.len()+crypto_aead_chacha20poly1305_IETF_ABYTES,NULL); // create blank EncryptedData subTLV - crypto_aead_chacha20poly1305_ietf_encrypt(responseTLV.back(),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",sessionKey); // encrypt data with Session Curve25519 Key and padded nonce="PV-Msg02" LOG2("---------- END SUB-TLVS! ----------\n"); diff --git a/src/TLV8.cpp b/src/TLV8.cpp index 7020af0..51689f8 100644 --- a/src/TLV8.cpp +++ b/src/TLV8.cpp @@ -68,27 +68,28 @@ void tlv8_t::osprint(std::ostream& os){ ///////////////////////////////////// -TLV8 &TLV8::add(uint8_t tag, size_t len, const uint8_t* val){ +TLV8_it TLV8::add(uint8_t tag, size_t len, const uint8_t* val){ if(!empty() && back().tag==tag) back().update(len,val); else emplace_back(tag,len,val); - return(*this); + return(--end()); } ///////////////////////////////////// -TLV8 &TLV8::add(uint8_t tag, TLV8 &subTLV){ +TLV8_it TLV8::add(uint8_t tag, TLV8 &subTLV){ - subTLV.pack(add(tag,subTLV.pack_size(),NULL).back()); // add new blank element of sufficient size and pack subTLV into this new element - return(*this); + auto it=add(tag,subTLV.pack_size(),NULL); // create space for inserting sub TLV and store iterator to new element + subTLV.pack(*it); // pack subTLV into new element + return(--end()); } ///////////////////////////////////// -TLV8 &TLV8::add(uint8_t tag, uint64_t val){ +TLV8_it TLV8::add(uint8_t tag, uint64_t val){ uint8_t *p=reinterpret_cast(&val); size_t nBytes=sizeof(uint64_t); @@ -104,7 +105,7 @@ TLV8_it TLV8::find(uint8_t tag, TLV8_it it1, TLV8_it it2){ auto it=it1; while(it!=it2 && (*it).tag!=tag) it++; - return(it); + return(it==it2?end():it); } ///////////////////////////////////// diff --git a/src/TLV8.h b/src/TLV8.h index 93b3e34..aca36c4 100644 --- a/src/TLV8.h +++ b/src/TLV8.h @@ -84,11 +84,11 @@ class TLV8 : public std::list> { TLV8(){}; TLV8(const TLV8_names *names, int nNames) : names{names}, nNames{nNames} {}; - TLV8 & add(uint8_t tag, size_t len, const uint8_t *val); - TLV8 & add(uint8_t tag, uint64_t val); - TLV8 & add(uint8_t tag, TLV8 &subTLV); - TLV8 & add(uint8_t tag){return(add(tag, 0, NULL));} - TLV8 & add(uint8_t tag, const char *val){return(add(tag, strlen(val), reinterpret_cast(val)));} + TLV8_it add(uint8_t tag, size_t len, const uint8_t *val); + TLV8_it add(uint8_t tag, uint64_t val); + TLV8_it add(uint8_t tag, TLV8 &subTLV); + TLV8_it add(uint8_t tag){return(add(tag, 0, NULL));} + TLV8_it add(uint8_t tag, const char *val){return(add(tag, strlen(val), reinterpret_cast(val)));} TLV8_it find(uint8_t tag, TLV8_it it1, TLV8_it it2); TLV8_it find(uint8_t tag, TLV8_it it1){return(find(tag, it1, end()));} From 48cab1f82bec6b01c8a171593da06399af1c48ed Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 13 Apr 2024 22:59:37 -0500 Subject: [PATCH 20/74] Changed tlv8_t from struct to class and made variables private Add getLen() and getTag() method to get length and tag of tlv8_t. Also overrode subscript operator [] so you can access any element of internal uint8_t array. Given previous additions (such as getVal()) there is now no reason to need to access the underlying std::unique_ptr directly. --- src/HAP.cpp | 12 ++++++------ src/TLV8.cpp | 36 ++++++++++++++++++------------------ src/TLV8.h | 21 +++++++++++++++++++-- 3 files changed, 43 insertions(+), 26 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index 69720de..e90d188 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -404,7 +404,7 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ return(0); }; - srp->createSessionKey(*itPublicKey,(*itPublicKey).len); // create session key, K, from client Public Key, A + srp->createSessionKey(*itPublicKey,(*itPublicKey).getLen()); // create session key, K, from client Public Key, A if(!srp->verifyClientProof(*itClientProof)){ // verify client Proof, M1 LOG0("\n*** ERROR: SRP Proof Verification Failed\n\n"); @@ -454,9 +454,9 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ // use SessionKey to decrypt encryptedData TLV with padded nonce="PS-Msg05" - TempBuffer decrypted((*itEncryptedData).len-crypto_aead_chacha20poly1305_IETF_ABYTES); // temporary storage for decrypted data + 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).len, 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", 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 @@ -492,7 +492,7 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ // Concatenate iosDeviceX, IOS ID, and IOS PublicKey into iosDeviceInfo - TempBuffer iosDeviceInfo(iosDeviceX,iosDeviceX.len(),(*itIdentifier).val.get(),(*itIdentifier).len,(*itPublicKey).val.get(),(*itPublicKey).len,NULL); + TempBuffer iosDeviceInfo(iosDeviceX,iosDeviceX.len(),(uint8_t *)(*itIdentifier),(*itIdentifier).getLen(),(uint8_t *)(*itPublicKey),(*itPublicKey).getLen(),NULL); if(crypto_sign_verify_detached(*itSignature, iosDeviceInfo, iosDeviceInfo.len(), *itPublicKey) != 0){ // verify signature of iosDeviceInfo using iosDeviceLTPK LOG0("\n*** ERROR: LPTK Signature Verification Failed\n\n"); @@ -668,9 +668,9 @@ int HAPClient::postPairVerifyURL(uint8_t *content, size_t len){ // use Session Curve25519 Key (from previous step) to decrypt encrypytedData TLV with padded nonce="PV-Msg03" - TempBuffer decrypted((*itEncryptedData).len-crypto_aead_chacha20poly1305_IETF_ABYTES); // temporary storage for decrypted data + 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).len, 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", 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 diff --git a/src/TLV8.cpp b/src/TLV8.cpp index 51689f8..3156005 100644 --- a/src/TLV8.cpp +++ b/src/TLV8.cpp @@ -70,7 +70,7 @@ void tlv8_t::osprint(std::ostream& os){ TLV8_it TLV8::add(uint8_t tag, size_t len, const uint8_t* val){ - if(!empty() && back().tag==tag) + if(!empty() && back().getTag()==tag) back().update(len,val); else emplace_back(tag,len,val); @@ -103,9 +103,9 @@ TLV8_it TLV8::add(uint8_t tag, uint64_t val){ TLV8_it TLV8::find(uint8_t tag, TLV8_it it1, TLV8_it it2){ auto it=it1; - while(it!=it2 && (*it).tag!=tag) + while(it!=it2 && (*it).getTag()!=tag) it++; - return(it==it2?end():it); + return(it); } ///////////////////////////////////// @@ -115,9 +115,9 @@ size_t TLV8::pack_size(TLV8_it it1, TLV8_it it2){ size_t nBytes=0; while(it1!=it2){ - nBytes+=2+(*it1).len; - if((*it1).len>255) - nBytes+=2*(((*it1).len-1)/255); + nBytes+=2+(*it1).getLen(); + if((*it1).getLen()>255) + nBytes+=2*(((*it1).getLen()-1)/255); it1++; } @@ -134,13 +134,13 @@ size_t TLV8::pack(uint8_t *buf, size_t bufSize){ switch(currentPackPhase){ case 0: - currentPackBuf=(*currentPackIt).val.get(); - endPackBuf=(*currentPackIt).val.get()+(*currentPackIt).len; + currentPackBuf=*currentPackIt; + endPackBuf=(*currentPackIt)+(*currentPackIt).getLen(); currentPackPhase=1; break; case 1: - *buf++=(*currentPackIt).tag; + *buf++=(*currentPackIt).getTag(); nBytes++; currentPackPhase=2; break; @@ -228,7 +228,7 @@ int TLV8::unpack(TLV8_it it){ if(it==end()) return(0); - return(unpack(*it,(*it).len)); + return(unpack(*it,(*it).getLen())); } ///////////////////////////////////// @@ -251,19 +251,19 @@ const char *TLV8::getName(uint8_t tag){ void TLV8::print(TLV8_it it1, TLV8_it it2){ while(it1!=it2){ - const char *name=getName((*it1).tag); + const char *name=getName((*it1).getTag()); if(name) Serial.printf("%s",name); else - Serial.printf("%d",(*it1).tag); - Serial.printf("(%d) ",(*it1).len); - for(int i=0;i<(*it1).len;i++) - Serial.printf("%02X",(*it1).val.get()[i]); - if((*it1).len==0) + Serial.printf("%d",(*it1).getTag()); + Serial.printf("(%d) ",(*it1).getLen()); + for(int i=0;i<(*it1).getLen();i++) + Serial.printf("%02X",(*it1)[i]); + if((*it1).getLen()==0) Serial.printf(" [null]"); - else if((*it1).len<=4) + else if((*it1).getLen()<=4) Serial.printf(" [%u]",(*it1).getVal()); - else if((*it1).len<=8) + else if((*it1).getLen()<=8) Serial.printf(" [%llu]",(*it1).getVal()); Serial.printf("\n"); it1++; diff --git a/src/TLV8.h b/src/TLV8.h index aca36c4..f071b8a 100644 --- a/src/TLV8.h +++ b/src/TLV8.h @@ -34,11 +34,16 @@ #include "PSRAM.h" -struct tlv8_t { +class tlv8_t { + + private: + uint8_t tag; size_t len; std::unique_ptr val; + public: + tlv8_t(uint8_t tag, size_t len, const uint8_t* val); void update(size_t addLen, const uint8_t *addVal); void osprint(std::ostream& os); @@ -47,6 +52,18 @@ struct tlv8_t { return(val.get()); } + uint8_t & operator[](int index){ + return(val.get()[index]); + } + + size_t getLen(){ + return(len); + } + + uint8_t getTag(){ + return(tag); + } + template T getVal(){ T iVal=0; for(int i=0;i> { TLV8_it find(uint8_t tag, TLV8_it it1){return(find(tag, it1, end()));} TLV8_it find(uint8_t tag){return(find(tag, begin(), end()));} - int len(TLV8_it it){return(it==end()?-1:(*it).len);} + int len(TLV8_it it){return(it==end()?-1:(*it).getLen());} size_t pack_size(TLV8_it it1, TLV8_it it2); size_t pack_size(){return(pack_size(begin(), end()));} From e4df56293a85e2694f6bef38030a4e3d85c6d62c Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 14 Apr 2024 13:37:04 -0500 Subject: [PATCH 21/74] Added TLV8:printAll() Recursively prints an entire TLV and all embedded sub-TLVs based on a best-guess if the value of any TLV is a sub-TLV. --- src/TLV8.cpp | 14 ++++++++++++++ src/TLV8.h | 5 ++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/TLV8.cpp b/src/TLV8.cpp index 3156005..aae7686 100644 --- a/src/TLV8.cpp +++ b/src/TLV8.cpp @@ -272,6 +272,20 @@ void TLV8::print(TLV8_it it1, TLV8_it it2){ ////////////////////////////////////// +void TLV8::printAll_r(String label){ + + for(auto it=begin();it!=end();it++){ + Serial.printf("%s",label.c_str()); + print(it); + TLV8 tlv; + if(tlv.unpack(*it,(*it).getLen())==0) + tlv.printAll_r(label+String((*it).getTag())+"-"); + } + Serial.printf("%sDONE\n",label.c_str()); +} + +////////////////////////////////////// + void TLV8::osprint(std::ostream& os, TLV8_it it1, TLV8_it it2){ for(auto it=it1;it!=it2;it++) diff --git a/src/TLV8.h b/src/TLV8.h index f071b8a..2c08784 100644 --- a/src/TLV8.h +++ b/src/TLV8.h @@ -95,7 +95,9 @@ class TLV8 : public std::list> { const TLV8_names *names=NULL; int nNames=0; - + + void printAll_r(String label); + public: TLV8(){}; @@ -128,6 +130,7 @@ class TLV8 : public std::list> { void print(TLV8_it it1, TLV8_it it2); void print(TLV8_it it1){print(it1, std::next(it1));} void print(){print(begin(), end());} + void printAll(){printAll_r("");} void osprint(std::ostream& os, TLV8_it it1, TLV8_it it2); void osprint(std::ostream& os, TLV8_it it1){osprint(os, it1, std::next(it1));} From a7d57699a05cc0495e61fc86eb5d45f227e9ccec Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 14 Apr 2024 21:05:09 -0500 Subject: [PATCH 22/74] Added homeSpan.resetIID()... ...as well as SpanService.getIID() and SpanCharacteristic.getIID(). This provides control of IIDs used for each Service and Characteristic. By using this with adaptive lighting test, was able to verify that HomeKit can interpret TLV values of 1 byte, 2 bytes, and 4 bytes, but not 3 bytes. Suggests that TLV values need to be multiples of 2 (i.e. uint8, uint16, uint32). Will change TLV value-writing methodology so that padding zeros are used to round to 1, 2, 4, or 8 bytes. --- src/HomeSpan.cpp | 20 ++++++++++++++++++++ src/HomeSpan.h | 19 ++++++++++++------- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index e8153bc..b0bcbe7 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -1586,6 +1586,26 @@ boolean Span::printfAttributes(char **ids, int numIDs, int flags){ /////////////////////////////// +Span& Span::resetIID(int newIID){ + + if(Accessories.empty()){ + LOG0("\nFATAL ERROR! Can't reset the Accessory IID count without a defined Accessory ***\n"); + LOG0("\n=== PROGRAM HALTED ==="); + while(1); + } + + if(newIID<1){ + LOG0("\nFATAL ERROR! Can't reset the Accessory IID count to a value less than 1 ***\n"); + LOG0("\n=== PROGRAM HALTED ==="); + while(1); + } + + Accessories.back()->iidCount=newIID-1; + return(*this); +} + +/////////////////////////////// + boolean Span::updateDatabase(boolean updateMDNS){ printfAttributes(GET_META|GET_PERMS|GET_TYPE|GET_DESC); // stream attributes database, which automtically produces a SHA-384 hash diff --git a/src/HomeSpan.h b/src/HomeSpan.h index e0563a8..f22c55d 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -353,7 +353,8 @@ class Span{ Span& setStatusCallback(void (*f)(HS_STATUS status)){statusCallback=f;return(*this);} // sets an optional user-defined function to call when HomeSpan status changes const char* statusString(HS_STATUS s); // returns char string for HomeSpan status change messages Span& setPairingCode(const char *s, boolean progCall=true); // sets the Pairing Code - use is NOT recommended. Use 'S' from CLI instead - void deleteStoredValues(){processSerialCommand("V");} // deletes stored Characteristic values from NVS + void deleteStoredValues(){processSerialCommand("V");} // deletes stored Characteristic values from NVS + Span& resetIID(int newIID); // resets the IID count for the current Accessory to start at newIID int enableOTA(boolean auth=true, boolean safeLoad=true){return(spanOTA.init(auth, safeLoad, NULL));} // enables Over-the-Air updates, with (auth=true) or without (auth=false) authorization password int enableOTA(const char *pwd, boolean safeLoad=true){return(spanOTA.init(true, safeLoad, pwd));} // enables Over-the-Air updates, with custom authorization password (overrides any password stored with the 'O' command) @@ -452,12 +453,14 @@ class SpanService{ public: - void *operator new(size_t size){return(HS_MALLOC(size));} // override new operator to use PSRAM when available - SpanService(const char *type, const char *hapName, boolean isCustom=false); // constructor - SpanService *setPrimary(); // sets the Service Type to be primary and returns pointer to self - SpanService *setHidden(); // sets the Service Type to be hidden and returns pointer to self - SpanService *addLink(SpanService *svc); // adds svc as a Linked Service and returns pointer to self - vector> getLinks(){return(linkedServices);} // returns linkedServices vector for use as range in "for-each" loops + void *operator new(size_t size){return(HS_MALLOC(size));} // override new operator to use PSRAM when available + SpanService(const char *type, const char *hapName, boolean isCustom=false); // constructor + SpanService *setPrimary(); // sets the Service Type to be primary and returns pointer to self + SpanService *setHidden(); // sets the Service Type to be hidden and returns pointer to self + SpanService *addLink(SpanService *svc); // adds svc as a Linked Service and returns pointer to self + vector> getLinks(){return(linkedServices);} // returns linkedServices vector for use as range in "for-each" loops + + int getIID(){return(iid);} virtual boolean update() {return(true);} // placeholder for code that is called when a Service is updated via a Controller. Must return true/false depending on success of update virtual void loop(){} // loops for each Service - called every cycle if over-ridden with user-defined code @@ -653,6 +656,8 @@ class SpanCharacteristic{ 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 + int getIID(){return(iid);} + template T getVal(){ return(uvGet(value)); } From 73561328e620ac9be814a59e5aac04192d4bfa6b Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 14 Apr 2024 21:29:02 -0500 Subject: [PATCH 23/74] Changed TLV8:add() for *values* to ensure resulting size is always multiple of 2 Checked that this is correctly interpreted by HomeKit using adaptive light TLV. TLV code is now ready for documentation and then transfer to dev branch. --- src/TLV8.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/TLV8.cpp b/src/TLV8.cpp index aae7686..6c629ad 100644 --- a/src/TLV8.cpp +++ b/src/TLV8.cpp @@ -93,8 +93,12 @@ TLV8_it TLV8::add(uint8_t tag, uint64_t val){ uint8_t *p=reinterpret_cast(&val); size_t nBytes=sizeof(uint64_t); - while(nBytes>1 && p[nBytes-1]==0) // TLV requires little endian without any trailing zero bytes (i.e. only use what is needed to fully represent the value) + while(nBytes>1 && p[nBytes-1]==0) // TLV requires little endian of size 1, 2, 4, or 8 bytes (include trailing zeros as needed) nBytes--; + if(nBytes==3) // need to include a trailing zero so that total bytes=4 + nBytes=4; + else if(nBytes>4) // need to include multiple trailing zeros so that total bytes=8 + nBytes=8; return(add(tag, nBytes, p)); } From fe3269e9ef59b03e7b13f5eefc996180dbedc60a Mon Sep 17 00:00:00 2001 From: Gregg Date: Mon, 15 Apr 2024 22:14:05 -0500 Subject: [PATCH 24/74] Added new validation check for homeSpan.resetIID() New value must be equal to, or greater than, the last IID value used. This prevents possible re-use. --- src/HomeSpan.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index b0bcbe7..9380bd7 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -1594,13 +1594,15 @@ Span& Span::resetIID(int newIID){ while(1); } - if(newIID<1){ - LOG0("\nFATAL ERROR! Can't reset the Accessory IID count to a value less than 1 ***\n"); + newIID--; + + if(newIIDiidCount){ + LOG0("\nFATAL ERROR! Can't reset the Accessory IID count to a value less than already used ***\n"); LOG0("\n=== PROGRAM HALTED ==="); while(1); } - Accessories.back()->iidCount=newIID-1; + Accessories.back()->iidCount=newIID; return(*this); } From b9efa873dc76a5547293fcab4c3852301e754f88 Mon Sep 17 00:00:00 2001 From: Gregg Date: Tue, 16 Apr 2024 21:56:07 -0500 Subject: [PATCH 25/74] Added error-checking for homeSpan.resetIID() HomeSpan will throw a Warning when 'i' CLI command finds a duplicate IID within the same Accessory. --- src/HomeSpan.cpp | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 9380bd7..7cba72b 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -874,6 +874,7 @@ void Span::processSerialCommand(const char *c){ LOG0(" *** ERROR #%d! AID already in use for another Accessory ***\n",++nErrors); aidValues.push_back((*acc)->aid); + vector> iidValues; for(auto svc=(*acc)->Services.begin(); svc!=(*acc)->Services.end(); svc++){ LOG0(" \u279f Service %s: IID=%d, %sUUID=\"%s\"\n",(*svc)->hapName,(*svc)->iid,(*svc)->isCustom?"Custom-":"",(*svc)->type); @@ -881,11 +882,16 @@ void Span::processSerialCommand(const char *c){ if(!strcmp((*svc)->type,"3E")){ foundInfo=true; if((*svc)->iid!=1) - LOG0(" *** ERROR #%d! The Accessory Information Service must be defined before any other Services in an Accessory ***\n",++nErrors); + LOG0(" *** ERROR #%d! The Accessory Information Service must be defined with IID=1 (i.e. before any other Services in an Accessory) ***\n",++nErrors); } else if((*acc)->aid==1) // this is an Accessory with aid=1, but it has more than just AccessoryInfo. So... - isBridge=false; // ...this is not a bridge device - + isBridge=false; // ...this is not a bridge device + + if(std::find(iidValues.begin(),iidValues.end(),(*svc)->iid)!=iidValues.end()) + LOG0(" *** ERROR #%d! IID already in use for another Service or Characteristic within this Accessory ***\n",++nErrors); + + iidValues.push_back((*svc)->iid); + for(auto chr=(*svc)->Characteristics.begin(); chr!=(*svc)->Characteristics.end(); chr++){ LOG0(" \u21e8 Characteristic %s(%.33s%s): IID=%d, %sUUID=\"%s\", %sPerms=", (*chr)->hapName,(*chr)->uvPrint((*chr)->value).c_str(),strlen((*chr)->uvPrint((*chr)->value).c_str())>33?"...\"":"",(*chr)->iid,(*chr)->isCustom?"Custom-":"",(*chr)->type,(*chr)->perms!=(*chr)->hapChar->perms?"Custom-":""); @@ -939,6 +945,11 @@ void Span::processSerialCommand(const char *c){ if((*chr)->formatuvGet((*chr)->value) >= (*chr)->uvGet((*chr)->minValue)) && ((*chr)->uvGet((*chr)->value) <= (*chr)->uvGet((*chr)->maxValue))))) LOG0(" *** WARNING #%d! Value of %g is out of range [%g,%g] ***\n",++nWarnings,(*chr)->uvGet((*chr)->value),(*chr)->uvGet((*chr)->minValue),(*chr)->uvGet((*chr)->maxValue)); + + if(std::find(iidValues.begin(),iidValues.end(),(*chr)->iid)!=iidValues.end()) + LOG0(" *** ERROR #%d! IID already in use for another Service or Characteristic within this Accessory ***\n",++nErrors); + + iidValues.push_back((*chr)->iid); } // Characteristics @@ -1593,16 +1604,14 @@ Span& Span::resetIID(int newIID){ LOG0("\n=== PROGRAM HALTED ==="); while(1); } - - newIID--; - if(newIIDiidCount){ - LOG0("\nFATAL ERROR! Can't reset the Accessory IID count to a value less than already used ***\n"); + if(newIID<1){ + LOG0("\nFATAL ERROR! Request to reset the Accessory IID count to 0 not allowed (IID must be 1 or greater) ***\n"); LOG0("\n=== PROGRAM HALTED ==="); while(1); } - Accessories.back()->iidCount=newIID; + Accessories.back()->iidCount=newIID-1; return(*this); } From 723c343277a2bd23ff2a64a69ef34937eaba5e0a Mon Sep 17 00:00:00 2001 From: Gregg Date: Tue, 16 Apr 2024 22:17:00 -0500 Subject: [PATCH 26/74] Added error checking to warn if non-bridge device defines more than 3 Accessories It appears that HomeKit requires devices with more than 3 Accessories to be configured as a bridge. If not, the Home App will ignore any functional Services in the first Accessory and treat it like a bridge regardless. Device with 3 or less Accessories do not require a bridge configuration. --- src/HomeSpan.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 7cba72b..38667fa 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -1027,6 +1027,9 @@ void Span::processSerialCommand(const char *c){ } LOG0("\nConfigured as Bridge: %s\n",isBridge?"YES":"NO"); + if(!isBridge && Accessories.size()>3) + LOG0("*** WARNING #%d! HomeKit requires the device be configured as a Bridge when more than 3 Accessories are defined ***\n",++nWarnings); + if(hapConfig.configNumber>0) LOG0("Configuration Number: %d\n",hapConfig.configNumber); LOG0("\nDatabase Validation: Warnings=%d, Errors=%d\n",nWarnings,nErrors); From d1dd56547810edd1ef2d549f9b919cec898683b3 Mon Sep 17 00:00:00 2001 From: Gregg Date: Wed, 17 Apr 2024 07:24:08 -0500 Subject: [PATCH 27/74] Converted IID from int to uint32_t everywhere --- src/HomeSpan.cpp | 63 +++++++++++++++++++++--------------------------- src/HomeSpan.h | 48 ++++++++++++++++++------------------ 2 files changed, 51 insertions(+), 60 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 38667fa..bd8c65a 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -864,7 +864,7 @@ void Span::processSerialCommand(const char *c){ char pNames[][7]={"PR","PW","EV","AA","TW","HD","WR"}; for(auto acc=Accessories.begin(); acc!=Accessories.end(); acc++){ - LOG0("\u27a4 Accessory: AID=%d\n",(*acc)->aid); + LOG0("\u27a4 Accessory: AID=%u\n",(*acc)->aid); boolean foundInfo=false; if(acc==Accessories.begin() && (*acc)->aid!=1) @@ -877,7 +877,7 @@ void Span::processSerialCommand(const char *c){ vector> iidValues; for(auto svc=(*acc)->Services.begin(); svc!=(*acc)->Services.end(); svc++){ - LOG0(" \u279f Service %s: IID=%d, %sUUID=\"%s\"\n",(*svc)->hapName,(*svc)->iid,(*svc)->isCustom?"Custom-":"",(*svc)->type); + LOG0(" \u279f Service %s: IID=%u, %sUUID=\"%s\"\n",(*svc)->hapName,(*svc)->iid,(*svc)->isCustom?"Custom-":"",(*svc)->type); if(!strcmp((*svc)->type,"3E")){ foundInfo=true; @@ -893,7 +893,7 @@ void Span::processSerialCommand(const char *c){ iidValues.push_back((*svc)->iid); for(auto chr=(*svc)->Characteristics.begin(); chr!=(*svc)->Characteristics.end(); chr++){ - LOG0(" \u21e8 Characteristic %s(%.33s%s): IID=%d, %sUUID=\"%s\", %sPerms=", + LOG0(" \u21e8 Characteristic %s(%.33s%s): IID=%u, %sUUID=\"%s\", %sPerms=", (*chr)->hapName,(*chr)->uvPrint((*chr)->value).c_str(),strlen((*chr)->uvPrint((*chr)->value).c_str())>33?"...\"":"",(*chr)->iid,(*chr)->isCustom?"Custom-":"",(*chr)->type,(*chr)->perms!=(*chr)->hapChar->perms?"Custom-":""); int foundPerms=0; @@ -996,7 +996,7 @@ void Span::processSerialCommand(const char *c){ for(int i=0;iServices.size();j++){ SpanService *s=Accessories[i]->Services[j]; - LOG0("%-30s %8.8s %10u %3d %6s %4s %6s ",s->hapName,s->type,Accessories[i]->aid,s->iid, + LOG0("%-30s %8.8s %10u %3u %6s %4s %6s ",s->hapName,s->type,Accessories[i]->aid,s->iid, (void(*)())(s->*(&SpanService::update))!=(void(*)())(&SpanService::update)?"YES":"NO", (void(*)())(s->*(&SpanService::loop))!=(void(*)())(&SpanService::loop)?"YES":"NO", (void(*)(int,boolean))(s->*(&SpanService::button))!=(void(*)(int,boolean))(&SpanService::button)?"YES":"NO" @@ -1004,7 +1004,7 @@ void Span::processSerialCommand(const char *c){ if(s->linkedServices.empty()) LOG0("-"); for(int k=0;klinkedServices.size();k++){ - LOG0("%d",s->linkedServices[k]->iid); + LOG0("%u",s->linkedServices[k]->iid); if(klinkedServices.size()-1) LOG0(","); } @@ -1323,7 +1323,7 @@ boolean Span::deleteAccessory(uint32_t n){ /////////////////////////////// -SpanCharacteristic *Span::find(uint32_t aid, int iid){ +SpanCharacteristic *Span::find(uint32_t aid, uint32_t iid){ int index=-1; for(int i=0;iservice->update()?StatusCode::OK:StatusCode::Unable; // update service and save statusCode as OK or Unable depending on whether return is true or false + StatusCode status=pObj[i].characteristic->service->update()?StatusCode::OK:StatusCode::Unable; // update service and save statusCode as OK or Unable depending on whether return is true or false - for(int j=i;jservice==pObj[i].characteristic->service){ // if service of this characteristic matches service that was updated - pObj[j].status=status; // save statusCode for this object - LOG1("Updating aid="); - LOG1(pObj[j].characteristic->aid); - LOG1(" iid="); - LOG1(pObj[j].characteristic->iid); - if(status==StatusCode::OK){ // if status is okay - pObj[j].characteristic->uvSet(pObj[j].characteristic->value,pObj[j].characteristic->newValue); // update characteristic value with new value - if(pObj[j].characteristic->nvsKey){ // if storage key found + if(pObj[j].characteristic->service==pObj[i].characteristic->service){ // if service of this characteristic matches service that was updated + pObj[j].status=status; // save statusCode for this object + LOG1("Updating aid=%u iid=%u",pObj[j].characteristic->aid,pObj[j].characteristic->iid); + if(status==StatusCode::OK){ // if status is okay + pObj[j].characteristic->uvSet(pObj[j].characteristic->value,pObj[j].characteristic->newValue); // update characteristic value with new value + if(pObj[j].characteristic->nvsKey){ // if storage key found if(pObj[j].characteristic->formatnvsKey,pObj[j].characteristic->value.UINT64); // store data as uint64_t regardless of actual type (it will be read correctly when access through uvGet()) + nvs_set_u64(charNVS,pObj[j].characteristic->nvsKey,pObj[j].characteristic->value.UINT64); // store data as uint64_t regardless of actual type (it will be read correctly when access through uvGet()) else - nvs_set_str(charNVS,pObj[j].characteristic->nvsKey,pObj[j].characteristic->value.STRING); // store data + nvs_set_str(charNVS,pObj[j].characteristic->nvsKey,pObj[j].characteristic->value.STRING); // store data nvs_commit(charNVS); } LOG1(" (okay)\n"); - } else { // if status not okay - pObj[j].characteristic->uvSet(pObj[j].characteristic->newValue,pObj[j].characteristic->value); // replace characteristic new value with original value + } else { // if status not okay + pObj[j].characteristic->uvSet(pObj[j].characteristic->newValue,pObj[j].characteristic->value); // replace characteristic new value with original value LOG1(" (failed)\n"); } - pObj[j].characteristic->updateFlag=0; // reset updateFlag for characteristic + pObj[j].characteristic->updateFlag=0; // reset updateFlag for characteristic } } @@ -1555,13 +1552,13 @@ void Span::printfAttributes(SpanBuf *pObj, int nObj){ boolean Span::printfAttributes(char **ids, int numIDs, int flags){ uint32_t aid; - int iid; + uint32_t iid; SpanCharacteristic *Characteristics[numIDs]; StatusCode status[numIDs]; for(int i=0;iprintfAttributes(flags); // get JSON attributes for characteristic (may or may not include status=0 attribute) else{ // else create JSON status attribute based on requested aid/iid - sscanf(ids[i],"%u.%d",&aid,&iid); + sscanf(ids[i],"%u.%u",&aid,&iid); hapOut << "{\"iid\":" << iid << ",\"aid\":" << aid << ",\"status\":" << (int)status[i] << "}"; } @@ -1600,7 +1597,7 @@ boolean Span::printfAttributes(char **ids, int numIDs, int flags){ /////////////////////////////// -Span& Span::resetIID(int newIID){ +Span& Span::resetIID(uint32_t newIID){ if(Accessories.empty()){ LOG0("\nFATAL ERROR! Can't reset the Accessory IID count without a defined Accessory ***\n"); @@ -1767,7 +1764,7 @@ SpanService::~SpanService(){ } } - LOG1("Deleted Service AID=%d IID=%d\n",accessory->aid,iid); + LOG1("Deleted Service AID=%u IID=%u\n",accessory->aid,iid); } /////////////////////////////// @@ -1872,7 +1869,7 @@ SpanCharacteristic::~SpanCharacteristic(){ free(newValue.STRING); } - LOG1("Deleted Characteristic AID=%d IID=%d\n",aid,iid); + LOG1("Deleted Characteristic AID=%u IID=%u\n",aid,iid); } /////////////////////////////// @@ -1961,13 +1958,7 @@ StatusCode SpanCharacteristic::loadUpdate(char *val, char *ev, boolean wr){ if(evFlag && !(perms&EV)) // notification is not supported for characteristic return(StatusCode::NotifyNotAllowed); - LOG1("Notification Request for aid="); - LOG1(aid); - LOG1(" iid="); - LOG1(iid); - LOG1(": "); - LOG1(evFlag?"true":"false"); - LOG1("\n"); + LOG1("Notification Request for aid=%u iid=%u: %s\n",aid,iid,evFlag?"true":"false"); this->ev[HAPClient::conNum]=evFlag; } diff --git a/src/HomeSpan.h b/src/HomeSpan.h index f22c55d..be024b8 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -136,7 +136,7 @@ struct SpanConfig{ struct SpanBuf{ // temporary storage buffer for use with putCharacteristicsURL() and checkTimedResets() uint32_t aid=0; // updated aid - int iid=0; // updated iid + uint32_t iid=0; // updated iid boolean wr=false; // flag to indicate write-response has been requested char *val=NULL; // updated value (optional, though either at least 'val' or 'ev' must be specified) char *ev=NULL; // updated event notification flag (optional, though either at least 'val' or 'ev' must be specified) @@ -280,7 +280,7 @@ class Span{ void printfAttributes(int flags=GET_VALUE|GET_META|GET_PERMS|GET_TYPE|GET_DESC); // writes Attributes JSON database to hapOut stream - SpanCharacteristic *find(uint32_t aid, int iid); // return Characteristic with matching aid and iid (else NULL if not found) + SpanCharacteristic *find(uint32_t aid, uint32_t iid); // return Characteristic with matching aid and iid (else NULL if not found) int countCharacteristics(char *buf); // return number of characteristic objects referenced in PUT /characteristics JSON request int updateCharacteristics(char *buf, SpanBuf *pObj); // parses PUT /characteristics JSON request 'buf into 'pObj' and updates referenced characteristics; returns 1 on success, 0 on fail void printfAttributes(SpanBuf *pObj, int nObj); // writes SpanBuf objects to hapOut stream @@ -354,7 +354,7 @@ class Span{ const char* statusString(HS_STATUS s); // returns char string for HomeSpan status change messages Span& setPairingCode(const char *s, boolean progCall=true); // sets the Pairing Code - use is NOT recommended. Use 'S' from CLI instead void deleteStoredValues(){processSerialCommand("V");} // deletes stored Characteristic values from NVS - Span& resetIID(int newIID); // resets the IID count for the current Accessory to start at newIID + Span& resetIID(uint32_t newIID); // resets the IID count for the current Accessory to start at newIID int enableOTA(boolean auth=true, boolean safeLoad=true){return(spanOTA.init(auth, safeLoad, NULL));} // enables Over-the-Air updates, with (auth=true) or without (auth=false) authorization password int enableOTA(const char *pwd, boolean safeLoad=true){return(spanOTA.init(true, safeLoad, pwd));} // enables Over-the-Air updates, with custom authorization password (overrides any password stored with the 'O' command) @@ -408,15 +408,15 @@ class SpanAccessory{ friend class SpanButton; friend class SpanRange; - uint32_t aid=0; // Accessory Instance ID (HAP Table 6-1) - int iidCount=0; // running count of iid to use for Services and Characteristics associated with this Accessory - vector> Services; // vector of pointers to all Services in this Accessory + uint32_t aid=0; // Accessory Instance ID (HAP Table 6-1) + uint32_t iidCount=0; // running count of iid to use for Services and Characteristics associated with this Accessory + vector> Services; // vector of pointers to all Services in this Accessory - void printfAttributes(int flags); // writes Accessory JSON to hapOut stream + void printfAttributes(int flags); // writes Accessory JSON to hapOut stream protected: - ~SpanAccessory(); // destructor + ~SpanAccessory(); // destructor public: @@ -433,23 +433,23 @@ class SpanService{ friend class SpanCharacteristic; friend class SpanRange; - int iid=0; // Instance ID (HAP Table 6-2) - const char *type; // Service Type - const char *hapName; // HAP Name - boolean hidden=false; // optional property indicating service is hidden - boolean primary=false; // optional property indicating service is primary - vector> Characteristics; // vector of pointers to all Characteristics in this Service - vector> linkedServices; // vector of pointers to any optional linked Services - boolean isCustom; // flag to indicate this is a Custom Service - SpanAccessory *accessory=NULL; // pointer to Accessory containing this Service + uint32_t iid=0; // Instance ID (HAP Table 6-2) + const char *type; // Service Type + const char *hapName; // HAP Name + boolean hidden=false; // optional property indicating service is hidden + boolean primary=false; // optional property indicating service is primary + vector> Characteristics; // vector of pointers to all Characteristics in this Service + vector> linkedServices; // vector of pointers to any optional linked Services + boolean isCustom; // flag to indicate this is a Custom Service + SpanAccessory *accessory=NULL; // pointer to Accessory containing this Service - void printfAttributes(int flags); // writes Service JSON to hapOut stream + void printfAttributes(int flags); // writes Service JSON to hapOut stream protected: - virtual ~SpanService(); // destructor - vector> req; // vector of pointers to all required HAP Characteristic Types for this Service - vector> opt; // vector of pointers to all optional HAP Characteristic Types for this Service + virtual ~SpanService(); // destructor + vector> req; // vector of pointers to all required HAP Characteristic Types for this Service + vector> opt; // vector of pointers to all optional HAP Characteristic Types for this Service public: @@ -460,7 +460,7 @@ class SpanService{ SpanService *addLink(SpanService *svc); // adds svc as a Linked Service and returns pointer to self vector> getLinks(){return(linkedServices);} // returns linkedServices vector for use as range in "for-each" loops - int getIID(){return(iid);} + uint32_t getIID(){return(iid);} // returns IID of Service virtual boolean update() {return(true);} // placeholder for code that is called when a Service is updated via a Controller. Must return true/false depending on success of update virtual void loop(){} // loops for each Service - called every cycle if over-ridden with user-defined code @@ -485,7 +485,7 @@ class SpanCharacteristic{ STRING_t STRING = NULL; }; - int iid=0; // Instance ID (HAP Table 6-3) + uint32_t iid=0; // Instance ID (HAP Table 6-3) HapChar *hapChar; // pointer to HAP Characteristic structure const char *type; // Characteristic Type const char *hapName; // HAP Name @@ -656,7 +656,7 @@ class SpanCharacteristic{ 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 - int getIID(){return(iid);} + uint32_t getIID(){return(iid);} // returns IID of Characteristic template T getVal(){ return(uvGet(value)); From bb531184d7e80ea1201a0658b11196aefb3ad906 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Thu, 18 Apr 2024 06:43:58 -0500 Subject: [PATCH 28/74] Documents resetIID(), Service::getIID() and Characteristic::getIID() --- docs/Reference.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/Reference.md b/docs/Reference.md index 56109b9..5222833 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -281,7 +281,7 @@ The following **optional** `homeSpan` methods provide additional run-time functi * allows for dynamically changing the Accessory database during run-time (i.e. changing the configuration *after* the Arduino `setup()` has finished) * deleting an Accessory automatically deletes all Services, Characteristics, and any other resources it contains * outputs Level-1 Log Messages listing all deleted components - * note: though deletions take effect immediately, HomeKit Controllers, such as the Home App, will not be aware of these changes until the database configuration number is updated and rebroadcast - see updateDatabase() below + * note: though deletions take effect immediately, HomeKit Controllers, such as the Home App, will not be aware of these changes until the database configuration number is updated and rebroadcast - see `updateDatabase()` below * `boolean updateDatabase()` * recomputes the database configuration number and, if changed, rebroadcasts the new number via MDNS so all connected HomeKit Controllers, such as the Home App, can request a full refresh to accurately reflect the new configuration @@ -290,6 +290,12 @@ The following **optional** `homeSpan` methods provide additional run-time functi * use anytime after dynamically adding one or more Accessories (with `new SpanAccessory(aid)`) or deleting one or more Accessories (with `homeSpan.deleteAccessory(aid)`) * **important**: once you delete an Accessory, you cannot re-use the same *aid* when adding a new Accessory (on the same device) unless the new Accessory is configured with the exact same Services and Characteristics as the deleted Accessory * note: this method is **not** needed if you have a static Accessory database that is fully defined in the Arduino `setup()` function of a sketch + +* `Span& resetIID(uint32_t newIID)` + * resets the IID count for the current Accessory to *newIID*, which must be greater than 0 + * throws an error and halts program if called before at least one Accessory is created + * example: `homeSpan.resetIID(100)` causes HomeSpan to set the IID to 100 for the very next Service or Characteristic defined within the current Accessory, and then increment the IID count going forward so that any Services or Characteristics subsequently defined (within the same Accessory) have IID=101, 102, etc. + * note: calling this function only affects the IID generation for the current Accessory (the count will be reset to IID=1 upon instantiation of a new Accessory) --- @@ -371,6 +377,9 @@ The following methods are supported: * 0=single press (SpanButton::SINGLE) * 1=double press (SpanButton::DOUBLE) * 2=long press (SpanButton::LONG) + +* `uint32_t getIID()` + * returns the IID of the Service ## *SpanCharacteristic(value [,boolean nvsStore])* @@ -485,6 +494,9 @@ This is a **base class** from which all HomeSpan Characteristics are derived, an * returns a pointer to the Characteristic itself so that the method can be chained during instantiation * example: `(new Characteristic::RotationSpeed())->setUnit("percentage");` +* `uint32_t getIID()` + * returns the IID of the Characteristic + ### *SpanButton(int pin, uint16_t longTime, uint16_t singleTime, uint16_t doubleTime, boolean (\*triggerType)(int))* Creating an instance of this **class** attaches a pushbutton handler to the ESP32 *pin* specified. From 64f67fbb8a32621aa11e7ca651d52b92abb0b94d Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Thu, 18 Apr 2024 07:04:13 -0500 Subject: [PATCH 29/74] Update Reference.md --- docs/Reference.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/Reference.md b/docs/Reference.md index 5222833..3ae3a75 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -404,16 +404,16 @@ This is a **base class** from which all HomeSpan Characteristics are derived, an * `type T getNewVal()` * a template method that returns the desired **new** value to which a HomeKit Controller has requested the Characteristic be updated. Same casting rules as for `getVal<>()` + * only applicable when called from within the `update()` loop of a **SpanService** (if called outside of the `update()` loop, the return value is that same as calling `getVal<>()`) * `void setVal(value [,boolean notify])` * sets the value of a numerical-based Characteristic to *value*, and, if *notify* is set to true, notifies all HomeKit Controllers of the change. The *notify* flag is optional and will be set to true if not specified. Setting the *notify* flag to false allows you to update a Characateristic without notifying any HomeKit Controllers, which is useful for Characteristics that HomeKit automatically adjusts (such as a countdown timer) but will be requested from the Accessory if the Home App closes and is then re-opened * works with any integer, boolean, or floating-based numerical *value*, though HomeSpan will convert *value* into the appropriate type for each Characteristic (e.g. calling `setValue(5.5)` on an integer-based Characteristic results in *value*=5) - * throws a runtime warning if any of the conditions hold: - * the Characteristic is not configured with Event Notification (EV) permission enabled; or - * this method is being called from within the `update()` routine of a **SpanService** and `isUpdated()` is *true* for the Characteristic (i.e. it is being updated at the same time via the Home App); or - * *value* is outside of the min/max range for the Characteristic, where min/max is either the HAP default, or any new min/max range set via a prior call to `setRange()` - * the first two restrictions above do not apply to the use of `setVal()` from within the `update()` method of a **SpanService** if you are changing the value of a Characteristic in response to a *write-response* request from HomeKit - * *value* is **not** restricted to being an increment of the step size; for example it is perfectly valid to call `setVal(43.5)` after calling `setRange(0,100,5)` on a floating-based Characteristic even though 43.5 does does not align with the step size specified. The Home App will properly retain the value as 43.5, though it will round to the nearest step size increment (in this case 45) when used in a slider graphic (such as setting the temperature of a thermostat) + * throws a runtime warning if *value* is outside of the min/max range for the Characteristic, where min/max is either the HAP default, or any new min/max range set via a prior call to `setRange()` + * note that *value* is **not** restricted to being an increment of the step size; for example it is perfectly valid to call `setVal(43.5)` after calling `setRange(0,100,5)` on a floating-based Characteristic even though 43.5 does does not align with the step size specified. The Home App will properly retain the value as 43.5, though it will round to the nearest step size increment (in this case 45) when used in a slider graphic (such as setting the temperature of a thermostat) + * throws a runtime warning if called from within the `update()` routine of a **SpanService** *and* `isUpdated()` is *true* for the Characteristic (i.e. it is being updated at the same time via the Home App), *unless* you are changing the value of a Characteristic in response to a *write-response* request from HomeKit (typically used only for certain TLV-based Characteristics) + * note this method can be used to update the value of a Characteristic even if the Characteristic is not permissioned for event notifications (EV), in which case the value stored by HomeSpan will be updated but the Home App will *not* be notified of the change + * `SpanCharacteristic *setRange(min, max, step)` * overrides the default HAP range for a Characteristic with the *min*, *max*, and *step* parameters specified From 6794a804f9f7aeef7c7955954916ce4c0b45f503 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Thu, 18 Apr 2024 17:59:31 -0500 Subject: [PATCH 30/74] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9aeaf50..bb379ad 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ HomeSpan requires version 2.0.0 or later of the [Arduino-ESP32 Board Manager](ht * Utilizes a unique *Service-Centric* approach to creating HomeKit devices * Takes full advantage of the widely-popular Arduino IDE * 100% HAP-R2 compliance -* 38 integrated HomeKit Services +* Dozens of integrated HomeKit Services * Operates in either Accessory or Bridge mode * Supports pairing with Setup Codes or QR Codes From 2ecbed9f26e47f15f253a90e006e0d27645db4ee Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Thu, 18 Apr 2024 18:21:02 -0500 Subject: [PATCH 31/74] Update Reference.md --- docs/Reference.md | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/docs/Reference.md b/docs/Reference.md index 3ae3a75..fe658a9 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -440,10 +440,10 @@ This is a **base class** from which all HomeSpan Characteristics are derived, an * `char *getNewString()` * equivalent to `getNewVal()`, but used exclusively for string-characteristics (i.e. a null-terminated array of characters) -* `void setString(const char *value)` +* `void setString(const char *value [,boolean notify])` * equivalent to `setVal(value)`, but used exclusively for string-characteristics (i.e. a null-terminated array of characters) - #### The following methods are supported for DATA (i.e. byte-array) Characteristics: +#### The following methods are supported for DATA (i.e. byte-array) Characteristics: * `size_t getData(uint8_t *data, size_t len)` * similar to `getVal()`, but exclusively used for byte-array Characteristics @@ -451,14 +451,34 @@ This is a **base class** from which all HomeSpan Characteristics are derived, an * returns the total number of bytes encoded in the Characteristic * if *len* is less than the total number of bytes encoded, no data is extracted (i.e. *data* is unmodified) and a warning message is thrown indicating that the size of the *data* array is insufficient to extract all the bytes encoded in the Characteristic * setting *data* to NULL returns the total number of bytes encoded without extracting any data. This can be used to help create a *data* array of sufficient size in advance of extracting the data + * note: byte-array Characteristics are encoded and transmitted as base-64 strings. HomeSpan automatically peforms all encoding and decoding between this format and the specified byte arrays. But when output to the Serial Monitor, the value of byte-array Characteristics are displayed in their base-64 format (as opposed to being shown as a byte array), since base-64 is the representation that is actually transmitted to and from HomeKit + * a warning message is thrown if the value stored in the Characteristic is not in base-64 format * `size_t getNewData(uint8_t *data, size_t len)` * similar to `getData()`, but fills byte array *data*, of specified size *len*, with bytes based on the desired **new** value to which a HomeKit Controller has requested the Characteristic be updated -* `void setData(uint8_t *data, size_t len)` +* `void setData(uint8_t *data, size_t len [,boolean notify])` * similar to `setVal()`, but exclusively used for byte-array Characteristics * updates the Characteristic by "filling" it with *len* bytes from bytes array *data* - * note: byte-array Characteristics are encoded and transmitted as base-64 strings. HomeSpan automatically peforms all encoding and decoding between this format and the specified byte arrays. But when output to the Serial Monitor, the value of byte-array Characteristics are displayed in their base-64 format (as opposed to being shown as a byte array), since base-64 is the representation that is actually transmitted to and from HomeKit + +#### The following methods are supported for TLV8 (structured-data) Characteristics: + +* `size_t getTLV(TLV8 &tlv)` + * similar to `getVal()`, but exclusively used for TLV8 Characteristics + * fills TLV8 structure *tlv* with TLV8 records from the current value of the Characteristic + * returns the total number of bytes encoded in the Characteristic + * if *tlv8* is not empty, TLV8 records from the Characteristic will be appended to any existing records + * similar DATA Characteristics, TLV8 Characteristics are encoded and transmittred as base-64 strings + * a warning message is thrown if the value stored in the Characteristic is not in base-64 format, or does not appear to contain TLV8 records + +* `size_t getNewTLV(TLV8 &tlv)` + * similar to `getTLV()`, but fills TLV8 structure *tlv* with TLV8 records based on the desired **new** value to which a HomeKit Controller has requested the Characteristic be updated + +* `void setTLV(TLV8 &tlv [,boolean notify])` + * similar to `setVal()`, but exclusively used for TLV8 Characteristics + * updates the Characteristic by packing the TLV8 structure *tlv* into a byte array and then encoding it in base-64 for storage as a string + +* see the [TLV8 Characteristics](TLV8.md) page for complete details on how to read/process/create TLV8 Characteristics #### The following methods are supported for all Characteristics: From 04443d70dd1b177ed4539514fd70eaff3ecbcdd5 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Thu, 18 Apr 2024 20:59:55 -0500 Subject: [PATCH 32/74] Update Reference.md --- docs/Reference.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Reference.md b/docs/Reference.md index fe658a9..93ea7b7 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -451,7 +451,7 @@ This is a **base class** from which all HomeSpan Characteristics are derived, an * returns the total number of bytes encoded in the Characteristic * if *len* is less than the total number of bytes encoded, no data is extracted (i.e. *data* is unmodified) and a warning message is thrown indicating that the size of the *data* array is insufficient to extract all the bytes encoded in the Characteristic * setting *data* to NULL returns the total number of bytes encoded without extracting any data. This can be used to help create a *data* array of sufficient size in advance of extracting the data - * note: byte-array Characteristics are encoded and transmitted as base-64 strings. HomeSpan automatically peforms all encoding and decoding between this format and the specified byte arrays. But when output to the Serial Monitor, the value of byte-array Characteristics are displayed in their base-64 format (as opposed to being shown as a byte array), since base-64 is the representation that is actually transmitted to and from HomeKit + * note: byte-array Characteristics are encoded and transmitted as base-64 strings. HomeSpan automatically peforms all encoding and decoding between this format and the specified byte arrays. But when output to the Serial Monitor using the 'i' CLI command, the value of byte-array Characteristics are displayed in their base-64 string format (only the first 32 characters are shown), since base-64 is the representation that is actually transmitted to and from HomeKit * a warning message is thrown if the value stored in the Characteristic is not in base-64 format * `size_t getNewData(uint8_t *data, size_t len)` @@ -468,7 +468,7 @@ This is a **base class** from which all HomeSpan Characteristics are derived, an * fills TLV8 structure *tlv* with TLV8 records from the current value of the Characteristic * returns the total number of bytes encoded in the Characteristic * if *tlv8* is not empty, TLV8 records from the Characteristic will be appended to any existing records - * similar DATA Characteristics, TLV8 Characteristics are encoded and transmittred as base-64 strings + * similar to DATA Characteristics, TLV8 Characteristics are stored and transmitted as base-64 strings * a warning message is thrown if the value stored in the Characteristic is not in base-64 format, or does not appear to contain TLV8 records * `size_t getNewTLV(TLV8 &tlv)` From dfec5332122e884e9b601a4f27ffc0ca805e8ff2 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Thu, 18 Apr 2024 21:06:47 -0500 Subject: [PATCH 33/74] Create TLV8.md --- docs/TLV8.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/TLV8.md diff --git a/docs/TLV8.md b/docs/TLV8.md new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/docs/TLV8.md @@ -0,0 +1 @@ + From b05be87a294f85a3e2c97d9c8e206fe34f6ec6a3 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Thu, 18 Apr 2024 22:20:58 -0500 Subject: [PATCH 34/74] Update TLV8.md --- docs/TLV8.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/TLV8.md b/docs/TLV8.md index 8b13789..6926978 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -1 +1,23 @@ +# TLV8 Characteristics +Most HomeKit Characteristics store a single numerical value or simple string. However, HomeKit supports two additional storage formats - a simple list of bytes (the **DATA** format) and a structured list of tags and values (the **TLV8** format). The DATA format is not used by any Apple-defined Characterstics but it is included in HomeSpan for use when creating Custom Characteristics for non-Apple applications. + +In contrast, The TLV8 format is used extensively by HomeKit during the initial pairing process as well as whenever a new secure (verified) connection is established between HomeKit and a HomeSpan device. There are also a variety of Apple-defined Characteristics that use the TLV8 format to store and transmit multiple sets of values, each represented as byte-arrays of arbitrary length. + +The TLV8 format itself is quite simple. A TLV8 Characteristic comprises one or more byte-arrays (sometimes called TLV8 records), where the first byte in a record represents an identifying TAG (from 0-255), the second byte represents the LENGTH of the value, and the remaining LENGTH-bytes represent the VALUE itself (hence the acronym TLV). Some notable points include: + +* since the LENGTH is stored only as a single byte, VALUES requiring more than 255 bytes must be represented as sequential TLV8 records *with the same TAG* +* it is fine (and in fact common) for a Characteristic to include multiple records with the same TAG, except that they must be *separated by a record with a different TAG*, otherwise the parser reading the data will concatenate the VALUES from sequential records having the same TAG into a single record (as per above) +* records representing zero-LENGTH values are allowed, and consist of only two bytes: a TAG and a zero (indicating a zero-length VALUE). TAGS with zero-LENGTH values are often used to separate multiple records having the same TAG +* if the VALUE stored in TLV8 format represents an integer, it should be presented in little endian format (least-significant byte first) and padded with trailing zeros as needed to bring the total LENGTH of the value to 1, 2, 4, or 8 bytes (representing uint8_t, uint16_t, uint32_t, and uint64_t values) +* if the value stored in TLV8 format represents a string, it should not include the terminating null since the LENGTH tells you have many characters are in the string +* a parser reading TLV8 records should silently ignore any TAG it is not expecting. It may be an error to omit a TAG that the parser requires, but it is not an error to include a TAG it does not recognize +* since HomeKit data transmissions are often represented in JSON, and JSON is a text-only format, HomeKit requires that TLV8 records are first encoded in base-64 when transmitting JSON to and from Controllers to Accessories. + +Fortunately, HomeSpan includes a dedicated TLV8 library (see below) that takes care of everything above automatically, which enables you to read, create, and process TLV8 data without worrying about TLV8 records with more than 255 bytes, converting numerical values to little-endian, or encoding/decoding records into base-64. + + + + + +🚧 From 9b0555d2561e41c818ea8d490f2f8aefc9bf7aaa Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Fri, 19 Apr 2024 12:58:43 -0500 Subject: [PATCH 35/74] Update TLV8.md --- docs/TLV8.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/TLV8.md b/docs/TLV8.md index 6926978..5bb0b20 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -2,19 +2,21 @@ Most HomeKit Characteristics store a single numerical value or simple string. However, HomeKit supports two additional storage formats - a simple list of bytes (the **DATA** format) and a structured list of tags and values (the **TLV8** format). The DATA format is not used by any Apple-defined Characterstics but it is included in HomeSpan for use when creating Custom Characteristics for non-Apple applications. -In contrast, The TLV8 format is used extensively by HomeKit during the initial pairing process as well as whenever a new secure (verified) connection is established between HomeKit and a HomeSpan device. There are also a variety of Apple-defined Characteristics that use the TLV8 format to store and transmit multiple sets of values, each represented as byte-arrays of arbitrary length. +In contrast, the TLV8 format is used extensively by HomeKit during the initial pairing process as well as whenever a new secure (verified) connection is established between HomeKit and a HomeSpan device. There are also a variety of Apple-defined Characteristics that use the TLV8 format to store and transmit multiple sets of values, each represented as byte-arrays of arbitrary length. -The TLV8 format itself is quite simple. A TLV8 Characteristic comprises one or more byte-arrays (sometimes called TLV8 records), where the first byte in a record represents an identifying TAG (from 0-255), the second byte represents the LENGTH of the value, and the remaining LENGTH-bytes represent the VALUE itself (hence the acronym TLV). Some notable points include: +The TLV8 format itself is quite simple. A TLV8 Characteristic comprises one or more byte-arrays (sometimes called TLV8 records), where the first byte in a record represents an identifying TAG (from 0-255), the second byte represents the LENGTH of the value, and the remaining LENGTH-bytes represent the VALUE itself (hence the acronym TLV). Notable points about the TLV8 format are as follows: * since the LENGTH is stored only as a single byte, VALUES requiring more than 255 bytes must be represented as sequential TLV8 records *with the same TAG* * it is fine (and in fact common) for a Characteristic to include multiple records with the same TAG, except that they must be *separated by a record with a different TAG*, otherwise the parser reading the data will concatenate the VALUES from sequential records having the same TAG into a single record (as per above) -* records representing zero-LENGTH values are allowed, and consist of only two bytes: a TAG and a zero (indicating a zero-length VALUE). TAGS with zero-LENGTH values are often used to separate multiple records having the same TAG -* if the VALUE stored in TLV8 format represents an integer, it should be presented in little endian format (least-significant byte first) and padded with trailing zeros as needed to bring the total LENGTH of the value to 1, 2, 4, or 8 bytes (representing uint8_t, uint16_t, uint32_t, and uint64_t values) -* if the value stored in TLV8 format represents a string, it should not include the terminating null since the LENGTH tells you have many characters are in the string +* records representing a zero-LENGTH VALUE are allowed, and consist of only two bytes: a TAG and a zero (indicating a zero-length VALUE). TAGS with a zero-LENGTH VALUE are often used to separate multiple records having the same TAG +* if the VALUE stored in TLV8 format represents an integer, it should be presented in little endian format (least-significant byte first) and padded with trailing zeros as needed to bring the total LENGTH of the VALUE to either 1, 2, 4, or 8 bytes (representing uint8_t, uint16_t, uint32_t, and uint64_t values) +* if the VALUE stored in TLV8 format represents a string, it should not include the terminating null since the LENGTH tells you have many characters are in the string +* the bytes that make up a VALUE can themselves represent a separate, complete TLV8 structure. There is no limit on the number of "nested" TLV8 records that may be embedded in TLV8 Characteristic * a parser reading TLV8 records should silently ignore any TAG it is not expecting. It may be an error to omit a TAG that the parser requires, but it is not an error to include a TAG it does not recognize +* it is not possible to determine whether any given VALUE in a TLV8 record represents an unsigned integer, a string, an arbitrary series of bytes, a separate TLV8 structure, or something else entirely. The only identifying information for any given TLV8 record is the TAG number, which ranges from 0-255. There is no general schema or TLV8 protocol that maps TAG types to VALUE types. Rather, the TAG numbers are arbitrary and users must consult the documentation for each Characteristic to learn what TAG numbers are expected, and what their VALUEs are supposed to represent for that specific Characteristic * since HomeKit data transmissions are often represented in JSON, and JSON is a text-only format, HomeKit requires that TLV8 records are first encoded in base-64 when transmitting JSON to and from Controllers to Accessories. -Fortunately, HomeSpan includes a dedicated TLV8 library (see below) that takes care of everything above automatically, which enables you to read, create, and process TLV8 data without worrying about TLV8 records with more than 255 bytes, converting numerical values to little-endian, or encoding/decoding records into base-64. +Fortunately, HomeSpan includes a dedicated TLV8 library (see below) that automatically takes care of many of the above points, which enables you to read, create, and process TLV8 data without worrying about parsing TLV8 records with more than 255 bytes, converting numerical values to little-endian, or encoding/decoding records into base-64. From 520a4f3df0c4749c6fa97d2bbf4801cc0fe5c9d7 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Fri, 19 Apr 2024 22:05:02 -0500 Subject: [PATCH 36/74] Update TLV8.md --- docs/TLV8.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/docs/TLV8.md b/docs/TLV8.md index 5bb0b20..37d79d6 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -4,6 +4,8 @@ Most HomeKit Characteristics store a single numerical value or simple string. Ho In contrast, the TLV8 format is used extensively by HomeKit during the initial pairing process as well as whenever a new secure (verified) connection is established between HomeKit and a HomeSpan device. There are also a variety of Apple-defined Characteristics that use the TLV8 format to store and transmit multiple sets of values, each represented as byte-arrays of arbitrary length. +## Overview of TLV8 Format + The TLV8 format itself is quite simple. A TLV8 Characteristic comprises one or more byte-arrays (sometimes called TLV8 records), where the first byte in a record represents an identifying TAG (from 0-255), the second byte represents the LENGTH of the value, and the remaining LENGTH-bytes represent the VALUE itself (hence the acronym TLV). Notable points about the TLV8 format are as follows: * since the LENGTH is stored only as a single byte, VALUES requiring more than 255 bytes must be represented as sequential TLV8 records *with the same TAG* @@ -18,6 +20,39 @@ The TLV8 format itself is quite simple. A TLV8 Characteristic comprises one or Fortunately, HomeSpan includes a dedicated TLV8 library (see below) that automatically takes care of many of the above points, which enables you to read, create, and process TLV8 data without worrying about parsing TLV8 records with more than 255 bytes, converting numerical values to little-endian, or encoding/decoding records into base-64. +## *TLV8()* + +Creating an instance of HomeSpan's TLV8 **class** using the above constructor builds an empty TLV8 object into which you can add and process TLV8 records. TLV8 objects are instantiated as standard C++ linked-list containers derived from `std::list`, where *tlv8_t* is an opaque structure used to store individual TLV8 records[^1]. Note that many of the TLV8 methods below rely heavily on linked-list *iterators*.[^2] + +[^1]:The *tlv8_t* structure is opaque because in general you will not have to create or interact directly with the structure or its data. Note that in addition to the above TLV8-specific methods, you can use any `std::list` method with a TLV8 object if desired. + +[^2]:You do not need expert knowledge of C++ containers and iterators in order to use the TLV8 library, but a basic understanding of containers and iterators will make the library much easier to learn and enjoyable to use. + +The method for adding a generic TLV8 record to a TLV8 object is as follows: + +* `TLV8_it add(uint8_t tag, size_t len, const uint8_t *val)` + + * where *tag* is the TAG identifier for the record to add and *val* is a pointer to a byte-array containing *len* elements + * example: `TLV8 myTLV; uint8_t v[]={0x01, 0x05, 0xE3, 0x4C}; tlv.add(1, sizeof(v), v); + * setting *val* to NULL reserves *len* bytes of space for the TLV8 record within the TLV8 object, but does not copy any data + * this method returns an *iterator* to the resulting TLV8 record so you can reference the record at a later time if needed + +In addition to the above generic method suitable for any type of data, the following methods make it easier to add TLV8 records with specific, frequently-used types of data: + +* `TLV8_it add(uint8_t tag, uintXX_t val)` + * adds a TLV8 record containing a single, unsigned numeric value, *val* (i.e. uint8_t, uint16_t, uint32_t, or uint64_t) + +* `TLV8_it add(uint8_t tag, const char *val)` + * adds a TLV8 record containing all the non-null bytes of a null-terminated character string, *val* + +* `TLV8_it add(uint8_t tag, TLV8 &subTLV)` + * adds a TLV8 record containing all the bytes of an entire TLV8 object, *subTLV* + +* `TLV8_it add(uint8_t tag)` + * adds a zero-length TLV8 record containing nothing but a TAG identifer + + + From f96721b9486a828e21cc69804a2bcef0f3c0e981 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 20 Apr 2024 07:23:18 -0500 Subject: [PATCH 37/74] Update TLV8.md --- docs/TLV8.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/TLV8.md b/docs/TLV8.md index 37d79d6..2e973b7 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -51,6 +51,14 @@ In addition to the above generic method suitable for any type of data, the follo * `TLV8_it add(uint8_t tag)` * adds a zero-length TLV8 record containing nothing but a TAG identifer +Note that if you *add* consecutive records with the same TAG identifier, the TLV8 library will concatenate their data and combine into a single record. For example, `myTLV.add(1,13); myTLV.add(1,300)` will be combined to produce a single 3-byte recording containing the data 0x0D2C01, where the first byte represents from the number 13 and the second two bytes represent the number 300. This may have been your desired outcome, but likely not what you wanted to happen. + +Instead, to create two distinct records with the same tag value, simply interpose a zero-length record with a different TAG identifier between the two as a "separator" liek this: `myTLV.add(1,13); myTLV.add(255); myTLV.add(1,300);` Here we used a TAG identifer of 255 to represent the separator, but that choice is arbitrary, unless that TAG happens to be used by the Characteristic for something else (TAG identifiers of 0 or 255 are commonly used as separators). + + + + + From cd3b525dbb4aef5081bbc8e5982f6e52d595d61c Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 20 Apr 2024 07:54:41 -0500 Subject: [PATCH 38/74] Update TLV8.md --- docs/TLV8.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/TLV8.md b/docs/TLV8.md index 2e973b7..2c413fd 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -22,7 +22,9 @@ Fortunately, HomeSpan includes a dedicated TLV8 library (see below) that automat ## *TLV8()* -Creating an instance of HomeSpan's TLV8 **class** using the above constructor builds an empty TLV8 object into which you can add and process TLV8 records. TLV8 objects are instantiated as standard C++ linked-list containers derived from `std::list`, where *tlv8_t* is an opaque structure used to store individual TLV8 records[^1]. Note that many of the TLV8 methods below rely heavily on linked-list *iterators*.[^2] +Creating an instance of HomeSpan's TLV8 **class** using the above constructor builds an empty TLV8 object into which you can add and process TLV8 records. TLV8 objects are instantiated as standard C++ linked-list containers derived from `std::list`, where *tlv8_t* is an opaque structure used to store individual TLV8 records.[^1] + +Also, as shown below, many of the TLV8 methods utilize linked-list iterators. These are represented by the typedef *TLV8_it*.[^2] [^1]:The *tlv8_t* structure is opaque because in general you will not have to create or interact directly with the structure or its data. Note that in addition to the above TLV8-specific methods, you can use any `std::list` method with a TLV8 object if desired. From 15a0396897a208dcb4ccf6d92473d74d793cf238 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 20 Apr 2024 08:00:19 -0500 Subject: [PATCH 39/74] Update TLV8.md --- docs/TLV8.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/TLV8.md b/docs/TLV8.md index 2c413fd..431da26 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -55,7 +55,14 @@ In addition to the above generic method suitable for any type of data, the follo Note that if you *add* consecutive records with the same TAG identifier, the TLV8 library will concatenate their data and combine into a single record. For example, `myTLV.add(1,13); myTLV.add(1,300)` will be combined to produce a single 3-byte recording containing the data 0x0D2C01, where the first byte represents from the number 13 and the second two bytes represent the number 300. This may have been your desired outcome, but likely not what you wanted to happen. -Instead, to create two distinct records with the same tag value, simply interpose a zero-length record with a different TAG identifier between the two as a "separator" liek this: `myTLV.add(1,13); myTLV.add(255); myTLV.add(1,300);` Here we used a TAG identifer of 255 to represent the separator, but that choice is arbitrary, unless that TAG happens to be used by the Characteristic for something else (TAG identifiers of 0 or 255 are commonly used as separators). +Instead, to create two distinct records with the same tag value, simply interpose a zero-length record with a different TAG identifier between the two as a "separator" like this: `myTLV.add(1,13); myTLV.add(255); myTLV.add(1,300);` Here we used a TAG identifer of 255 to represent the separator, but that choice is arbitrary, unless that TAG happens to be used by the Characteristic for something else (TAG identifiers of 0 or 255 are commonly used as separators). + +The method for finding a TLV8 record within a TLV8 object that contains a specific TAG identifer is as follows: + +* `TLV8_it find(uint8_t tag)` + + * where *tag* is the TAG identifier you are seeking + * From 173ba036f06700a429d24f043b1535581ff8c09c Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 20 Apr 2024 09:05:43 -0500 Subject: [PATCH 40/74] Update TLV8.md --- docs/TLV8.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/docs/TLV8.md b/docs/TLV8.md index 431da26..8061965 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -37,7 +37,7 @@ The method for adding a generic TLV8 record to a TLV8 object is as follows: * where *tag* is the TAG identifier for the record to add and *val* is a pointer to a byte-array containing *len* elements * example: `TLV8 myTLV; uint8_t v[]={0x01, 0x05, 0xE3, 0x4C}; tlv.add(1, sizeof(v), v); * setting *val* to NULL reserves *len* bytes of space for the TLV8 record within the TLV8 object, but does not copy any data - * this method returns an *iterator* to the resulting TLV8 record so you can reference the record at a later time if needed + * this method returns a TLV8 *iterator* to the resulting TLV8 record so you can reference the record at a later time if needed In addition to the above generic method suitable for any type of data, the following methods make it easier to add TLV8 records with specific, frequently-used types of data: @@ -62,7 +62,17 @@ The method for finding a TLV8 record within a TLV8 object that contains a specif * `TLV8_it find(uint8_t tag)` * where *tag* is the TAG identifier you are seeking - * + * returns a TLV8 iterator to *first* record that matches; returns *end()* if no records match + +To restrict the search range to a limited set of records, add optional starting and ending iterators *it1* and *it2*: + +* `TLV8_it find(uint8_t tag [, TLV8_it it1 [, TLV8_it it2]])` + + * returns a TLV8 iterator to the *first* record within the range of iterators from *it1* to *it2* that matches the specified *tag* + * search range is inclusive of *it1* but exclusive of *it2* + * returns *it2* if no records match + * if *it2* is unspecified, default is *end()*; if *it1* is unspecified, default is *begin()* + * note `myTLV.find(tag)` is equivalent to `myTLV.find(tag, myTLV.begin(), myTLV.end())` From b4bfac54e7f135a1bb043a81502aba966303b278 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 20 Apr 2024 10:14:31 -0500 Subject: [PATCH 41/74] small tweaks to the TLV8 library for ease of use --- src/HAP.cpp | 22 +++++++++++----------- src/TLV8.cpp | 26 +++++++++++++------------- src/TLV8.h | 4 ++++ 3 files changed, 28 insertions(+), 24 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index e90d188..417fbcd 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -334,7 +334,7 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ return(0); } - int tlvState=(*itState).getVal(); + int tlvState=itState->getVal(); if(nAdminControllers()){ // error: Device already paired (i.e. there is at least one admin Controller). We should not be receiving any requests for Pair-Setup! LOG0("\n*** ERROR: Device already paired!\n\n"); @@ -363,7 +363,7 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ auto itMethod=iosTLV.find(kTLVType_Method); - if(iosTLV.len(itMethod)!=1 || (*itMethod).getVal()!=0){ // error: "Pair Setup" method must always be 0 to indicate setup without MiFi Authentification (HAP Table 5-3) + if(iosTLV.len(itMethod)!=1 || itMethod->getVal()!=0){ // error: "Pair Setup" method must always be 0 to indicate setup without MiFi Authentification (HAP Table 5-3) LOG0("\n*** ERROR: Pair 'Method' missing or not set to 0\n\n"); responseTLV.add(kTLVType_Error,tagError_Unavailable); // set Error=Unavailable tlvRespond(responseTLV); // send response to client @@ -404,7 +404,7 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ return(0); }; - srp->createSessionKey(*itPublicKey,(*itPublicKey).getLen()); // create session key, K, from client Public Key, A + srp->createSessionKey(*itPublicKey,itPublicKey->getLen()); // create session key, K, from client Public Key, A if(!srp->verifyClientProof(*itClientProof)){ // verify client Proof, M1 LOG0("\n*** ERROR: SRP Proof Verification Failed\n\n"); @@ -454,9 +454,9 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ // use SessionKey to decrypt encryptedData TLV with padded nonce="PS-Msg05" - TempBuffer decrypted((*itEncryptedData).getLen()-crypto_aead_chacha20poly1305_IETF_ABYTES); // temporary storage for decrypted data + 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", 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 @@ -492,7 +492,7 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){ // Concatenate iosDeviceX, IOS ID, and IOS PublicKey into iosDeviceInfo - TempBuffer iosDeviceInfo(iosDeviceX,iosDeviceX.len(),(uint8_t *)(*itIdentifier),(*itIdentifier).getLen(),(uint8_t *)(*itPublicKey),(*itPublicKey).getLen(),NULL); + TempBuffer iosDeviceInfo(iosDeviceX,iosDeviceX.len(),(uint8_t *)(*itIdentifier),itIdentifier->getLen(),(uint8_t *)(*itPublicKey),itPublicKey->getLen(),NULL); if(crypto_sign_verify_detached(*itSignature, iosDeviceInfo, iosDeviceInfo.len(), *itPublicKey) != 0){ // verify signature of iosDeviceInfo using iosDeviceLTPK LOG0("\n*** ERROR: LPTK Signature Verification Failed\n\n"); @@ -585,7 +585,7 @@ int HAPClient::postPairVerifyURL(uint8_t *content, size_t len){ return(0); } - int tlvState=(*itState).getVal(); + int tlvState=itState->getVal(); if(!nAdminControllers()){ // error: Device not yet paired - we should not be receiving any requests for Pair-Verify! LOG0("\n*** ERROR: Device not yet paired!\n\n"); @@ -670,7 +670,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", 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 @@ -771,7 +771,7 @@ int HAPClient::postPairingsURL(uint8_t *content, size_t len){ auto itState=iosTLV.find(kTLVType_State); auto itMethod=iosTLV.find(kTLVType_Method); - if(iosTLV.len(itState)!=1 || (*itState).getVal()!=1){ // missing STATE TLV + if(iosTLV.len(itState)!=1 || itState->getVal()!=1){ // missing STATE TLV LOG0("\n*** ERROR: Parirings 'State' is either missing or not set to \n\n"); badRequestError(); // return with 400 error, which closes connection return(0); @@ -783,7 +783,7 @@ int HAPClient::postPairingsURL(uint8_t *content, size_t len){ return(0); } - int tlvMethod=(*itMethod).getVal(); + int tlvMethod=itMethod->getVal(); responseTLV.add(kTLVType_State,pairState_M2); // all responses include State=M2 @@ -810,7 +810,7 @@ int HAPClient::postPairingsURL(uint8_t *content, size_t len){ return(0); } - tagError err=addController(*itIdentifier,*itPublicKey,(*itPermissions).getVal()); + tagError err=addController(*itIdentifier,*itPublicKey,itPermissions->getVal()); if(err!=tagError_None) responseTLV.add(kTLVType_Error,err); diff --git a/src/TLV8.cpp b/src/TLV8.cpp index 6c629ad..d3b6cff 100644 --- a/src/TLV8.cpp +++ b/src/TLV8.cpp @@ -107,7 +107,7 @@ TLV8_it TLV8::add(uint8_t tag, uint64_t val){ TLV8_it TLV8::find(uint8_t tag, TLV8_it it1, TLV8_it it2){ auto it=it1; - while(it!=it2 && (*it).getTag()!=tag) + while(it!=it2 && it->getTag()!=tag) it++; return(it); } @@ -139,12 +139,12 @@ size_t TLV8::pack(uint8_t *buf, size_t bufSize){ case 0: currentPackBuf=*currentPackIt; - endPackBuf=(*currentPackIt)+(*currentPackIt).getLen(); + endPackBuf=(*currentPackIt)+currentPackIt->getLen(); currentPackPhase=1; break; case 1: - *buf++=(*currentPackIt).getTag(); + *buf++=currentPackIt->getTag(); nBytes++; currentPackPhase=2; break; @@ -232,7 +232,7 @@ int TLV8::unpack(TLV8_it it){ if(it==end()) return(0); - return(unpack(*it,(*it).getLen())); + return(unpack(*it,it->getLen())); } ///////////////////////////////////// @@ -255,20 +255,20 @@ const char *TLV8::getName(uint8_t tag){ void TLV8::print(TLV8_it it1, TLV8_it it2){ while(it1!=it2){ - const char *name=getName((*it1).getTag()); + const char *name=getName(it1->getTag()); if(name) Serial.printf("%s",name); else - Serial.printf("%d",(*it1).getTag()); - Serial.printf("(%d) ",(*it1).getLen()); - for(int i=0;i<(*it1).getLen();i++) + Serial.printf("%d",it1->getTag()); + Serial.printf("(%d) ",it1->getLen()); + for(int i=0;igetLen();i++) Serial.printf("%02X",(*it1)[i]); - if((*it1).getLen()==0) + if(it1->getLen()==0) Serial.printf(" [null]"); - else if((*it1).getLen()<=4) - Serial.printf(" [%u]",(*it1).getVal()); - else if((*it1).getLen()<=8) - Serial.printf(" [%llu]",(*it1).getVal()); + else if(it1->getLen()<=4) + Serial.printf(" [%u]",it1->getVal()); + else if(it1->getLen()<=8) + Serial.printf(" [%llu]",it1->getVal()); Serial.printf("\n"); it1++; } diff --git a/src/TLV8.h b/src/TLV8.h index 2c08784..282ce97 100644 --- a/src/TLV8.h +++ b/src/TLV8.h @@ -56,6 +56,10 @@ class tlv8_t { return(val.get()[index]); } + uint8_t *get(){ + return(val.get()); + } + size_t getLen(){ return(len); } From e6c7637825e90579d7e4c9b95b87e414ea6f0f7c Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 20 Apr 2024 18:35:29 -0500 Subject: [PATCH 42/74] Update TLV8.md --- docs/TLV8.md | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/docs/TLV8.md b/docs/TLV8.md index 8061965..27af9c3 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -72,7 +72,28 @@ To restrict the search range to a limited set of records, add optional starting * search range is inclusive of *it1* but exclusive of *it2* * returns *it2* if no records match * if *it2* is unspecified, default is *end()*; if *it1* is unspecified, default is *begin()* - * note `myTLV.find(tag)` is equivalent to `myTLV.find(tag, myTLV.begin(), myTLV.end())` + * note `myTLV.find(tag)` is equivalent to `myTLV.find(tag, myTLV.begin(), myTLV.end())` + +Use of the C++ `auto` keyword is generally the best way to save the TVL8_it iterator that is returned from the `find()` and `add()` methods. For example, `auto myIT = myTLV.find(6)` sets *myIT* to an iterator pointing to the first TLV8 record in *myTLV* that has a TAG identifer of 6. + +The method for finding the LENGTH of the data VALUE stored in a particular TLV8 record is as follows: + +* `int len(TLV8_it it)` + * where *it* is an iterator pointing to a specific TLV8 record + * returns the length of the data VALUE stored in the associated record, which may be zero for a zero-LENGTH record + * returns -1 if *it* points to the *end()* of the TLV8 object + +A typical use of the `len()` method is to simultaneously check whether a TLV8 object contains a particular TAG identifier, and that the LENGTH of the TAG matches an expected value. For example, if a certain Characteristic requires a TLV8 record with a TAG identifer of 6 to contain a 32-byte registration number, you can perform the following check: + +```C++ +auto myIT = myTLV.find(6); +if(myTLV.len(myIT)!=32) + Serial.printf("Error: TAG 6 is either missing or of improper length\n"); +else + Serial.printf("TAG 6 containing 32 bytes of data has been found\n"); +``` + +Once you have the iterator *myIT* pointing to the desired TLV8 record, you can then use any of the methods below to access the VALUE stored in the TLV8 record: @@ -82,6 +103,3 @@ To restrict the search range to a limited set of records, add optional starting - - -🚧 From d9e9783dc1431f19e40cd700b312c67f2d7b3b0c Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 20 Apr 2024 19:01:50 -0500 Subject: [PATCH 43/74] Update TLV8.md --- docs/TLV8.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/TLV8.md b/docs/TLV8.md index 27af9c3..436348b 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -93,6 +93,29 @@ else Serial.printf("TAG 6 containing 32 bytes of data has been found\n"); ``` +The method for printing all of the records in a TLV8 object to the Serial Monitor is as follows: + +* `void print()` + + * prints all TLV8 records, one per line, to the Serial Monitor + * format of the output is: TAG(LENGTH) VALUE [NUMERIC], where + * TAG = the TAG identifer (0-255) + * LENGTH = length of the VALUE byte-array (may be zero) + * VALUE = a sequential list, in hexadecimal, of all the bytes in the VALUE byte-array (only displayed if LENGTH>0) + * NUMERIC = an unsigned-integer interpretation of the bytes in VALUE, assuming little-endian ordering + * this decimal value is only displayed if LENGTH<=8 + * if LENGTH=0, the word "null" is displayed instead + +To restrict the the printing range to a limited set of records, add optional starting and ending iterators *it1* and *it2*: + +* `void print(TLV8_it it1 [, TLV8_it it2])` + + * prints all TLV8 records between iterators *it1* and *it2* + * print range is inclusive of *it1* but exclusive of *it2* + * if *it2* is unspecified, prints only the record pointed to by *it1* + * note `myTLV.print()` is equivalent to `myTLV.print(myTLV.begin(), myTLV.end())` + + Once you have the iterator *myIT* pointing to the desired TLV8 record, you can then use any of the methods below to access the VALUE stored in the TLV8 record: From e0f593bc2e5aeacabcaf508dcb472d5931a66d84 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 20 Apr 2024 19:50:46 -0500 Subject: [PATCH 44/74] Update TLV8.md --- docs/TLV8.md | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/docs/TLV8.md b/docs/TLV8.md index 436348b..a225a13 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -22,13 +22,13 @@ Fortunately, HomeSpan includes a dedicated TLV8 library (see below) that automat ## *TLV8()* -Creating an instance of HomeSpan's TLV8 **class** using the above constructor builds an empty TLV8 object into which you can add and process TLV8 records. TLV8 objects are instantiated as standard C++ linked-list containers derived from `std::list`, where *tlv8_t* is an opaque structure used to store individual TLV8 records.[^1] +Creating an instance of HomeSpan's TLV8 **class** using the above constructor builds an empty TLV8 object into which you can add and process TLV8 records. TLV8 objects are instantiated as standard C++ linked-list containers derived from `std::list`, where *tlv8_t* is an opaque structure used to store individual TLV8 records.[^opaque] -Also, as shown below, many of the TLV8 methods utilize linked-list iterators. These are represented by the typedef *TLV8_it*.[^2] +Also, as shown below, many of the TLV8 methods utilize linked-list iterators. These are represented by the typedef *TLV8_it*.[^iterators] -[^1]:The *tlv8_t* structure is opaque because in general you will not have to create or interact directly with the structure or its data. Note that in addition to the above TLV8-specific methods, you can use any `std::list` method with a TLV8 object if desired. +[^opaque]:The *tlv8_t* structure is opaque because in general you will not have to create or interact directly with the structure or its data. Note that in addition to the above TLV8-specific methods, you can use any `std::list` method with a TLV8 object if desired. -[^2]:You do not need expert knowledge of C++ containers and iterators in order to use the TLV8 library, but a basic understanding of containers and iterators will make the library much easier to learn and enjoyable to use. +[^iterators]:You do not need expert knowledge of C++ containers and iterators in order to use the TLV8 library, but a basic understanding of containers and iterators will make the library much easier to learn and enjoyable to use. The method for adding a generic TLV8 record to a TLV8 object is as follows: @@ -115,6 +115,18 @@ To restrict the the printing range to a limited set of records, add optional sta * if *it2* is unspecified, prints only the record pointed to by *it1* * note `myTLV.print()` is equivalent to `myTLV.print(myTLV.begin(), myTLV.end())` +The output generated by `print()` can contain some very long lines, especially if the VALUE of some of the TLV8 records represents other complete TLV8 objects (known as sub-TLVs or "nested" TLVs). To recursively print all sub-TLV objects, use the following method: + +* `void printAll()` + * recursively prints all TLV8 records, one per line, to the Serial Monitor + * inspects each TLV8 record and tries to parse as if the record represented a sub-TLV object + * if parsing is successful, prints the record and then calls `printAll()` on the sub-TLV + * if not, prints the record and ends this branch of the recursion + * the format of each line is the same as that of `print()` except that TAG displays the full path of all TAGs through the branch + * note that the output can be very voluminous if your TLV8 object contains many levels of nested sub-TLVs + * warning: some care is required when interpretating the output[^subTLVs] + +[^subTLVs]:The `printAll()` method assumes that any VALUE that is consistent with the format of a sub-TLV must be a sub-TLV, even if its just a simple numeric value. For example, `add(10,65536)` yields a record with a TAG identifer of 10 and a 4-byte VALUE of 0x00000100. The `printAll()` method will display this record along with NUMERIC=65536, but it will also then interpret (and thus display) this VALUE as a sub-TLV containing one zero-length record with TAG identifier=0 and another zero-length record with TAG identifer=1, since the VALUE can be successfully parsed as such. Once you have the iterator *myIT* pointing to the desired TLV8 record, you can then use any of the methods below to access the VALUE stored in the TLV8 record: From e83df601b2a8b548a3d3d43062d049a6c1aa57fe Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 20 Apr 2024 20:08:53 -0500 Subject: [PATCH 45/74] Update TLV8.md --- docs/TLV8.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/docs/TLV8.md b/docs/TLV8.md index a225a13..4219c4f 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -118,6 +118,7 @@ To restrict the the printing range to a limited set of records, add optional sta The output generated by `print()` can contain some very long lines, especially if the VALUE of some of the TLV8 records represents other complete TLV8 objects (known as sub-TLVs or "nested" TLVs). To recursively print all sub-TLV objects, use the following method: * `void printAll()` + * recursively prints all TLV8 records, one per line, to the Serial Monitor * inspects each TLV8 record and tries to parse as if the record represented a sub-TLV object * if parsing is successful, prints the record and then calls `printAll()` on the sub-TLV @@ -128,7 +129,20 @@ The output generated by `print()` can contain some very long lines, especially i [^subTLVs]:The `printAll()` method assumes that any VALUE that is consistent with the format of a sub-TLV must be a sub-TLV, even if its just a simple numeric value. For example, `add(10,65536)` yields a record with a TAG identifer of 10 and a 4-byte VALUE of 0x00000100. The `printAll()` method will display this record along with NUMERIC=65536, but it will also then interpret (and thus display) this VALUE as a sub-TLV containing one zero-length record with TAG identifier=0 and another zero-length record with TAG identifer=1, since the VALUE can be successfully parsed as such. -Once you have the iterator *myIT* pointing to the desired TLV8 record, you can then use any of the methods below to access the VALUE stored in the TLV8 record: +TLV8 objects manage all of their internal memory requirements, and free up all resources and memory when they go out of scope or are otherwise deleted. However, if you need to "erase" all the contents of a TLV8 object but stil retain the object so you can re-fill with new data, use the following method: + +* `void wipe()` + * erases all TLV8 records and frees all associated memory + * leaves an empty TLV8 object ready for re-use + +## *TLV8_it()* + +Objects of type *TLV8_it* are iterators that point to a specific record in an TLV8 objects (or to *end()*). If you are using the TLV8 library correctly you should rarely, if ever, need to directly instatiate a TLV8_it using its constructor. Instead, simply use `auto` as described above. + +TLV8 iterators are the **most imporant** part of the TLV8 library, since a TLV8 iterator is used to directly access the data contained in the TLV8 record to which it points. The methods to access this data are as follows: + + + From aa9d64cb5839b6c1263ca309dfec7eb47d7656da Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 20 Apr 2024 20:17:02 -0500 Subject: [PATCH 46/74] Update TLV8.md --- docs/TLV8.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/TLV8.md b/docs/TLV8.md index 4219c4f..9f259cb 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -137,9 +137,11 @@ TLV8 objects manage all of their internal memory requirements, and free up all r ## *TLV8_it()* -Objects of type *TLV8_it* are iterators that point to a specific record in an TLV8 objects (or to *end()*). If you are using the TLV8 library correctly you should rarely, if ever, need to directly instatiate a TLV8_it using its constructor. Instead, simply use `auto` as described above. +Objects of type *TLV8_it* are iterators that point to a specific record in an TLV8 objects (or to *end()*). If you are using the TLV8 library correctly you should rarely, if ever, need to directly instantiate a TLV8_it using its constructor. Instead, simply use `auto` as described above. -TLV8 iterators are the **most imporant** part of the TLV8 library, since a TLV8 iterator is used to directly access the data contained in the TLV8 record to which it points. The methods to access this data are as follows: +**TLV8 iterators are the most imporant part of the TLV8 library. A TLV8 iterator provides the only means to directly access, read from, and write to, the VALUE element in the TLV8 record to which it points.** + +TLV8_it supports the following methods: From d51b74e1c1e9da19b15d5f0264d797e663aff3f8 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 21 Apr 2024 07:06:36 -0500 Subject: [PATCH 47/74] Update TLV8.md --- docs/TLV8.md | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/docs/TLV8.md b/docs/TLV8.md index 9f259cb..5dcb3c6 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -137,11 +137,31 @@ TLV8 objects manage all of their internal memory requirements, and free up all r ## *TLV8_it()* -Objects of type *TLV8_it* are iterators that point to a specific record in an TLV8 objects (or to *end()*). If you are using the TLV8 library correctly you should rarely, if ever, need to directly instantiate a TLV8_it using its constructor. Instead, simply use `auto` as described above. +Objects of type *TLV8_it* are iterators that point to specific records in a TLV8 object (or to *end()*). TLV8 iterators are how you access, read from, and write to, the VALUE element in any given TLV8 record, and are thus a critical part of the TLV8 library. However, if you are using the TLV8 library correctly you should rarely, if ever, need to directly instantiate a *TLV8_it* using its constructor. Instead, simply use the C++ `auto` keyword as noted above. -**TLV8 iterators are the most imporant part of the TLV8 library. A TLV8 iterator provides the only means to directly access, read from, and write to, the VALUE element in the TLV8 record to which it points.** +TLV8_it supports the following methods (a detailed example is provided below that illustrates the use of each method): -TLV8_it supports the following methods: +```C++ +TLV8 myTLV; // instantiates an empty TLV8 object + +myTLV.add(1,32000); // add a TLV8 record with TAG=1 and VALUE=32000 +auto it_A = myTLV.add(2,180); // add a TLV8 record with TAG=2 and VALUE=18, and save the iterator that is returned + +uint8_t v[]={0x01, 0x05, 0xE3, 0x4C}; // create a fixed array, v, of 4 bytes +myTLV.add(200,4,v); // add a TLV8 record with TAG=200 and copy all 4 bytes of array v into its VALUE + +myTLV.add(50,60000); // add a TLV8 record with TAG=50 and VALUE=60000 +myTLV.add(255); // add a zero-length TLV8 record with TAG=255 to act as separator +myTLV.add(50,120000); // add a TLV8 record with TAG=50 and VALUE=120000 +myTLV.add(255); // add a zero-length TLV8 record with TAG=255 to act as separator +auto it_B = myTLV.add(50,30000); // add a TLV8 record with TAG=50 and VALUE=30000, and save the iterator that is returned + +auto it_C = myTLV.find(200); // find an iterator to first TLV8 record with TAG=200; +auto it_D = myTLV.find(50); // find an iterator to first TLV8 record with TAG=50; +auto it_E = myTLV.find(200); // find an iterator to first TLV8 record with TAG=200; + +myTLV.print(); // prints the contents of myTLV to the Serial Monitor +``` From bfdf114ed35ebf77d936c508e4cd1597c6a5e559 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 21 Apr 2024 07:46:01 -0500 Subject: [PATCH 48/74] Update TLV8.md --- docs/TLV8.md | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/docs/TLV8.md b/docs/TLV8.md index 5dcb3c6..1a90601 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -6,16 +6,16 @@ In contrast, the TLV8 format is used extensively by HomeKit during the initial p ## Overview of TLV8 Format -The TLV8 format itself is quite simple. A TLV8 Characteristic comprises one or more byte-arrays (sometimes called TLV8 records), where the first byte in a record represents an identifying TAG (from 0-255), the second byte represents the LENGTH of the value, and the remaining LENGTH-bytes represent the VALUE itself (hence the acronym TLV). Notable points about the TLV8 format are as follows: +The TLV8 format itself is quite simple. A TLV8 object comprises one or more TLV8 *records*, where the first byte in a record represents an identifying TAG (from 0-255), the second byte represents the LENGTH of the value, and the remaining LENGTH-bytes represent the VALUE itself, which is always in the form of a *byte-array* (i.e. an array of 0 or more *uint8_t* elements). Notable points about the TLV8 format are as follows: * since the LENGTH is stored only as a single byte, VALUES requiring more than 255 bytes must be represented as sequential TLV8 records *with the same TAG* -* it is fine (and in fact common) for a Characteristic to include multiple records with the same TAG, except that they must be *separated by a record with a different TAG*, otherwise the parser reading the data will concatenate the VALUES from sequential records having the same TAG into a single record (as per above) +* it is fine (and in fact common) for a TLV8 object to include multiple records with the same TAG, except that they must be *separated by a record with a different TAG*, otherwise the parser reading the data will concatenate the VALUES from sequential records having the same TAG into a single record (as per above) * records representing a zero-LENGTH VALUE are allowed, and consist of only two bytes: a TAG and a zero (indicating a zero-length VALUE). TAGS with a zero-LENGTH VALUE are often used to separate multiple records having the same TAG -* if the VALUE stored in TLV8 format represents an integer, it should be presented in little endian format (least-significant byte first) and padded with trailing zeros as needed to bring the total LENGTH of the VALUE to either 1, 2, 4, or 8 bytes (representing uint8_t, uint16_t, uint32_t, and uint64_t values) -* if the VALUE stored in TLV8 format represents a string, it should not include the terminating null since the LENGTH tells you have many characters are in the string -* the bytes that make up a VALUE can themselves represent a separate, complete TLV8 structure. There is no limit on the number of "nested" TLV8 records that may be embedded in TLV8 Characteristic -* a parser reading TLV8 records should silently ignore any TAG it is not expecting. It may be an error to omit a TAG that the parser requires, but it is not an error to include a TAG it does not recognize -* it is not possible to determine whether any given VALUE in a TLV8 record represents an unsigned integer, a string, an arbitrary series of bytes, a separate TLV8 structure, or something else entirely. The only identifying information for any given TLV8 record is the TAG number, which ranges from 0-255. There is no general schema or TLV8 protocol that maps TAG types to VALUE types. Rather, the TAG numbers are arbitrary and users must consult the documentation for each Characteristic to learn what TAG numbers are expected, and what their VALUEs are supposed to represent for that specific Characteristic +* if the VALUE's byte-array is supposed to represent an single, unsigned integer, it should be arranged in little endian format (i.e. least-significant byte first) and padded with trailing zeros as needed to bring the total LENGTH of the VALUE to either 1, 2, 4, or 8 bytes (representing uint8_t, uint16_t, uint32_t, and uint64_t values) +* if the VALUE's byte-array is supposed to represent a string, it should not include the terminating null since the LENGTH tells you have many characters are in the string +* the bytes that make up a VALUE can themselves represent a separate, complete TLV8 object. There is no limit on the number of "sub-TLVs" that can be recursively nested in a "parent" TLV8 object +* a parser reading TLV8 records should silently ignore any TAG it is not expecting. It may be an error to omit a TAG that the parser requires, but it will be not an error to include a TAG it does not recognize +* it is **not** possible to unambigously determine whether the VALUE byte-array in a TLV8 record is supposed to represent an unsigned integer, a string, an arbitrary series of bytes, a sub-TLV object, or something else entirely. The only identifying information for any given TLV8 record is its TAG number, which ranges from 0-255. There is no general schema or TLV8 protocol that maps TAG types to VALUE types. Rather, the TAG numbers are arbitrary and users must consult the documentation for each Characteristic to learn what TAG numbers are expected, and what their VALUEs are supposed to represent for that specific Characteristic * since HomeKit data transmissions are often represented in JSON, and JSON is a text-only format, HomeKit requires that TLV8 records are first encoded in base-64 when transmitting JSON to and from Controllers to Accessories. Fortunately, HomeSpan includes a dedicated TLV8 library (see below) that automatically takes care of many of the above points, which enables you to read, create, and process TLV8 data without worrying about parsing TLV8 records with more than 255 bytes, converting numerical values to little-endian, or encoding/decoding records into base-64. @@ -137,10 +137,17 @@ TLV8 objects manage all of their internal memory requirements, and free up all r ## *TLV8_it()* -Objects of type *TLV8_it* are iterators that point to specific records in a TLV8 object (or to *end()*). TLV8 iterators are how you access, read from, and write to, the VALUE element in any given TLV8 record, and are thus a critical part of the TLV8 library. However, if you are using the TLV8 library correctly you should rarely, if ever, need to directly instantiate a *TLV8_it* using its constructor. Instead, simply use the C++ `auto` keyword as noted above. +Objects of type *TLV8_it* are iterators that point to specific records in a TLV8 object (or to *end()*). TLV8 iterators are used to access, read from, and write to, the data elements in any given TLV8 record, and are thus a critical part of the TLV8 library. However, if you are using the TLV8 library correctly you should rarely, if ever, need to directly instantiate a *TLV8_it* using its constructor. Instead, simply use the C++ `auto` keyword as noted above. TLV8_it supports the following methods (a detailed example is provided below that illustrates the use of each method): +* `uint8_t getTag()` + * returns the TAG identifier (0-255) of the TLV8 record + +* `size_t getLen()` + * returns the LENGTH of the VALUE byte-array of the TLV8 record + + ```C++ TLV8 myTLV; // instantiates an empty TLV8 object From 998bd873bcbae8a06e7ea79604f07f5d3ceeeaaa Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 21 Apr 2024 08:27:00 -0500 Subject: [PATCH 49/74] Update TLV8.md --- docs/TLV8.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/docs/TLV8.md b/docs/TLV8.md index 1a90601..c822864 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -139,13 +139,33 @@ TLV8 objects manage all of their internal memory requirements, and free up all r Objects of type *TLV8_it* are iterators that point to specific records in a TLV8 object (or to *end()*). TLV8 iterators are used to access, read from, and write to, the data elements in any given TLV8 record, and are thus a critical part of the TLV8 library. However, if you are using the TLV8 library correctly you should rarely, if ever, need to directly instantiate a *TLV8_it* using its constructor. Instead, simply use the C++ `auto` keyword as noted above. -TLV8_it supports the following methods (a detailed example is provided below that illustrates the use of each method): +TLV8_it supports the following methods: * `uint8_t getTag()` + * returns the TAG identifier (0-255) of the TLV8 record + * example: `uint8_t tag = myIT->getTag()` or, equivalently, `uint8_t tag = (*myIT).getTag()` * `size_t getLen()` + * returns the LENGTH of the VALUE byte-array of the TLV8 record + * example: `size_t len = myIT->getLen()` or, equivalently, `size_t len = (*myIT).getLen()` + +* `uint8_t *get()` + + * returns `uint8_t *` pointing to the first element of the VALUE byte-array of the TLV8 record + * for zero-LENGTH TLV8 records, the return value is NULL + * example: `uint8_t *v = myIT->get();` or, equivalently, `uint8_t *v = (*myIT).get();` + * the `(uint8_t *)` casting operator has been overloaded so you can also obtain this same `uint8_t *` pointer by simply dereferencing the iterator + * example: `auto myIT = myTLV.find(6); uint8_t *v = *myIT;` + * note this only works if the compiler can determine the need to auto-cast into a `uint8_t *` pointer based on the context of the code + +* `uint8_t get()[i]` + * returns the *ith* element of the VALUE byte-array + * example: `uint8_t n = myIT->get()[i]` or, equivalently, `uint8_t n = (*myIT).get()[i]` + * the subscript operator has also been overloaded so you can obtain the *ith* element by simply dereferencing the iterator + * example: `uint8_t n = (*myIT)[i]` + * note there is no range-checking so make sure *i* does not try to reference an element beyond the end of the VALUE byte-array ```C++ From b995a759079c82a1a0d867e1843f7ed12318479b Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 21 Apr 2024 08:30:07 -0500 Subject: [PATCH 50/74] Update TLV8.md --- docs/TLV8.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/TLV8.md b/docs/TLV8.md index c822864..b7b63ff 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -167,6 +167,9 @@ TLV8_it supports the following methods: * example: `uint8_t n = (*myIT)[i]` * note there is no range-checking so make sure *i* does not try to reference an element beyond the end of the VALUE byte-array +* `T getVal()` + * this template function interprets the VALUE byte-array as a single unsigned integer of type T + * example: ```C++ TLV8 myTLV; // instantiates an empty TLV8 object From ccda8f04d9ceec94eaf3fb4ea736014dd149ec37 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 21 Apr 2024 10:31:10 -0500 Subject: [PATCH 51/74] Update TLV8.md --- docs/TLV8.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/TLV8.md b/docs/TLV8.md index b7b63ff..a3b26c0 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -168,8 +168,13 @@ TLV8_it supports the following methods: * note there is no range-checking so make sure *i* does not try to reference an element beyond the end of the VALUE byte-array * `T getVal()` - * this template function interprets the VALUE byte-array as a single unsigned integer of type T - * example: + * this template function returns a single numeric value of type *T* on the assumption that the VALUE byte-array is storing an unsigned integer in little endian format + * *T* can be *uint8_t*, *uint16_t*, *uint32_t*, or *uint64_t* (if unspecified *T* defaults to *uint32_t*) + * example: `auto myIT = myTLV.add(50,60000); uint16_t n = myIT->getVal();` + * this method returns the correct numeric value as long as sizeof(*T*) >= LENGTH of the byte-array. For example: + * setting *T=uint64_t* with a VALUE byte-array containing 2 bytes returns the *correct* numeric value + * setting *T=uint16_t* with a VALUE byte-array containing 4 bytes return an *incorrect* numeric value + * this function returns zero for all zero-LENGTH TLV8 records ```C++ TLV8 myTLV; // instantiates an empty TLV8 object From fa25369ebbaf9972442621d5648f4e6056aa94aa Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 21 Apr 2024 12:37:51 -0500 Subject: [PATCH 52/74] Update TLV8.md --- docs/TLV8.md | 112 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 99 insertions(+), 13 deletions(-) diff --git a/docs/TLV8.md b/docs/TLV8.md index a3b26c0..04f1862 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -176,29 +176,115 @@ TLV8_it supports the following methods: * setting *T=uint16_t* with a VALUE byte-array containing 4 bytes return an *incorrect* numeric value * this function returns zero for all zero-LENGTH TLV8 records +### A detailed example using the above methods + +The following code: + ```C++ TLV8 myTLV; // instantiates an empty TLV8 object -myTLV.add(1,32000); // add a TLV8 record with TAG=1 and VALUE=32000 -auto it_A = myTLV.add(2,180); // add a TLV8 record with TAG=2 and VALUE=18, and save the iterator that is returned +myTLV.add(1,8700); // add a TLV8 record with TAG=1 and VALUE=8700 +auto it_A = myTLV.add(2,180); // add a TLV8 record with TAG=2 and VALUE=180, and save the iterator that is returned -uint8_t v[]={0x01, 0x05, 0xE3, 0x4C}; // create a fixed array, v, of 4 bytes -myTLV.add(200,4,v); // add a TLV8 record with TAG=200 and copy all 4 bytes of array v into its VALUE +uint8_t v[32]; // create a 32-byte array, v, and fill it with some data +for(int i=0;i<32;i++) + v[i]=i; + +myTLV.add(200,32,v); // add a TLV8 record with TAG=200 and copy all 32 bytes of array v into its VALUE -myTLV.add(50,60000); // add a TLV8 record with TAG=50 and VALUE=60000 -myTLV.add(255); // add a zero-length TLV8 record with TAG=255 to act as separator -myTLV.add(50,120000); // add a TLV8 record with TAG=50 and VALUE=120000 -myTLV.add(255); // add a zero-length TLV8 record with TAG=255 to act as separator -auto it_B = myTLV.add(50,30000); // add a TLV8 record with TAG=50 and VALUE=30000, and save the iterator that is returned +myTLV.add(50,60000); // add a TLV8 record with TAG=50 and VALUE=60000 +myTLV.add(255); // add a zero-length TLV8 record with TAG=255 to act as separator +myTLV.add(50,120000); // add a TLV8 record with TAG=50 and VALUE=120000 +myTLV.add(255); // add a zero-length TLV8 record with TAG=255 to act as separator +myTLV.add(50,180000); // add a TLV8 record with TAG=50 and VALUE=180000 +myTLV.add(255); // add a zero-length TLV8 record with TAG=255 to act as separator +auto it_B = myTLV.add(50,240000); // add a TLV8 record with TAG=50 and VALUE=240000, and save the iterator that is returned -auto it_C = myTLV.find(200); // find an iterator to first TLV8 record with TAG=200; -auto it_D = myTLV.find(50); // find an iterator to first TLV8 record with TAG=50; -auto it_E = myTLV.find(200); // find an iterator to first TLV8 record with TAG=200; +auto it_C = myTLV.find(50); // find an iterator to the first TLV8 record with TAG=50; +auto it_D = myTLV.find(50,std::next(it_C)); // find an iterator to the first TLV8 record with TAG=50 that occurs AFTER it_C; -myTLV.print(); // prints the contents of myTLV to the Serial Monitor +auto it_E = myTLV.find(200); // find an iterator to first TLV8 record with TAG=200; + +Serial.printf("results of myTLV.print():\n\n"); + +myTLV.print(); // print the contents of myTLV to the Serial Monitor + +Serial.printf("\n"); + +// print content of it_A: + +Serial.printf("it_A: TAG=%d, LENGTH=%d, Value=%d\n", it_A->getTag(), it_A->getLen(), it_A->getVal()); + +// print content of it_B using alternative syntax: + +Serial.printf("it_B: TAG=%d, LENGTH=%d, Value=%d\n", (*it_B).getTag(), (*it_B).getLen(), (*it_B).getVal()); + +// print contents of it_C and it_D, based on previous find() above: + +Serial.printf("it_C TAG=%d, LENGTH=%d, Value=%d\n", (*it_C).getTag(), (*it_C).getLen(), (*it_C).getVal()); +Serial.printf("it_D TAG=%d, LENGTH=%d, Value=%d\n", (*it_D).getTag(), (*it_D).getLen(), (*it_D).getVal()); + +// you can also use the results of find() directly without saving as a separate iterator, though this is computationally inefficient: + +if(myTLV.find(1)!=myTLV.end()) // check for match + Serial.printf("Found: TAG=%d, LENGTH=%d, Value=%d\n", myTLV.find(1)->getTag(), myTLV.find(1)->getLen(), myTLV.find(1)->getVal()); + +// sum up all the bytes in it_E: + +int sum=0; +for(int i=0; i < it_E->getLen(); i++) + sum+= (*it_E)[i]; + +Serial.printf("it_E TAG=%d, LENGTH=%d, Sum of all bytes = %d\n", (*it_E).getTag(), (*it_E).getLen(), sum); + +// create a "blank" TLV8 record with TAG=90 and space for 16 bytes: + +auto it_F = myTLV.add(90,16,NULL); + +// copy the first 16 bytes of it_E into it_F and print the record: + +memcpy(*it_F,*it_E,16); +myTLV.print(it_F); ``` +produces the following output: +```C++ +results of myTLV.print(): + +1(2) FC21 [8700] +2(1) B4 [180] +200(32) 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F +50(2) 60EA [60000] +255(0) [null] +50(4) C0D40100 [120000] +255(0) [null] +50(4) 20BF0200 [180000] +255(0) [null] +50(4) 80A90300 [240000] + +it_A: TAG=2, LENGTH=1, Value=180 +it_B: TAG=50, LENGTH=4, Value=240000 +it_C TAG=50, LENGTH=2, Value=60000 +it_D TAG=50, LENGTH=4, Value=120000 +Found: TAG=1, LENGTH=2, Value=8700 +it_E TAG=200, LENGTH=32, Sum of all bytes = 496 +90(16) 000102030405060708090A0B0C0D0E0F +``` + +## Reading and Writing TLV8 Characteristics + +As documented in the [API Reference](Reference.md#spancharacteristicvalue-boolean-nvsstore), the following *SpanCharacteristic* methods are used to read and write TLV8 objects to TLV8 Characteristics: + +* `getVal(TLV8 &tlv)` +* `getNewVal(TLV8 &tlv)` +* `setVal(TLV8 &tlv)` + +These are analagous to the `getVal()` and `setVal()` methods used for numerical-based Characteristics. + +Note that since TLV8 Characteristics are stored as base-64 encoded strings, you can use `getString()` to read the base-64 text, or `getData()` to decode the string into the full byte-array that represents the entire TLV8 object. + +Also, if you really don't want to use HomeSpan's TLV8 library to produce TLV8 objects, but instead prefer to use your own methods to create a TLV8-compliant byte-array, you can do so and then use `setData()` to save the byte-array you produced to the TLV8 Characteristic, which will perform the base-64 encoding. Or if you want to additionally perform your own base-64 encoding (why?) you can do so and then simply use `setString()` to save the resulting encoded string to the TLV8 Characteristic. From 4334789d504615f604b047380cac78fe17437fec Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 21 Apr 2024 12:39:39 -0500 Subject: [PATCH 53/74] Update TLV8.md --- docs/TLV8.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TLV8.md b/docs/TLV8.md index 04f1862..2f03af8 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -288,7 +288,7 @@ Also, if you really don't want to use HomeSpan's TLV8 library to produce TLV8 ob - +[API Reference](https://github.com/HomeSpan/HomeSpan/blob/tlvwork/docs/Reference.md#spancharacteristicvalue-boolean-nvsstore) From 059c58ca463bb4808811d80aa27072da63c55c2a Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 21 Apr 2024 12:41:14 -0500 Subject: [PATCH 54/74] Update TLV8.md --- docs/TLV8.md | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/docs/TLV8.md b/docs/TLV8.md index 2f03af8..3a9508a 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -274,7 +274,7 @@ it_E TAG=200, LENGTH=32, Sum of all bytes = 496 ## Reading and Writing TLV8 Characteristics -As documented in the [API Reference](Reference.md#spancharacteristicvalue-boolean-nvsstore), the following *SpanCharacteristic* methods are used to read and write TLV8 objects to TLV8 Characteristics: +As documented in the [API Reference](Reference.md), the following *SpanCharacteristic* methods are used to read and write TLV8 objects to TLV8 Characteristics: * `getVal(TLV8 &tlv)` * `getNewVal(TLV8 &tlv)` @@ -286,12 +286,8 @@ Note that since TLV8 Characteristics are stored as base-64 encoded strings, you Also, if you really don't want to use HomeSpan's TLV8 library to produce TLV8 objects, but instead prefer to use your own methods to create a TLV8-compliant byte-array, you can do so and then use `setData()` to save the byte-array you produced to the TLV8 Characteristic, which will perform the base-64 encoding. Or if you want to additionally perform your own base-64 encoding (why?) you can do so and then simply use `setString()` to save the resulting encoded string to the TLV8 Characteristic. - - -[API Reference](https://github.com/HomeSpan/HomeSpan/blob/tlvwork/docs/Reference.md#spancharacteristicvalue-boolean-nvsstore) - - - - +--- + +[↩️](../README.md) Back to the Welcome page From 04cb07a0fc999c2066abfd7dab68fd9f2e5929af Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 21 Apr 2024 12:45:27 -0500 Subject: [PATCH 55/74] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index bb379ad..34e97ce 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,7 @@ HomeSpan includes the following documentation: * [HomeSpan SpanPoint](docs/NOW.md) - facilitates point-to-point, bi-directional communication between ESP32 Devices using ESP-NOW * [HomeSpan Television Services](docs/TVServices.md) - how to use HomeKit's undocumented Television Services and Characteristics * [HomeSpan Message Logging](docs/Logging.md) - how to generate log messages for display on the Arduino Serial Monitor as well as optionally posted to an integrated Web Log page +* [HomeSpan TLV8 Characteristics](docs/TLV8.md) - classes and methods for creating TLV8 objects to use with TLV8-based Characteristics * [HomeSpan Device Cloning](docs/Cloning.md) - seamlessly swap a broken device for a new one without needing to re-pair and lose HomeKit automations * [HomeSpan Projects](https://github.com/topics/homespan) - real-world applications of the HomeSpan Library * [HomeSpan FAQ](docs/FAQ.md) - answers to frequently-asked questions From 4e5d03f63f41ac1d96225d2ceea0a3ddde9823ef Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 21 Apr 2024 15:18:22 -0500 Subject: [PATCH 56/74] renamed CUSTOM_CHAR_TLV() macro to CUSTOM_CHAR_TLV8() macro --- src/Span.h | 10 +++++++--- src/src.ino | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Span.h b/src/Span.h index 23bba59..e3893a7 100644 --- a/src/Span.h +++ b/src/Span.h @@ -614,9 +614,9 @@ namespace Characteristic { #define CUSTOM_CHAR_DATA(NAME,UUID,PERMISISONS) \ HapChar _CUSTOM_##NAME {#UUID,#NAME,(PERMS)(PERMISISONS),DATA,true}; \ - namespace Characteristic { struct NAME : SpanCharacteristic { NAME(const char * val="AA==", boolean nvsStore=false) : SpanCharacteristic {&_CUSTOM_##NAME,true} { init(val,nvsStore); } }; } + namespace Characteristic { struct NAME : SpanCharacteristic { NAME(const char * val="", boolean nvsStore=false) : SpanCharacteristic {&_CUSTOM_##NAME,true} { init(val,nvsStore); } }; } -#define CUSTOM_CHAR_TLV(NAME,UUID,PERMISISONS) \ +#define CUSTOM_CHAR_TLV8(NAME,UUID,PERMISISONS) \ HapChar _CUSTOM_##NAME {#UUID,#NAME,(PERMS)(PERMISISONS),TLV_ENC,true}; \ namespace Characteristic { struct NAME : SpanCharacteristic { NAME(const char * val="", boolean nvsStore=false) : SpanCharacteristic {&_CUSTOM_##NAME,true} { init(val,nvsStore); } }; } @@ -632,7 +632,11 @@ namespace Characteristic { #define CUSTOM_CHAR_DATA(NAME,UUID,PERMISISONS) \ extern HapChar _CUSTOM_##NAME; \ - namespace Characteristic { struct NAME : SpanCharacteristic { NAME(const char * val="AA==", boolean nvsStore=false) : SpanCharacteristic {&_CUSTOM_##NAME,true} { init(val,nvsStore); } }; } + namespace Characteristic { struct NAME : SpanCharacteristic { NAME(const char * val="", boolean nvsStore=false) : SpanCharacteristic {&_CUSTOM_##NAME,true} { init(val,nvsStore); } }; } + +#define CUSTOM_CHAR_TLV8(NAME,UUID,PERMISISONS) \ + extern HapChar _CUSTOM_##NAME; \ + namespace Characteristic { struct NAME : SpanCharacteristic { NAME(const char * val="", boolean nvsStore=false) : SpanCharacteristic {&_CUSTOM_##NAME,true} { init(val,nvsStore); } }; } #endif diff --git a/src/src.ino b/src/src.ino index 4f57226..bfc4942 100644 --- a/src/src.ino +++ b/src/src.ino @@ -27,7 +27,7 @@ #include "HomeSpan.h" -CUSTOM_CHAR_TLV(DisplayOrder,136,PR+EV); +CUSTOM_CHAR_TLV8(DisplayOrder,136,PR+EV); CUSTOM_CHAR_DATA(TestData,333,PR+EV); struct HomeSpanTV : Service::Television { From 050a30f72ccaad2d0468ab693f89729ca9f562fe Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 21 Apr 2024 21:16:08 -0500 Subject: [PATCH 57/74] Update Reference.md --- docs/Reference.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/Reference.md b/docs/Reference.md index 93ea7b7..0ec35cf 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -627,8 +627,9 @@ To create more than one user-defined command, simply create multiple instances o ### *CUSTOM_CHAR(name,uuid,perms,format,defaultValue,minValue,maxValue,staticRange)* ### *CUSTOM_CHAR_STRING(name,uuid,perms,defaultValue)* ### *CUSTOM_CHAR_DATA(name,uuid,perms)* +### *CUSTOM_CHAR_TLV8(name,uuid,perms)* -Creates a custom Characteristic that can be added to any Service. Custom Characteristics are generally ignored by the Home App but may be used by other third-party applications (such as *Eve for HomeKit*). The first form should be used create numerical Characterstics (e.g., UINT8, BOOL...). The second form is used to STRING-based Characteristics. The third form is used for DATA-based (i.e. byte-array) Characteristics. Parameters are as follows (note that quotes should NOT be used in any of the macro parameters, except for *defaultValue* when applied to a STRING-based Characteristic): +Creates a custom Characteristic that can be added to any Service. Custom Characteristics are generally ignored by the Home App but may be used by other third-party applications (such as *Eve for HomeKit*). The first form should be used create numerical Characterstics (e.g., UINT8, BOOL...); the second form is used to STRING-based Characteristics; the third form is used for DATA-based (i.e. byte-array) Characteristics; and the fourth form is used for TLV8-based (i.e. *structured* byte-array) Characteristics Parameters are as follows (note that quotes should NOT be used in any of the macro parameters, except for *defaultValue* when applied to a STRING-based Characteristic): * *name* - the name of the custom Characteristic. This will be added to the Characteristic namespace so that it is accessed the same as any HomeSpan Characteristic. Use UTF-8 coded string for non-ASCII characters. * *uuid* - the UUID of the Characteristic as defined by the manufacturer. Must be *exactly* 36 characters in the form XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX, where *X* represent a valid hexidecimal digit. Leading zeros are required if needed as described more fully in HAP-R2 Section 6.6.1 From 0be3b0cd1b4a768e9a110084b98c14b3769161e4 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 21 Apr 2024 21:23:44 -0500 Subject: [PATCH 58/74] Update Reference.md --- docs/Reference.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference.md b/docs/Reference.md index 0ec35cf..1bf1bb2 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -478,7 +478,7 @@ This is a **base class** from which all HomeSpan Characteristics are derived, an * similar to `setVal()`, but exclusively used for TLV8 Characteristics * updates the Characteristic by packing the TLV8 structure *tlv* into a byte array and then encoding it in base-64 for storage as a string -* see the [TLV8 Characteristics](TLV8.md) page for complete details on how to read/process/create TLV8 Characteristics +* see the [TLV8 Characteristics](TLV8.md) page for complete details on how to read/process/create TLV8 Objects using HomeSpan's TLV8 Library. #### The following methods are supported for all Characteristics: From c7f67225d6276c96dcf03b04c6f46ef476dd9f7f Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 21 Apr 2024 22:08:04 -0500 Subject: [PATCH 59/74] Update TLV8.md --- docs/TLV8.md | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/docs/TLV8.md b/docs/TLV8.md index 3a9508a..de79755 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -274,17 +274,35 @@ it_E TAG=200, LENGTH=32, Sum of all bytes = 496 ## Reading and Writing TLV8 Characteristics -As documented in the [API Reference](Reference.md), the following *SpanCharacteristic* methods are used to read and write TLV8 objects to TLV8 Characteristics: +As fully documented in the [API Reference](Reference.md), the following *SpanCharacteristic* methods are used to read and write TLV8 objects to TLV8 Characteristics: -* `getVal(TLV8 &tlv)` -* `getNewVal(TLV8 &tlv)` -* `setVal(TLV8 &tlv)` +* `getTLV(TLV8 &tlv)` +* `getNewTLV(TLV8 &tlv)` +* `setTLV(TLV8 &tlv)` -These are analagous to the `getVal()` and `setVal()` methods used for numerical-based Characteristics. +These are analagous to the `getVal()`, `getNewVal()` and `setVal()` methods used for numerical-based Characteristics. -Note that since TLV8 Characteristics are stored as base-64 encoded strings, you can use `getString()` to read the base-64 text, or `getData()` to decode the string into the full byte-array that represents the entire TLV8 object. +Note that using the above methods *do not* require you to create a separate byte-array that splits records into chunks of 255 bytes, nor does it require you to encode or decode anything into base-64. Rather, you directly read and write to and from the Characteristic into a TLV8 object. -Also, if you really don't want to use HomeSpan's TLV8 library to produce TLV8 objects, but instead prefer to use your own methods to create a TLV8-compliant byte-array, you can do so and then use `setData()` to save the byte-array you produced to the TLV8 Characteristic, which will perform the base-64 encoding. Or if you want to additionally perform your own base-64 encoding (why?) you can do so and then simply use `setString()` to save the resulting encoded string to the TLV8 Characteristic. +However, since TLV8 Characteristics are stored as base-64 encoded strings, you can nevertheless use `getString()` to read the base-64 text, or `getData()` to decode the string into the full byte-array that represents the entire TLV8 object, if you desire. + +Also, if you really don't want to use HomeSpan's TLV8 library to produce TLV8 objects, but instead prefer to use your own methods to create a TLV8-compliant byte-array, you can do so and then use `setData()` to save the byte-array you produced to the TLV8 Characteristic, which will perform the base-64 encoding for you. Or, if you want to additionally perform your own base-64 encoding (why?), you can do so and then simply use `setString()` to save the resulting encoded text to the TLV8 Characteristic. + +### Write-Response Requests + +For most Characteristics, when the Home App sends HomeSpan a request to update a value, it is instructing HomeSpan to perform some sort of action, such as "change the brightness of a lightbulb to 30%" or "change the target state of the door to open." The only feedback the Home App expects to receive in response to such requests is basically an "OK" or "NOT OKAY" message, which is the purpose of the boolean return value in the `update()` method for every Service. + +However, sometimes the Home App sends HomeSpan a request for information, rather than a direct instruction to perform a task. In such instances, rather than sending back just an OK/NOT-OKAY message, the Home App expects the Accessory device to update the value of the Characteristic *not* with the new value that the Home App sent, but rather with the information it requested. It then expects this information to be transmitted back to the Home App at the conclusion of the update. + +This procedure is known as a "Write-Response Request", and it is the primary purpose for having TLV8 Characteristics, since TLV8 objects are ideal for storing structured information. + +Though the procedure is complex, HomeSpan fortunately handles all of the protocol details. The only thing you need to do when working with a TLV8 Characteristic that implements Write-Response Requests is: + +* check to see if the Characteristic is updated when inside the `update()` loop of a Service; +* if so, create a new TLV8 object and use `getNewTLV()` to load the contents of the updated Characterstic into that TLV8 object; +* use the TLV8 Library to read through the TAGS and VALUES in the TLV8 to determine (based on the specs for the Characteristic) what data the Home App is conveying and what information it wants returned; +* create a second TLV8 object and add the appropriate TAG and VALUES needed to respond to the information request (again, based on the on the specs for the Characteristic); +* use `setVal()` to update the Characteristic with the second TLV8 object --- From 20f74aa968f837d6f17a104a69c2796b8f1d013a Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Mon, 22 Apr 2024 20:43:11 -0500 Subject: [PATCH 60/74] Update TLV8.md --- docs/TLV8.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/TLV8.md b/docs/TLV8.md index de79755..85e853c 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -294,15 +294,17 @@ For most Characteristics, when the Home App sends HomeSpan a request to update a However, sometimes the Home App sends HomeSpan a request for information, rather than a direct instruction to perform a task. In such instances, rather than sending back just an OK/NOT-OKAY message, the Home App expects the Accessory device to update the value of the Characteristic *not* with the new value that the Home App sent, but rather with the information it requested. It then expects this information to be transmitted back to the Home App at the conclusion of the update. -This procedure is known as a "Write-Response Request", and it is the primary purpose for having TLV8 Characteristics, since TLV8 objects are ideal for storing structured information. +This procedure is known as a "Write-Response Request", and it is the primary purpose for having TLV8 Characteristics, since TLV8 objects are ideal for storing structured information. -Though the procedure is complex, HomeSpan fortunately handles all of the protocol details. The only thing you need to do when working with a TLV8 Characteristic that implements Write-Response Requests is: +Though the procedure is complex, HomeSpan handles all of the protocol details. You only need to focus on reading the TLV8 Characteristic and updating it with the required TLV8 response as follows: -* check to see if the Characteristic is updated when inside the `update()` loop of a Service; -* if so, create a new TLV8 object and use `getNewTLV()` to load the contents of the updated Characterstic into that TLV8 object; -* use the TLV8 Library to read through the TAGS and VALUES in the TLV8 to determine (based on the specs for the Characteristic) what data the Home App is conveying and what information it wants returned; -* create a second TLV8 object and add the appropriate TAG and VALUES needed to respond to the information request (again, based on the on the specs for the Characteristic); -* use `setVal()` to update the Characteristic with the second TLV8 object +* first, from within the `update()` loop of the applicable Service, check to see if the Home App has requested an update to the TLV8 Characteristic; +* if so, create a new TLV8 object and use `getNewTLV()` to load the contents of the updated Characteristic into that TLV8 object; +* then, use the TLV8 library methods described above to read through the TAGS and VALUES in the TLV8 object to determine what data the Home App is conveying and what information it wants returned (based on the specs for the Characteristic); +* next, create a *second* TLV8 object and use the TLV8 library methods above to create the appropriate TAGS and VALUES needed to respond to the information request (again, based on the on the specs for the Characteristic); +* finally, use `setVal()` to update the TLV8 Characteristic with the second TLV8 object + +HomeSpan will automatically send the new TLV8 data you placed in the TLV8 Characterstic back to the Home App in its response at the conclusion of the `update()` loop. --- From 3db4676b7d697fed1519c5616630bc4eac9eadd1 Mon Sep 17 00:00:00 2001 From: Gregg Date: Mon, 22 Apr 2024 22:49:55 -0500 Subject: [PATCH 61/74] Created initial Example 22 - TLV8_Characteristics Requires some debugging... --- .../22-TLV8_Characteristics.ino | 161 ++++++++++++++++++ src/Characteristics.h | 1 + src/Span.h | 2 + src/src.ino | 1 - 4 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 examples/22-TLV8_Characteristics/22-TLV8_Characteristics.ino diff --git a/examples/22-TLV8_Characteristics/22-TLV8_Characteristics.ino b/examples/22-TLV8_Characteristics/22-TLV8_Characteristics.ino new file mode 100644 index 0000000..5f81568 --- /dev/null +++ b/examples/22-TLV8_Characteristics/22-TLV8_Characteristics.ino @@ -0,0 +1,161 @@ +/********************************************************************************* + * MIT License + * + * Copyright (c) 2020-2024 Gregg E. Berman + * + * https://github.com/HomeSpan/HomeSpan + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + ********************************************************************************/ + +//////////////////////////////////////////////////////////////// +// // +// HomeSpan: A HomeKit implementation for the ESP32 // +// ------------------------------------------------ // +// // +// Example 24: Demonstrates the use of the TLV8 Library // +// by implementing DisplayOrder, an optional // +// TLV8 Characteristic used with the TV Service // +// to sets the order in which TV Inputs are // +// displayed for selection in the Home App // +// // +//////////////////////////////////////////////////////////////// + +#include "HomeSpan.h" + +// NOTE: Please see the "Other Examples -> Television" sketch for complete details on how to implement a Television Service. The focus +// of this sketch is solely to demonstrate how to use the TLV8 Library to create TLV8 data for use with the DisplayOrder Characteristic. + +// First we define a simple Television Input Source Service with only the Identifer and Name Characteristics + +struct TVInput : Service::InputSource { + + SpanCharacteristic *inputID; + SpanCharacteristic *inputName; + + TVInput(uint32_t id, const char *name) : Service::InputSource() { + + inputID = new Characteristic::Identifier(id); + inputName = new Characteristic::ConfiguredName(name); + new Characteristic::IsConfigured(1); + } +}; + +// Next we define a very simple Television Service + +struct HomeSpanTV : Service::Television { + + SpanCharacteristic *active = new Characteristic::Active(0,true); // TV ON/OFF (set to OFF at start-up) + SpanCharacteristic *activeID = new Characteristic::ActiveIdentifier(30,true); // Set TV to input source with ID=30 + + // SpanCharacteristic *displayOrder = new Characteristic::DisplayOrder(); // <-- This is the new TLV8 Characteristic. Note the constructor has no argument + + HomeSpanTV() : Service::Television() { + + // Unlike the constructors for numerical and string-based Characteristics (such as ActiveIdentifier and ConfiguredName), + // we cannot set the initial value of TLV8 Characteristics during construction, but must instead first instantiate the + // Characteristic (as we did above), then build a TLV8 object with the information required by the TLV8 Characteristic, and + // then use setTLV() to load the completed TLV8 object into the Characteristic's value. + + // The (undocumented by Apple!) TLV8 specifications for the DisplayOrder Characteristic are as follows: + + // TAG NAME FORMAT DESCRIPTION + // ---- ------------- ------ -------------------------------------------- + // 0x01 inputSourceID uint32 ID of the Input Source to be displayed first + // 0x00 separator none Empty element to separate the inputSourceIDs + // 0x01 inputSourceID uint32 ID of the Input Source to be displayed second + // 0x00 separator none Empty element to separate the inputSourceIDs + // 0x01 inputSourceID uint32 ID of the Input Source to be displayed third + // 0x00 separator none Empty element to separate the inputSourceIDs + // etc... + + // To start, instantiate a new TLV8 object + + TLV8 orderTLV; // creates an empty TLV8 object + + // Next, fill it with TAGS and VALUES based on the above specification. The easiest, + // though not necessarily most elegant, way to do this is as follows: + + orderTLV.add(1,10); // TAG=1, VALUE=ID of first Input Source to be displayed + orderTLV.add(0); // TAG=0 (no value) + orderTLV.add(1,20); // TAG=1, VALUE=ID of the second Input Source to be displayed + orderTLV.add(0); // TAG=0 (no value) + orderTLV.add(1,50); // TAG=1, VALUE=ID of the third Input Source to be displayed + orderTLV.add(0); // TAG=0 (no value) + orderTLV.add(1,30); // TAG=1, VALUE=ID of the fourth Input Source to be displayed + orderTLV.add(0); // TAG=0 (no value) + orderTLV.add(1,40); // TAG=1, VALUE=ID of the fifth Input Source to be displayed + + // Based on the above structure, we expect the Home App to display our input sources based on their IDs + // in the following order: 100, 200, 500, 300, 400. These IDs must of course match the IDs you choose + // for your input sources when you create them at the end of this sketch in setup() + + // The final step is to load this TLV8 object into the DisplayOrder Characteristic + + // displayOrder->setTLV(orderTLV); // set the "value" of DisplayOrder to be the orderTLV object we just created + + // That's it - you've created your first TLV8 Characteristic! + } + + // Below we define the usual update() loop. There is nothing "TLV-specific" about this part of the code + + boolean update() override { + + if(active->updated()){ + Serial.printf("Set TV Power to: %s\n",active->getNewVal()?"ON":"OFF"); + } + + if(activeID->updated()){ + Serial.printf("Set Input Source to ID=%d\n",activeID->getNewVal()); + } + + return(true); + } +}; + +/////////////////////////////// + +void setup() { + + Serial.begin(115200); + + homeSpan.setLogLevel(2); + + homeSpan.begin(Category::Television,"HomeSpan Television"); + + SPAN_ACCESSORY(); + + (new HomeSpanTV()) // Define a Television Service and link in the InputSources! + ->addLink(new TVInput(10,"Xfinity")) + ->addLink(new TVInput(20,"BlueRay Disc")) + ->addLink(new TVInput(30,"Amazon Prime")) + ->addLink(new TVInput(40,"Netflix")) + ->addLink(new TVInput(50,"Hulu")) + ; + +} + +////////////////////////////////////// + +void loop(){ + homeSpan.poll(); +} + +////////////////////////////////////// diff --git a/src/Characteristics.h b/src/Characteristics.h index 679ea9b..52d43aa 100644 --- a/src/Characteristics.h +++ b/src/Characteristics.h @@ -114,6 +114,7 @@ struct HapCharacteristics { HAPCHAR( CurrentTemperature, 11, PR+EV, FLOAT, false ); HAPCHAR( CurrentTiltAngle, C1, PR+EV, INT, false ); HAPCHAR( CurrentVisibilityState, 135, PR+EV, UINT8, true ); + HAPCHAR( DisplayOrder, 136, PR+EV, TLV_ENC, true ); HAPCHAR( FilterLifeLevel, AB, PR+EV, FLOAT, false ); HAPCHAR( FilterChangeIndication, AC, PR+EV, UINT8, true ); HAPCHAR( FirmwareRevision, 52, PR+EV, STRING, true ); diff --git a/src/Span.h b/src/Span.h index e3893a7..624f0f7 100644 --- a/src/Span.h +++ b/src/Span.h @@ -429,6 +429,7 @@ namespace Service { CREATE_SERV(Television,D8) // Defines a TV. Optional Linked Services: InputSource and TelevisionSpeaker. REQ(Active); OPT(ActiveIdentifier); + OPT(DisplayOrder); OPT(RemoteKey); OPT(PowerModeSelection); OPT(ConfiguredName); @@ -513,6 +514,7 @@ namespace Characteristic { CREATE_CHAR(double,CurrentRelativeHumidity,0,0,100); // current humidity measured as a percentage CREATE_CHAR(double,CurrentTemperature,0,0,100); // current temperature measured in Celsius CREATE_CHAR(int,CurrentTiltAngle,0,-90,90); // current angle (in degrees) of slats from fully up or left (-90) to fully open (0) to fully down or right (90) + CREATE_CHAR(const char *,DisplayOrder,"",0,1); // specifies the order in which the TV inputs are displayed for selection in the Home App CREATE_CHAR(double,FilterLifeLevel,100,0,100); // measured as a percentage of remaining life CREATE_CHAR(uint8_t,FilterChangeIndication,0,0,1,NO_CHANGE_NEEDED,CHANGE_NEEDED); // indicates state of filter CREATE_CHAR(const char *,FirmwareRevision,"1.0.0",0,1); // must be in form x[.y[.z]] - informational only diff --git a/src/src.ino b/src/src.ino index bfc4942..44be4ec 100644 --- a/src/src.ino +++ b/src/src.ino @@ -27,7 +27,6 @@ #include "HomeSpan.h" -CUSTOM_CHAR_TLV8(DisplayOrder,136,PR+EV); CUSTOM_CHAR_DATA(TestData,333,PR+EV); struct HomeSpanTV : Service::Television { From 3273f7f24d43824a5aae3ac66a4873d33c830f63 Mon Sep 17 00:00:00 2001 From: Gregg Date: Tue, 23 Apr 2024 06:41:44 -0500 Subject: [PATCH 62/74] Completed Example 22-TLV8_Characteristics Must update Television.md documentation as well as Television Example to reflect recent changes to the Characteristics Apple made - there seems to be less flexibility in what needs to be defined to use the input sources. Also need to add DisplayOrder Characteristic to TV documentation. --- .../22-TLV8_Characteristics.ino | 113 +++++++++--------- 1 file changed, 57 insertions(+), 56 deletions(-) diff --git a/examples/22-TLV8_Characteristics/22-TLV8_Characteristics.ino b/examples/22-TLV8_Characteristics/22-TLV8_Characteristics.ino index 5f81568..43b1073 100644 --- a/examples/22-TLV8_Characteristics/22-TLV8_Characteristics.ino +++ b/examples/22-TLV8_Characteristics/22-TLV8_Characteristics.ino @@ -33,7 +33,7 @@ // Example 24: Demonstrates the use of the TLV8 Library // // by implementing DisplayOrder, an optional // // TLV8 Characteristic used with the TV Service // -// to sets the order in which TV Inputs are // +// to set the order in which TV Inputs are // // displayed for selection in the Home App // // // //////////////////////////////////////////////////////////////// @@ -43,7 +43,7 @@ // NOTE: Please see the "Other Examples -> Television" sketch for complete details on how to implement a Television Service. The focus // of this sketch is solely to demonstrate how to use the TLV8 Library to create TLV8 data for use with the DisplayOrder Characteristic. -// First we define a simple Television Input Source Service with only the Identifer and Name Characteristics +// First we define a simple Television Input Source Service struct TVInput : Service::InputSource { @@ -54,80 +54,81 @@ struct TVInput : Service::InputSource { inputID = new Characteristic::Identifier(id); inputName = new Characteristic::ConfiguredName(name); - new Characteristic::IsConfigured(1); + new Characteristic::IsConfigured(Characteristic::IsConfigured::CONFIGURED); + new Characteristic::CurrentVisibilityState(Characteristic::CurrentVisibilityState::VISIBLE); } }; -// Next we define a very simple Television Service +// Next we define a simple Television Service struct HomeSpanTV : Service::Television { - SpanCharacteristic *active = new Characteristic::Active(0,true); // TV ON/OFF (set to OFF at start-up) - SpanCharacteristic *activeID = new Characteristic::ActiveIdentifier(30,true); // Set TV to input source with ID=30 - - // SpanCharacteristic *displayOrder = new Characteristic::DisplayOrder(); // <-- This is the new TLV8 Characteristic. Note the constructor has no argument + SpanCharacteristic *active = new Characteristic::Active(0); + SpanCharacteristic *activeID = new Characteristic::ActiveIdentifier(10); + + SpanCharacteristic *displayOrder = new Characteristic::DisplayOrder(); // <-- This is the new TLV8 Characteristic. Note the constructor has no argument - HomeSpanTV() : Service::Television() { + HomeSpanTV() : Service::Television() { - // Unlike the constructors for numerical and string-based Characteristics (such as ActiveIdentifier and ConfiguredName), - // we cannot set the initial value of TLV8 Characteristics during construction, but must instead first instantiate the - // Characteristic (as we did above), then build a TLV8 object with the information required by the TLV8 Characteristic, and - // then use setTLV() to load the completed TLV8 object into the Characteristic's value. + // Unlike the constructors for numerical and string-based Characteristics (such as ActiveIdentifier and ConfiguredName), + // we cannot set the initial value of TLV8 Characteristics during construction, but must instead first instantiate the + // Characteristic (as we did above), then build a TLV8 object with the information required by the TLV8 Characteristic, and + // then use setTLV() to load the completed TLV8 object into the Characteristic's value. - // The (undocumented by Apple!) TLV8 specifications for the DisplayOrder Characteristic are as follows: + // The (undocumented by Apple!) TLV8 specifications for the DisplayOrder Characteristic are as follows: - // TAG NAME FORMAT DESCRIPTION - // ---- ------------- ------ -------------------------------------------- - // 0x01 inputSourceID uint32 ID of the Input Source to be displayed first - // 0x00 separator none Empty element to separate the inputSourceIDs - // 0x01 inputSourceID uint32 ID of the Input Source to be displayed second - // 0x00 separator none Empty element to separate the inputSourceIDs - // 0x01 inputSourceID uint32 ID of the Input Source to be displayed third - // 0x00 separator none Empty element to separate the inputSourceIDs - // etc... + // TAG NAME FORMAT DESCRIPTION + // ---- ------------- ------ -------------------------------------------- + // 0x01 inputSourceID uint32 ID of the Input Source to be displayed first + // 0x00 separator none Empty element to separate the inputSourceIDs + // 0x01 inputSourceID uint32 ID of the Input Source to be displayed second + // 0x00 separator none Empty element to separate the inputSourceIDs + // 0x01 inputSourceID uint32 ID of the Input Source to be displayed third + // 0x00 separator none Empty element to separate the inputSourceIDs + // etc... - // To start, instantiate a new TLV8 object - - TLV8 orderTLV; // creates an empty TLV8 object + // To start, instantiate a new TLV8 object + + TLV8 orderTLV; // creates an empty TLV8 object - // Next, fill it with TAGS and VALUES based on the above specification. The easiest, - // though not necessarily most elegant, way to do this is as follows: + // Next, fill it with TAGS and VALUES based on the above specification. The easiest, though + // not necessarily most elegant, way to do this is by simply adding each TAG/VALUE as follows: - orderTLV.add(1,10); // TAG=1, VALUE=ID of first Input Source to be displayed - orderTLV.add(0); // TAG=0 (no value) - orderTLV.add(1,20); // TAG=1, VALUE=ID of the second Input Source to be displayed - orderTLV.add(0); // TAG=0 (no value) - orderTLV.add(1,50); // TAG=1, VALUE=ID of the third Input Source to be displayed - orderTLV.add(0); // TAG=0 (no value) - orderTLV.add(1,30); // TAG=1, VALUE=ID of the fourth Input Source to be displayed - orderTLV.add(0); // TAG=0 (no value) - orderTLV.add(1,40); // TAG=1, VALUE=ID of the fifth Input Source to be displayed + orderTLV.add(1,10); // TAG=1, VALUE=ID of first Input Source to be displayed + orderTLV.add(0); // TAG=0 (no value) + orderTLV.add(1,20); // TAG=1, VALUE=ID of the second Input Source to be displayed + orderTLV.add(0); // TAG=0 (no value) + orderTLV.add(1,50); // TAG=1, VALUE=ID of the third Input Source to be displayed + orderTLV.add(0); // TAG=0 (no value) + orderTLV.add(1,30); // TAG=1, VALUE=ID of the fourth Input Source to be displayed + orderTLV.add(0); // TAG=0 (no value) + orderTLV.add(1,40); // TAG=1, VALUE=ID of the fifth Input Source to be displayed - // Based on the above structure, we expect the Home App to display our input sources based on their IDs - // in the following order: 100, 200, 500, 300, 400. These IDs must of course match the IDs you choose - // for your input sources when you create them at the end of this sketch in setup() + // Based on the above structure, we expect the Home App to display our input sources based on their IDs + // in the following order: 10, 20, 50, 30, 40. These IDs must of course match the IDs you choose + // for your input sources when you create them at the end of this sketch in setup() - // The final step is to load this TLV8 object into the DisplayOrder Characteristic + // The final step is to load this TLV8 object into the DisplayOrder Characteristic - // displayOrder->setTLV(orderTLV); // set the "value" of DisplayOrder to be the orderTLV object we just created + displayOrder->setTLV(orderTLV); // set the "value" of DisplayOrder to be the orderTLV object we just created - // That's it - you've created your first TLV8 Characteristic! + // That's it - you've created your first TLV8 Characteristic! + } + + // Below we define the usual update() loop. There is nothing "TLV-specific" about this part of the code + + boolean update() override { + + if(active->updated()){ + LOG0("Set TV Power to: %s\n",active->getNewVal()?"ON":"OFF"); } - // Below we define the usual update() loop. There is nothing "TLV-specific" about this part of the code - - boolean update() override { + if(activeID->updated()){ + LOG0("Set Input Source to ID=%d\n",activeID->getNewVal()); + } - if(active->updated()){ - Serial.printf("Set TV Power to: %s\n",active->getNewVal()?"ON":"OFF"); - } - - if(activeID->updated()){ - Serial.printf("Set Input Source to ID=%d\n",activeID->getNewVal()); - } - - return(true); - } + return(true); + } }; /////////////////////////////// From a92f73666a8c8884f2a400494d69d5f07b48fd3b Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 27 Apr 2024 10:40:34 -0500 Subject: [PATCH 63/74] Update TLV8.md --- docs/TLV8.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/TLV8.md b/docs/TLV8.md index 85e853c..d02a1d2 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -282,11 +282,11 @@ As fully documented in the [API Reference](Reference.md), the following *SpanCha These are analagous to the `getVal()`, `getNewVal()` and `setVal()` methods used for numerical-based Characteristics. -Note that using the above methods *do not* require you to create a separate byte-array that splits records into chunks of 255 bytes, nor does it require you to encode or decode anything into base-64. Rather, you directly read and write to and from the Characteristic into a TLV8 object. +Note that using the above methods *do not* require you to create a separate byte-array that splits records into chunks of 255 bytes, nor does it require you to encode or decode anything into base-64. Rather, you directly read and write to and from the Characteristic into a TLV8 object.[^getString] -However, since TLV8 Characteristics are stored as base-64 encoded strings, you can nevertheless use `getString()` to read the base-64 text, or `getData()` to decode the string into the full byte-array that represents the entire TLV8 object, if you desire. +For a detailed example of how TLV8 Characteristics are used in practice, see [Tutorial Example 22-TL8_Characteristics](../examples/22-TL8_Characteristics) demonstrating how the **DisplayOrder** TLV8 Charactersitic can be used to set the order in which Input Sources for a TV Service are displayed in the Home App. -Also, if you really don't want to use HomeSpan's TLV8 library to produce TLV8 objects, but instead prefer to use your own methods to create a TLV8-compliant byte-array, you can do so and then use `setData()` to save the byte-array you produced to the TLV8 Characteristic, which will perform the base-64 encoding for you. Or, if you want to additionally perform your own base-64 encoding (why?), you can do so and then simply use `setString()` to save the resulting encoded text to the TLV8 Characteristic. +[^getString]:Since TLV8 Characteristics are stored as base-64 encoded strings, you can always use `getString()` to read the base-64 text, or `getData()` to decode the string into the full byte-array that represents the entire TLV8 object, if you desire. Also, if you really don't want to use HomeSpan's TLV8 library to produce TLV8 objects, but instead prefer to use your own methods to create a TLV8-compliant byte-array, you can do so and then use `setData()` to save the byte-array you produced to the TLV8 Characteristic, which will perform the base-64 encoding for you. Or, if you want to additionally perform your own base-64 encoding (why?), you can do so and then simply use `setString()` to save the resulting encoded text to the TLV8 Characteristic. ### Write-Response Requests From d2d1b79c4d192ee94e191b37fdcd0ac9f47e92d7 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 27 Apr 2024 10:44:30 -0500 Subject: [PATCH 64/74] Update TLV8.md --- docs/TLV8.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TLV8.md b/docs/TLV8.md index d02a1d2..b085178 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -284,7 +284,7 @@ These are analagous to the `getVal()`, `getNewVal()` and `setVal()` methods used Note that using the above methods *do not* require you to create a separate byte-array that splits records into chunks of 255 bytes, nor does it require you to encode or decode anything into base-64. Rather, you directly read and write to and from the Characteristic into a TLV8 object.[^getString] -For a detailed example of how TLV8 Characteristics are used in practice, see [Tutorial Example 22-TL8_Characteristics](../examples/22-TL8_Characteristics) demonstrating how the **DisplayOrder** TLV8 Charactersitic can be used to set the order in which Input Sources for a TV Service are displayed in the Home App. +For a detailed example of how TLV8 Characteristics are used in practice, see [Tutorial Example 22-TL8_Characteristics](../examples/22-TLV8_Characteristics) demonstrating how the **DisplayOrder** TLV8 Charactersitic can be used to set the order in which Input Sources for a TV Service are displayed in the Home App. [^getString]:Since TLV8 Characteristics are stored as base-64 encoded strings, you can always use `getString()` to read the base-64 text, or `getData()` to decode the string into the full byte-array that represents the entire TLV8 object, if you desire. Also, if you really don't want to use HomeSpan's TLV8 library to produce TLV8 objects, but instead prefer to use your own methods to create a TLV8-compliant byte-array, you can do so and then use `setData()` to save the byte-array you produced to the TLV8 Characteristic, which will perform the base-64 encoding for you. Or, if you want to additionally perform your own base-64 encoding (why?), you can do so and then simply use `setString()` to save the resulting encoded text to the TLV8 Characteristic. From 0eec3920f41de28ded6472f5200b1d2c75ff99b1 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 27 Apr 2024 10:45:18 -0500 Subject: [PATCH 65/74] Update TLV8.md --- docs/TLV8.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TLV8.md b/docs/TLV8.md index b085178..f87ae52 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -284,7 +284,7 @@ These are analagous to the `getVal()`, `getNewVal()` and `setVal()` methods used Note that using the above methods *do not* require you to create a separate byte-array that splits records into chunks of 255 bytes, nor does it require you to encode or decode anything into base-64. Rather, you directly read and write to and from the Characteristic into a TLV8 object.[^getString] -For a detailed example of how TLV8 Characteristics are used in practice, see [Tutorial Example 22-TL8_Characteristics](../examples/22-TLV8_Characteristics) demonstrating how the **DisplayOrder** TLV8 Charactersitic can be used to set the order in which Input Sources for a TV Service are displayed in the Home App. +For a detailed example of how TLV8 Characteristics are used in practice, see [Tutorial Example 22-TLV8_Characteristics](../examples/22-TLV8_Characteristics) demonstrating how the **DisplayOrder** TLV8 Charactersitic can be used to set the order in which Input Sources for a TV Service are displayed in the Home App. [^getString]:Since TLV8 Characteristics are stored as base-64 encoded strings, you can always use `getString()` to read the base-64 text, or `getData()` to decode the string into the full byte-array that represents the entire TLV8 object, if you desire. Also, if you really don't want to use HomeSpan's TLV8 library to produce TLV8 objects, but instead prefer to use your own methods to create a TLV8-compliant byte-array, you can do so and then use `setData()` to save the byte-array you produced to the TLV8 Characteristic, which will perform the base-64 encoding for you. Or, if you want to additionally perform your own base-64 encoding (why?), you can do so and then simply use `setString()` to save the resulting encoded text to the TLV8 Characteristic. From 012c778f150e32abdb0c5057b7658c4de74ece2a Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 27 Apr 2024 10:47:52 -0500 Subject: [PATCH 66/74] Update Tutorials.md --- docs/Tutorials.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/Tutorials.md b/docs/Tutorials.md index 5bb1888..28c3b2d 100644 --- a/docs/Tutorials.md +++ b/docs/Tutorials.md @@ -113,6 +113,10 @@ Example 20 illustrates a number of advanced techniques through the implementatio ### [Example 21 - AccessoryIdentifier](../examples/21-AccessoryIdentifier) Example 21 shows how the Identifier Characteristic that is always present in each Accessory's required AccessoryInformation Service can be used to create a custom "identification routine" that can be triggered from within the Home App when pairing a device. This example does not use any new HomeSpan methods. + +### [Example 22-TLV8_Characteristics](../examples/22-TLV8_Characteristics) +Example 22 demonstrates, through the implementation of the DisplayOrder Characteristic used in conjunction with the InputSource and Television Services... + ## Other Examples From 7a796bac2fc45bb0402d657e3fd112a315e5a8f5 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 27 Apr 2024 16:13:22 -0500 Subject: [PATCH 67/74] Update Tutorials.md --- docs/Tutorials.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/Tutorials.md b/docs/Tutorials.md index 28c3b2d..732c9ae 100644 --- a/docs/Tutorials.md +++ b/docs/Tutorials.md @@ -73,7 +73,6 @@ Example 13 demonstrates the simultaneous use of both the `update()` and `loop()` * using Enumerated Constants to set the values of Characteristics that represent discrete states (e.g. "raising", "closing") - ### [Example 14 - EmulatedPushButtons](../examples/14-EmulatedPushButtons) Example 14 demonstrates how you can use the `setVal()` and `timeVal()` methods inside a Service's `loop()` method to create a tile in the Home App that emulates a pushbutton switch. In this example pressing the tile in the Home App will cause it to turn on, blink an LED 3 times, and then turn off (just like a real pushbutton might do). @@ -114,8 +113,11 @@ Example 20 illustrates a number of advanced techniques through the implementatio ### [Example 21 - AccessoryIdentifier](../examples/21-AccessoryIdentifier) Example 21 shows how the Identifier Characteristic that is always present in each Accessory's required AccessoryInformation Service can be used to create a custom "identification routine" that can be triggered from within the Home App when pairing a device. This example does not use any new HomeSpan methods. -### [Example 22-TLV8_Characteristics](../examples/22-TLV8_Characteristics) -Example 22 demonstrates, through the implementation of the DisplayOrder Characteristic used in conjunction with the InputSource and Television Services... +### [Example 22 - TLV8 Characteristics](../examples/22-TLV8_Characteristics) +Example 22 demonstrates how to create and utilize TLV8-based Characteristics through the implementation of the DisplayOrder Characteristic used to set the order in which input sources for a Television Service are presented in the Home App. New HomeSpan API topics covered in this example include: + +* creating TLV8 objects using HomeSpan's TLV8 class +* updating TLV8 Characteristics using `setTLV()` ## Other Examples From ee2e850505c73c37382ea17406ce46e57349bdfe Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sat, 27 Apr 2024 16:14:24 -0500 Subject: [PATCH 68/74] Update TLV8.md --- docs/TLV8.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TLV8.md b/docs/TLV8.md index f87ae52..f750233 100644 --- a/docs/TLV8.md +++ b/docs/TLV8.md @@ -284,7 +284,7 @@ These are analagous to the `getVal()`, `getNewVal()` and `setVal()` methods used Note that using the above methods *do not* require you to create a separate byte-array that splits records into chunks of 255 bytes, nor does it require you to encode or decode anything into base-64. Rather, you directly read and write to and from the Characteristic into a TLV8 object.[^getString] -For a detailed example of how TLV8 Characteristics are used in practice, see [Tutorial Example 22-TLV8_Characteristics](../examples/22-TLV8_Characteristics) demonstrating how the **DisplayOrder** TLV8 Charactersitic can be used to set the order in which Input Sources for a TV Service are displayed in the Home App. +For a detailed example of how TLV8 Characteristics are used in practice, see [Tutorial Example 22 - TLV8_Characteristics](../examples/22-TLV8_Characteristics) demonstrating how the **DisplayOrder** TLV8 Charactersitic can be used to set the order in which Input Sources for a TV Service are displayed in the Home App. [^getString]:Since TLV8 Characteristics are stored as base-64 encoded strings, you can always use `getString()` to read the base-64 text, or `getData()` to decode the string into the full byte-array that represents the entire TLV8 object, if you desire. Also, if you really don't want to use HomeSpan's TLV8 library to produce TLV8 objects, but instead prefer to use your own methods to create a TLV8-compliant byte-array, you can do so and then use `setData()` to save the byte-array you produced to the TLV8 Characteristic, which will perform the base-64 encoding for you. Or, if you want to additionally perform your own base-64 encoding (why?), you can do so and then simply use `setString()` to save the resulting encoded text to the TLV8 Characteristic. From 74d27485d53e5cd76213adaad11c956f080ac4c4 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 28 Apr 2024 08:15:04 -0500 Subject: [PATCH 69/74] Added homeSpan.setControllerCallback() Also adds: homeSpan.controllerListBegin(), homeSpan.controllerListEnd() Also adds: Controller::isAdmin(), Controller::getID(), Controller::getLTPK() To accomplish this, needed to move Controller definition from HAP.h to HomeSpan.h Also, converted Controller from struct to class to ensure Controller data is protected from being modified now that it is exposed through condstant iterators. --- src/HAP.cpp | 9 +- src/HAP.h | 26 +---- src/HomeSpan.cpp | 18 +++- src/HomeSpan.h | 35 +++++++ src/src.ino | 242 +++++------------------------------------------ 5 files changed, 84 insertions(+), 246 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index 417fbcd..e5d6d07 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -1345,7 +1345,7 @@ int HAPClient::receiveEncrypted(uint8_t *httpBuf, int messageSize){ ///////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////// -void HAPClient::hexPrintColumn(uint8_t *buf, int n, int minLogLevel){ +void HAPClient::hexPrintColumn(const uint8_t *buf, int n, int minLogLevel){ if(homeSpan.logLevelcPair){ LOG0(" ID="); - HAPClient::charPrintRow(hap[i]->cPair->ID,36); - LOG0(hap[i]->cPair->admin?" (admin)":" (regular)"); + HAPClient::charPrintRow(hap[i]->cPair->getID(),36); + LOG0(hap[i]->cPair->isAdmin()?" (admin)":" (regular)"); } else { LOG0(" (unverified)"); } @@ -1092,7 +1092,7 @@ void Span::processSerialCommand(const char *c){ reboot(); } else { HAPClient::controllerList.push_back(tCont); - HAPClient::charPrintRow(tCont.ID,36); + HAPClient::charPrintRow(tCont.getID(),36); LOG0("\n"); } } @@ -1658,6 +1658,18 @@ boolean Span::updateDatabase(boolean updateMDNS){ return(changed); } +/////////////////////////////// + +list>::const_iterator Span::controllerListBegin(){ + return(HAPClient::controllerList.cbegin()); +} + +/////////////////////////////// + +list>::const_iterator Span::controllerListEnd(){ + return(HAPClient::controllerList.cend()); +} + /////////////////////////////// // SpanAccessory // /////////////////////////////// diff --git a/src/HomeSpan.h b/src/HomeSpan.h index be024b8..d5b656b 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -113,6 +113,7 @@ struct SpanRange; struct SpanBuf; struct SpanButton; struct SpanUserCommand; +class Controller; extern Span homeSpan; @@ -189,6 +190,35 @@ 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 // ////////////////////////////////////// @@ -250,6 +280,7 @@ class Span{ void (*apFunction)()=NULL; // optional function to invoke when starting Access Point void (*statusCallback)(HS_STATUS status)=NULL; // optional callback when HomeSpan status changes void (*rebootCallback)(uint8_t)=NULL; // optional callback when device reboots + void (*controllerCallback)()=NULL; // optional callback when Controller is added/removed/changed WiFiServer *hapServer; // pointer to the HAP Server connection Blinker *statusLED; // indicates HomeSpan status @@ -355,6 +386,7 @@ class Span{ Span& setPairingCode(const char *s, boolean progCall=true); // sets the Pairing Code - use is NOT recommended. Use 'S' from CLI instead void deleteStoredValues(){processSerialCommand("V");} // deletes stored Characteristic values from NVS Span& resetIID(uint32_t newIID); // resets the IID count for the current Accessory to start at newIID + Span& setControllerCallback(void (*f)()){controllerCallback=f;return(*this);} // sets an optional user-defined function to call whenever a Controller is added/removed/changed int enableOTA(boolean auth=true, boolean safeLoad=true){return(spanOTA.init(auth, safeLoad, NULL));} // enables Over-the-Air updates, with (auth=true) or without (auth=false) authorization password int enableOTA(const char *pwd, boolean safeLoad=true){return(spanOTA.init(true, safeLoad, pwd));} // enables Over-the-Air updates, with custom authorization password (overrides any password stored with the 'O' command) @@ -393,6 +425,9 @@ class Span{ TaskHandle_t getAutoPollTask(){return(pollTaskHandle);} 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 diff --git a/src/src.ino b/src/src.ino index 44be4ec..13e2f91 100644 --- a/src/src.ino +++ b/src/src.ino @@ -27,227 +27,21 @@ #include "HomeSpan.h" -CUSTOM_CHAR_DATA(TestData,333,PR+EV); - -struct HomeSpanTV : Service::Television { - - SpanCharacteristic *active = new Characteristic::Active(0); // TV On/Off (set to Off at start-up) - SpanCharacteristic *activeID = new Characteristic::ActiveIdentifier(3); // Sets HDMI 3 on start-up - SpanCharacteristic *remoteKey = new Characteristic::RemoteKey(); // Used to receive button presses from the Remote Control widget - SpanCharacteristic *settingsKey = new Characteristic::PowerModeSelection(); // Adds "View TV Setting" option to Selection Screen - SpanCharacteristic *displayOrder = new Characteristic::DisplayOrder(); - SpanCharacteristic *testData = new Characteristic::TestData(); - SpanCharacteristic *tvname; - - HomeSpanTV(const char *name) : Service::Television() { - tvname = new Characteristic::ConfiguredName(name); // Name of TV - Serial.printf("Configured TV: %s\n",name); - - TLV8 orderTLV; - uint32_t order[]={5,10,6,2,1,9,11,3,18,12}; - - for(int i=0;i0) - orderTLV.add(6); - orderTLV.add(1,sizeof(uint32_t),(uint8_t*)(order+i)); - } - - orderTLV.print(); - displayOrder->setTLV(orderTLV); - - uint8_t blob[]={1,2,3,4,5,6,7,8,9,10,11,12}; -// testData->setData(blob,sizeof(blob)); - testData->setData(blob,1); - - new SpanUserCommand('P', "- change order of inputs", changeOrder, this); - new SpanUserCommand('C', "- change name of TV", setTVName, this); - } - - boolean update() override { - - if(active->updated()){ - Serial.printf("Set TV Power to: %s\n",active->getNewVal()?"ON":"OFF"); - } - - if(activeID->updated()){ - Serial.printf("Set Input Source to HDMI-%d\n",activeID->getNewVal()); - } - - if(settingsKey->updated()){ - Serial.printf("Received request to \"View TV Settings\"\n"); - } - - if(remoteKey->updated()){ - Serial.printf("Remote Control key pressed: "); - switch(remoteKey->getNewVal()){ - case 4: - Serial.printf("UP ARROW\n"); - break; - case 5: - Serial.printf("DOWN ARROW\n"); - break; - case 6: - Serial.printf("LEFT ARROW\n"); - break; - case 7: - Serial.printf("RIGHT ARROW\n"); - break; - case 8: - Serial.printf("SELECT\n"); - break; - case 9: - Serial.printf("BACK\n"); - break; - case 11: - Serial.printf("PLAY/PAUSE\n"); - break; - case 15: - Serial.printf("INFO\n"); - break; - default: - Serial.print("UNKNOWN KEY\n"); - } - } - - return(true); - } - - static void setTVName(const char *buf, void *arg){ - HomeSpanTV *hsTV=(HomeSpanTV *)arg; - hsTV->tvname->setString("New Name"); - Serial.printf("Reset TV Name to '%s'\n",hsTV->tvname->getString()); - Serial.printf("Showing displayOrder '%s'\n",hsTV->displayOrder->getString()); - } - - static void changeOrder(const char *buf, void *arg){ - HomeSpanTV *hsTV=(HomeSpanTV *)arg; - - TLV8 orderTLV; - - hsTV->displayOrder->getTLV(orderTLV); - orderTLV.print(); - orderTLV.wipe(); - - uint8_t order[]={12,10,6,2,1,9,11,3,18,5}; - - for(int i=0;i0) - orderTLV.add(0); - orderTLV.add(1,sizeof(uint8_t),(uint8_t*)(order+i)); - } - - Serial.printf("AFTER:\n"); - orderTLV.print(); - size_t n=orderTLV.pack_size(); - Serial.printf("Size=%d\n",n); - uint8_t c[n]; - orderTLV.pack(c); - hsTV->displayOrder->setData(c,n); - } - -}; - -/////////////////////////////// - void setup() { - + Serial.begin(115200); - homeSpan.setLogLevel(2); - - homeSpan.begin(Category::Television,"HomeSpan Television"); + homeSpan.begin(Category::Lighting,"HomeSpan Light"); + + new SpanAccessory(); + new Service::AccessoryInformation(); + new Characteristic::Identify(); + new Service::LightBulb(); + new Characteristic::On(); - SPAN_ACCESSORY(); - - SpanService *hdmi1 = new Service::InputSource(); // Source included in Selection List, but excluded from Settings Screen - new Characteristic::ConfiguredName("Alpha"); - new Characteristic::Identifier(5); - new Characteristic::IsConfigured(1); - new Characteristic::CurrentVisibilityState(0); - new Characteristic::TargetVisibilityState(0); - - SpanService *hdmi2 = new Service::InputSource(); - new Characteristic::ConfiguredName("Gamma"); - new Characteristic::Identifier(10); - new Characteristic::IsConfigured(1); - new Characteristic::CurrentVisibilityState(0); - new Characteristic::TargetVisibilityState(0); - - SpanService *hdmi3 = new Service::InputSource(); - new Characteristic::ConfiguredName("Beta"); - new Characteristic::Identifier(6); - new Characteristic::IsConfigured(1); - new Characteristic::CurrentVisibilityState(0); - new Characteristic::TargetVisibilityState(0); - - SpanService *hdmi4 = new Service::InputSource(); - new Characteristic::ConfiguredName("Zebra"); - new Characteristic::Identifier(2); - new Characteristic::IsConfigured(1); - new Characteristic::CurrentVisibilityState(0); - new Characteristic::TargetVisibilityState(0); - - SpanService *hdmi5 = new Service::InputSource(); - new Characteristic::ConfiguredName("Delta"); - new Characteristic::Identifier(1); - new Characteristic::IsConfigured(1); - new Characteristic::CurrentVisibilityState(0); - new Characteristic::TargetVisibilityState(0); - - SpanService *hdmi6 = new Service::InputSource(); - new Characteristic::ConfiguredName("Trident"); - new Characteristic::Identifier(9); - new Characteristic::IsConfigured(1); - new Characteristic::CurrentVisibilityState(0); - new Characteristic::TargetVisibilityState(0); - - SpanService *hdmi7 = new Service::InputSource(); - new Characteristic::ConfiguredName("Netflix"); - new Characteristic::Identifier(11); - new Characteristic::IsConfigured(1); - new Characteristic::CurrentVisibilityState(0); - new Characteristic::TargetVisibilityState(0); - - SpanService *hdmi8 = new Service::InputSource(); - new Characteristic::ConfiguredName("Alpha2"); - new Characteristic::Identifier(3); - new Characteristic::IsConfigured(1); - new Characteristic::CurrentVisibilityState(0); - new Characteristic::TargetVisibilityState(0); - - SpanService *hdmi9 = new Service::InputSource(); - new Characteristic::ConfiguredName("Moon"); - new Characteristic::Identifier(18); - new Characteristic::IsConfigured(1); - new Characteristic::CurrentVisibilityState(0); - new Characteristic::TargetVisibilityState(0); - - SpanService *hdmi10 = new Service::InputSource(); - new Characteristic::ConfiguredName("Gamba"); - new Characteristic::Identifier(12); - new Characteristic::IsConfigured(1); - new Characteristic::CurrentVisibilityState(0); - new Characteristic::TargetVisibilityState(0); - - SpanService *speaker = new Service::TelevisionSpeaker(); - new Characteristic::VolumeSelector(); - new Characteristic::VolumeControlType(3); - - (new HomeSpanTV("Test TV")) // Define a Television Service. Must link in InputSources! - ->addLink(hdmi1) - ->addLink(hdmi2) - ->addLink(hdmi3) - ->addLink(hdmi4) - ->addLink(hdmi5) - ->addLink(hdmi6) - ->addLink(hdmi7) - ->addLink(hdmi8) - ->addLink(hdmi9) - ->addLink(hdmi10) - ->addLink(speaker) - ; - -} +// new SpanUserCommand('k',"- list controllers",list_controllers); + homeSpan.setControllerCallback(list_controllers); +} ////////////////////////////////////// @@ -258,3 +52,17 @@ 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 db80dab963e73f99457d77bd87145bfaae230ea5 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 28 Apr 2024 11:03:57 -0500 Subject: [PATCH 70/74] Update Reference.md --- docs/Reference.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/Reference.md b/docs/Reference.md index 1bf1bb2..e1d17cd 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -181,7 +181,13 @@ The following **optional** `homeSpan` methods enable additional features and pro * this one-time call to *func* is provided for users that would like to trigger additional actions when the device is first paired, or the device is later unpaired * note this *func* is **not** called upon start-up and should not be used to simply check whether a device is paired or unpaired. It is only called when pairing status changes * the function *func* must be of type *void* and accept one *boolean* argument - + +* `Span& setControllerCallback(void (*func)())` + * sets an optional user-defined callback function, *func*, to be called by HomeSpan every time a new controller is added, removed, or updated, even if the pairing status does not change + * note this method differs from `setPairCallback()`, which is only called if the device's pairing status changes, such as when the first controller is added during initial pairing, or the last controller is removed when unpairing + * the function *func* must be of type *void* and have no arguments + * see the `controllerListBegin()` and `controllerListEnd()` methods for details on how to read the pairing data for each paired controller (*only needed to support certain advanced use cases*) + * `Span& setStatusCallback(void (*func)(HS_STATUS status))` * sets an optional user-defined callback function, *func*, to be called by HomeSpan whenever its running state (e.g. WiFi Connecting, Pairing Needed...) changes in way that would alter the blinking pattern of the (optional) Status LED * if *func* is set, it will be called regardless of whether or not a Status LED has actually been defined From a8883c911d03d029d9fbb007d8ade2e86a12c5dd Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 28 Apr 2024 11:13:53 -0500 Subject: [PATCH 71/74] Update Reference.md --- docs/Reference.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/Reference.md b/docs/Reference.md index e1d17cd..b96b20c 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -302,6 +302,11 @@ The following **optional** `homeSpan` methods provide additional run-time functi * throws an error and halts program if called before at least one Accessory is created * example: `homeSpan.resetIID(100)` causes HomeSpan to set the IID to 100 for the very next Service or Characteristic defined within the current Accessory, and then increment the IID count going forward so that any Services or Characteristics subsequently defined (within the same Accessory) have IID=101, 102, etc. * note: calling this function only affects the IID generation for the current Accessory (the count will be reset to IID=1 upon instantiation of a new Accessory) + +* `const_iterator controllerListBegin()` and `const_iterator controllerListEnd()` + * respectively returns constant iterators pointing to the beginning (*cbegin()*) or end (*cend()*) of an opaque linked list that stores all controller data + * primarily used to loop through all controller data + * use `auto` keyword to define and save an iterator as such: `for(auto it=homeSpan.controllerListBegin(); it!=homeSpan.controllerListEnd(); ++it) {}` --- From 1267603e714d654c2f03aa252ed54eb3627a94f9 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 28 Apr 2024 12:59:42 -0500 Subject: [PATCH 72/74] Update Reference.md --- docs/Reference.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/Reference.md b/docs/Reference.md index b96b20c..d1b40ff 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -304,9 +304,13 @@ The following **optional** `homeSpan` methods provide additional run-time functi * note: calling this function only affects the IID generation for the current Accessory (the count will be reset to IID=1 upon instantiation of a new Accessory) * `const_iterator controllerListBegin()` and `const_iterator controllerListEnd()` - * respectively returns constant iterators pointing to the beginning (*cbegin()*) or end (*cend()*) of an opaque linked list that stores all controller data - * primarily used to loop through all controller data - * use `auto` keyword to define and save an iterator as such: `for(auto it=homeSpan.controllerListBegin(); it!=homeSpan.controllerListEnd(); ++it) {}` + * returns a *constant iterator* pointing to either the beginning, or the end, of an opaque linked list that stores all controller data + * iterators should be defined using the `auto` keyword as follows: `auto myIt=homeSpan.controllerListBegin();` + * controller data can be read from a de-referenced iterator using the following methods: + * `const uint8_t *getID()` returns pointer to the 36-byte ID of the controller + * `const uint8_t *getLTPK()` returns pointer to the 32-byte Long Term Public Key of the controller + * `boolean isAdmin()` returns true if controller has admin permissions, else returns false + * see this gist for an example of how to use these methods to extract the same data about each controller that HomeSpan prints to the Serial Monitor when using the 's' CLI command --- From 96a08ea84b9a3d9babb5a7dae393b078dbaf0eec Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 28 Apr 2024 13:23:54 -0500 Subject: [PATCH 73/74] Update Reference.md --- docs/Reference.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference.md b/docs/Reference.md index d1b40ff..1da6237 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -310,7 +310,7 @@ The following **optional** `homeSpan` methods provide additional run-time functi * `const uint8_t *getID()` returns pointer to the 36-byte ID of the controller * `const uint8_t *getLTPK()` returns pointer to the 32-byte Long Term Public Key of the controller * `boolean isAdmin()` returns true if controller has admin permissions, else returns false - * see this gist for an example of how to use these methods to extract the same data about each controller that HomeSpan prints to the Serial Monitor when using the 's' CLI command + * see this [gist](https://gist.github.com/HomeSpan/5486704b42027e31ab38a9f193451308) for an example of how to use these methods to extract the same data about each controller that HomeSpan prints to the Serial Monitor when using the 's' CLI command --- From bdf25cbaf96a54bf43a5c712dcf1a80141f7ad7a Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Sun, 28 Apr 2024 20:41:16 -0500 Subject: [PATCH 74/74] Update Reference.md --- docs/Reference.md | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/docs/Reference.md b/docs/Reference.md index 1da6237..3ce781d 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -304,13 +304,35 @@ The following **optional** `homeSpan` methods provide additional run-time functi * note: calling this function only affects the IID generation for the current Accessory (the count will be reset to IID=1 upon instantiation of a new Accessory) * `const_iterator controllerListBegin()` and `const_iterator controllerListEnd()` - * returns a *constant iterator* pointing to either the beginning, or the end, of an opaque linked list that stores all controller data + * returns a *constant iterator* pointing to either the *beginning*, or the *end*, of an opaque linked list that stores all controller data * iterators should be defined using the `auto` keyword as follows: `auto myIt=homeSpan.controllerListBegin();` * controller data can be read from a de-referenced iterator using the following methods: * `const uint8_t *getID()` returns pointer to the 36-byte ID of the controller * `const uint8_t *getLTPK()` returns pointer to the 32-byte Long Term Public Key of the controller * `boolean isAdmin()` returns true if controller has admin permissions, else returns false - * see this [gist](https://gist.github.com/HomeSpan/5486704b42027e31ab38a9f193451308) for an example of how to use these methods to extract the same data about each controller that HomeSpan prints to the Serial Monitor when using the 's' CLI command + *
click here for example code
+ + ```C++ + // Extract and print the same data about each controller that HomeSpan prints to the Serial Monitor when using the 's' CLI command + + Serial.printf("\nController Data\n"); + + for(auto it=homeSpan.controllerListBegin(); it!=homeSpan.controllerListEnd(); ++it){ // loop over each controller + + Serial.printf("Admin=%d",it->isAdmin()); // indicate if controller has admin permissions + + Serial.printf(" ID="); // print the 36-byte Device ID of the controller + for(int i=0;i<36;i++) + Serial.printf("%02X",it->getID()[i]); + + Serial.printf(" LTPK="); // print the 32-byte Long-Term Public Key of the controller) + for(int i=0;i<32;i++) + Serial.printf("%02X",it->getLTPK()[i]); + + Serial.printf("\n"); + } + ``` +
---