From 4557e3866ff1b975bbe816e59c1254a16d346ffd Mon Sep 17 00:00:00 2001 From: Gregg Date: Fri, 21 Jul 2023 13:27:31 -0500 Subject: [PATCH 01/29] Converted various homeSpan.set() methods from void to Span& to enable chaining --- src/HomeSpan.cpp | 11 +++++--- src/HomeSpan.h | 68 +++++++++++++++++++++++++----------------------- src/src.ino | 25 +++--------------- 3 files changed, 45 insertions(+), 59 deletions(-) diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 07dd911..e4fbd8a 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -539,7 +539,7 @@ void Span::checkConnect(){ /////////////////////////////// -void Span::setQRID(const char *id){ +Span& Span::setQRID(const char *id){ char tBuf[5]; sscanf(id,"%4[0-9A-Za-z]",tBuf); @@ -547,7 +547,8 @@ void Span::setQRID(const char *id){ if(strlen(id)==4 && strlen(tBuf)==4){ sprintf(qrID,"%s",id); } - + + return(*this); } // setQRID /////////////////////////////// @@ -833,7 +834,7 @@ void Span::processSerialCommand(const char *c){ case 'm': { multi_heap_info_t heapInfo; heap_caps_get_info(&heapInfo,MALLOC_CAP_INTERNAL); - LOG0("Total Heap=%d ",heapInfo.total_free_bytes); + LOG0("Total Heap=%d (low=%d) ",heapInfo.total_free_bytes,heapInfo.minimum_free_bytes); heap_caps_get_info(&heapInfo,MALLOC_CAP_DEFAULT); LOG0("DRAM-Capable=%d ",heapInfo.total_free_bytes); heap_caps_get_info(&heapInfo,MALLOC_CAP_EXEC); @@ -1197,13 +1198,15 @@ const char* Span::statusString(HS_STATUS s){ /////////////////////////////// -void Span::setWifiCredentials(const char *ssid, const char *pwd){ +Span& Span::setWifiCredentials(const char *ssid, const char *pwd){ sprintf(network.wifiData.ssid,"%.*s",MAX_SSID,ssid); sprintf(network.wifiData.pwd,"%.*s",MAX_PWD,pwd); if(wifiNVS){ // is begin() already called and wifiNVS is open nvs_set_blob(wifiNVS,"WIFIDATA",&network.wifiData,sizeof(network.wifiData)); // update data nvs_commit(wifiNVS); // commit to NVS } + + return(*this); } /////////////////////////////// diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 5a22b59..2334ab2 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -291,49 +291,51 @@ class Span{ boolean updateDatabase(boolean updateMDNS=true); // updates HAP Configuration Number and Loop vector; if updateMDNS=true and config number has changed, re-broadcasts MDNS 'c#' record; returns true if config number changed boolean deleteAccessory(uint32_t aid); // deletes Accessory with matching aid; returns true if found, else returns false - void setControlPin(uint8_t pin){controlButton=new PushButton(pin);} // sets Control Pin - void setStatusPin(uint8_t pin){statusDevice=new GenericLED(pin);} // sets Status Device to a simple LED on specified pin - void setStatusAutoOff(uint16_t duration){autoOffLED=duration;} // sets Status LED auto off (seconds) - int getStatusPin(){return(statusLED->getPin());} // get Status Pin (getPin will return -1 if underlying statusDevice is undefined) - int getControlPin(){return(controlButton?controlButton->getPin():-1);} // get Control Pin (returns -1 if undefined) + Span& setControlPin(uint8_t pin){controlButton=new PushButton(pin);return(*this);} // sets Control Pin + Span& setStatusPin(uint8_t pin){statusDevice=new GenericLED(pin);return(*this);} // sets Status Device to a simple LED on specified pin + Span& setStatusAutoOff(uint16_t duration){autoOffLED=duration;return(*this);} // sets Status LED auto off (seconds) + int getStatusPin(){return(statusLED->getPin());} // get Status Pin (getPin will return -1 if underlying statusDevice is undefined) + int getControlPin(){return(controlButton?controlButton->getPin():-1);} // get Control Pin (returns -1 if undefined) - void setStatusPixel(uint8_t pin,float h=0,float s=100,float v=100){ // sets Status Device to an RGB Pixel on specified pin + Span& setStatusPixel(uint8_t pin,float h=0,float s=100,float v=100){ // sets Status Device to an RGB Pixel on specified pin statusDevice=((new Pixel(pin))->setOnColor(Pixel::HSV(h,s,v))); + return(*this); } - void setStatusDevice(Blinkable *sDev){statusDevice=sDev;} + Span& setStatusDevice(Blinkable *sDev){statusDevice=sDev;return(*this);} void refreshStatusDevice(){if(statusLED)statusLED->refresh();} - void setApSSID(const char *ssid){network.apSSID=ssid;} // sets Access Point SSID - void setApPassword(const char *pwd){network.apPassword=pwd;} // sets Access Point Password - void setApTimeout(uint16_t nSec){network.lifetime=nSec*1000;} // sets Access Point Timeout (seconds) - void setCommandTimeout(uint16_t nSec){comModeLife=nSec*1000;} // sets Command Mode Timeout (seconds) - void setLogLevel(int level){logLevel=level;} // sets Log Level for log messages (0=baseline, 1=intermediate, 2=all, -1=disable all serial input/output) - int getLogLevel(){return(logLevel);} // get Log Level - void setSerialInputDisable(boolean val){serialInputDisabled=val;} // sets whether serial input is disabled (true) or enabled (false) - boolean getSerialInputDisable(){return(serialInputDisabled);} // returns true if serial input is disabled, or false if serial input in enabled - void reserveSocketConnections(uint8_t n){maxConnections-=n;} // reserves n socket connections *not* to be used for HAP - void setHostNameSuffix(const char *suffix){hostNameSuffix=suffix;} // sets the hostName suffix to be used instead of the 6-byte AccessoryID - void setPortNum(uint16_t port){tcpPortNum=port;} // sets the TCP port number to use for communications between HomeKit and HomeSpan - void setQRID(const char *id); // sets the Setup ID for optional pairing with a QR Code - void setSketchVersion(const char *sVer){sketchVersion=sVer;} // set optional sketch version number - const char *getSketchVersion(){return sketchVersion;} // get sketch version number - void setWifiCallback(void (*f)()){wifiCallback=f;} // sets an optional user-defined function to call once WiFi connectivity is established - void setPairCallback(void (*f)(boolean isPaired)){pairCallback=f;} // sets an optional user-defined function to call when Pairing is established (true) or lost (false) - void setApFunction(void (*f)()){apFunction=f;} // sets an optional user-defined function to call when activating the WiFi Access Point - void enableAutoStartAP(){autoStartAPEnabled=true;} // enables auto start-up of Access Point when WiFi Credentials not found - void setWifiCredentials(const char *ssid, const char *pwd); // sets WiFi Credentials - void setStatusCallback(void (*f)(HS_STATUS status)){statusCallback=f;} // 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& setApSSID(const char *ssid){network.apSSID=ssid;return(*this);} // sets Access Point SSID + Span& setApPassword(const char *pwd){network.apPassword=pwd;return(*this);} // sets Access Point Password + Span& setApTimeout(uint16_t nSec){network.lifetime=nSec*1000;return(*this);} // sets Access Point Timeout (seconds) + Span& setCommandTimeout(uint16_t nSec){comModeLife=nSec*1000;return(*this);} // sets Command Mode Timeout (seconds) + Span& setLogLevel(int level){logLevel=level;return(*this);} // sets Log Level for log messages (0=baseline, 1=intermediate, 2=all, -1=disable all serial input/output) + int getLogLevel(){return(logLevel);} // get Log Level + Span& setSerialInputDisable(boolean val){serialInputDisabled=val;return(*this);} // sets whether serial input is disabled (true) or enabled (false) + boolean getSerialInputDisable(){return(serialInputDisabled);} // returns true if serial input is disabled, or false if serial input in enabled + Span& reserveSocketConnections(uint8_t n){maxConnections-=n;return(*this);} // reserves n socket connections *not* to be used for HAP + Span& setHostNameSuffix(const char *suffix){hostNameSuffix=suffix;return(*this);} // sets the hostName suffix to be used instead of the 6-byte AccessoryID + Span& setPortNum(uint16_t port){tcpPortNum=port;return(*this);} // sets the TCP port number to use for communications between HomeKit and HomeSpan + Span& setQRID(const char *id); // sets the Setup ID for optional pairing with a QR Code + Span& setSketchVersion(const char *sVer){sketchVersion=sVer;return(*this);} // set optional sketch version number + const char *getSketchVersion(){return sketchVersion;} // get sketch version number + Span& setWifiCallback(void (*f)()){wifiCallback=f;return(*this);} // sets an optional user-defined function to call once WiFi connectivity is established + Span& setPairCallback(void (*f)(boolean isPaired)){pairCallback=f;return(*this);} // sets an optional user-defined function to call when Pairing is established (true) or lost (false) + Span& setApFunction(void (*f)()){apFunction=f;return(*this);} // sets an optional user-defined function to call when activating the WiFi Access Point + Span& enableAutoStartAP(){autoStartAPEnabled=true;return(*this);} // enables auto start-up of Access Point when WiFi Credentials not found + Span& setWifiCredentials(const char *ssid, const char *pwd); // sets WiFi Credentials + 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 - void setPairingCode(const char *s){sprintf(pairingCodeCommand,"S %9s",s);} // sets the Pairing Code - use is NOT recommended. Use 'S' from CLI instead - void deleteStoredValues(){processSerialCommand("V");} // deletes stored Characteristic values from NVS + Span& setPairingCode(const char *s){sprintf(pairingCodeCommand,"S %9s",s);return(*this);} // sets the Pairing Code - use is NOT recommended. Use 'S' from CLI instead + void deleteStoredValues(){processSerialCommand("V");} // deletes stored Characteristic values from NVS 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) - void enableWebLog(uint16_t maxEntries=0, const char *serv=NULL, const char *tz="UTC", const char *url=DEFAULT_WEBLOG_URL){ // enable Web Logging + Span& enableWebLog(uint16_t maxEntries=0, const char *serv=NULL, const char *tz="UTC", const char *url=DEFAULT_WEBLOG_URL){ // enable Web Logging webLog.init(maxEntries, serv, tz, url); + return(*this); } void addWebLog(boolean sysMsg, const char *fmt, ...){ // add Web Log entry @@ -343,14 +345,14 @@ class Span{ va_end(ap); } - void setWebLogCSS(const char *css){webLog.css="\n" + String(css) + "\n";} + Span& setWebLogCSS(const char *css){webLog.css="\n" + String(css) + "\n";return(*this);} void autoPoll(uint32_t stackSize=8192, uint32_t priority=1, uint32_t cpu=0){ // start pollTask() xTaskCreateUniversal([](void *parms){for(;;)homeSpan.pollTask();}, "pollTask", stackSize, NULL, priority, &pollTaskHandle, cpu); LOG0("\n*** AutoPolling Task started with priority=%d\n\n",uxTaskPriorityGet(pollTaskHandle)); } - void setTimeServerTimeout(uint32_t tSec){webLog.waitTime=tSec*1000;} // sets wait time (in seconds) for optional web log time server to connect + Span& setTimeServerTimeout(uint32_t tSec){webLog.waitTime=tSec*1000;return(*this);} // sets wait time (in seconds) for optional web log time server to connect [[deprecated("Please use reserveSocketConnections(n) method instead.")]] void setMaxConnections(uint8_t n){requestedMaxCon=n;} // sets maximum number of simultaneous HAP connections diff --git a/src/src.ino b/src/src.ino index d82cf30..26674a7 100644 --- a/src/src.ino +++ b/src/src.ino @@ -45,35 +45,16 @@ struct LED_Service : Service::LightBulb { }; -////////////////////////////////////// - -struct invertedLED : Blinkable { // create a child class derived from Blinkable - - int pin; // variable to store the pin number - - invertedLED(int pin) : pin{pin} { // constructor that initializes the pin parameter - pinMode(pin,OUTPUT); // set the pin to OUTPUT - digitalWrite(pin,HIGH); // set pin HIGH (which is off for an inverted LED) - } - - void on() override { digitalWrite(pin,LOW); } // required function on() - sets pin LOW - void off() override { digitalWrite(pin,HIGH); } // required function off() - sets pin HIGH - int getPin() override { return(pin); } // required function getPin() - returns pin number -}; - - ////////////////////////////////////// void setup() { Serial.begin(115200); -// homeSpan.setLogLevel(-1); + homeSpan.setLogLevel(2) + .setStatusPin(13); // homeSpan.setSerialInputDisable(true); - homeSpan.enableOTA(); - - homeSpan.setStatusDevice(new invertedLED(13)); // set Status LED to be a new Blinkable device attached to pin 13 - homeSpan.setStatusAutoOff(30); +// homeSpan.enableOTA(); homeSpan.begin(Category::Lighting,"HomeSpan LED"); From 604c76bc9452df953dd1d970258a779d3bc83a3c Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Fri, 21 Jul 2023 13:54:51 -0500 Subject: [PATCH 02/29] Update Reference.md --- docs/Reference.md | 57 ++++++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/docs/Reference.md b/docs/Reference.md index 472c8cb..f721cbe 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -8,7 +8,7 @@ The HomeSpan Library is invoked by including *HomeSpan.h* in your Arduino sketch ## *homeSpan* -At runtime HomeSpan will create a global **object** named `homeSpan` that supports the following methods: +At runtime HomeSpan will create a global **object** named `homeSpan` (of type *class Span*) that supports the following methods: * `void begin(Category catID, const char *displayName, const char *hostNameBase, const char *modelName)` * initializes HomeSpan @@ -26,20 +26,21 @@ At runtime HomeSpan will create a global **object** named `homeSpan` that suppor --- -The following **optional** `homeSpan` methods override various HomeSpan initialization parameters used in `begin()`, and therefore **should** be called before `begin()` to take effect. If a method is *not* called, HomeSpan uses the default parameter indicated below: +The following **optional** `homeSpan` methods override various HomeSpan initialization parameters used in `begin()`, and therefore **should** be called before `begin()` to take effect. +Methods with a return type of `Span&` return a reference to `homeSpan` itself and can thus be chained together (e.g. `homeSpan.setControlPin(21).setStatusPin(13);`). If a method is *not* called, HomeSpan uses the default parameter indicated below: -* `void setControlPin(uint8_t pin)` +* `Span& setControlPin(uint8_t pin)` * sets the ESP32 pin to use for the HomeSpan Control Button. If not specified, HomeSpan will assume there is no Control Button * `int getControlPin()` * returns the pin number of the HomeSpan Control Button as set by `setControlPin(pin)`, or -1 if no pin has been set -* `void setStatusPin(uint8_t pin)` +* `Span& setStatusPin(uint8_t pin)` * sets the ESP32 pin to use for the HomeSpan Status LED * assumes a standard LED will be connected to *pin* * if neither this method nor any equivalent method is called, HomeSpan will assume there is no Status LED -* `void setStatusPixel(uint8_t pin, float h=0, float s=100, float v=100)` +* `Span& setStatusPixel(uint8_t pin, float h=0, float s=100, float v=100)` * sets the ESP32 pin to use for the HomeSpan Status LED * this method is an *alternative* to using `setStatusPin()` above * assumes an RGB NeoPixel (or equivalent) will be connected to *pin* @@ -52,14 +53,14 @@ The following **optional** `homeSpan` methods override various HomeSpan initiali * example: `homeSpan.setStatusPixel(8,120,100,20)` sets the Status LED to light green using a NeoPixel attached to pin 8 * if neither this method nor any equivalent method is called, HomeSpan will assume there is no Status LED -* `void setStatusDevice(Blinkable *sDev)` +* `Span& setStatusDevice(Blinkable *sDev)` * sets the Status LED to a user-specified Blinkable device, *sDev* * this method is an *alternative* to using either `setStatusPin()` or `setStatusPixel()` above * see [Blinkable](Blinkable.md) for details on how to create generic Blinkable devices * useful when using an LED connected to a pin expander, or other specialized driver, as the Status LED * if neither this method nor any equivalent method is called, HomeSpan will assume there is no Status LED -* `void setStatusAutoOff(uint16_t duration)` +* `Span& setStatusAutoOff(uint16_t duration)` * sets Status LED to automatically turn off after *duration* seconds * Status LED will automatically turn on, and duration timer will be reset, whenever HomeSpan activates a new blinking pattern * if *duration* is set to zero, auto-off is disabled (Status LED will remain on indefinitely) @@ -67,19 +68,19 @@ The following **optional** `homeSpan` methods override various HomeSpan initiali * `int getStatusPin()` * returns the pin number of the Status LED as set by `setStatusPin(pin)`, or -1 if no pin has been set -* `void setApSSID(const char *ssid)` +* `Span& setApSSID(const char *ssid)` * sets the SSID (network name) of the HomeSpan Setup Access Point (default="HomeSpan-Setup") -* `void setApPassword(const char *pwd)` +* `Span& setApPassword(const char *pwd)` * sets the password of the HomeSpan Setup Access Point (default="homespan") -* `void setApTimeout(uint16_t nSec)` +* `Span& setApTimeout(uint16_t nSec)` * sets the duration (in seconds) that the HomeSpan Setup Access Point, once activated, stays alive before timing out (default=300 seconds) -* `void setCommandTimeout(uint16_t nSec)` +* `Span& setCommandTimeout(uint16_t nSec)` * sets the duration (in seconds) that the HomeSpan End-User Command Mode, once activated, stays alive before timing out (default=120 seconds) -* `void setLogLevel(int level)` +* `Span& setLogLevel(int level)` * sets the logging level for diagnostic messages, where: * 0 = top-level HomeSpan status messages, and any `LOG0()` messages specified in the sketch by the user (default) * 1 = all HomeSpan status messages, and any `LOG1()` messages specified in the sketch by the user @@ -93,7 +94,7 @@ The following **optional** `homeSpan` methods override various HomeSpan initiali * `int getLogLevel()` * returns the current Log Level as set by `setLogLevel(level)` -* `void reserveSocketConnections(uint8_t nSockets)` +* `Span& reserveSocketConnections(uint8_t nSockets)` * reserves *nSockets* network sockets for uses **other than** by the HomeSpan HAP Server for HomeKit Controller Connections * for sketches compiled under Arduino-ESP32 v2.0.1 or later, HomeSpan reserves 14 sockets for HAP Controller Connections * each call to `reserveSocketConnections(nSockets)` reduces this number by *nSockets* @@ -102,16 +103,16 @@ The following **optional** `homeSpan` methods override various HomeSpan initiali * note you do not need to separately reserve sockets for built-in HomeSpan functionality * for example, `enableOTA()` already contains an embedded call to `reserveSocketConnections(1)` since HomeSpan knows one socket must be reserved to support OTA -* `void setPortNum(uint16_t port)` +* `Span& setPortNum(uint16_t port)` * sets the TCP port number used for communication between HomeKit and HomeSpan (default=80) -* `void setHostNameSuffix(const char *suffix)` +* `Span& setHostNameSuffix(const char *suffix)` * sets the suffix HomeSpan appends to *hostNameBase* to create the full hostName * if not specified, the default is for HomeSpan to append a dash "-" followed the 6-byte Accessory ID of the HomeSpan device * setting *suffix* to a null string "" is permitted * example: `homeSpan.begin(Category::Fans, "Living Room Ceiling Fan", "LivingRoomFan");` will yield a default *hostName* of the form *LivingRoomFan-A1B2C3D4E5F6.local*. Calling `homeSpan.setHostNameSuffix("v2")` prior to `homeSpan.begin()` will instead yield a *hostName* of *LivingRoomFanv2.local* -* `void setQRID(const char *id)` +* `Span& setQRID(const char *id)` * changes the Setup ID, which is used for pairing a device with a [QR Code](QRCodes.md), from the HomeSpan default to *id* * the HomeSpan default is "HSPN" unless permanently changed for the device via the [HomeSpan CLI](CLI.md) using the 'Q' command * *id* must be exactly 4 alphanumeric characters (0-9, A-Z, and a-z). If not, the request to change the Setup ID is silently ignored and the default is used instead @@ -135,11 +136,11 @@ The following **optional** `homeSpan` methods enable additional features and pro * this command causes HomeSpan to ignore, but does not otherwise alter, any password stored using the 'O' command * returns 0 if enabling OTA was successful, or -1 and reports an error to the Serial Monitor if not -* `void enableAutoStartAP()` +* `Span& enableAutoStartAP()` * enables automatic start-up of WiFi Access Point if WiFi Credentials are **not** found at boot time * methods to alter the behavior of HomeSpan's Access Point, such as `setApTimeout()`, must be called prior to `enableAutoStartAP()` to have an effect -* `void setApFunction(void (*func)())` +* `Span& setApFunction(void (*func)())` * replaces HomeSpan's built-in WiFi Access Point with user-defined function *func* * *func* must be of type *void* and have no arguments * *func* will be called instead of HomeSpan's built-in WiFi Access Point whenever the Access Point is launched: @@ -149,22 +150,22 @@ The following **optional** `homeSpan` methods enable additional features and pro * after identifying the SSID and password of the desired network, *func* must call `setWifiCredentials()` to save and use these values * it is recommended that *func* terminates by restarting the device using `ESP.restart()`. Upon restart HomeSpan will use the SSID and password just saved -* `void setWifiCredentials(const char *ssid, const char *pwd)` +* `Span& setWifiCredentials(const char *ssid, const char *pwd)` * sets the SSID (*ssid*) and password (*pwd*) of the WiFi network to which HomeSpan will connect * *ssid* and *pwd* are automatically saved in HomeSpan's non-volatile storage (NVS) for retrieval when the device restarts * note that the saved values are truncated if they exceed the maximum allowable characters (ssid=32; pwd=64) * :warning: SECURITY WARNING: The purpose of this function is to allow advanced users to *dynamically* set the device's WiFi Credentials using a customized Access Point function specified by `setApFunction(func)`. It it NOT recommended to use this function to hardcode your WiFi SSID and password directly into your sketch. Instead, use one of the more secure methods provided by HomeSpan, such as typing 'W' from the CLI, or launching HomeSpan's Access Point, to set your WiFi credentials without hardcoding them into your sketch -* `void setWifiCallback(void (*func)())` +* `Span& setWifiCallback(void (*func)())` * sets an optional user-defined callback function, *func*, to be called by HomeSpan upon start-up just after WiFi connectivity has been established. This one-time call to *func* is provided for users that are implementing other network-related services as part of their sketch, but that cannot be started until WiFi connectivity is established. The function *func* must be of type *void* and have no arguments -* `void setPairCallback(void (*func)(boolean status))` +* `Span& setPairCallback(void (*func)(boolean status))` * sets an optional user-defined callback function, *func*, to be called by HomeSpan upon completion of pairing to a controller (*status=true*) or unpairing from a controller (*status=false*) * 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 -* `void setStatusCallback(void (*func)(HS_STATUS status))` +* `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 * this allows users to reflect changes to the current state of HomeSpan using alternative methods, such as outputting messages to an embedded LCD or E-Ink display @@ -174,14 +175,14 @@ The following **optional** `homeSpan` methods enable additional features and pro * returns a pre-defined character string message representing *s*, which must be of enum type [HS_STATUS](HS_STATUS.md) * typically used in conjunction with `setStatusCallback()` above -* `void setPairingCode(const char *s)` +* `Span& setPairingCode(const char *s)` * sets the Setup Pairing Code to *s*, which **must** be exactly eight numerical digits (no dashes) * example: `homeSpan.setPairingCode("46637726");` * a hashed version of the Pairing Code will be saved to the device's non-volatile storage, overwriting any currently-stored Pairing Code * if *s* contains an invalid code, an error will be reported and the code will *not* be saved. Instead, the currently-stored Pairing Code (or the HomeSpan default Pairing Code if no code has been stored) will be used * :warning: SECURTY WARNING: Hardcoding a device's Pairing Code into your sketch is considered a security risk and is **not** recommended. Instead, use one of the more secure methods provided by HomeSpan, such as typing 'S \' from the CLI, or launching HomeSpan's Access Point, to set your Pairing Code without hardcoding it into your sketch -* `void setSketchVersion(const char *sVer)` +* `Span& setSketchVersion(const char *sVer)` * sets the version of a HomeSpan sketch to *sVer*, which can be any arbitrary character string * if unspecified, HomeSpan uses "n/a" as the default version text * HomeSpan displays the version of the sketch in the Arduino IDE Serial Monitor upon start-up @@ -191,7 +192,7 @@ The following **optional** `homeSpan` methods enable additional features and pro * returns the version of a HomeSpan sketch, as set using `void setSketchVersion(const char *sVer)`, or "n/a" if not set * can by called from anywhere in a sketch -* `void enableWebLog(uint16_t maxEntries, const char *timeServerURL, const char *timeZone, const char *logURL)` +* `Span& enableWebLog(uint16_t maxEntries, const char *timeServerURL, const char *timeZone, const char *logURL)` * enables a rolling Web Log that displays the most recent *maxEntries* entries created by the user with the `WEBLOG()` macro. Parameters, and their default values if unspecified, are as follows: * *maxEntries* - maximum number of (most recent) entries to save. If unspecified, defaults to 0, in which case the Web Log will only display status without any log entries * *timeServerURL* - the URL of a time server that HomeSpan will use to set its clock upon startup after a WiFi connection has been established. If unspecified, defaults to NULL, in which case HomeSpan skips setting the device clock @@ -201,10 +202,10 @@ The following **optional** `homeSpan` methods enable additional features and pro * when attemping to connect to *timeServerURL*, HomeSpan waits 120 seconds for a response. This is done in the background and does not block HomeSpan from running as usual while it tries to set the time. If no response is received after the 120-second timeout period, HomeSpan assumes the server is unreachable and skips the clock-setting procedure. Use `setTimeServerTimeout()` to re-configure the 120-second timeout period to another value * see [Message Logging](Logging.md) for complete details -* `void setTimeServerTimeout(uint32_t tSec)` +* `Span& setTimeServerTimeout(uint32_t tSec)` * changes the default 120-second timeout period HomeSpan uses when `enableWebLog()` tries set the device clock from an internet time server to *tSec* seconds -* `void setWebLogCSS(const char *css)` +* `Span& setWebLogCSS(const char *css)` * sets the format of the HomeSpan Web Log to the custom style sheet specified by *css* * see [Message Logging](Logging.md) for details on how to construct *css* @@ -215,7 +216,7 @@ The following **optional** `homeSpan` methods enable additional features and pro * example: `homeSpan.processSerialCommand("A");` starts the HomeSpan Setup Access Point * example: `homeSpan.processSerialCommand("Q HUB3");` changes the HomeKit Setup ID for QR Codes to "HUB3" -* `void setSerialInputDisable(boolean val)` +* `Span& setSerialInputDisable(boolean val)` * if *val* is true, disables HomeSpan from reading input from the Serial port * if *val* is false, re-enables HomeSpan reading input from the Serial port * useful when the main USB Serial port is needed for reading data from an external Serial peripheral, rather than being used to read input from the Arduino Serial Monitor From 3f62c228af34a5b8bb70af649ae894c5a5879497 Mon Sep 17 00:00:00 2001 From: HomeSpan Date: Fri, 21 Jul 2023 14:55:22 -0500 Subject: [PATCH 03/29] 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 f721cbe..8227587 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -259,7 +259,7 @@ The following `homeSpan` methods are considered experimental, since not all use * *stackSize* - size of stack, in bytes, used by the polling task. Default=8192 if unspecified * *priority* - priority at which task runs. Minimum is 1. Maximum is typically 24, but it depends on how the ESP32 operating system is configured. If you set it to an arbitrarily high value (e.g. 999), it will be set to the maximum priority allowed. Default=1 if unspecified - * *cpu* - specifies the CPU on which the polling task will run. Valid values are 0 and 1. This paramater is ignored on single-cpu boards. Default=0 if unspecified + * *cpu* - specifies the CPU on which the polling task will run. Valid values are 0 and 1. This parameter is ignored on single-cpu boards. Default=0 if unspecified * if used, **must** be placed in a sketch as the last line in the Arduino `setup()` method * HomeSpan will throw and error and halt if both `poll()`and `autoPoll()` are used in the same sketch - either place `poll()` in the Arduino `loop()` method **or** place `autoPoll()` at the the end of the Arduino `setup()` method * if this method is used, and you have no need to add your own code to the main Arduino `loop()`, you can safely skip defining a blank `void loop(){}` function in your sketch @@ -497,7 +497,7 @@ Creating an instance of this **class** attaches a toggle-switch handler to the E * 3=switch is closed (`SpanToggle::CLOSED`) * 4=switch is open (`SpanToggle::OPEN`) -Note there are no *singleTime*, *longTime*, or *doubleTime* paramaters in the constructor since you can't single-press, double-press, or long-press a toggle switch. Instead, the constructor supports the single parameter *toggleTime* (default=5ms if left unspecified) that sets the minimum time at which the switch needs to be moved to the closed position in order to trigger a call to the `button()` method. This effectively "debounces" the toggle switch. +Note there are no *singleTime*, *longTime*, or *doubleTime* parameters in the constructor since you can't single-press, double-press, or long-press a toggle switch. Instead, the constructor supports the single parameter *toggleTime* (default=5ms if left unspecified) that sets the minimum time at which the switch needs to be moved to the closed position in order to trigger a call to the `button()` method. This effectively "debounces" the toggle switch. SpanToggle also supports the following additional method: From a73b206531487b61e1c01944f10f0bbef4735990 Mon Sep 17 00:00:00 2001 From: Gregg Date: Fri, 21 Jul 2023 17:36:57 -0500 Subject: [PATCH 04/29] Update src.ino --- src/src.ino | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/src.ino b/src/src.ino index 26674a7..378710a 100644 --- a/src/src.ino +++ b/src/src.ino @@ -51,6 +51,8 @@ void setup() { Serial.begin(115200); + homeSpan.setControlPin(21); + homeSpan.setLogLevel(2) .setStatusPin(13); // homeSpan.setSerialInputDisable(true); From d6f5612f9f15d2c9f426eb2d464ba441ec06d404 Mon Sep 17 00:00:00 2001 From: Gregg Date: Mon, 24 Jul 2023 09:24:58 -0500 Subject: [PATCH 05/29] Update Pixel.ino changed nPixels from uint8_t to int to allow for longer pixel strands (library is coded for int, so there is no need to limit to uint8_t in the example). --- examples/Other Examples/Pixel/Pixel.ino | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/Other Examples/Pixel/Pixel.ino b/examples/Other Examples/Pixel/Pixel.ino index 5723de4..7c78dd6 100644 --- a/examples/Other Examples/Pixel/Pixel.ino +++ b/examples/Other Examples/Pixel/Pixel.ino @@ -72,9 +72,9 @@ struct NeoPixel_RGB : Service::LightBulb { // Addressable single-wire RGB L Characteristic::Saturation S{0,true}; Characteristic::Brightness V{100,true}; Pixel *pixel; - uint8_t nPixels; + int nPixels; - NeoPixel_RGB(uint8_t pin, uint8_t nPixels) : Service::LightBulb(){ + NeoPixel_RGB(uint8_t pin, int nPixels) : Service::LightBulb(){ V.setRange(5,100,1); // sets the range of the Brightness to be from a min of 5%, to a max of 100%, in steps of 1% pixel=new Pixel(pin); // creates Pixel LED on specified pin @@ -106,9 +106,9 @@ struct NeoPixel_RGBW : Service::LightBulb { // Addressable single-wire RGBW Characteristic::Brightness V{100,true}; Characteristic::ColorTemperature T{140,true}; Pixel *pixel; - uint8_t nPixels; + int nPixels; - NeoPixel_RGBW(uint8_t pin, uint8_t nPixels) : Service::LightBulb(){ + NeoPixel_RGBW(uint8_t pin, int nPixels) : Service::LightBulb(){ V.setRange(5,100,1); // sets the range of the Brightness to be from a min of 5%, to a max of 100%, in steps of 1% pixel=new Pixel(pin,true); // creates Pixel RGBW LED (second parameter set to true for RGBW) on specified pin @@ -142,9 +142,9 @@ struct DotStar_RGB : Service::LightBulb { // Addressable two-wire RGB LED S Characteristic::Saturation S{0,true}; Characteristic::Brightness V{100,true}; Dot *pixel; - uint8_t nPixels; + int nPixels; - DotStar_RGB(uint8_t dataPin, uint8_t clockPin, uint8_t nPixels) : Service::LightBulb(){ + DotStar_RGB(uint8_t dataPin, uint8_t clockPin, int nPixels) : Service::LightBulb(){ V.setRange(5,100,1); // sets the range of the Brightness to be from a min of 5%, to a max of 100%, in steps of 1% pixel=new Dot(dataPin,clockPin); // creates Dot LED on specified pins From 5f9458e6254d1fe712bfbf4a07bda8f89a37faf5 Mon Sep 17 00:00:00 2001 From: Gregg Date: Tue, 25 Jul 2023 05:54:40 -0500 Subject: [PATCH 06/29] Converted static HTTP Buffer to dynamic TempBuffer Saved about 8K in RAM! --- src/HAP.cpp | 44 +++++++++++++++++++++++++++----------------- src/HAP.h | 5 ++--- src/Utils.h | 4 ++-- 3 files changed, 31 insertions(+), 22 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index 39bed60..57cc938 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -154,16 +154,27 @@ void HAPClient::init(){ void HAPClient::processRequest(){ - int nBytes; + int nBytes, messageSize; + + messageSize=client.available(); + + if(messageSize>MAX_HTTP){ // exceeded maximum number of bytes allowed + badRequestError(); + LOG0("\n*** ERROR: HTTP message of %d bytes exceeds maximum allowed (%d)\n\n",messageSize,MAX_HTTP); + return; + } + + TempBuffer tempBuffer(messageSize+1); // leave room for null character added below + uint8_t *httpBuf=tempBuffer.buf; if(cPair){ // expecting encrypted message LOG2("<<<< #### "); LOG2(client.remoteIP()); LOG2(" #### <<<<\n"); - nBytes=receiveEncrypted(); // decrypt and return number of bytes + nBytes=receiveEncrypted(httpBuf,messageSize); // decrypt and return number of bytes read - if(!nBytes){ // decryption failed (error message already printed in function) + if(!nBytes){ // decryption failed (error message already printed in function) badRequestError(); return; } @@ -173,21 +184,21 @@ void HAPClient::processRequest(){ LOG2(client.remoteIP()); LOG2(" <<<<<<<<<\n"); - nBytes=client.read(httpBuf,MAX_HTTP+1); // read all available bytes up to maximum allowed+1 - - if(nBytes>MAX_HTTP){ // exceeded maximum number of bytes allowed + nBytes=client.read(httpBuf,messageSize); // read expected number of bytes + + if(nBytes!=messageSize || client.available()!=0){ badRequestError(); - LOG0("\n*** ERROR: Exceeded maximum HTTP message length\n\n"); + LOG0("\n*** ERROR: HTTP message not read correctly. Expected %d bytes, read %d bytes, %d bytes remaining\n\n",messageSize,nBytes,client.available()); return; } - + } // encrypted/plaintext httpBuf[nBytes]='\0'; // add null character to enable string functions char *body=(char *)httpBuf; // char pointer to start of HTTP Body - char *p; // char pointer used for searches - + char *p; // char pointer used for searches + if(!(p=strstr((char *)httpBuf,"\r\n\r\n"))){ badRequestError(); LOG0("\n*** ERROR: Malformed HTTP request (can't find blank line indicating end of BODY)\n\n"); @@ -233,7 +244,7 @@ void HAPClient::processRequest(){ tlv8.print(2); // print TLV records in form "TAG(INT) LENGTH(INT) VALUES(HEX)" LOG2("------------ END TLVS! ------------\n"); - postPairVerifyURL(); // process URL + postPairVerifyURL(); // process URL return; } @@ -243,7 +254,7 @@ void HAPClient::processRequest(){ tlv8.print(2); // print TLV records in form "TAG(INT) LENGTH(INT) VALUES(HEX)" LOG2("------------ END TLVS! ------------\n"); - postPairingsURL(); // process URL + postPairingsURL(); // process URL return; } @@ -878,7 +889,7 @@ int HAPClient::getAccessoriesURL(){ LOG2("\n"); sendEncrypted(body,(uint8_t *)jBuf.buf,nBytes); - + return(1); } // getAccessories @@ -1443,7 +1454,7 @@ void HAPClient::tlvRespond(){ ////////////////////////////////////// -int HAPClient::receiveEncrypted(){ +int HAPClient::receiveEncrypted(uint8_t *httpBuf, int messageSize){ uint8_t buf[1042]; // maximum size of encoded message = 2+1024+16 bytes (HAP Section 6.5.2) int nBytes=0; @@ -1452,8 +1463,8 @@ int HAPClient::receiveEncrypted(){ int n=buf[0]+buf[1]*256; // compute number of bytes expected in encoded message - if(nBytes+n>MAX_HTTP){ // exceeded maximum number of bytes allowed in plaintext message - LOG0("\n\n*** ERROR: Exceeded maximum HTTP message length\n\n"); + if(nBytes+n>messageSize){ // exceeded maximum number of bytes allowed in plaintext message + LOG0("\n\n*** ERROR: Decrypted message of %d bytes exceeded maximum expected message length of %d bytes\n\n",nBytes+n,messageSize); return(0); } @@ -1722,7 +1733,6 @@ void Nonce::inc(){ TLV HAPClient::tlv8; nvs_handle HAPClient::hapNVS; nvs_handle HAPClient::srpNVS; -uint8_t HAPClient::httpBuf[MAX_HTTP+1]; HKDF HAPClient::hkdf; pairState HAPClient::pairStatus; Accessory HAPClient::accessory; diff --git a/src/HAP.h b/src/HAP.h index 8c8b937..67dda3c 100644 --- a/src/HAP.h +++ b/src/HAP.h @@ -73,14 +73,13 @@ struct HAPClient { // common structures and data shared across all HAP Clients - static const int MAX_HTTP=8095; // max number of bytes in HTTP message buffer + static const int MAX_HTTP=8096; // max number of bytes allowed for HTTP message static const int MAX_CONTROLLERS=16; // maximum number of paired controllers (HAP requires at least 16) static const int MAX_ACCESSORIES=41; // maximum number of allowed Acessories (HAP limit=150, but not enough memory in ESP32 to run that many) static TLV tlv8; // TLV8 structure (HAP Section 14.1) with space for 10 TLV records of type kTLVType (HAP Table 5-6) static nvs_handle hapNVS; // handle for non-volatile-storage of HAP data static nvs_handle srpNVS; // handle for non-volatile-storage of SRP data - static uint8_t httpBuf[MAX_HTTP+1]; // buffer to store HTTP messages (+1 to leave room for storing an extra 'overflow' character) static HKDF hkdf; // generates (and stores) HKDF-SHA-512 32-byte keys derived from an inputKey of arbitrary length, a salt string, and an info string static pairState pairStatus; // tracks pair-setup status static SRP6A srp; // stores all SRP-6A keys used for Pair-Setup @@ -121,7 +120,7 @@ struct HAPClient { void tlvRespond(); // respond to client with HTTP OK header and all defined TLV data records (those with length>0) void sendEncrypted(char *body, uint8_t *dataBuf, int dataLen); // send client complete ChaCha20-Poly1305 encrypted HTTP mesage comprising a null-terminated 'body' and 'dataBuf' with 'dataLen' bytes - int receiveEncrypted(); // decrypt HTTP request (HAP Section 6.5) + int receiveEncrypted(uint8_t *httpBuf, int messageSize); // decrypt HTTP request (HAP Section 6.5) int notFoundError(); // return 404 error int badRequestError(); // return 400 error diff --git a/src/Utils.h b/src/Utils.h index 0a06462..818d98c 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -48,7 +48,7 @@ struct TempBuffer { TempBuffer(size_t len){ nBytes=len*sizeof(bufType); - buf=(bufType *)heap_caps_malloc(nBytes,MALLOC_CAP_8BIT); + buf=(bufType *)malloc(nBytes); if(buf==NULL){ Serial.print("\n\n*** FATAL ERROR: Requested allocation of "); Serial.print(nBytes); @@ -58,7 +58,7 @@ struct TempBuffer { } ~TempBuffer(){ - heap_caps_free(buf); + free(buf); } int len(){ From 62f68cb33cd1623f46e8bcdb7eeaa364ab612cf6 Mon Sep 17 00:00:00 2001 From: Gregg Date: Wed, 26 Jul 2023 06:27:54 -0500 Subject: [PATCH 07/29] Upgraded TempBuffer logic to use .get() to return pointer Next up: update Network.cpp to use client.available() and reduce fixed memory usage. --- src/HAP.cpp | 37 ++++++++++++++++++------------------- src/HAP.h | 2 +- src/HomeSpan.cpp | 32 ++++++++++++++++---------------- src/HomeSpan.h | 4 ++-- src/Network.cpp | 12 ++++++------ src/Utils.h | 11 ++++++++++- 6 files changed, 53 insertions(+), 45 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index 57cc938..cd0012a 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -163,16 +163,15 @@ void HAPClient::processRequest(){ LOG0("\n*** ERROR: HTTP message of %d bytes exceeds maximum allowed (%d)\n\n",messageSize,MAX_HTTP); return; } - - TempBuffer tempBuffer(messageSize+1); // leave room for null character added below - uint8_t *httpBuf=tempBuffer.buf; + + TempBuffer httpBuf(messageSize+1); // leave room for null character added below if(cPair){ // expecting encrypted message LOG2("<<<< #### "); LOG2(client.remoteIP()); LOG2(" #### <<<<\n"); - nBytes=receiveEncrypted(httpBuf,messageSize); // decrypt and return number of bytes read + nBytes=receiveEncrypted(httpBuf.get(),messageSize); // decrypt and return number of bytes read if(!nBytes){ // decryption failed (error message already printed in function) badRequestError(); @@ -184,7 +183,7 @@ void HAPClient::processRequest(){ LOG2(client.remoteIP()); LOG2(" <<<<<<<<<\n"); - nBytes=client.read(httpBuf,messageSize); // read expected number of bytes + nBytes=client.read(httpBuf.get(),messageSize); // read expected number of bytes if(nBytes!=messageSize || client.available()!=0){ badRequestError(); @@ -194,12 +193,12 @@ void HAPClient::processRequest(){ } // encrypted/plaintext - httpBuf[nBytes]='\0'; // add null character to enable string functions + httpBuf.get()[nBytes]='\0'; // add null character to enable string functions - char *body=(char *)httpBuf; // char pointer to start of HTTP Body - char *p; // char pointer used for searches + char *body=(char *)httpBuf.get(); // char pointer to start of HTTP Body + char *p; // char pointer used for searches - if(!(p=strstr((char *)httpBuf,"\r\n\r\n"))){ + if(!(p=strstr((char *)httpBuf.get(),"\r\n\r\n"))){ badRequestError(); LOG0("\n*** ERROR: Malformed HTTP request (can't find blank line indicating end of BODY)\n\n"); return; @@ -875,7 +874,7 @@ int HAPClient::getAccessoriesURL(){ int nBytes = homeSpan.sprintfAttributes(NULL); // get size of HAP attributes JSON TempBuffer jBuf(nBytes+1); - homeSpan.sprintfAttributes(jBuf.buf); // create JSON database (will need to re-cast to uint8_t* below) + homeSpan.sprintfAttributes(jBuf.get()); // create JSON database (will need to re-cast to uint8_t* below) int nChars=snprintf(NULL,0,"HTTP/1.1 200 OK\r\nContent-Type: application/hap+json\r\nContent-Length: %d\r\n\r\n",nBytes); // create '200 OK' Body with Content Length = size of JSON Buf char body[nChars+1]; @@ -885,10 +884,10 @@ int HAPClient::getAccessoriesURL(){ LOG2(client.remoteIP()); LOG2(" >>>>>>>>>>\n"); LOG2(body); - LOG2(jBuf.buf); + LOG2(jBuf.get()); LOG2("\n"); - sendEncrypted(body,(uint8_t *)jBuf.buf,nBytes); + sendEncrypted(body,(uint8_t *)jBuf.get(),nBytes); return(1); @@ -1507,10 +1506,10 @@ void HAPClient::sendEncrypted(char *body, uint8_t *dataBuf, int dataLen){ TempBuffer tBuf(totalBytes); - tBuf.buf[count]=bodyLen%256; // store number of bytes in first frame that encrypts the Body (AAD bytes) - tBuf.buf[count+1]=bodyLen/256; + tBuf.get()[count]=bodyLen%256; // store number of bytes in first frame that encrypts the Body (AAD bytes) + tBuf.get()[count+1]=bodyLen/256; - crypto_aead_chacha20poly1305_ietf_encrypt(tBuf.buf+count+2,&nBytes,(uint8_t *)body,bodyLen,tBuf.buf+count,2,NULL,a2cNonce.get(),a2cKey); // encrypt the Body with authentication tag appended + crypto_aead_chacha20poly1305_ietf_encrypt(tBuf.get()+count+2,&nBytes,(uint8_t *)body,bodyLen,tBuf.get()+count,2,NULL,a2cNonce.get(),a2cKey); // encrypt the Body with authentication tag appended a2cNonce.inc(); // increment nonce @@ -1523,17 +1522,17 @@ void HAPClient::sendEncrypted(char *body, uint8_t *dataBuf, int dataLen){ if(n>FRAME_SIZE) // maximum number of bytes to encrypt=FRAME_SIZE n=FRAME_SIZE; - tBuf.buf[count]=n%256; // store number of bytes that encrypts this frame (AAD bytes) - tBuf.buf[count+1]=n/256; + tBuf.get()[count]=n%256; // store number of bytes that encrypts this frame (AAD bytes) + tBuf.get()[count+1]=n/256; - crypto_aead_chacha20poly1305_ietf_encrypt(tBuf.buf+count+2,&nBytes,dataBuf+i,n,tBuf.buf+count,2,NULL,a2cNonce.get(),a2cKey); // encrypt the next portion of dataBuf with authentication tag appended + crypto_aead_chacha20poly1305_ietf_encrypt(tBuf.get()+count+2,&nBytes,dataBuf+i,n,tBuf.get()+count,2,NULL,a2cNonce.get(),a2cKey); // encrypt the next portion of dataBuf with authentication tag appended a2cNonce.inc(); // increment nonce count+=2+n+16; // increment count by 2-byte AAD record + length of JSON + 16-byte authentication tag } - client.write(tBuf.buf,count); // transmit all encrypted frames to Client + client.write(tBuf.get(),count); // transmit all encrypted frames to Client LOG2("-------- SENT ENCRYPTED! --------\n"); diff --git a/src/HAP.h b/src/HAP.h index 67dda3c..7b04973 100644 --- a/src/HAP.h +++ b/src/HAP.h @@ -134,7 +134,7 @@ struct HAPClient { static void hexPrintRow(uint8_t *buf, int n, int minLogLevel=0); // prints 'n' bytes of *buf as HEX, all on one row, subject to specified minimum log level static void charPrintRow(uint8_t *buf, int n, int minLogLevel=0); // prints 'n' bytes of *buf as CHAR, all on one row, subject to specified minimum log level - static Controller *findController(uint8_t *id); // returns pointer to controller with mathching ID (or NULL if no match) + static Controller *findController(uint8_t *id); // returns pointer to controller with matching ID (or NULL if no match) static Controller *getFreeController(); // return pointer to next free controller slot (or NULL if no free slots) static Controller *addController(uint8_t *id, uint8_t *ltpk, boolean admin); // stores data for new Controller with specified data. Returns pointer to Controller slot on success, else NULL static int nAdminControllers(); // returns number of admin Controllers stored diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index e4fbd8a..6d74cb0 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -599,10 +599,10 @@ void Span::processSerialCommand(const char *c){ case 'd': { TempBuffer qBuf(sprintfAttributes(NULL)+1); - sprintfAttributes(qBuf.buf); + sprintfAttributes(qBuf.get()); LOG0("\n*** Attributes Database: size=%d configuration=%d ***\n\n",qBuf.len()-1,hapConfig.configNumber); - prettyPrint(qBuf.buf); + prettyPrint(qBuf.get()); LOG0("\n*** End Database ***\n\n"); } break; @@ -1009,12 +1009,12 @@ void Span::processSerialCommand(const char *c){ LOG0("\n*** Pairing Data used for Cloning another Device\n\n"); size_t olen; TempBuffer tBuf(256); - mbedtls_base64_encode((uint8_t *)tBuf.buf,256,&olen,(uint8_t *)&HAPClient::accessory,sizeof(struct Accessory)); - LOG0("Accessory data: %s\n",tBuf.buf); + mbedtls_base64_encode((uint8_t *)tBuf.get(),256,&olen,(uint8_t *)&HAPClient::accessory,sizeof(struct Accessory)); + LOG0("Accessory data: %s\n",tBuf.get()); for(int i=0;i tBuf(200); size_t olen; - tBuf.buf[0]='\0'; + tBuf.get()[0]='\0'; LOG0(">>> Accessory data: "); - readSerial(tBuf.buf,199); - if(strlen(tBuf.buf)==0){ + readSerial(tBuf.get(),199); + if(strlen(tBuf.get())==0){ LOG0("(cancelled)\n\n"); return; } - mbedtls_base64_decode((uint8_t *)&HAPClient::accessory,sizeof(struct Accessory),&olen,(uint8_t *)tBuf.buf,strlen(tBuf.buf)); + mbedtls_base64_decode((uint8_t *)&HAPClient::accessory,sizeof(struct Accessory),&olen,(uint8_t *)tBuf.get(),strlen(tBuf.get())); if(olen!=sizeof(struct Accessory)){ LOG0("\n*** Error in size of Accessory data - cloning cancelled. Restarting...\n\n"); reboot(); @@ -1044,15 +1044,15 @@ void Span::processSerialCommand(const char *c){ } for(int i=0;i>> Controller data: "); - readSerial(tBuf.buf,199); - if(strlen(tBuf.buf)==0){ + readSerial(tBuf.get(),199); + if(strlen(tBuf.get())==0){ LOG0("(done)\n"); while(i tBuf(sprintfAttributes(NULL,GET_META|GET_PERMS|GET_TYPE|GET_DESC)+1); - sprintfAttributes(tBuf.buf,GET_META|GET_PERMS|GET_TYPE|GET_DESC); - mbedtls_sha512_ret((uint8_t *)tBuf.buf,tBuf.len(),tHash,1); // create SHA-384 hash of JSON (can be any hash - just looking for a unique key) + sprintfAttributes(tBuf.get(),GET_META|GET_PERMS|GET_TYPE|GET_DESC); + mbedtls_sha512_ret((uint8_t *)tBuf.get(),tBuf.len(),tHash,1); // create SHA-384 hash of JSON (can be any hash - just looking for a unique key) boolean changed=false; diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 2334ab2..c1fe5ed 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -712,8 +712,8 @@ 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) TempBuffer tBuf(olen); // create temporary string buffer, with room for trailing null - mbedtls_base64_encode((uint8_t*)tBuf.buf,olen,&olen,data,len ); // encode data into string buf - setString(tBuf.buf); // call setString to continue processing as if characteristic was a string + mbedtls_base64_encode((uint8_t*)tBuf.get(),olen,&olen,data,len ); // encode data into string buf + setString(tBuf.get()); // call setString to continue processing as if characteristic was a string } template void setVal(T val, boolean notify=true){ diff --git a/src/Network.cpp b/src/Network.cpp index bcfe676..4676fb0 100644 --- a/src/Network.cpp +++ b/src/Network.cpp @@ -117,8 +117,8 @@ void Network::apConfigure(){ WiFiServer apServer(80); client=0; - TempBuffer tempBuffer(MAX_HTTP+1); - uint8_t *httpBuf=tempBuffer.buf; + TempBuffer httpBuf(MAX_HTTP+1); +// uint8_t *httpBuf=tempBuffer.buf; const byte DNS_PORT = 53; DNSServer dnsServer; @@ -179,7 +179,7 @@ void Network::apConfigure(){ LOG2(client.remoteIP()); LOG2(" <<<<<<<<<\n"); - int nBytes=client.read(httpBuf,MAX_HTTP+1); // read all available bytes up to maximum allowed+1 + int nBytes=client.read(httpBuf.get(),MAX_HTTP+1); // read all available bytes up to maximum allowed+1 if(nBytes>MAX_HTTP){ // exceeded maximum number of bytes allowed badRequestError(); @@ -187,11 +187,11 @@ void Network::apConfigure(){ continue; } - httpBuf[nBytes]='\0'; // add null character to enable string functions - char *body=(char *)httpBuf; // char pointer to start of HTTP Body + httpBuf.get()[nBytes]='\0'; // add null character to enable string functions + char *body=(char *)httpBuf.get(); // char pointer to start of HTTP Body char *p; // char pointer used for searches - if(!(p=strstr((char *)httpBuf,"\r\n\r\n"))){ + if(!(p=strstr((char *)httpBuf.get(),"\r\n\r\n"))){ badRequestError(); LOG0("\n*** ERROR: Malformed HTTP request (can't find blank line indicating end of BODY)\n\n"); continue; diff --git a/src/Utils.h b/src/Utils.h index 818d98c..46b52a3 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -42,9 +42,14 @@ String mask(char *c, int n); // simply utility that creates a String fr // going out of scope template -struct TempBuffer { +class TempBuffer { + + private: + bufType *buf; int nBytes; + + public: TempBuffer(size_t len){ nBytes=len*sizeof(bufType); @@ -64,6 +69,10 @@ struct TempBuffer { int len(){ return(nBytes); } + + bufType *get(){ + return(buf); + } }; From 0f6e58435ef1699f05324e8dcd8feb6c38a6bebc Mon Sep 17 00:00:00 2001 From: Gregg Date: Wed, 26 Jul 2023 20:35:27 -0500 Subject: [PATCH 08/29] Update Network.cpp --- src/Network.cpp | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/src/Network.cpp b/src/Network.cpp index 4676fb0..32daa67 100644 --- a/src/Network.cpp +++ b/src/Network.cpp @@ -116,10 +116,7 @@ void Network::apConfigure(){ WiFiServer apServer(80); client=0; - - TempBuffer httpBuf(MAX_HTTP+1); -// uint8_t *httpBuf=tempBuffer.buf; - + const byte DNS_PORT = 53; DNSServer dnsServer; IPAddress apIP(192, 168, 4, 1); @@ -178,17 +175,27 @@ void Network::apConfigure(){ LOG2("<<<<<<<<< "); LOG2(client.remoteIP()); LOG2(" <<<<<<<<<\n"); - - int nBytes=client.read(httpBuf.get(),MAX_HTTP+1); // read all available bytes up to maximum allowed+1 - - if(nBytes>MAX_HTTP){ // exceeded maximum number of bytes allowed + + int messageSize=client.available(); + + if(messageSize>MAX_HTTP){ // exceeded maximum number of bytes allowed badRequestError(); - LOG0("\n*** ERROR: Exceeded maximum HTTP message length\n\n"); + LOG0("\n*** ERROR: HTTP message of %d bytes exceeds maximum allowed (%d)\n\n",messageSize,MAX_HTTP); + continue; + } + + TempBuffer httpBuf(messageSize+1); // leave room for null character added below + + int nBytes=client.read(httpBuf.get(),messageSize); // read all available bytes up to maximum allowed+1 + + if(nBytes!=messageSize || client.available()!=0){ + badRequestError(); + LOG0("\n*** ERROR: HTTP message not read correctly. Expected %d bytes, read %d bytes, %d bytes remaining\n\n",messageSize,nBytes,client.available()); continue; } - - httpBuf.get()[nBytes]='\0'; // add null character to enable string functions - char *body=(char *)httpBuf.get(); // char pointer to start of HTTP Body + + httpBuf.get()[nBytes]='\0'; // add null character to enable string functions + char *body=(char *)httpBuf.get(); // char pointer to start of HTTP Body char *p; // char pointer used for searches if(!(p=strstr((char *)httpBuf.get(),"\r\n\r\n"))){ From 793f7882b1eee80ef0d8aadc605731f0c8bee976 Mon Sep 17 00:00:00 2001 From: Gregg Date: Wed, 26 Jul 2023 22:46:20 -0500 Subject: [PATCH 09/29] Replaced appropriate snprintf() with asprintf() and used *m in sscanf Optimized use of heap memory instead of stack memory for temporary variables, and makes code easier to read, provided all heap usage is free() at end of each function. Also removed char *hostName as a member variable of homeSpan. It's never actually used since MDNS constructs its own copy of hostName as needed. --- src/HAP.cpp | 45 ++++++++++++++++++++------------------------- src/HomeSpan.cpp | 25 +++++++++++-------------- src/HomeSpan.h | 1 - src/src.ino | 9 ++++++--- 4 files changed, 37 insertions(+), 43 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index cd0012a..15ae469 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -114,12 +114,6 @@ void HAPClient::init(){ printControllers(); - // create broadcaset name from server base name plus accessory ID (without ':') - - int nChars=snprintf(NULL,0,"%s-%2.2s%2.2s%2.2s%2.2s%2.2s%2.2s",homeSpan.hostNameBase,accessory.ID,accessory.ID+3,accessory.ID+6,accessory.ID+9,accessory.ID+12,accessory.ID+15); - homeSpan.hostName=(char *)malloc(nChars+1); - sprintf(homeSpan.hostName,"%s-%2.2s%2.2s%2.2s%2.2s%2.2s%2.2s",homeSpan.hostNameBase,accessory.ID,accessory.ID+3,accessory.ID+6,accessory.ID+9,accessory.ID+12,accessory.ID+15); - tlv8.create(kTLVType_State,1,"STATE"); // define the actual TLV records needed for the implementation of HAP; one for each kTLVType needed (HAP Table 5-6) tlv8.create(kTLVType_PublicKey,384,"PUBKEY"); tlv8.create(kTLVType_Method,1,"METHOD"); @@ -876,9 +870,8 @@ int HAPClient::getAccessoriesURL(){ TempBuffer jBuf(nBytes+1); homeSpan.sprintfAttributes(jBuf.get()); // create JSON database (will need to re-cast to uint8_t* below) - int nChars=snprintf(NULL,0,"HTTP/1.1 200 OK\r\nContent-Type: application/hap+json\r\nContent-Length: %d\r\n\r\n",nBytes); // create '200 OK' Body with Content Length = size of JSON Buf - char body[nChars+1]; - sprintf(body,"HTTP/1.1 200 OK\r\nContent-Type: application/hap+json\r\nContent-Length: %d\r\n\r\n",nBytes); + char *body; + asprintf(&body,"HTTP/1.1 200 OK\r\nContent-Type: application/hap+json\r\nContent-Length: %d\r\n\r\n",nBytes); LOG2("\n>>>>>>>>>> "); LOG2(client.remoteIP()); @@ -888,6 +881,7 @@ int HAPClient::getAccessoriesURL(){ LOG2("\n"); sendEncrypted(body,(uint8_t *)jBuf.get(),nBytes); + free(body); return(1); @@ -1087,9 +1081,8 @@ int HAPClient::getCharacteristicsURL(char *urlBuf){ boolean sFlag=strstr(jsonBuf,"status"); // status attribute found? - int nChars=snprintf(NULL,0,"HTTP/1.1 %s\r\nContent-Type: application/hap+json\r\nContent-Length: %d\r\n\r\n",!sFlag?"200 OK":"207 Multi-Status",nBytes); - char body[nChars+1]; - sprintf(body,"HTTP/1.1 %s\r\nContent-Type: application/hap+json\r\nContent-Length: %d\r\n\r\n",!sFlag?"200 OK":"207 Multi-Status",nBytes); + char *body; + asprintf(&body,"HTTP/1.1 %s\r\nContent-Type: application/hap+json\r\nContent-Length: %d\r\n\r\n",!sFlag?"200 OK":"207 Multi-Status",nBytes); LOG2("\n>>>>>>>>>> "); LOG2(client.remoteIP()); @@ -1099,7 +1092,8 @@ int HAPClient::getCharacteristicsURL(char *urlBuf){ LOG2("\n"); sendEncrypted(body,(uint8_t *)jsonBuf,nBytes); // note recasting of jsonBuf into uint8_t* - + free(body); + return(1); } @@ -1148,9 +1142,8 @@ int HAPClient::putCharacteristicsURL(char *json){ char jsonBuf[nBytes+1]; homeSpan.sprintfAttributes(pObj,n,jsonBuf); - int nChars=snprintf(NULL,0,"HTTP/1.1 207 Multi-Status\r\nContent-Type: application/hap+json\r\nContent-Length: %d\r\n\r\n",nBytes); // create Body with Content Length = size of JSON Buf - char body[nChars+1]; - sprintf(body,"HTTP/1.1 207 Multi-Status\r\nContent-Type: application/hap+json\r\nContent-Length: %d\r\n\r\n",nBytes); + char *body; + asprintf(&body,"HTTP/1.1 207 Multi-Status\r\nContent-Type: application/hap+json\r\nContent-Length: %d\r\n\r\n",nBytes); LOG2("\n>>>>>>>>>> "); LOG2(client.remoteIP()); @@ -1160,6 +1153,7 @@ int HAPClient::putCharacteristicsURL(char *json){ LOG2("\n"); sendEncrypted(body,(uint8_t *)jsonBuf,nBytes); // note recasting of jsonBuf into uint8_t* + free(body); } @@ -1209,9 +1203,8 @@ int HAPClient::putPrepareURL(char *json){ sprintf(jsonBuf,"{\"status\":%d}",(int)status); int nBytes=strlen(jsonBuf); - int nChars=snprintf(NULL,0,"HTTP/1.1 200 OK\r\nContent-Type: application/hap+json\r\nContent-Length: %d\r\n\r\n",nBytes); // create Body with Content Length = size of JSON Buf - char body[nChars+1]; - sprintf(body,"HTTP/1.1 200 OK\r\nContent-Type: application/hap+json\r\nContent-Length: %d\r\n\r\n",nBytes); + char *body; + asprintf(&body,"HTTP/1.1 200 OK\r\nContent-Type: application/hap+json\r\nContent-Length: %d\r\n\r\n",nBytes); LOG2("\n>>>>>>>>>> "); LOG2(client.remoteIP()); @@ -1221,6 +1214,7 @@ int HAPClient::putPrepareURL(char *json){ LOG2("\n"); sendEncrypted(body,(uint8_t *)jsonBuf,nBytes); // note recasting of jsonBuf into uint8_t* + free(body); return(1); } @@ -1403,9 +1397,8 @@ void HAPClient::eventNotify(SpanBuf *pObj, int nObj, int ignoreClient){ char jsonBuf[nBytes+1]; homeSpan.sprintfNotify(pObj,nObj,jsonBuf,cNum); - int nChars=snprintf(NULL,0,"EVENT/1.0 200 OK\r\nContent-Type: application/hap+json\r\nContent-Length: %d\r\n\r\n",nBytes); // create Body with Content Length = size of JSON Buf - char body[nChars+1]; - sprintf(body,"EVENT/1.0 200 OK\r\nContent-Type: application/hap+json\r\nContent-Length: %d\r\n\r\n",nBytes); + char *body; + asprintf(&body,"EVENT/1.0 200 OK\r\nContent-Type: application/hap+json\r\nContent-Length: %d\r\n\r\n",nBytes); LOG2("\n>>>>>>>>>> "); LOG2(hap[cNum]->client.remoteIP()); @@ -1415,6 +1408,7 @@ void HAPClient::eventNotify(SpanBuf *pObj, int nObj, int ignoreClient){ LOG2("\n"); hap[cNum]->sendEncrypted(body,(uint8_t *)jsonBuf,nBytes); // note recasting of jsonBuf into uint8_t* + free(body); } // if there are characteristic updates to notify client cNum } // if client exists @@ -1431,9 +1425,8 @@ void HAPClient::tlvRespond(){ uint8_t tlvData[nBytes]; // create buffer tlv8.pack(tlvData); // pack TLV records into buffer - int nChars=snprintf(NULL,0,"HTTP/1.1 200 OK\r\nContent-Type: application/pairing+tlv8\r\nContent-Length: %d\r\n\r\n",nBytes); // create Body with Content Length = size of TLV data - char body[nChars+1]; - sprintf(body,"HTTP/1.1 200 OK\r\nContent-Type: application/pairing+tlv8\r\nContent-Length: %d\r\n\r\n",nBytes); + char *body; + asprintf(&body,"HTTP/1.1 200 OK\r\nContent-Type: application/pairing+tlv8\r\nContent-Length: %d\r\n\r\n",nBytes); // create Body with Content Length = size of TLV data LOG2("\n>>>>>>>>>> "); LOG2(client.remoteIP()); @@ -1449,6 +1442,8 @@ void HAPClient::tlvRespond(){ sendEncrypted(body,tlvData,nBytes); } + free(body); + } // tlvRespond ////////////////////////////////////// diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 6d74cb0..70ab0d2 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -429,31 +429,26 @@ void Span::checkConnect(){ memcpy(id,HAPClient::accessory.ID,17); // copy ID bytes id[17]='\0'; // add terminating null - // create broadcaset name from server base name plus accessory ID (without ':') + // create broadcast name from server base name plus accessory ID (without ':') - int nChars; - - if(!hostNameSuffix) - nChars=snprintf(NULL,0,"%s-%.2s%.2s%.2s%.2s%.2s%.2s",hostNameBase,id,id+3,id+6,id+9,id+12,id+15); - else - nChars=snprintf(NULL,0,"%s%s",hostNameBase,hostNameSuffix); - - char hostName[nChars+1]; + char *hostName; if(!hostNameSuffix) - sprintf(hostName,"%s-%.2s%.2s%.2s%.2s%.2s%.2s",hostNameBase,id,id+3,id+6,id+9,id+12,id+15); + asprintf(&hostName,"%s-%.2s%.2s%.2s%.2s%.2s%.2s",hostNameBase,id,id+3,id+6,id+9,id+12,id+15); else - sprintf(hostName,"%s%s",hostNameBase,hostNameSuffix); + asprintf(&hostName,"%s%s",hostNameBase,hostNameSuffix); - char d[strlen(hostName)+1]; - sscanf(hostName,"%[A-Za-z0-9-]",d); + char *d; + sscanf(hostName,"%m[A-Za-z0-9-]",&d); - if(strlen(hostName)>255|| hostName[0]=='-' || hostName[strlen(hostName)-1]=='-' || strlen(hostName)!=strlen(d)){ + if(strlen(hostName)>255 || hostName[0]=='-' || hostName[strlen(hostName)-1]=='-' || strlen(hostName)!=strlen(d)){ LOG0("\n*** Error: Can't start MDNS due to invalid hostname '%s'.\n",hostName); LOG0("*** Hostname must consist of 255 or less alphanumeric characters or a hyphen, except that the hyphen cannot be the first or last character.\n"); LOG0("*** PROGRAM HALTED!\n\n"); while(1); } + + free(d); LOG0("\nStarting MDNS...\n\n"); LOG0("HostName: %s.local:%d\n",hostName,tcpPortNum); @@ -534,6 +529,8 @@ void Span::checkConnect(){ if(wifiCallback) wifiCallback(); + + free(hostName); } // initWiFi diff --git a/src/HomeSpan.h b/src/HomeSpan.h index c1fe5ed..7edb2c7 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -202,7 +202,6 @@ class Span{ const char *displayName; // display name for this device - broadcast as part of Bonjour MDNS const char *hostNameBase; // base of hostName of this device - full host name broadcast by Bonjour MDNS will have 6-byte accessoryID as well as '.local' automatically appended const char *hostNameSuffix=NULL; // optional "suffix" of hostName of this device. If specified, will be used as the hostName suffix instead of the 6-byte accessoryID - char *hostName; // full host name of this device - constructed from hostNameBase and 6-byte AccessoryID const char *modelName; // model name of this device - broadcast as Bonjour field "md" char category[3]=""; // category ID of primary accessory - broadcast as Bonjour field "ci" (HAP Section 13) unsigned long snapTime; // current time (in millis) snapped before entering Service loops() or updates() diff --git a/src/src.ino b/src/src.ino index 378710a..9e8ed0c 100644 --- a/src/src.ino +++ b/src/src.ino @@ -51,10 +51,13 @@ void setup() { Serial.begin(115200); - homeSpan.setControlPin(21); +// homeSpan.setHostNameSuffix(""); + +// homeSpan.setControlPin(21); - homeSpan.setLogLevel(2) - .setStatusPin(13); + homeSpan.setLogLevel(2); + +// .setStatusPin(13); // homeSpan.setSerialInputDisable(true); // homeSpan.enableOTA(); From 131e5b1a929dc68c362c6162a43458e7e3882fbd Mon Sep 17 00:00:00 2001 From: Gregg Date: Fri, 28 Jul 2023 19:38:16 -0500 Subject: [PATCH 10/29] Removed duplicate if(POST /pairings) --- src/HAP.cpp | 18 ++++++------------ src/src.ino | 4 ++++ 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index 15ae469..9da9f5b 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -251,16 +251,6 @@ void HAPClient::processRequest(){ return; } - if(!strncmp(body,"POST /pairings ",15) && // POST PAIRINGS - strstr(body,"Content-Type: application/pairing+tlv8") && // check that content is TLV8 - tlv8.unpack(content,cLen)){ // read TLV content - tlv8.print(2); // print TLV records in form "TAG(INT) LENGTH(INT) VALUES(HEX)" - LOG2("------------ END TLVS! ------------\n"); - - postPairingsURL(); // process URL - return; - } - notFoundError(); LOG0("\n*** ERROR: Bad POST request - URL not found\n\n"); return; @@ -1648,7 +1638,7 @@ void HAPClient::removeController(uint8_t *id){ Controller *slot; if((slot=findController(id))){ // remove controller if found - LOG2("\n***Removed Controller: "); + LOG2("\n*** Removed Controller: "); charPrintRow(id,36,2); LOG2(slot->admin?" (admin)\n":" (regular)\n"); slot->allocated=false; @@ -1660,10 +1650,14 @@ void HAPClient::removeController(uint8_t *id){ STATUS_UPDATE(start(LED_PAIRING_NEEDED),HS_PAIRING_NEEDED) - if(homeSpan.pairCallback) // if set, invoke user-defined Pairing Callback to indicate device has been paired + if(homeSpan.pairCallback) // if set, invoke user-defined Pairing Callback to indicate device has been un-paired homeSpan.pairCallback(false); } + LOG2("\n"); + } else { + LOG2("\n*** Request to Remove Controller Ignored - Controller Not Found: "); + charPrintRow(id,36,2); LOG2("\n"); } diff --git a/src/src.ino b/src/src.ino index 9e8ed0c..431500e 100644 --- a/src/src.ino +++ b/src/src.ino @@ -56,6 +56,10 @@ void setup() { // homeSpan.setControlPin(21); homeSpan.setLogLevel(2); +// homeSpan.reserveSocketConnections(10); + +// homeSpan.setApSSID("HS_Setup"); +// homeSpan.setApPassword(""); // .setStatusPin(13); // homeSpan.setSerialInputDisable(true); From bf057e2fad088dee0a467c7424b40a2e3c9cc8f6 Mon Sep 17 00:00:00 2001 From: Gregg Date: Fri, 28 Jul 2023 19:51:56 -0500 Subject: [PATCH 11/29] modified HAP::receivedEncrypted() to use TempBuffer instead of fixed stack array --- src/HAP.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index 9da9f5b..0296c39 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -1440,24 +1440,26 @@ void HAPClient::tlvRespond(){ int HAPClient::receiveEncrypted(uint8_t *httpBuf, int messageSize){ - uint8_t buf[1042]; // maximum size of encoded message = 2+1024+16 bytes (HAP Section 6.5.2) + uint8_t aad[2]; int nBytes=0; - while(client.read(buf,2)==2){ // read initial 2-byte AAD record + while(client.read(aad,2)==2){ // read initial 2-byte AAD record - int n=buf[0]+buf[1]*256; // compute number of bytes expected in encoded message + int n=aad[0]+aad[1]*256; // compute number of bytes expected in message after decoding if(nBytes+n>messageSize){ // exceeded maximum number of bytes allowed in plaintext message LOG0("\n\n*** ERROR: Decrypted message of %d bytes exceeded maximum expected message length of %d bytes\n\n",nBytes+n,messageSize); return(0); } - if(client.read(buf+2,n+16)!=n+16){ // read expected number of total bytes = n bytes in encoded message + 16 bytes for appended authentication tag + TempBuffer tBuf(n+16); // expected number of total bytes = n bytes in encoded message + 16 bytes for appended authentication tag + + if(client.read(tBuf.get(),tBuf.len())!=tBuf.len()){ LOG0("\n\n*** ERROR: Malformed encrypted message frame\n\n"); return(0); } - if(crypto_aead_chacha20poly1305_ietf_decrypt(httpBuf+nBytes, NULL, NULL, buf+2, n+16, buf, 2, c2aNonce.get(), c2aKey)==-1){ + if(crypto_aead_chacha20poly1305_ietf_decrypt(httpBuf+nBytes, NULL, NULL, tBuf.get(), tBuf.len(), aad, 2, c2aNonce.get(), c2aKey)==-1){ LOG0("\n\n*** ERROR: Can't Decrypt Message\n\n"); return(0); } From 7325baa1a596ff2ca7c3138ed0821ab0c3ebdff7 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 29 Jul 2023 01:21:55 -0500 Subject: [PATCH 12/29] Added HAPClient::listControllers() Will be used for pairings list request --- src/HAP.cpp | 53 ++++++++++++++++++++++++++++++++++++++++-------- src/HAP.h | 1 + src/HomeSpan.cpp | 9 ++++++++ src/TLV.h | 18 ++++++++++++++-- 4 files changed, 71 insertions(+), 10 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index 0296c39..ad6fe14 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -966,8 +966,8 @@ int HAPClient::postPairingsURL(){ tlv8.val(kTLVType_State,pairState_M2); // set State= break; - case 5: - LOG1("List...\n"); + case 5: + LOG1("List...\n"); // NEEDS TO BE IMPLEMENTED - UNSURE IF THIS IS EVER USED BY HOMEKIT @@ -1411,12 +1411,11 @@ void HAPClient::eventNotify(SpanBuf *pObj, int nObj, int ignoreClient){ void HAPClient::tlvRespond(){ - int nBytes=tlv8.pack(NULL); // return number of bytes needed to pack TLV records into a buffer - uint8_t tlvData[nBytes]; // create buffer - tlv8.pack(tlvData); // pack TLV records into buffer + TempBuffer tBuf(tlv8.pack(NULL)); // create buffer to hold TLV data + tlv8.pack(tBuf.get()); // pack TLV records into buffer char *body; - asprintf(&body,"HTTP/1.1 200 OK\r\nContent-Type: application/pairing+tlv8\r\nContent-Length: %d\r\n\r\n",nBytes); // create Body with Content Length = size of TLV data + asprintf(&body,"HTTP/1.1 200 OK\r\nContent-Type: application/pairing+tlv8\r\nContent-Length: %d\r\n\r\n",tBuf.len()); // create Body with Content Length = size of TLV data LOG2("\n>>>>>>>>>> "); LOG2(client.remoteIP()); @@ -1426,10 +1425,10 @@ void HAPClient::tlvRespond(){ if(!cPair){ // unverified, unencrypted session client.print(body); - client.write(tlvData,nBytes); + client.write(tBuf.get(),tBuf.len()); LOG2("------------ SENT! --------------\n"); } else { - sendEncrypted(body,tlvData,nBytes); + sendEncrypted(body,tBuf.get(),tBuf.len()); } free(body); @@ -1667,6 +1666,44 @@ void HAPClient::removeController(uint8_t *id){ ////////////////////////////////////// +int HAPClient::listControllers(uint8_t *tlvBuf){ + + int nBytes=0; + int n; + + tlv8.clear(); + tlv8.val(kTLVType_State,pairState_M2); + + for(int i=0;i tBuf(HAPClient::listControllers(NULL)); + Serial.printf("Buffer Size: %d\n",tBuf.len()); + HAPClient::listControllers(tBuf.get()); + HAPClient::hexPrintRow(tBuf.get(),tBuf.len(),0); + break; + } + case 's': { LOG0("\n*** HomeSpan Status ***\n\n"); diff --git a/src/TLV.h b/src/TLV.h index 505c4f8..f6d7e0a 100644 --- a/src/TLV.h +++ b/src/TLV.h @@ -30,7 +30,7 @@ template class TLV { - int cLen; // total number of bytes in all defined TLV records, including TAG andf LEN (suitable for use as Content-Length in HTTP Body) + int cLen; // total number of bytes in all defined TLV records, including TAG and LEN (suitable for use as Content-Length in HTTP Body) int numTags; // actual number of tags defined struct tlv_t { @@ -59,7 +59,7 @@ public: void print(int minLogLevel=0); // prints all defined TLVs (those with length>0), subject to specified minimum log level int unpack(uint8_t *tlvBuf, int nBytes); // unpacks nBytes of TLV content from single byte buffer into individual TLV records (return 1 on success, 0 if fail) int pack(uint8_t *tlvBuf); // if tlvBuf!=NULL, packs all defined TLV records (LEN>0) into a single byte buffer, spitting large TLVs into separate 255-byte chunks. Returns number of bytes (that would be) stored in buffer - int pack_old(uint8_t *buf); // packs all defined TLV records (LEN>0) into a single byte buffer, spitting large TLVs into separate 255-byte records. Returns number of bytes stored in buffer + int separate(uint8_t *tlvBuf); // if tlvBuf!=NULL, packs a TLV separator into a single byte buffer. Returns number of bytes (that would be) stored in buffer }; // TLV @@ -211,6 +211,20 @@ void TLV::print(int minLogLevel){ } // loop over all TLVs } +////////////////////////////////////// +// TLV separate(tlvBuf) + +template +int TLV::separate(uint8_t *tlvBuf){ + + if(tlvBuf){ // load separator + *tlvBuf++=kTLVType_Separator; + *tlvBuf=0; + } + + return(2); +} + ////////////////////////////////////// // TLV pack(tlvBuf) From 17410e825e38233c880d3810c35389a2a15a13c7 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sat, 29 Jul 2023 08:35:01 -0500 Subject: [PATCH 13/29] Completed adding LIST to HAPClient::postPairingsURL() HomeSpan now correctly responds to pairing list requests --- src/HAP.cpp | 21 +++++++++++++++------ src/HomeSpan.cpp | 9 --------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index ad6fe14..2e5c51a 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -966,14 +966,23 @@ int HAPClient::postPairingsURL(){ tlv8.val(kTLVType_State,pairState_M2); // set State= break; - case 5: - LOG1("List...\n"); + case 5: { + LOG1("List...\n"); - // NEEDS TO BE IMPLEMENTED - UNSURE IF THIS IS EVER USED BY HOMEKIT + TempBuffer tBuf(listControllers(NULL)); - tlv8.clear(); // clear TLV records - tlv8.val(kTLVType_State,pairState_M2); // set State= - break; + char *body; + asprintf(&body,"HTTP/1.1 200 OK\r\nContent-Type: application/pairing+tlv8\r\nContent-Length: %d\r\n\r\n",tBuf.len()); // create Body with Content Length = size of TLV data + + LOG2("\n>>>>>>>>>> "); + LOG2(client.remoteIP()); + LOG2(" >>>>>>>>>>\n"); + LOG2(body); + listControllers(tBuf.get()); + sendEncrypted(body,tBuf.get(),tBuf.len()); + free(body); + return(1); + } default: LOG0("\n*** ERROR: 'Method' TLV record is either missing or not set to either 3, 4, or 5 as required\n\n"); diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 082c547..70ab0d2 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -554,15 +554,6 @@ void Span::processSerialCommand(const char *c){ switch(c[0]){ - case 'Z': { - Serial.printf("\n\n"); - TempBuffer tBuf(HAPClient::listControllers(NULL)); - Serial.printf("Buffer Size: %d\n",tBuf.len()); - HAPClient::listControllers(tBuf.get()); - HAPClient::hexPrintRow(tBuf.get(),tBuf.len(),0); - break; - } - case 's': { LOG0("\n*** HomeSpan Status ***\n\n"); From a84429f930858e321da45dcce3276fcfea49bdc3 Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 30 Jul 2023 21:37:47 -0500 Subject: [PATCH 14/29] Refactored TLV * Added support for zero-length TLV * Added SEPARATOR as a formal kTLVType (and updated listControllers() to use) * Added `uint8_t *buf(tagType tag, uint8_t *src, int len);` to load buffer needing external memcpy (and updated listControllers() to use) --- src/HAP.cpp | 97 +++++++++++++++++++++++++++++------------------- src/HAP.h | 4 +- src/HomeSpan.cpp | 8 ++++ src/HomeSpan.h | 2 + src/TLV.h | 77 +++++++++++++++++++++++--------------- src/Utils.h | 9 ++++- 6 files changed, 126 insertions(+), 71 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index 2e5c51a..57d474a 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -106,6 +106,16 @@ void HAPClient::init(){ nvs_commit(hapNVS); // commit to NVS } + if(!nvs_get_blob(hapNVS,"CONTROLLERS",NULL,&len)){ // if found long-term Controller Pairings data from NVS + TempBuffer tBuf(len/sizeof(Controller)); + nvs_get_blob(hapNVS,"CONTROLLERS",tBuf.get(),&len); // retrieve data + Serial.printf("*** SIZE %d ***\n",tBuf.size()); + for(int i=0;i @@ -526,7 +537,7 @@ int HAPClient::postPairSetupURL(){ tlv8.print(2); // print decrypted TLV data LOG2("------- END DECRYPTED TLVS! -------\n"); - if(!tlv8.buf(kTLVType_Identifier) || !tlv8.buf(kTLVType_PublicKey) || !tlv8.buf(kTLVType_Signature)){ + if(!tlv8.len(kTLVType_Identifier) || !tlv8.len(kTLVType_PublicKey) || !tlv8.len(kTLVType_Signature)){ LOG0("\n*** ERROR: One or more of required 'Identifier,' 'PublicKey,' and 'Signature' TLV records for this step is bad or missing\n\n"); tlv8.clear(); // clear TLV records tlv8.val(kTLVType_State,pairState_M6); // set State= @@ -679,7 +690,7 @@ int HAPClient::postPairVerifyURL(){ case pairState_M1: // 'Verify Start Request' - if(!tlv8.buf(kTLVType_PublicKey)){ + if(!tlv8.len(kTLVType_PublicKey)){ LOG0("\n*** ERROR: Required 'PublicKey' TLV record for this step is bad or missing\n\n"); tlv8.clear(); // clear TLV records tlv8.val(kTLVType_State,pairState_M2); // set State= @@ -745,7 +756,7 @@ int HAPClient::postPairVerifyURL(){ case pairState_M3: // 'Verify Finish Request' - if(!tlv8.buf(kTLVType_EncryptedData)){ + if(!tlv8.len(kTLVType_EncryptedData)){ LOG0("\n*** ERROR: Required 'EncryptedData' TLV record for this step is bad or missing\n\n"); tlv8.clear(); // clear TLV records tlv8.val(kTLVType_State,pairState_M4); // set State= @@ -782,7 +793,7 @@ int HAPClient::postPairVerifyURL(){ tlv8.print(2); // print decrypted TLV data LOG2("------- END DECRYPTED TLVS! -------\n"); - if(!tlv8.buf(kTLVType_Identifier) || !tlv8.buf(kTLVType_Signature)){ + if(!tlv8.len(kTLVType_Identifier) || !tlv8.len(kTLVType_Signature)){ LOG0("\n*** ERROR: One or more of required 'Identifier,' and 'Signature' TLV records for this step is bad or missing\n\n"); tlv8.clear(); // clear TLV records tlv8.val(kTLVType_State,pairState_M4); // set State= @@ -905,7 +916,7 @@ int HAPClient::postPairingsURL(){ case 3: LOG1("Add...\n"); - if(!tlv8.buf(kTLVType_Identifier) || !tlv8.buf(kTLVType_PublicKey) || !tlv8.buf(kTLVType_Permissions)){ + if(!tlv8.len(kTLVType_Identifier) || !tlv8.len(kTLVType_PublicKey) || !tlv8.len(kTLVType_Permissions)){ LOG0("\n*** ERROR: One or more of required 'Identifier,' 'PublicKey,' and 'Permissions' TLV records for this step is bad or missing\n\n"); tlv8.clear(); // clear TLV records tlv8.val(kTLVType_State,pairState_M2); // set State= @@ -944,7 +955,7 @@ int HAPClient::postPairingsURL(){ case 4: LOG1("Remove...\n"); - if(!tlv8.buf(kTLVType_Identifier)){ + if(!tlv8.len(kTLVType_Identifier)){ LOG0("\n*** ERROR: Required 'Identifier' TLV record for this step is bad or missing\n\n"); tlv8.clear(); // clear TLV records tlv8.val(kTLVType_State,pairState_M2); // set State= @@ -1683,21 +1694,13 @@ int HAPClient::listControllers(uint8_t *tlvBuf){ tlv8.clear(); tlv8.val(kTLVType_State,pairState_M2); - for(int i=0;i tBuf(controllerList.size()); // create temporary buffer to hold Controller data + + for(auto it=controllerList.begin();it!=controllerList.end();it++) // store Controller data in temporary buffer + tBuf.get()[n++]=(*it); + + nvs_set_blob(hapNVS,"CONTROLLERS",tBuf.get(),tBuf.len()); // update data + nvs_commit(hapNVS); // commit to NVS } + ////////////////////////////////////// Nonce::Nonce(){ @@ -1766,13 +1786,14 @@ void Nonce::inc(){ // instantiate all static HAP Client structures and data -TLV HAPClient::tlv8; +TLV HAPClient::tlv8; nvs_handle HAPClient::hapNVS; nvs_handle HAPClient::srpNVS; HKDF HAPClient::hkdf; pairState HAPClient::pairStatus; Accessory HAPClient::accessory; -Controller HAPClient::controllers[MAX_CONTROLLERS]; +Controller HAPClient::controllers[MAX_CONTROLLERS]; +list HAPClient::controllerList; SRP6A HAPClient::srp; int HAPClient::conNum; diff --git a/src/HAP.h b/src/HAP.h index d46ea63..0c7cedd 100644 --- a/src/HAP.h +++ b/src/HAP.h @@ -77,7 +77,7 @@ struct HAPClient { static const int MAX_CONTROLLERS=16; // maximum number of paired controllers (HAP requires at least 16) static const int MAX_ACCESSORIES=41; // maximum number of allowed Acessories (HAP limit=150, but not enough memory in ESP32 to run that many) - static TLV tlv8; // TLV8 structure (HAP Section 14.1) with space for 10 TLV records of type kTLVType (HAP Table 5-6) + static TLV tlv8; // TLV8 structure (HAP Section 14.1) with space for 11 TLV records of type kTLVType (HAP Table 5-6) static nvs_handle hapNVS; // handle for non-volatile-storage of HAP data static nvs_handle srpNVS; // handle for non-volatile-storage of SRP data static HKDF hkdf; // generates (and stores) HKDF-SHA-512 32-byte keys derived from an inputKey of arbitrary length, a salt string, and an info string @@ -85,6 +85,7 @@ struct HAPClient { static SRP6A srp; // stores all SRP-6A keys used for Pair-Setup static Accessory accessory; // Accessory ID and Ed25519 public and secret keys- permanently stored static Controller controllers[MAX_CONTROLLERS]; // Paired Controller IDs and ED25519 long-term public keys - permanently stored + static list controllerList; // linked-list of Paired Controller IDs and ED25519 long-term public keys - permanently stored static int conNum; // connection number - used to keep track of per-connection EV notifications // individual structures and data defined for each Hap Client connection @@ -142,6 +143,7 @@ struct HAPClient { static void removeController(uint8_t *id); // removes specific Controller. If no remaining admin Controllers, remove all others (if any) as per HAP requirements. static void printControllers(int minLogLevel=0); // prints IDs of all allocated (paired) Controller, subject to specified minimum log level static int listControllers(uint8_t *tlvBuf); // creates and prints a multi-TLV list of Controllers (HAP Section 5.12) + static void saveControllers(); // saves Controller list in NVS static void checkNotifications(); // checks for Event Notifications and reports to controllers as needed (HAP Section 6.8) static void checkTimedWrites(); // checks for expired Timed Write PIDs, and clears any found (HAP Section 6.7.2.4) static void eventNotify(SpanBuf *pObj, int nObj, int ignoreClient=-1); // transmits EVENT Notifications for nObj SpanBuf objects, pObj, with optional flag to ignore a specific client diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index 70ab0d2..a5fbc5f 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -554,6 +554,14 @@ void Span::processSerialCommand(const char *c){ switch(c[0]){ + case 'Z': { + TempBuffer tBuf(HAPClient::listControllers(NULL)); + HAPClient::listControllers(tBuf.get()); + Serial.printf("SIZE = %d\n",tBuf.len()); + HAPClient::hexPrintRow(tBuf.get(),tBuf.len()); + break; + } + case 's': { LOG0("\n*** HomeSpan Status ***\n\n"); diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 7edb2c7..99a58ea 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -55,6 +56,7 @@ using std::vector; using std::unordered_map; using std::unordered_set; +using std::list; enum { GET_AID=1, diff --git a/src/TLV.h b/src/TLV.h index f6d7e0a..6bca6db 100644 --- a/src/TLV.h +++ b/src/TLV.h @@ -55,11 +55,11 @@ public: int val(tagType tag, uint8_t val); // sets and returns VAL for TLV with matching TAG (or -1 if no match) uint8_t *buf(tagType tag); // returns VAL Buffer for TLV with matching TAG (or NULL if no match) uint8_t *buf(tagType tag, int len); // set length and returns VAL Buffer for TLV with matching TAG (or NULL if no match or if LEN>MAX) + uint8_t *buf(tagType tag, uint8_t *src, int len); // copies len bytes of src into VAL buffer, and sets length to len, for TLV with matching TAG; returns VAL Buffer on success, or NULL if no match or if LEN>MAX int len(tagType tag); // returns LEN for TLV matching TAG (or 0 if TAG is found but LEN not yet set; -1 if no match at all) void print(int minLogLevel=0); // prints all defined TLVs (those with length>0), subject to specified minimum log level int unpack(uint8_t *tlvBuf, int nBytes); // unpacks nBytes of TLV content from single byte buffer into individual TLV records (return 1 on success, 0 if fail) int pack(uint8_t *tlvBuf); // if tlvBuf!=NULL, packs all defined TLV records (LEN>0) into a single byte buffer, spitting large TLVs into separate 255-byte chunks. Returns number of bytes (that would be) stored in buffer - int separate(uint8_t *tlvBuf); // if tlvBuf!=NULL, packs a TLV separator into a single byte buffer. Returns number of bytes (that would be) stored in buffer }; // TLV @@ -129,8 +129,12 @@ int TLV::val(tagType tag){ tlv_t *tlv=find(tag); - if(tlv && tlv->len>0) - return(tlv->val[0]); + if(tlv && tlv->len>=0){ + if(tlv->maxLen>0) + return(tlv->val[0]); + else + return(0); + } return(-1); } @@ -144,8 +148,9 @@ int TLV::val(tagType tag, uint8_t val){ tlv_t *tlv=find(tag); if(tlv){ - tlv->val[0]=val; - tlv->len=1; + if(tlv->maxLen>0) + tlv->val[0]=val; + tlv->len=(tlv->maxLen>0); cLen+=tlv->len+2; return(val); } @@ -188,6 +193,28 @@ uint8_t *TLV::buf(tagType tag, int len){ return(NULL); } +////////////////////////////////////// +// TLV buf(tag, src, len) + +template +uint8_t *TLV::buf(tagType tag, uint8_t *src, int len){ + + tlv_t *tlv=find(tag); + + if(tlv && len<=tlv->maxLen){ + tlv->len=len; + cLen+=tlv->len; + + for(int i=0;ilen;i+=255) + cLen+=2; + + memcpy(tlv->val,src,len); + return(tlv->val); + } + + return(NULL); +} + ////////////////////////////////////// // TLV print() @@ -199,7 +226,7 @@ void TLV::print(int minLogLevel){ for(int i=0;i0){ + if(tlv[i].len>=0){ Serial.printf("%s(%d) ",tlv[i].name,tlv[i].len); for(int j=0;j::print(int minLogLevel){ } // len>0 } // loop over all TLVs -} - -////////////////////////////////////// -// TLV separate(tlvBuf) - -template -int TLV::separate(uint8_t *tlvBuf){ - - if(tlvBuf){ // load separator - *tlvBuf++=kTLVType_Separator; - *tlvBuf=0; - } - - return(2); -} +} ////////////////////////////////////// // TLV pack(tlvBuf) @@ -234,19 +247,23 @@ int TLV::pack(uint8_t *tlvBuf){ int n=0; int nBytes; - for(int i=0;i0){ - for(int j=0;j=0){ + int j=0; + do{ + int wBytes=nBytes>255?255:nBytes; if(tlvBuf!=NULL){ *tlvBuf++=tlv[i].tag; - *tlvBuf++=nBytes>255?255:nBytes; - memcpy(tlvBuf,tlv[i].val+j,nBytes>255?255:nBytes); - tlvBuf+=nBytes>255?255:nBytes; + *tlvBuf++=wBytes; + memcpy(tlvBuf,tlv[i].val+j,wBytes); + tlvBuf+=wBytes; } - n+=(nBytes>255?255:nBytes)+2; - } // j-loop - } // len>0 + n+=wBytes+2; + j+=wBytes; + nBytes-=wBytes; + } while(nBytes>0); + } // len>=0 } // loop over all TLVs diff --git a/src/Utils.h b/src/Utils.h index 46b52a3..4272494 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -48,11 +48,12 @@ class TempBuffer { bufType *buf; int nBytes; + int nElements; public: - TempBuffer(size_t len){ - nBytes=len*sizeof(bufType); + TempBuffer(int _nElements) : nElements(_nElements) { + nBytes=nElements*sizeof(bufType); buf=(bufType *)malloc(nBytes); if(buf==NULL){ Serial.print("\n\n*** FATAL ERROR: Requested allocation of "); @@ -70,6 +71,10 @@ class TempBuffer { return(nBytes); } + int size(){ + return(nElements); + } + bufType *get(){ return(buf); } From 5a356432b3e1cd3a5718a1b7d16b02c781b3a5bb Mon Sep 17 00:00:00 2001 From: Gregg Date: Sun, 30 Jul 2023 21:54:14 -0500 Subject: [PATCH 15/29] Simplified `uint8_t *TLV::buf(tagType tag, int len)` Also updated HAP.cpp to use new `uint8_t *TLV::buf(tagType tag, uint8_t *src, int len)` --- src/HAP.cpp | 12 ++++++------ src/TLV.h | 18 ++++-------------- 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index 57d474a..0947103 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -609,8 +609,8 @@ int HAPClient::postPairSetupURL(){ crypto_sign_detached(tlv8.buf(kTLVType_Signature,64),NULL,accessoryInfo,accessoryInfoLen,accessory.LTSK); // produce signature of accessoryInfo using AccessoryLTSK (Ed25519 long-term secret key) - memcpy(tlv8.buf(kTLVType_Identifier,accessoryPairingIDLen),accessoryPairingID,accessoryPairingIDLen); // set Identifier TLV record as accessoryPairingID - memcpy(tlv8.buf(kTLVType_PublicKey,accessoryLTPKLen),accessoryLTPK,accessoryLTPKLen); // set PublicKey TLV record as accessoryLTPK + tlv8.buf(kTLVType_Identifier,accessoryPairingID,accessoryPairingIDLen); // set Identifier TLV record as accessoryPairingID + tlv8.buf(kTLVType_PublicKey,accessoryLTPK,accessoryLTPKLen); // set PublicKey TLV record as accessoryLTPK LOG2("------- ENCRYPTING SUB-TLVS -------\n"); @@ -722,7 +722,7 @@ int HAPClient::postPairVerifyURL(){ crypto_sign_detached(tlv8.buf(kTLVType_Signature,64),NULL,accessoryInfo,accessoryInfoLen,accessory.LTSK); // produce signature of accessoryInfo using AccessoryLTSK (Ed25519 long-term secret key) - memcpy(tlv8.buf(kTLVType_Identifier,accessoryPairingIDLen),accessoryPairingID,accessoryPairingIDLen); // set Identifier TLV record as accessoryPairingID + tlv8.buf(kTLVType_Identifier,accessoryPairingID,accessoryPairingIDLen); // set Identifier TLV record as accessoryPairingID LOG2("------- ENCRYPTING SUB-TLVS -------\n"); @@ -744,9 +744,9 @@ int HAPClient::postPairVerifyURL(){ LOG2("---------- END SUB-TLVS! ----------\n"); - tlv8.buf(kTLVType_EncryptedData,edLen); // set length of EncryptedData TLV record, which should now include the Authentication Tag at the end as required by HAP - tlv8.val(kTLVType_State,pairState_M2); // set State= - memcpy(tlv8.buf(kTLVType_PublicKey,32),publicCurveKey,32); // set PublicKey to Accessory's Curve25519 public key + tlv8.buf(kTLVType_EncryptedData,edLen); // set length of EncryptedData TLV record, which should now include the Authentication Tag at the end as required by HAP + tlv8.val(kTLVType_State,pairState_M2); // set State= + tlv8.buf(kTLVType_PublicKey,publicCurveKey,32); // set PublicKey to Accessory's Curve25519 public key tlvRespond(); // send response to client return(1); diff --git a/src/TLV.h b/src/TLV.h index 6bca6db..9890b54 100644 --- a/src/TLV.h +++ b/src/TLV.h @@ -178,19 +178,7 @@ uint8_t *TLV::buf(tagType tag){ template uint8_t *TLV::buf(tagType tag, int len){ - tlv_t *tlv=find(tag); - - if(tlv && len<=tlv->maxLen){ - tlv->len=len; - cLen+=tlv->len; - - for(int i=0;ilen;i+=255) - cLen+=2; - - return(tlv->val); - } - - return(NULL); + return(buf(tag,NULL,len)); } ////////////////////////////////////// @@ -208,7 +196,9 @@ uint8_t *TLV::buf(tagType tag, uint8_t *src, int len){ for(int i=0;ilen;i+=255) cLen+=2; - memcpy(tlv->val,src,len); + if(src) + memcpy(tlv->val,src,len); + return(tlv->val); } From 3396a5ff96bf51fe318c6178c6695bcb9d723f45 Mon Sep 17 00:00:00 2001 From: Gregg Date: Thu, 3 Aug 2023 22:29:31 -0500 Subject: [PATCH 16/29] Refactored Controller Structure and Add/Remove/List/Save Functions Changed fixed-size array `struct Controller[MAX_CONTROLLERS]` to a dynamic linked-list of Controllers. Re-coded all related functions. MAX_CONTROLLERS no sets the size of any buffers but is only used to limit the ultimate size of the linked-list. Saved about 1K of RAM since most of the time there are only 2 Controllers defined (as opposed to the 16 allowed). --- src/HAP.cpp | 306 +++++++++++++++++++-------------------------- src/HAP.h | 22 ++-- src/HAPConstants.h | 1 + src/HomeSpan.cpp | 47 +++---- 4 files changed, 164 insertions(+), 212 deletions(-) diff --git a/src/HAP.cpp b/src/HAP.cpp index 0947103..5a4c4d9 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -80,7 +80,7 @@ void HAPClient::init(){ if(!nvs_get_blob(hapNVS,"ACCESSORY",NULL,&len)){ // if found long-term Accessory data in NVS nvs_get_blob(hapNVS,"ACCESSORY",&accessory,&len); // retrieve data } else { - LOG0("Generating new random Accessory ID and Long-Term Ed25519 Signature Keys...\n"); + LOG0("Generating new random Accessory ID and Long-Term Ed25519 Signature Keys...\n\n"); uint8_t buf[6]; char cBuf[18]; @@ -95,21 +95,9 @@ void HAPClient::init(){ nvs_commit(hapNVS); // commit to NVS } - if(!nvs_get_blob(hapNVS,"CONTROLLERS",NULL,&len)){ // if found long-term Controller Pairings data from NVS - nvs_get_blob(hapNVS,"CONTROLLERS",controllers,&len); // retrieve data - } else { - LOG0("Initializing storage for Paired Controllers data...\n\n"); - - HAPClient::removeControllers(); // clear all Controller data - - nvs_set_blob(hapNVS,"CONTROLLERS",controllers,sizeof(controllers)); // update data - nvs_commit(hapNVS); // commit to NVS - } - if(!nvs_get_blob(hapNVS,"CONTROLLERS",NULL,&len)){ // if found long-term Controller Pairings data from NVS TempBuffer tBuf(len/sizeof(Controller)); nvs_get_blob(hapNVS,"CONTROLLERS",tBuf.get(),&len); // retrieve data - Serial.printf("*** SIZE %d ***\n",tBuf.size()); for(int i=0;i tlv8.val(kTLVType_Error,tagError_Authentication); // set Error=Authentication @@ -813,6 +800,10 @@ int HAPClient::postPairVerifyURL(){ return(0); } + LOG2("\n*** Verifying session with Controller ID: "); + charPrintRow(tPair->ID,36,2); + LOG2("...\n"); + size_t iosDeviceInfoLen=32+36+32; uint8_t iosDeviceInfo[iosDeviceInfoLen]; @@ -833,7 +824,7 @@ int HAPClient::postPairVerifyURL(){ tlv8.val(kTLVType_State,pairState_M4); // set State= tlvRespond(); // send response to client (unencrypted since cPair=NULL) - cPair=tPair; // save Controller for this connection slot - connection is not verified and should be encrypted going forward + cPair=tPair; // save Controller for this connection slot - connection is now verified and should be encrypted going forward hkdf.create(a2cKey,sharedCurveKey,32,"Control-Salt","Control-Read-Encryption-Key"); // create AccessoryToControllerKey (HAP Section 6.5.2) hkdf.create(c2aKey,sharedCurveKey,32,"Control-Salt","Control-Write-Encryption-Key"); // create ControllerToAccessoryKey (HAP Section 6.5.2) @@ -897,8 +888,6 @@ int HAPClient::postPairingsURL(){ return(0); } - Controller *newCont; - LOG1("In Post Pairings #"); LOG1(conNum); LOG1(" ("); @@ -910,72 +899,61 @@ int HAPClient::postPairingsURL(){ badRequestError(); // return with 400 error, which closes connection return(0); } - + switch(tlv8.val(kTLVType_Method)){ - case 3: + case 3: { LOG1("Add...\n"); if(!tlv8.len(kTLVType_Identifier) || !tlv8.len(kTLVType_PublicKey) || !tlv8.len(kTLVType_Permissions)){ LOG0("\n*** ERROR: One or more of required 'Identifier,' 'PublicKey,' and 'Permissions' TLV records for this step is bad or missing\n\n"); - tlv8.clear(); // clear TLV records - tlv8.val(kTLVType_State,pairState_M2); // set State= - tlv8.val(kTLVType_Error,tagError_Unknown); // set Error=Unknown (there is no specific error type for missing/bad TLV data) - break; - } + tlv8.clear(); + tlv8.val(kTLVType_Error,tagError_Unknown); - if(!cPair->admin){ + } else if(!cPair->admin){ LOG0("\n*** ERROR: Controller making request does not have admin privileges to add/update other Controllers\n\n"); - tlv8.clear(); // clear TLV records - tlv8.val(kTLVType_State,pairState_M2); // set State= - tlv8.val(kTLVType_Error,tagError_Authentication); // set Error=Authentication - break; - } - - if((newCont=findController(tlv8.buf(kTLVType_Identifier))) && memcmp(tlv8.buf(kTLVType_PublicKey),newCont->LTPK,32)){ // requested Controller already exists, but LTPKs don't match - LOG0("\n*** ERROR: Invalid request to update the LTPK of an exsiting Controller\n\n"); - tlv8.clear(); // clear TLV records - tlv8.val(kTLVType_State,pairState_M2); // set State= - tlv8.val(kTLVType_Error,tagError_Unknown); // set Error=Unknown - break; - } - - if(!addController(tlv8.buf(kTLVType_Identifier),tlv8.buf(kTLVType_PublicKey),tlv8.val(kTLVType_Permissions)==1?true:false)){ - LOG0("\n*** ERROR: Can't pair more than %d Controllers\n\n",MAX_CONTROLLERS); - tlv8.clear(); // clear TLV records - tlv8.val(kTLVType_State,pairState_M2); // set State= - tlv8.val(kTLVType_Error,tagError_MaxPeers); // set Error=MaxPeers - break; + tlv8.clear(); + tlv8.val(kTLVType_Error,tagError_Authentication); + + } else { + tagError err=addController(tlv8.buf(kTLVType_Identifier),tlv8.buf(kTLVType_PublicKey),tlv8.val(kTLVType_Permissions)); + tlv8.clear(); + if(err!=tagError_None) + tlv8.val(kTLVType_Error,err); } - tlv8.clear(); // clear TLV records - tlv8.val(kTLVType_State,pairState_M2); // set State= - break; + tlv8.val(kTLVType_State,pairState_M2); + tlvRespond(); + return(1); + } - case 4: + case 4: { LOG1("Remove...\n"); + uint8_t id[36]; + if(!tlv8.len(kTLVType_Identifier)){ LOG0("\n*** ERROR: Required 'Identifier' TLV record for this step is bad or missing\n\n"); - tlv8.clear(); // clear TLV records - tlv8.val(kTLVType_State,pairState_M2); // set State= - tlv8.val(kTLVType_Error,tagError_Unknown); // set Error=Unknown (there is no specific error type for missing/bad TLV data) - break; - } - - if(!cPair->admin){ + tlv8.clear(); + tlv8.val(kTLVType_Error,tagError_Unknown); + + } else if(!cPair->admin){ LOG0("\n*** ERROR: Controller making request does not have admin privileges to remove Controllers\n\n"); - tlv8.clear(); // clear TLV records - tlv8.val(kTLVType_State,pairState_M2); // set State= - tlv8.val(kTLVType_Error,tagError_Authentication); // set Error=Authentication - break; + tlv8.clear(); + tlv8.val(kTLVType_Error,tagError_Authentication); + } else { + memcpy(id,tlv8.buf(kTLVType_Identifier),36); + tlv8.clear(); } - removeController(tlv8.buf(kTLVType_Identifier)); + tlv8.val(kTLVType_State,pairState_M2); + tlvRespond(); // must send response before removing Controller below - tlv8.clear(); // clear TLV records - tlv8.val(kTLVType_State,pairState_M2); // set State= - break; + if(tlv8.val(kTLVType_Error)==-1) + removeController(id); + + return(1); + } case 5: { LOG1("List...\n"); @@ -991,37 +969,17 @@ int HAPClient::postPairingsURL(){ LOG2(body); listControllers(tBuf.get()); sendEncrypted(body,tBuf.get(),tBuf.len()); - free(body); + free(body); + return(1); } - default: + default: { LOG0("\n*** ERROR: 'Method' TLV record is either missing or not set to either 3, 4, or 5 as required\n\n"); badRequestError(); // return with 400 error, which closes connection return(0); - break; - } - - nvs_set_blob(hapNVS,"CONTROLLERS",controllers,sizeof(controllers)); // update Controller data - nvs_commit(hapNVS); // commit to NVS - - tlvRespond(); - - // re-check connections and close any (or all) clients as a result of controllers that were removed above - // must be performed AFTER sending the TLV response, since that connection itself may be terminated below - - for(int i=0;iclient){ // if slot is connected - - if(!nAdminControllers() || (hap[i]->cPair && !hap[i]->cPair->allocated)){ // accessory unpaired, OR client connection is verified but points to a newly *unallocated* controller - LOG1("*** Terminating Client #"); - LOG1(i); - LOG1("\n"); - hap[i]->client.stop(); - } - - } // if client connected - } // loop over all connection slots + } + } // switch return(1); } @@ -1316,8 +1274,8 @@ int HAPClient::getStatusURL(){ char mbtlsv[64]; mbedtls_version_get_string_full(mbtlsv); response+="MbedTLS Version:" + String(mbtlsv) + "\n"; - - response+="HomeKit Status:" + String(nAdminControllers()?"PAIRED":"NOT PAIRED") + "\n"; + + response+="HomeKit Status:" + String(HAPClient::nAdminControllers()?"PAIRED":"NOT PAIRED") + "\n"; response+="Max Log Entries:" + String(homeSpan.webLog.maxEntries) + "\n"; response+="\n"; response+="

"; @@ -1581,10 +1539,10 @@ void HAPClient::charPrintRow(uint8_t *buf, int n, int minLogLevel){ ////////////////////////////////////// Controller *HAPClient::findController(uint8_t *id){ - - for(int i=0;iLTPK,ltpk,32); - slot->admin=admin; - LOG2("\n*** Updated Controller: "); - charPrintRow(id,36,2); - LOG2(slot->admin?" (admin)\n\n":" (regular)\n\n"); - return(slot); - } - - if((slot=getFreeController())){ // get slot for new controller, if available - slot->allocated=true; - memcpy(slot->ID,id,36); - memcpy(slot->LTPK,ltpk,32); - slot->admin=admin; - LOG2("\n*** Added Controller: "); - charPrintRow(id,36,2); - LOG2(slot->admin?" (admin)\n\n":" (regular)\n\n"); - return(slot); - } - - return(NULL); -} - -////////////////////////////////////// - int HAPClient::nAdminControllers(){ int n=0; - - for(int i=0;iLTPK,sizeof(cTemp->LTPK))){ // existing controller with same LTPK + LOG2("\n*** Updated Controller: "); + charPrintRow(id,36,2); + LOG2(" from %s to %s\n\n",cTemp->admin?"(admin)":"(regular)",admin?"(admin)":"(regular)"); + cTemp->admin=admin; + saveControllers(); + } else { + LOG0("\n*** ERROR: Invalid request to update the LTPK of an existing Controller\n\n"); + err=tagError_Unknown; + } + + return(err); +} ////////////////////////////////////// void HAPClient::removeController(uint8_t *id){ - Controller *slot; + auto it=std::find_if(controllerList.begin(), controllerList.end(), [id](const Controller& cTemp){return(!memcmp(cTemp.ID,id,sizeof(cTemp.ID)));}); - if((slot=findController(id))){ // remove controller if found - LOG2("\n*** Removed Controller: "); - charPrintRow(id,36,2); - LOG2(slot->admin?" (admin)\n":" (regular)\n"); - slot->allocated=false; - - if(nAdminControllers()==0){ // if no more admins, remove all controllers - removeControllers(); - LOG1("That was last Admin Controller! Removing any remaining Regular Controllers and unpairing Accessory\n"); - mdns_service_txt_item_set("_hap","_tcp","sf","1"); // set Status Flag = 1 (Table 6-8) - - STATUS_UPDATE(start(LED_PAIRING_NEEDED),HS_PAIRING_NEEDED) - - if(homeSpan.pairCallback) // if set, invoke user-defined Pairing Callback to indicate device has been un-paired - homeSpan.pairCallback(false); - } - - LOG2("\n"); - } else { + if(it==controllerList.end()){ LOG2("\n*** Request to Remove Controller Ignored - Controller Not Found: "); charPrintRow(id,36,2); LOG2("\n"); + return; } -} + LOG1("\n*** Removing Controller: "); + charPrintRow((*it).ID,36,2); + LOG1((*it).admin?" (admin)\n":" (regular)\n"); + + tearDown((*it).ID); // teardown any connections using this Controller + controllerList.erase(it); // remove Controller + + if(!nAdminControllers()){ // no more admin Controllers + + LOG1("That was last Admin Controller! Removing any remaining Regular Controllers and unpairing Accessory\n"); + + tearDown(NULL); // teardown all remaining connections + controllerList.clear(); // remove all remaining Controllers + mdns_service_txt_item_set("_hap","_tcp","sf","1"); // set Status Flag = 1 (Table 6-8) + STATUS_UPDATE(start(LED_PAIRING_NEEDED),HS_PAIRING_NEEDED) // set optional Status LED + if(homeSpan.pairCallback) // if set, invoke user-defined Pairing Callback to indicate device has been un-paired + homeSpan.pairCallback(false); + } + + saveControllers(); +} + +////////////////////////////////////// + +void HAPClient::tearDown(uint8_t *id){ + + for(int i=0;iclient && (id==NULL || (hap[i]->cPair && !memcmp(id,hap[i]->cPair->ID,36)))){ + LOG1("*** Terminating Client #%d\n",i); + hap[i]->client.stop(); + } + } +} ////////////////////////////////////// @@ -1744,12 +1698,9 @@ void HAPClient::saveControllers(){ return; } - int n=0; - TempBuffer tBuf(controllerList.size()); // create temporary buffer to hold Controller data + TempBuffer tBuf(controllerList.size()); // create temporary buffer to hold Controller data + std::copy(controllerList.begin(),controllerList.end(),tBuf.get()); // copy data from linked list to buffer - for(auto it=controllerList.begin();it!=controllerList.end();it++) // store Controller data in temporary buffer - tBuf.get()[n++]=(*it); - nvs_set_blob(hapNVS,"CONTROLLERS",tBuf.get(),tBuf.len()); // update data nvs_commit(hapNVS); // commit to NVS } @@ -1792,7 +1743,6 @@ nvs_handle HAPClient::srpNVS; HKDF HAPClient::hkdf; pairState HAPClient::pairStatus; Accessory HAPClient::accessory; -Controller HAPClient::controllers[MAX_CONTROLLERS]; list HAPClient::controllerList; SRP6A HAPClient::srp; int HAPClient::conNum; diff --git a/src/HAP.h b/src/HAP.h index 0c7cedd..b8d2107 100644 --- a/src/HAP.h +++ b/src/HAP.h @@ -50,10 +50,20 @@ struct Nonce { // Paired Controller Structure for Permanently-Stored Data struct Controller { - boolean allocated=false; // slot is allocated with Controller data + 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 + + Controller(){} + + Controller(uint8_t *id, uint8_t *ltpk, boolean ad){ + allocated=true; + admin=ad; + memcpy(ID,id,sizeof(ID)); + memcpy(LTPK,ltpk,sizeof(LTPK)); + } + }; ///////////////////////////////////////////////// @@ -84,14 +94,13 @@ struct HAPClient { static pairState pairStatus; // tracks pair-setup status static SRP6A srp; // stores all SRP-6A keys used for Pair-Setup static Accessory accessory; // Accessory ID and Ed25519 public and secret keys- permanently stored - static Controller controllers[MAX_CONTROLLERS]; // Paired Controller IDs and ED25519 long-term public keys - permanently stored static list controllerList; // linked-list of Paired Controller IDs and ED25519 long-term public keys - permanently stored static int conNum; // connection number - used to keep track of per-connection EV notifications // individual structures and data defined for each Hap Client connection WiFiClient client; // handle to client - Controller *cPair; // pointer to info on current, session-verified Paired Controller (NULL=un-verified, and therefore un-encrypted, connection) + Controller *cPair=NULL; // pointer to info on current, session-verified Paired Controller (NULL=un-verified, and therefore un-encrypted, connection) // These keys are generated in the first call to pair-verify and used in the second call to pair-verify so must persist for a short period @@ -136,14 +145,13 @@ struct HAPClient { static void charPrintRow(uint8_t *buf, int n, int minLogLevel=0); // prints 'n' bytes of *buf as CHAR, all on one row, subject to specified minimum log level static Controller *findController(uint8_t *id); // returns pointer to controller with matching ID (or NULL if no match) - static Controller *getFreeController(); // return pointer to next free controller slot (or NULL if no free slots) - static Controller *addController(uint8_t *id, uint8_t *ltpk, boolean admin); // stores data for new Controller with specified data. Returns pointer to Controller slot on success, else NULL - static int nAdminControllers(); // returns number of admin Controllers stored - static void removeControllers(); // removes all Controllers (sets allocated flags to false for all slots) + static tagError addController(uint8_t *id, uint8_t *ltpk, boolean admin); // stores data for new Controller with specified data. Returns tagError (if any) static void removeController(uint8_t *id); // removes specific Controller. If no remaining admin Controllers, remove all others (if any) as per HAP requirements. static void printControllers(int minLogLevel=0); // prints IDs of all allocated (paired) Controller, subject to specified minimum log level static int listControllers(uint8_t *tlvBuf); // creates and prints a multi-TLV list of Controllers (HAP Section 5.12) static void saveControllers(); // saves Controller list in NVS + static int nAdminControllers(); // returns number of admin Controller + static void tearDown(uint8_t *id); // tears down connections using Controller with ID=id; tears down all connections if id=NULL static void checkNotifications(); // checks for Event Notifications and reports to controllers as needed (HAP Section 6.8) static void checkTimedWrites(); // checks for expired Timed Write PIDs, and clears any found (HAP Section 6.7.2.4) static void eventNotify(SpanBuf *pObj, int nObj, int ignoreClient=-1); // transmits EVENT Notifications for nObj SpanBuf objects, pObj, with optional flag to ignore a specific client diff --git a/src/HAPConstants.h b/src/HAPConstants.h index 5ed6ab3..fc87ad3 100644 --- a/src/HAPConstants.h +++ b/src/HAPConstants.h @@ -52,6 +52,7 @@ typedef enum { // HAP Error Codes (HAP Table 5-5) typedef enum { + tagError_None=0x00, tagError_Unknown=0x01, tagError_Authentication=0x02, tagError_Backoff=0x03, diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index a5fbc5f..dac0451 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -260,7 +260,7 @@ void Span::pollTask() { homeSpan.lastClientIP="0.0.0.0"; // reset stored IP address to show "0.0.0.0" if homeSpan.getClientIP() is used in any other context if(!hap[i]->client){ // client disconnected by server - LOG1("** Disconnecting Client #"); + LOG1("** Disconnected Client #"); LOG1(i); LOG1(" ("); LOG1(millis()/1000); @@ -555,6 +555,8 @@ void Span::processSerialCommand(const char *c){ switch(c[0]){ case 'Z': { + HAPClient::saveControllers(); + break; TempBuffer tBuf(HAPClient::listControllers(NULL)); HAPClient::listControllers(tBuf.get()); Serial.printf("SIZE = %d\n",tBuf.len()); @@ -688,22 +690,13 @@ void Span::processSerialCommand(const char *c){ case 'U': { - HAPClient::removeControllers(); // clear all Controller data - nvs_set_blob(HAPClient::hapNVS,"CONTROLLERS",HAPClient::controllers,sizeof(HAPClient::controllers)); // update data - nvs_commit(HAPClient::hapNVS); // commit to NVS + HAPClient::controllerList.clear(); // clear all Controller data + HAPClient::saveControllers(); LOG0("\n*** HomeSpan Pairing Data DELETED ***\n\n"); - - for(int i=0;iclient){ // if slot is connected - LOG1("*** Terminating Client #"); - LOG1(i); - LOG1("\n"); - hap[i]->client.stop(); - } - } - + HAPClient::tearDown(NULL); // tear down all verified connections + LOG0("\nDEVICE NOT YET PAIRED -- PLEASE PAIR WITH HOMEKIT APP\n\n"); - mdns_service_txt_item_set("_hap","_tcp","sf","1"); // set Status Flag = 1 (Table 6-8) + mdns_service_txt_item_set("_hap","_tcp","sf","1"); // set Status Flag = 1 (Table 6-8) if(homeSpan.pairCallback) homeSpan.pairCallback(false); @@ -1016,11 +1009,9 @@ void Span::processSerialCommand(const char *c){ TempBuffer tBuf(256); mbedtls_base64_encode((uint8_t *)tBuf.get(),256,&olen,(uint8_t *)&HAPClient::accessory,sizeof(struct Accessory)); LOG0("Accessory data: %s\n",tBuf.get()); - for(int i=0;i>> Controller data: "); readSerial(tBuf.get(),199); if(strlen(tBuf.get())==0){ LOG0("(done)\n"); - while(i Date: Sun, 20 Aug 2023 20:05:47 +0800 Subject: [PATCH 17/29] Support Non-English WiFi name --- src/Network.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Network.cpp b/src/Network.cpp index bcfe676..1afe931 100644 --- a/src/Network.cpp +++ b/src/Network.cpp @@ -230,7 +230,7 @@ void Network::processRequest(char *body, char *formData){ String responseHead="HTTP/1.1 200 OK\r\nContent-type: text/html\r\n"; - String responseBody="