diff --git a/ESP32/HomeSpan-master/LICENSE b/ESP32/HomeSpan-master/LICENSE
index 6f23de4..9a85e84 100644
--- a/ESP32/HomeSpan-master/LICENSE
+++ b/ESP32/HomeSpan-master/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2020-2023 Gregg E. Berman
+Copyright (c) 2020-2025 Gregg E. Berman
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/ESP32/HomeSpan-master/README.md b/ESP32/HomeSpan-master/README.md
index ef3fefd..7e2b34e 100644
--- a/ESP32/HomeSpan-master/README.md
+++ b/ESP32/HomeSpan-master/README.md
@@ -4,12 +4,16 @@ Welcome to HomeSpan - a robust and extremely easy-to-use Arduino library for cre
HomeSpan provides a microcontroller-focused implementation of Apple's HomeKit Accessory Protocol Specification Release R2 (HAP-R2) designed specifically for the Espressif ESP32 microcontroller running within the Arduino IDE. HomeSpan pairs directly to HomeKit via your home WiFi network without the need for any external bridges or components. With HomeSpan you can use the full power of the ESP32's I/O functionality to create custom control software and/or hardware to automatically operate external devices from the Home App on your iPhone, iPad, or Mac, or with Siri.
-HomeSpan requires version 2 of the [Arduino-ESP32 Board Manager](https://github.com/espressif/arduino-esp32). HomeSpan can be run on the original ESP32 as well as Espressif's ESP32-S2, ESP32-C3, and ESP32-S3 chips.
+Requirements to run HomeSpan depend on which version you choose:
-HomeSpan is currently NOT compatible with version 3.X of the Arduino-ESP32 Board Manager, since version 3 contains many breaking changes and is not backwards-compatible with version 2.X of the Arduino-ESP32 Board Manager. At present, HomeSpan can only be compiled under version 2.X of the Board Manager.
+|HomeSpan Version | Arduino-ESP32 Board Manager | Partition Scheme | Supported Chips|
+|:---:|:---:|:---:|---|
+|1.9.1 or earlier | v2.0.0 - v2.0.17 | *Default* (1.3MB APP) | ESP32, S2, S3, C3 |
+|2.0.0 or later | v3.0.2 - **v3.2.0*** | *Minimal SPIFFS* (1.9MB APP) | ESP32, S2, S3, C3, *and C6* |
-> [!NOTE]
-> Apple's new HomeKit architecture [requires the use of a Home Hub](https://support.apple.com/en-us/HT207057) (either a HomePod or Apple TV) for full and proper operation of any HomeKit device, including those based on HomeSpan. Without a Home Hub, HomeSpan cannot send notifications to the Home App - things like pushbuttons and temperature sensors will not be able to transmit updates to the Home App. Use of HomeSpan without a Home Hub is NOT recommended.
+*HomeSpan has been tested through **version 3.2.0** of the Arduino-ESP32 Board Manager (built on IDF 5.4.1). Later releases may work fine, but have not (yet) been tested. Note HomeSpan does not support the use of alpha, beta, or pre-release candidates of the Arduino-ESP32 Board Manager - testing is only done on production releases of the Board Manager.
+
+**ADDITIONAL REQUIREMENTS**: Apple's HomeKit architecture [requires the use of a Home Hub](https://support.apple.com/en-us/HT207057) (either a HomePod or Apple TV) for full and proper operation of any HomeKit device, including those based on HomeSpan. ***Use of HomeSpan without a Home Hub is NOT supported.***
### HomeSpan Highlights
@@ -20,6 +24,7 @@ HomeSpan is currently NOT compatible with version 3.X of the Arduino-ESP32 Board
* Dozens of integrated HomeKit Services
* Operates in either Accessory or Bridge mode
* Supports pairing with Setup Codes or QR Codes
+* Supports both WiFi and Ethernet connectivity to your home network
### For the HomeSpan Developer
@@ -35,7 +40,7 @@ HomeSpan is currently NOT compatible with version 3.X of the Arduino-ESP32 Board
* Physical pushbuttons that connect an ESP32 pin to either ground or VCC
* Touch pads/sensors connected to an ESP32 pin (for ESP32 devices that support touch pads)
* Integrated access to the ESP32's on-chip Remote Control peripheral for easy generation of IR and RF signals
-* Dedicated classes to control one- and two-wire addressable RGB and RGBW LEDs and LED strips
+* Dedicated classes to control one- and two-wire addressable RGB LEDs and LED strips
* Dedicated classes to control stepper motors that can run smoothly in the background without interfering with HomeSpan
* Dedicated class that faciliates seamless point-to-point communication between ESP32 devices using ESP-NOW
* Integrated Web Log for user-defined log messages
@@ -54,97 +59,48 @@ HomeSpan is currently NOT compatible with version 3.X of the Arduino-ESP32 Board
* Launch the WiFi Access Point
* A standalone, detailed End-User Guide
-## ❗Latest Update - HomeSpan 1.9.1 (07/03/2024)
+## ❗Latest Update - HomeSpan 2.1.2 (05/08/2025)
-* **HomeSpan now supports *Tag-Length-Value ("TLV8")* Characteristics!**
+### Updates and Corrections
- * adds new, fully-integrated `TLV8()` class library for the creation and management of TLV8 objects
- * includes methods to handle standard byte-stream VALUES as well as strings, numerical values, zero-length tags, and sub-TLVs
- * utilizes standard C++ iterators for easy access to reading and writing TLV8 records
- * adds new `Characteristic` methods `getTLV()`, `getNewTLV()`, and `setTLV()`
- * adds new `CUSTOM_CHAR_TLV8()` that allows for easy creation of custom TLV8 Characteristics
- * includes new [Tutorial Example 22 - TLV8 Characteristics](examples/22-TLV8_Characteristics) demonstrating use of the `TLV8()` class and TLV8 Characteristics
- * see the new [TLV8 Characteristics](docs/TLV8.md) page for complete details and documentation
-
-* **New *DisplayOrder* TLV8 Characteristic**
-
- * utlizes HomeSpan's new `TLV8()` library
- * allows you to specify the exact order in which the Input Sources for a Television Service are displayed in the Home App
- * see [Tutorial Example 22 - TLV8 Characteristics](examples/22-TLV8_Characteristics) for details
+* **Added UUID validation for Custom Services**
+ * reports an error in CLI at startup if invalid Service UUID is found
+ * similar to existing UUID validation for Custom Characteristics
+
+* **Renamed example sketch *RemoteDevice8286.ino* to *RemoteDevice8266.ino***
+ * corrects a long-standing typo in the filename
+
+* **Modified OTA updating so that the HomeSpan check for its Magic Cookie is only made if uploading a new *sketch***
+ * avoids OTA aborting when OTA is used to upload SPIFFS data
+
+* **Refactored the JSON parsing logic that handles PUT Characteristic requests from HomeKit**
+ * now properly supports any JSON-allowed Unicode character used in a JSON string value, from U+0020 to U+10FFFF
-* **New *AccessoryIdentifier* Tutorial**
+ * allows string-based Characteristics to include escaped quotes, escaped solidus and reverse solidus, and any of the JSON token characters *,:[]{}* that would have previously caused a parsing error
+ * also now allows for empty string-based Characteristics (previously would have led to a parsing error)
- * demonstrates how to trigger an Accessory's Identifier Characteristic, optionally used to help identify a device during initial pairing
- * see [Tutorial Example 21 - AccessoryIdentifier](examples/21-AccessoryIdentifier)
+* **Added new `setMaxStringLength(uint8_t n)` method to Characteristics**
+ * allows user to change maximum length of string-based Characteristics from HAP default of 64 to *n* (less than 256)
+ * though specified by HAP, this value does not seem to be used by HomeKit, and this method does not appear necessary
-* **Added support for customizing Pixel chips**
+* **Added new *homeSpan* method `assumeTimeAcquired()`**
+ * calling this method tells HomeSpan to assume that you have acquired the time using your own code
+ * useful if you don't want to specify a *timeServerURL* when enabling the Web Log, but would rather acquire it manually
- * new constructor `Pixel(uint8_t pin, [pixelType_t pixelType])` allows your to set the order in which colors are transmitted to the pixel chip, where *pixelType* is one of the following:
- * PixelType::RGB, PixelType::RBG, PixelType::BRG, PixelType::BGR, PixelType::GBR, PixelType::GRB
- * PixelType::RGBW, PixelType::RBGW, PixelType::BRGW, PixelType::BGRW, PixelType::GBRW, PixelType::GRBW*
- * deprecated previous constructor `Pixel(uint8_t pin, boolean isRGBW)`
- * this constructor will continue to work, but you will receive a warning during compilation that it has been deprecated
- * users should switch to the new constructor to avoid potential compatibility issues with future versions of HomeSpan
- * added new method `boolean isRGBW()`
- * returns *true* if Pixel was constructed as RGBW, else *false* if constructed as RGB only (i.e. no white LED)
- * added new [PixelTester](examples/Other%20Examples/PixelTester) sketch (found under *Other Examples*) to aid in determining the *pixelType* for any LED Strip
- * see the [Adressable RGB LEDs](docs/Pixels.md) page for details
-
-* **New ability to read and set the IIDs of Services and Characteristics**
+* **Added new *homeSpan* method `setGetCharacteristicsCallback(void (*func)(const char *getCharList))`**
+ * sets an optional user-defined callback function, *func*, to be called by HomeSpan whenever it receives a *GET /characteristics* request from HomeKit
+ * HomeKit generally sends this request to every paired device each time the Home App is opened on an iPhone or Mac
+ * this callback is useful in circumstances where the current state of a sensor-style Characteristic must be read by HomeSpan using a separate "expensive" process that should be called only when needed as opposed to being continuously updated in a Services `loop()` method
+ * the function *func* must be of type void and accept one argument of type *const char \** into which HomeSpan passes the list of Characteristic AID/IID pairs that HomeKit provided in its HTTP *GET* request
+ * *getCharList* can be used to determine if the HTTP *GET* request includes the AID/IID pair for any specific Characteristic
+ * this allows the user to act on the callback based on which specific Characteristics were requested by HomeKit
+ * see **new helper SpanCharacteristic method `foundIn(const char *getCharList)`** that returns *true* or *false* depending on whether the AID/IID for a specific Characteristic is found in *getCharList*
+ * for completeness, **also added `uint32_t getAID()` methods** to each of the SpanAccessory, SpanService, and SpanCharacteristic classes
- * adds new `SpanService` method `getIID()` that returns the IID of a Service
- * adds new `SpanCharacteristic` method `getIID()` that returns the IID of a Characteristic
- * adds new `homeSpan` method `resetIID(int newIID)` that resets the IID count for the current Accessory
- * see the [API Reference](docs/Reference.md) for details
-
-* **New ability to read Controller pairing data (for advanced use-cases only)**
+* **Explicitly added added `#include ` to *HomeSpan.cpp* to address compatibility issue with Arduino-ESP32 v3.2.0**
- * adds new `homeSpan` methods `controllerListBegin()` and `controllerListEnd()` that returns iterators to HomeSpan's internal linked-list of Controller data records
- * adds new methods to read each Controller's pairing data:
- * `getID()` - returns a pointer to the 36-byte Device ID of the controller
- * `getLTPK()` - a pointer to the 32-byte Long-Term Public Key of the controller
- * `isAdmin()` - returns true if the controller has admin permission, else returns false
- * adds new `homeSpan` method `setControllerCallback()` to set optional callback function that HomeSpan calls whenever a controller is added, removed, or updated
- * see the [API Reference](docs/Reference.md) for details
-
-* **HomeSpan now supports the *write-response ("WR")* protocol**
- * added automated handling of the HomeKits's *write-response ("WR")* protocol*
- * not needed for any Characteristics that are currently supported by HomeSpan, but useful for experimentation and work with Custom Characteristics
- * added extra checks when using `setVal()`
- * a warning message is output on the Serial Monitor if `setVal()` is called to change the value of a Characteristic from within the `update()` method at the same time the Home App is sending an update request for that value
- * does not apply if `setVal()` is called from within `update()` to change the value of a Characteristic in response to a *write-response* request from the Home App
-
-* **Converted the `getLinks()` SpanService method to a template function**
- * allows user to automatically cast the elements of the returned vector into any specific Service type
- * also adds an optional parameter to restrict the elements of the returned vector to match a specified HomeSpan Service
- * see the [API Reference](docs/Reference.md) for details
-
-* **New ability to halt the pulse generation for a ServoPin**
- * calling `set(NAN)` for a ServoPin halts the pulse generation, which (for most analog servos) allows the motor to be freely rotated
- * calling `set(position)`, where *position* equal the desired number of degrees, restarts the pulse generation and sets the servo position accordingly
-
-* **Refactored client/slot management to save memory and prepare for future integration of Ethernet support**
- * fixed-array of Client/Socket connections replaced by dynamic linked-list
- * serial interface now only shows active client connections (rather than a fixed list of client slots)
- * **deprecated** `homeSpan.reserveSocketConnections()` since it is no longer needed
+* **Fixed bug in `Pixel::getPin()` that would report channel number instead of pin number**
-* **Fixed bug introduced in 1.9.0 that prevented `homeSpan.setPairingCode()` from saving (and subsequently using) the request Setup Pairing Code**
- * this method now operates silently, unless an invalid pairing code is provided, in which case an error is reported to the Serial Monitor and *the sketch is halted*
- * the process for setting the Pairing Code using the CLI 'S' command or via the Access Point are unchanged - confirmation messages are still output to the Serial Monitor and errors do *not* cause the sketch to halt
-
-* **Fixed memory leak introduced in 1.9.0 that would fail to free a small temporary memory block created when verifying a new connection**
- * had no practical impact when using a Home Hub since Home Kit only creates a few permanent connections
- * had significant impact when not using a Home Hub in cases where the Home App repeatedly drops and re-establishes connections, resulting in slow erosion of heap memory and then out-of-memory failure of the device after a few days (note use of HomeSpan without a Home Hub is not formally supported)
-
-* **Fixed latent bug in SpanPoint**
- * HomeSpan would crash when printing **SpanPoint** configuration information to the Serial Monitor (the 'i' CLI command) if any of the instances of SpanPoint had *receiveSize=0*
- * this bug never surfaced before since all the **SpanPoint examples** were based on receiving data and therefore had a non-zero *receiveSize*
-
-* **Deleted `homeSpan.setMaxConnections()`, which had been *deprecated* many versions ago**
-
-* **Deleted stand-alone `SpanRange` structure, which had been *deprecated* many versions ago**
- * this has no impact on standard use of the Characteristic method `setRange()`
-
See [Releases](https://github.com/HomeSpan/HomeSpan/releases) for details on all changes and bug fixes included in this update.
# HomeSpan Resources
@@ -157,10 +113,12 @@ HomeSpan includes the following documentation:
* [HomeSpan Services and Characteristics](docs/ServiceList.md) - a list of all HAP Services and Characterstics supported by HomeSpan
* [HomeSpan Accessory Categories](docs/Categories.md) - a list of all HAP Accessory Categories defined by HomeSpan
* [HomeSpan Command-Line Interface (CLI)](docs/CLI.md) - configure a HomeSpan device's WiFi Credentials, modify its HomeKit Setup Code, monitor and update its status, and access detailed, real-time device diagnostics from the Arduino IDE Serial Monitor
+* [HomeSpan WiFi and Ethernet Connectivity](docs/Networks.md) - a high-level discussion of HomeSpan's WiFi and Ethernet connectivity options
* [HomeSpan User Guide](docs/UserGuide.md) - turnkey instructions on how to configure an already-programmed HomeSpan device's WiFi Credentials, modify its HomeKit Setup Code, and pair the device to HomeKit. No computer needed!
* [HomeSpan API Reference](docs/Reference.md) - a complete guide to the HomeSpan Library API
* [HomeSpan QR Codes](docs/QRCodes.md) - create and use QR Codes for pairing HomeSpan devices
* [HomeSpan OTA](docs/OTA.md) - update your sketches Over-the-Air directly from the Arduino IDE without a serial connection
+* [HomeSpan Watchdog Timer](docs/WDT.md) - optional protection that can trigger an automatic reboot if your sketch hangs or freezes for an extended period of time
* [HomeSpan PWM](docs/PWM.md) - integrated control of standard LEDs and Servo Motors using the ESP32's on-chip PWM peripheral
* [HomeSpan RFControl](docs/RMT.md) - easy generation of RF and IR Remote Control signals using the ESP32's on-chip RMT peripheral
* [HomeSpan Pixels](docs/Pixels.md) - integrated control of addressable one- and two-wire RGB and RGBW LEDs and LED strips
@@ -182,6 +140,11 @@ Note that all documentation is version-controlled and tied to each branch. The
In addition to HomeSpan resources, developers who are new to HomeKit programming may find useful Chapters 8 and 9 of Apple's HomeKit Accessory Protocol Specification, Non-Commercial Version, Release R2 (HAP-R2). This document is unfortunately no longer available from Apple (perhaps because it was last updated July, 2019, and is now somewhat out-of-date). However, you may be able find copies of this document elsewhere on the web. Note Apple has not replaced the HAP-R2 document with any other versions for non-commercial use, and Apple's open-source [HomeKit ADK](https://github.com/apple/HomeKitADK) only reflects the original HAP-R2 specs (rather than all the latest Services and Characteristics available in HomeKit for commercial devices).
---
+### Matter and Thread
+
+There are no plans to make HomeSpan compatible with Matter since HomeSpan was structured entirely around HAP R2. In addition, both Apple and Espressif have released Matter SDKs for public use, reducing the need for yet another Matter SDK.
+
+Connecting HomeSpan directly to HomeKit via Thread is not planned (and might not even be possible). However, Thread may be useful for inter-device communication similar to how HomeSpan uses ESP-NOW to implement remote, battery-operated devices. This may be added at some point in a future release.
### Feedback or Questions?
diff --git a/ESP32/HomeSpan-master/examples/07-AccessoryNames/07-AccessoryNames.ino b/ESP32/HomeSpan-master/examples/07-AccessoryNames/07-AccessoryNames.ino
index 4a9c2db..2d925ed 100644
--- a/ESP32/HomeSpan-master/examples/07-AccessoryNames/07-AccessoryNames.ino
+++ b/ESP32/HomeSpan-master/examples/07-AccessoryNames/07-AccessoryNames.ino
@@ -81,7 +81,7 @@ void setup() {
new Service::AccessoryInformation();
new Characteristic::Identify();
- new Characteristic::Name(u8"Special chars ÄÖÜß"); // Use UTF-8 coded string for non-ASCII characters
+ new Characteristic::Name("Special chars ÄÖÜß"); // Use UTF-8 coded string for non-ASCII characters
new DEV_DimmableLED(18);
diff --git a/ESP32/HomeSpan-master/examples/Other Examples/CustomNVSPartition/CustomNVSPartition.ino b/ESP32/HomeSpan-master/examples/Other Examples/CustomNVSPartition/CustomNVSPartition.ino
index 598ae19..a7e14f1 100644
--- a/ESP32/HomeSpan-master/examples/Other Examples/CustomNVSPartition/CustomNVSPartition.ino
+++ b/ESP32/HomeSpan-master/examples/Other Examples/CustomNVSPartition/CustomNVSPartition.ino
@@ -62,9 +62,13 @@
// Note that once HomeSpan is paired with HomeKit, additional NVS records will be consumed to store the
// pairing information for each verified HomeKit Controller.
-// Note also that when compiling under the Arduino IDE, the IDE reports the size of partition based on the
-// Partition Scheme you selected in the IDE menu, even though that scheme is not actually used if you have your
-// own "partition.csv" file, as in this example. This may lead the IDE to report an incorrect partition size.
+// IMPORTANT!! When compiling under the Arduino IDE, the IDE itself first needs to make sure the size of your
+// sketch is not larger than the size of the application partitions in your partition table. However, the IDE
+// has no access to your custom "partition.csv" file and instead computes the size of the application partitions
+// based on the Partition Scheme you selected in the IDE menu, even though that scheme is not actually used if
+// you have your own "partitions.csv" file, as in this example. Before compiling, you MUST select a Partition Scheme
+// in the IDE that has an application partition size matching the size of the application parititions in your
+// custom "partitions.csv" file.
///////////////////////////////////////////////////////////////////////////////////////////
diff --git a/ESP32/HomeSpan-master/examples/Other Examples/MultiThreading/MultiThreading.ino b/ESP32/HomeSpan-master/examples/Other Examples/MultiThreading/MultiThreading.ino
new file mode 100644
index 0000000..4688e8b
--- /dev/null
+++ b/ESP32/HomeSpan-master/examples/Other Examples/MultiThreading/MultiThreading.ino
@@ -0,0 +1,75 @@
+/*********************************************************************************
+ * MIT License
+ *
+ * Copyright (c) 2020-2025 Gregg E. Berman
+ *
+ * https://github.com/HomeSpan/HomeSpan
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ ********************************************************************************/
+
+// This sketch provides a variation of Example 1 (a Simple LightBulb Accessory) that demonstrates
+// the use of multi-threaded functionality to periodically flip the power of the light from a thread
+// outside of the main HomeSpan polling process. You will be able to turn on/off the LightBulb
+// Accessory from the Home App as usual, but every 10 seconds the light will flip state automatically
+// (turning it ON if it is OFF, or turning it OFF if it is ON).
+
+// Note this example does not implement an actual LED, just the logic to show how things work from
+// within the Home App.
+
+// This sketch can be used with both single- and dual-processor devices.
+
+#include "HomeSpan.h"
+
+Characteristic::On *power; // NEW! Create a global pointer to the On Characterstic (to be used below)
+
+//////////////////////////////////////
+
+void setup() {
+
+ Serial.begin(115200);
+
+ homeSpan.begin(Category::Lighting,"HomeSpan LightBulb");
+
+ new SpanAccessory();
+ new Service::AccessoryInformation();
+ new Characteristic::Identify();
+
+ new Service::LightBulb();
+ power = new Characteristic::On(); // NEW! Save the pointer to the Characteristic in the global variable, power
+
+ homeSpan.autoPoll(); // NEW! Start autopolling. HomeSpan will run its polling process in separate thread
+}
+
+//////////////////////////////////////
+
+void loop(){
+
+ // NOTE: we DO NOT call homeSpan.poll() from this loop() since we already started polling in a separate thread above by calling homeSpan.autoPoll()
+
+ delay(10000); // sleep for 10 seconds - note this has no effect on HomeSpan since the polling process is in a different thread
+
+ Serial.printf("*** Flipping power of Light!\n");
+
+ homeSpanPAUSE; // temporarily pause the HomeSpan polling process
+ power->setVal(1-power->getVal()); // flip the value of the On Characteristic using the pointer we saved above
+
+} // note once at the end of the loop() code block HomeSpan polling automatically resumes (no need to call homeSpanRESUME)
+
diff --git a/ESP32/HomeSpan-master/examples/Other Examples/Pixel-RGBWC/Pixel-RGBWC.ino b/ESP32/HomeSpan-master/examples/Other Examples/Pixel-RGBWC/Pixel-RGBWC.ino
new file mode 100644
index 0000000..c04d67e
--- /dev/null
+++ b/ESP32/HomeSpan-master/examples/Other Examples/Pixel-RGBWC/Pixel-RGBWC.ino
@@ -0,0 +1,121 @@
+/*********************************************************************************
+ * MIT License
+ *
+ * Copyright (c) 2024 Gregg E. Berman
+ *
+ * https://github.com/HomeSpan/HomeSpan
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ ********************************************************************************/
+
+// HomeSpan Addressable RGBCW LED Example. Demonstrates use of two separate Accessories to control a SINGLE
+// NeoPixel LED Strip. The first controls the RGB LEDs and the second controls the Warm-White and Cool-White LEDs.
+// Both sets can be on at the same time.
+//
+// Note use of Pixel::CCT() method to convert color temperature into values for the White LEDs.
+//
+// Also note that HomeKit uses the Mirek scale for color temperture instead of the Kelvin scale. To convert from
+// one to the other, divide into 1 million: Mirek=1.0e6/Kelvin and Kelvin=1.0e6/Mirek.
+//
+
+#define NEOPIXEL_PIN 23 // pin number to which NeoPixel strand is attached
+#define NPIXELS 100 // number of controllable pixels in strand (which may be less than actual number of LEDs)
+#define WARM_LED_TEMP 3000 // temperature (in Kelvin) of Warm-White LED
+#define COOL_LED_TEMP 6500 // temperature (in Kelvin) of Cool-White LED
+
+#include "HomeSpan.h"
+
+Pixel pixel(NEOPIXEL_PIN,"GRBWC"); // create a global instance of a Pixel strand supporting RGB LEDs plus warm AND cool white LED
+Pixel::Color colorRGB; // create a global instance of a Pixel color to be used to store the RGB color
+Pixel::Color colorWC; // create a global instance of a Pixel color to be used to store the WC color
+
+///////////////////////////////
+
+struct NeoPixel_RGB : Service::LightBulb { // define an RGB Lightbulb to control the RGB LEDs on the NeoPixel Light Strip
+
+ Characteristic::On power{0,true};
+ Characteristic::Hue H{0,true};
+ Characteristic::Saturation S{0,true};
+ Characteristic::Brightness V{100,true};
+
+ NeoPixel_RGB() : Service::LightBulb(){
+
+ V.setRange(5,100,1);
+ update(); // manually call update() to set pixel with restored initial values
+ }
+
+ boolean update() override {
+
+ colorRGB=pixel.HSV(H.getNewVal(), S.getNewVal(), V.getNewVal()*power.getNewVal());
+ pixel.set(colorRGB+colorWC,NPIXELS);
+ return(true);
+ }
+};
+
+///////////////////////////////
+
+struct NeoPixel_WC : Service::LightBulb { // define WC Lightbulb to control the Warm White and Cool White LEDs on the NeoPixel Light Strip
+
+ Characteristic::On power{0,true};
+ Characteristic::Brightness V{100,true};
+ Characteristic::ColorTemperature T{(uint32_t)1.0e6/((COOL_LED_TEMP+WARM_LED_TEMP)/2),true}; // set initial value to average of Warm and Cool Temp
+
+ NeoPixel_WC() : Service::LightBulb(){
+
+ V.setRange(5,100,1);
+ T.setRange(1.0e6/COOL_LED_TEMP,1.0e6/WARM_LED_TEMP); // set range of control to match range of Warm-White and Cool-White LEDs
+
+ update(); // manually call update() to set pixel with restored initial values
+ }
+
+ boolean update() override {
+
+ colorWC=pixel.CCT(1.0e6/T.getNewVal(), V.getNewVal()*power.getNewVal()); // convert HomeKit temperature (in Mirek) to Kelvin
+ pixel.set(colorRGB+colorWC,NPIXELS);
+ return(true);
+ }
+};
+
+///////////////////////////////
+
+void setup() {
+
+ Serial.begin(115200);
+
+ pixel.setTemperatures(WARM_LED_TEMP,COOL_LED_TEMP); // set color temperatures of Warm-White and Cool-White LEDs
+
+ homeSpan.begin(Category::Lighting,"RGBWC Light");
+
+ SPAN_ACCESSORY();
+
+ SPAN_ACCESSORY("Neo RGB");
+ new NeoPixel_RGB();
+
+ SPAN_ACCESSORY("Neo WC");
+ new NeoPixel_WC();
+}
+
+///////////////////////////////
+
+void loop() {
+ homeSpan.poll();
+}
+
+///////////////////////////////
diff --git a/ESP32/HomeSpan-master/examples/Other Examples/Pixel/Pixel.ino b/ESP32/HomeSpan-master/examples/Other Examples/Pixel/Pixel.ino
index 21dc284..8912220 100644
--- a/ESP32/HomeSpan-master/examples/Other Examples/Pixel/Pixel.ino
+++ b/ESP32/HomeSpan-master/examples/Other Examples/Pixel/Pixel.ino
@@ -110,7 +110,7 @@ struct NeoPixel_RGBW : Service::LightBulb { // Addressable single-wire RGBW
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,PixelType::GRBW); // creates Pixel RGBW LED on specified pin (with order of colors chnanged to reflect this specific NeoPixel device)
+ pixel=new Pixel(pin,"GRBW"); // creates Pixel RGBW LED on specified pin (with order of colors changed to reflect this specific NeoPixel device)
this->nPixels=nPixels; // save number of Pixels in this LED Strand
update(); // manually call update() to set pixel with restored initial values
}
diff --git a/ESP32/HomeSpan-master/examples/Other Examples/PixelTester/PixelTester.ino b/ESP32/HomeSpan-master/examples/Other Examples/PixelTester/PixelTester.ino
index 98a3278..a5ceffc 100644
--- a/ESP32/HomeSpan-master/examples/Other Examples/PixelTester/PixelTester.ino
+++ b/ESP32/HomeSpan-master/examples/Other Examples/PixelTester/PixelTester.ino
@@ -27,105 +27,146 @@
/////////////////////// PIXEL TESTER //////////////////////////
-// This sketch is designed to help identify the proper settings to use for a NeoPixel, NeoPixel Strip,
-// or any device containing one or more single-wire addressable RGB or RGBW LEDs (the "Pixel Device").
+// This sketch is designed to help identify the proper settings to use for a NeoPixel,
+// NeoPixel Strip, or any device containing one or more single-wire addressable RGB-style LEDs
-// Before compiling, set PIXEL_PIN to the ESP32 pin that is connected to your Pixel Device, and set NPIXELS to
-// the numnber of Pixels in the Pixel Device. Note that for some strips a single chip controls more than one LED,
-// in which case NPIXELS should be set to the number of controlling chips, NOT the number of LEDs.
-
-// To start, the second argument of the Pixel constructor for the testPixel object below should remain
-// set to PixelType::RGBW
-
-// When run, the sketch will repeatedly cycle colors by setting ALL pixels in the device first to RED, then GREEN,
-// followed by BLUE, and then finally WHITE. After a short pause, the cycle repeats.
-
-// For each color the brightness will increase from 0 through MAX_BRIGHTNESS, and then back to 0. You can change
-// MAX_BRIGHTNESS to something lower than 255 if you want to limit how bright the pixels get.
-
-// For Pixel Devices with more than one pixel, diagnostics are as follows:
-//
-// * If all 4 colors repeatedly flash in the order expected, this means the base setting of PixelType::RGBW is correct!
-//
-// * If instead of each pixel being set to the same color, the pixels in the strip each light up with a different color
-// (or no color at all), this means you have an RGB LED, not an RGBW LED. Change the second parameter of the constructor
-// to PixelType::RGB and re-run the sketch.
-//
-// * If all of the pixels are being set to the same color, but the sequence is NOT in the order RED, GREEN, BLUE, change
-// the second parameter of the constructor so that the order of the PixelType colors match the sequence of the colors
-// that appear on the Pixel Device. For example, if your RGBW Pixel Device flashes GREEN, RED, BLUE, and than WHITE, use
-// PixelType::GRBW.
-
-// For Pixel Devices with only a single pixel, diagnostics are as follows:
-
-// * If all 4 colors repeatedly flash in the order expected, this means the base setting of PixelType::RGBW is correct!
-//
-// * If the pixel does not light at all when set to WHITE this means you have an RGB LED, not an RGBW LED. Change the
-// second parameter of the constructor to PixelType::RGB and re-run the sketch.
-//
-// * If all of the pixels are being set to the same color, but the sequence is NOT in the order RED, GREEN, BLUE, change
-// the second parameter of the constructor so that the order of the PixelType colors match the sequence of the colors
-// that appear on the Pixel Device. For example, if your RGB Pixel Device flashes GREEN, RED, and then BLUE, use
-// PixelType::GRB.
+// DIRECTIONS: Run sketch and and follow on-screen instructions
//////////////////////////////////////
#include "HomeSpan.h"
-//////////////////////////////////////
+#define MAX_BRIGHTNESS 255 // lower this value (max=255) if pixels LEDs are too bright to look at when perfoming this test
-#define MAX_BRIGHTNESS 255 // maximum brightness when flashing RGBW [0-255]
+int pin=-1;
+int nPixels=0;
-#define PIXEL_PIN 26 // set this to whatever pin you are using - note pin cannot be "input only"
-#define NPIXELS 8 // set to number of pixels in strip
+Pixel::Color colors[5]={
+ Pixel::RGB(MAX_BRIGHTNESS,0,0,0,0),
+ Pixel::RGB(0,MAX_BRIGHTNESS,0,0,0),
+ Pixel::RGB(0,0,MAX_BRIGHTNESS,0,0),
+ Pixel::RGB(0,0,0,MAX_BRIGHTNESS,0),
+ Pixel::RGB(0,0,0,0,MAX_BRIGHTNESS)
+};
-Pixel testPixel(PIXEL_PIN, PixelType::RGBW); // change the second argument until device operates with correct colors
+Pixel *testPixel;
//////////////////////////////////////
-void setup() {
-
- Serial.begin(115200);
- delay(1000);
-
- Serial.printf("\n\nPixel Test on pin %d with %d pixels\n\n",PIXEL_PIN,NPIXELS);
+char *getSerial(){
+ static char buf[9];
+ strcpy(buf,"");
+ return(Utils::readSerial(buf,8));
}
//////////////////////////////////////
-void flashColor(boolean r, boolean g, boolean b, boolean w){
+void setup() {
+
+ Serial.begin(115200);
+ delay(1000);
- for(int i=0;i=0;i--){
- testPixel.set(Pixel::RGB(i*r,i*g,i*b,i*w),NPIXELS);
- delay(4);
+
+ testPixel=new Pixel(pin,"01234");
+
+ while(nPixels<=0){
+ Serial.printf("Enter number of PIXELS in NeoPixel device: ");
+ sscanf(getSerial(),"%d",&nPixels);
+ if(nPixels<=0)
+ Serial.printf("(invalid entry)\n");
+ else
+ Serial.printf("%d\n",nPixels);
}
+
+ Serial.printf("\nFor each test below, specify COLORS shown using the following characters:\n\n");
+ if(nPixels==1){
+ Serial.printf(" 'R' = Red\n");
+ Serial.printf(" 'G' = Green\n");
+ Serial.printf(" 'B' = Blue\n");
+ Serial.printf(" 'W' = White (or Warm-White)\n");
+ Serial.printf(" 'C' = Cool White\n");
+ Serial.printf(" '-' = Pixel is NOT lit\n");
+ }
+ else{
+ Serial.printf(" 'R' = FIRST Pixel is Red\n");
+ Serial.printf(" 'G' = FIRST Pixel is Green\n");
+ Serial.printf(" 'B' = FIRST Pixel is Blue\n");
+ Serial.printf(" 'W' = FIRST Pixel is White (or Warm-White)\n");
+ Serial.printf(" 'C' = FIRST Pixel is Cool White\n");
+ Serial.printf(" '-' = neither FIRST nor SECOND Pixel is lit\n");
+ Serial.printf(" 'X' = FIRST Pixel is not lit, but SECOND Pixel is lit (any color)\n");
+ }
+ Serial.printf("\nNote: entries are case-insensitive.\n\n");
+
+ char pType[6]="";
+
+ for(int i=0;i<5;i++){
+ testPixel->set(colors[i]);
+ while(strlen(pType)==i){
+ Serial.printf("Test #%d - enter COLOR: ",i+1);
+ if(nPixels==1)
+ sscanf(getSerial(),"%1[RGBWCrgbwc-]",pType+i);
+ else
+ sscanf(getSerial(),"%1[RGBWCrgbwcxX-]",pType+i);
+ if(strlen(pType)==i)
+ Serial.printf("(invalid entry)\n");
+ else{
+ pType[i]=toupper(pType[i]);
+ Serial.printf("'%s'\n",pType+i);
+ }
+ }
+ if(pType[i]=='X')
+ break;
+ }
+
+ while(strlen(pType)>3 && ((pType[strlen(pType)-1]=='-' && nPixels==1) || pType[strlen(pType)-1]=='X'))
+ pType[strlen(pType)-1]='\0';
+
+ Serial.printf("\nTest Concluded. Best match for your Pixel Type is '%s'\n\n",pType);
+ testPixel=new Pixel(pin,pType);
+ testPixel->set(Pixel::RGB(0,0,0,0,0),nPixels);
+ Serial.printf("Hit ENTER to verify with flashing test\n\n");
+ getSerial();
+
}
//////////////////////////////////////
void loop(){
+
+ char c[]="RGBWC";
- Serial.printf("Red...");
- flashColor(1,0,0,0);
-
- Serial.printf("Green...");
- flashColor(0,1,0,0);
-
- Serial.printf("Blue...");
- flashColor(0,0,1,0);
-
- if(testPixel.isRGBW()){
- Serial.printf("White...");
- flashColor(0,0,0,1);
+ for(int i=0;i<5;i++){
+ if(testPixel->hasColor(c[i])){
+ Serial.printf("Color '%c'...",c[i]);
+
+ for(int v=0;vset(Pixel::RGB(i==0?v:0,i==1?v:0,i==2?v:0,i==3?v:0,i==4?v:0),nPixels);
+ delay(4*255/MAX_BRIGHTNESS);
+ }
+
+ for(int v=MAX_BRIGHTNESS;v>=0;v--){
+ testPixel->set(Pixel::RGB(i==0?v:0,i==1?v:0,i==2?v:0,i==3?v:0,i==4?v:0),nPixels);
+ delay(4*255/MAX_BRIGHTNESS);
+ }
+ }
}
-
- Serial.printf("Pausing.\n");
- delay(1000);
+ testPixel->set(Pixel::RGB(0,0,0,0,0),nPixels);
+ Serial.printf("Done.\n");
+ Serial.printf("Hit ENTER to repeat with flashing test, or type 'R' to restart program...\n");
+ if(toupper(getSerial()[0])=='R')
+ ESP.restart();
}
//////////////////////////////////////
diff --git a/ESP32/HomeSpan-master/examples/Other Examples/ProgrammableHub/ProgrammableHub.ino b/ESP32/HomeSpan-master/examples/Other Examples/ProgrammableHub/ProgrammableHub.ino
index fec8a48..2cc8f43 100644
--- a/ESP32/HomeSpan-master/examples/Other Examples/ProgrammableHub/ProgrammableHub.ino
+++ b/ESP32/HomeSpan-master/examples/Other Examples/ProgrammableHub/ProgrammableHub.ino
@@ -79,9 +79,9 @@ void setup() {
homeSpan.setLogLevel(1);
- homeSpan.setHostNameSuffix(""); // use null string for suffix (rather than the HomeSpan device ID)
- homeSpan.setPortNum(1201); // change port number for HomeSpan so we can use port 80 for the Web Server
- homeSpan.setWifiCallback(setupWeb); // need to start Web Server after WiFi is established
+ homeSpan.setHostNameSuffix(""); // use null string for suffix (rather than the HomeSpan device ID)
+ homeSpan.setPortNum(1201); // change port number for HomeSpan so we can use port 80 for the Web Server
+ homeSpan.setConnectionCallback(setupWeb); // need to start Web Server after WiFi is established
homeSpan.begin(Category::Lighting,"HomeSpan Light Hub",HUB_NAME);
@@ -264,7 +264,10 @@ void listAccessories(const char *buf){
///////////////////////////
-void setupWeb(){
+void setupWeb(int count){
+
+ if(count>1)
+ return;
Serial.printf("Starting Light Server Hub at %s.local\n\n",HUB_NAME);
webServer.begin();
diff --git a/ESP32/HomeSpan-master/examples/Other Examples/RemoteSensors/MainDevice/MainDevice.ino b/ESP32/HomeSpan-master/examples/Other Examples/RemoteSensors/MainDevice/MainDevice.ino
index 270f4e4..0c971f2 100644
--- a/ESP32/HomeSpan-master/examples/Other Examples/RemoteSensors/MainDevice/MainDevice.ino
+++ b/ESP32/HomeSpan-master/examples/Other Examples/RemoteSensors/MainDevice/MainDevice.ino
@@ -51,7 +51,7 @@ struct RemoteTempSensor : Service::TemperatureSensor {
const char *name;
float temperature;
- RemoteTempSensor(const char *name, const char*macAddress) : Service::TemperatureSensor(){
+ RemoteTempSensor(const char *name, const char*macAddress, boolean is8266=false) : Service::TemperatureSensor(){
this->name=name;
@@ -60,7 +60,7 @@ struct RemoteTempSensor : Service::TemperatureSensor {
fault=new Characteristic::StatusFault(1); // set initial state = fault
- remoteTemp=new SpanPoint(macAddress,0,sizeof(float)); // create a SpanPoint with send size=0 and receive size=sizeof(float)
+ remoteTemp=new SpanPoint(macAddress,0,sizeof(float),1,is8266); // create a SpanPoint with send size=0 and receive size=sizeof(float)
} // end constructor
@@ -99,13 +99,13 @@ void setup() {
new Service::AccessoryInformation();
new Characteristic::Identify();
new Characteristic::Name("Indoor Temp");
- new RemoteTempSensor("Device 1","AC:67:B2:77:42:20"); // pass MAC Address of Remote Device
+ new RemoteTempSensor("Device 1","BC:FF:4D:40:8E:71",true); // pass MAC Address of Remote Device with flag noting it is an ESP8266
new SpanAccessory();
new Service::AccessoryInformation();
new Characteristic::Identify();
new Characteristic::Name("Outdoor Temp");
- new RemoteTempSensor("Device 2","84:CC:A8:11:B4:84"); // pass MAC Address of Remote Device
+ new RemoteTempSensor("Device 2","84:CC:A8:11:B4:84"); // pass MAC Address of Remote Device
} // end of setup()
diff --git a/ESP32/HomeSpan-master/examples/Other Examples/RemoteSensors/RemoteDevice/RemoteDevice.ino b/ESP32/HomeSpan-master/examples/Other Examples/RemoteSensors/RemoteDevice/RemoteDevice.ino
index 7dba05b..8cfb203 100644
--- a/ESP32/HomeSpan-master/examples/Other Examples/RemoteSensors/RemoteDevice/RemoteDevice.ino
+++ b/ESP32/HomeSpan-master/examples/Other Examples/RemoteSensors/RemoteDevice/RemoteDevice.ino
@@ -57,12 +57,12 @@ void setup() {
Serial.begin(115200);
delay(1000);
- Serial.printf("\n\nThis is a REMOTE Device with MAC Address = %s\n",WiFi.macAddress().c_str());
+ Serial.printf("\n\nThis is a REMOTE Device with MAC Address = %s\n",Network.macAddress().c_str());
Serial.printf("NOTE: This MAC Address must be entered into the corresponding SpanPoint() call of the MAIN Device.\n\n");
// In the line below, replace the MAC Address with that of your MAIN HOMESPAN DEVICE
- mainDevice=new SpanPoint("84:CC:A8:11:B4:84",sizeof(float),0); // create a SpanPoint with send size=sizeof(float) and receive size=0
+ mainDevice=new SpanPoint("AC:67:B2:77:42:20",sizeof(float),0); // create a SpanPoint with send size=sizeof(float) and receive size=0
homeSpan.setLogLevel(1);
}
diff --git a/ESP32/HomeSpan-master/examples/Other Examples/RemoteSensors/RemoteDevice8266/RemoteDevice8266.ino b/ESP32/HomeSpan-master/examples/Other Examples/RemoteSensors/RemoteDevice8266/RemoteDevice8266.ino
new file mode 100644
index 0000000..89ed441
--- /dev/null
+++ b/ESP32/HomeSpan-master/examples/Other Examples/RemoteSensors/RemoteDevice8266/RemoteDevice8266.ino
@@ -0,0 +1,129 @@
+/*********************************************************************************
+ * MIT License
+ *
+ * Copyright (c) 2023 Gregg E. Berman
+ *
+ * https://github.com/HomeSpan/HomeSpan
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ ********************************************************************************/
+
+#ifndef ARDUINO_ARCH_ESP8266
+#error ERROR: THIS SKETCH IS DESIGNED FOR ESP8266 MICROCONTROLLERS!
+#endif
+
+// *** THIS SKETCH IS FOR AN ESP8266, NOT AN ESP32 *** //
+
+// This sketch is similar to HomeSpan's RemoteDevice.ino example (designed for an ESP32 running HomeSpan) in which we simulate
+// a Remote Temperature Sensor using HomeSpan's SpanPoint class. However, since neither HomeSpan nor SpanPoint is designed to
+// run on an ESP8266, we will implement the BASIC communication functionality of SpanPoint by directly calling the equivalent
+// ESP-NOW commands that are supported by the ESP8266. This sketch does NOT seek to replicate all of SpanPoint's features, and
+// does not include automatic channel calibration or queue management.
+
+// Start by including the following ESP8266 libraries
+
+#include
+#include
+#include // this library is needed to implement the hash-code process SpanPoint uses to generate ESP-NOW encryption keys
+
+float temp=-10.0; // this global variable represents our "simulated" temperature (in degrees C)
+
+// Below we encode the MAC Address of the Main ESP32 Device running HomeSpan to which this ESP8266 device will connect
+
+// IMPORTANT: ESP32 devices have TWO MAC Addresses. One is used when the ESP32 is operating in Station (STA) mode. It is the address returned
+// by the WiFi.macAddress() function. The other is used when the ESP32 is operating in Access Point (AP) mode. This address is returned by the
+// WiFi.softAPmacAddress() function. HomeSpan normally operates the ESP32 with both modes (STA+AP), so both MAC Addresses are active.
+
+// On ESP32 devices, ESP-NOW seems to work fine when each device sends data to other devices via their STA MAC Address. The same is true for ESP8266
+// devices sending data to an ESP32 device via ESP-NOW with one critical exception: Once the ESP32 connects (via STA mode) to a WiFi network, which it must
+// do to run HomeSpan, for some reason ESP8266 devices can no longer send data via ESP-NOW to the ESP32 using its STA MAC Address.
+
+// The solution is to instead have the ESP8266 send data via ESP-NOW to the ESP32's AP MAC Address. This seems to work regardless of whether or not
+// the ESP32 is connected to a central WiFi newtork. To support such use on the ESP32, the SpanPoint constructor includes a fifth, optional parameter
+// called "useAPaddress". When creating SpanPoint links of the ESP32 using HomeSpan, set useAPaddress to TRUE if the Remote Device SpanPoint is connecting
+// to is an ESP8266. Set "useAPaddress" to FALSE (or leave unspecified, since FALSE is the default) if the Remote Device is an ESP32.
+
+// When HomeSpan first starts (and whenever you type 'i' into the CLI), the Serial Monitor will display the details of each SpanPoint object you instantiated
+// in your ESP32 sketch. This output includes the MAC Address at which SpanPoint will be listening for incoming data from Remote Devices. The MAC Address
+// shown for the instance of SpanPoint corresponding to this Remote Deivce (i.e. this sketch) is the MAC Address you should use below.
+
+uint8_t main_mac[6]={0xAC,0x67,0xB2,0x77,0x42,0x21}; // this is the **AP MAC Address** of the Main Device running HomeSpan on an ESP32 as reported in the HomeSpan Serial Monitor
+
+// Next we create a simple, standard ESP-NOW callback function to report on the status of each data transmission
+
+void OnDataSent(uint8_t *mac_addr, uint8_t sendStatus) {
+ Serial.printf("Last Packet Send Status: %s\n",sendStatus==0?"Success":"Fail");
+}
+
+//////////////////////
+
+void setup() {
+
+ Serial.begin(115200);
+ delay(1000);
+ Serial.printf("\nMAC Address: %s\n",WiFi.macAddress().c_str()); // enter this MAC address as the first argument of the matching SpanPoint object on the ESP32 running HomeSpan
+
+ WiFi.mode(WIFI_STA); // set the mode to Station
+ wifi_set_channel(3); // you also need to manually set the channel to match whatever channel is used by the ESP32 after it connects to your WiFi network
+
+ // Hint: As an alterntive, you can add code to this sketch to connect to the same WiFi network that HomeSpan uses. Though this sketch won't make any use of that WiFi network,
+ // by establishing the connection the ESP8266 automatically configures the channel, which will now match the ESP32.
+
+ // Next, initialize ESP-NOW
+
+ if (esp_now_init() != 0) {
+ Serial.println("Error initializing ESP-NOW");
+ return;
+ }
+
+ // SpanPoint uses ESP-NOW encryption for all communication. This encrpytion is based on two 16-byte keys: a local master key (LMK) and a primary master key (PMK). To generate
+ // these keys, SpanPoint takes a text-based password (the default is the word "HomeSpan"), creates a 32 byte (256 bit) hash of the text (using the SHA256 method), and uses
+ // the first 16 bytes as the LMK and the last 16 bytes as the PMK. This is easily replicated as follows:
+
+ uint8_t hash[32]; // create space to store as 32-byte hash code
+ char password[]="HomeSpan"; // specify the password
+
+ experimental::crypto::SHA256::hash(password,strlen(password),hash); // create the hash code to be used further below
+
+ esp_now_register_send_cb(OnDataSent); // register the callback function we defined above
+ esp_now_set_self_role(ESP_NOW_ROLE_CONTROLLER); // set the role of this device to be a controller (i.e. it sends data to the ESP32)
+
+ esp_now_set_kok(hash+16,16); // next we set the PMK. For some reason this is called KOK on the ESP8266. Note you must set the PMK BEFORE adding any peers
+
+ esp_now_add_peer(main_mac, ESP_NOW_ROLE_COMBO, 0, hash, 16); // now we add in the peer, set its role, and specify the LMK
+
+ // Hint: The third argument above is the WiFi Channel. However, this is only a reference number stored by ESP-NOW. ESP-NOW does NOT actually set the channel for you.
+ // We already set the WiFi channel above. To make things easier, ESP-NOW allows you to set the channel as zero, which means ESP-NOW should expect the channel to be whatever was
+ // already set for the WiFi controller. Recommend always setting this to zero to avoid having any mismatches if you instead specified a real channel.
+}
+
+//////////////////////
+
+void loop() {
+
+ Serial.printf("Sending Temperature: %f\n",temp);
+ esp_now_send(main_mac, (uint8_t *)&temp, sizeof(temp)); // Send the Data to the Main Device!
+
+ temp+=0.5; // increment the "temperature" by 0.5 C
+ if(temp>35.0)
+ temp=-10.0;
+
+ delay(5000); // wait 5 seconds before sending another update
+}
diff --git a/ESP32/HomeSpan-master/examples/Other Examples/RemoteSensors/RemoteTempSensor/RemoteTempSensor.ino b/ESP32/HomeSpan-master/examples/Other Examples/RemoteSensors/RemoteTempSensor/RemoteTempSensor.ino
index 63d5931..0a0cf46 100644
--- a/ESP32/HomeSpan-master/examples/Other Examples/RemoteSensors/RemoteTempSensor/RemoteTempSensor.ino
+++ b/ESP32/HomeSpan-master/examples/Other Examples/RemoteSensors/RemoteTempSensor/RemoteTempSensor.ino
@@ -70,7 +70,7 @@ void setup() {
homeSpan.setLogLevel(1);
Serial.begin(115200);
delay(1000);
- Serial.printf("Starting Remote Temperature Sensor. MAC Address of this device = %s\n",WiFi.macAddress().c_str());
+ Serial.printf("Starting Remote Temperature Sensor. MAC Address of this device = %s\n",Network.macAddress().c_str());
#endif
// In the line below, replace the MAC Address with that of your MAIN HOMESPAN DEVICE
diff --git a/ESP32/HomeSpan-master/examples/Other Examples/ServoControl/ServoControl.ino b/ESP32/HomeSpan-master/examples/Other Examples/ServoControl/ServoControl.ino
index 5c8c49d..a495f0d 100644
--- a/ESP32/HomeSpan-master/examples/Other Examples/ServoControl/ServoControl.ino
+++ b/ESP32/HomeSpan-master/examples/Other Examples/ServoControl/ServoControl.ino
@@ -26,9 +26,8 @@
********************************************************************************/
// This example demonstrates how to control a real-world Servo Motor using HomeSpan's
-// ServoPin Class, as included in "extras/PwmPin.h" The code builds upon the
-// WindowShade Accessory from Example 13 by adding a Horizontal Tilt Characteristic that
-// is controlled by a Servo connected to the ESP32.
+// ServoPin Class. The code builds upon the WindowShade Accessory from Example 13 by
+// adding a Horizontal Tilt Characteristic that is controlled by a Servo connected to the ESP32.
#include "HomeSpan.h"
#include "DEV_DoorsWindows.h"
diff --git a/ESP32/HomeSpan-master/library.properties b/ESP32/HomeSpan-master/library.properties
index 4316e91..f3124b5 100644
--- a/ESP32/HomeSpan-master/library.properties
+++ b/ESP32/HomeSpan-master/library.properties
@@ -1,9 +1,9 @@
name=HomeSpan
-version=1.9.1
+version=2.1.2
author=Gregg
maintainer=Gregg
sentence=A robust and extremely easy-to-use HomeKit implementation for the Espressif ESP32 running on the Arduino IDE.
-paragraph=This library provides a microcontroller-focused implementation of Apple's HomeKit Accessory Protocol (HAP - Release R2) designed specifically for the ESP32 running on the Arduino IDE. HomeSpan pairs directly to iOS Home via WiFi without the need for any external bridges or components. Supports ESP32, ESP32-S2, ESP32-C3, and ESP32-S3.
+paragraph=This library provides a microcontroller-focused implementation of Apple's HomeKit Accessory Protocol (HAP - Release R2) designed specifically for the ESP32 running on the Arduino IDE. HomeSpan pairs directly to iOS Home via WiFi or Ethernet without the need for any external bridges or components. Supports the original ESP32 as well as the S2, S3, C3 and C6.
url=https://github.com/HomeSpan/HomeSpan
architectures=esp32
includes=HomeSpan.h
diff --git a/ESP32/HomeSpan-master/src/Characteristics.h b/ESP32/HomeSpan-master/src/Characteristics.h
index 8500728..c5ffc78 100644
--- a/ESP32/HomeSpan-master/src/Characteristics.h
+++ b/ESP32/HomeSpan-master/src/Characteristics.h
@@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
- * Copyright (c) 2020-2024 Gregg E. Berman
+ * Copyright (c) 2020-2025 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
diff --git a/ESP32/HomeSpan-master/src/FeatherPins.h b/ESP32/HomeSpan-master/src/FeatherPins.h
index e513d94..7d33386 100644
--- a/ESP32/HomeSpan-master/src/FeatherPins.h
+++ b/ESP32/HomeSpan-master/src/FeatherPins.h
@@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
- * Copyright (c) 2020-2024 Gregg E. Berman
+ * Copyright (c) 2020-2025 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
@@ -26,13 +26,14 @@
********************************************************************************/
// For developer use and testing only - provides a common set of pin numbers mapped to the Adafruit Feather Board.
-// Facilitates the testing of identical code on an ESP32, ESP32-S2, and ESP32-C3 using a common jig without rewiring
+// Facilitates the testing of identical code on different ESP32 chips using a common jig without rewiring
#pragma once
#if defined(ARDUINO_FEATHER_ESP32)
enum {
- F13=13,F12=12,F27=27,F15=15,F32=32,F14=14,F16=16,F17=17,F21=21, // Digital Only (9 pins)
+ F13=13,F12=12,F27=27, // Digital w/Touch (3 pins)
+ F15=15,F32=32,F14=14,F16=16,F17=17,F21=21, // Digital Only (6 pins)
F26=26,F25=25,F34=34,F39=39,F36=36,F4=4, // A0-A5
F22=22,F23=23, // I2C SCL/SDA
F5=5,F18=18,F19=19,F33=33 // SPI SCK/SDO/SDI/CS
@@ -41,7 +42,8 @@
#elif defined(ARDUINO_ESP32S2_DEV)
enum {
- F13=1,F12=3,F27=7,F15=10,F32=42,F14=11,F16=20,F17=21,F21=16, // Digital Only (9 pins)
+ F13=1,F12=3,F27=7, // Digital w/Touch (3 pins)
+ F15=10,F32=42,F14=11,F16=20,F17=21,F21=16, // Digital Only (6 pins)
F26=17,F25=14,F34=13,F39=12,F36=18,F4=19, // A0-A5
F22=9,F23=8, // I2C SCL/SDA
F5=36,F18=35,F19=37,F33=34, // SPI SCK/SDO/SDI/CS
@@ -51,7 +53,7 @@
#elif defined(ARDUINO_ESP32C3_DEV)
enum {
- F27=19,F32=2,F14=10,F16=20,F17=21,F21=18, // Digital Only (6 pins)
+ F27=19,F32=2,F14=10,F16=20,F17=21,F21=18, // Digital Only (6 pins, but F16 and F17 used for Serial)
F26=0,F25=1,F4=3, // A0/A1/A5
F22=9,F23=8, // I2C SCL/SDA
F5=4,F18=6,F19=5,F33=7, // SPI SCK/SDO/SDI/CS
@@ -61,7 +63,8 @@
#elif defined(ARDUINO_ESP32S3_DEV)
enum {
- F13=5,F12=6,F27=7,F15=16,F32=17,F14=18,F16=37,F17=36,F21=38, // Digital Only (9 pins)
+ F13=5,F12=6,F27=7, // Digital w/Touch (3 pins)
+ F15=16,F32=17,F14=18,F16=41,F17=40,F21=38, // Digital Only (6 pins)
F26=1,F25=2,F34=20,F39=19,F36=15,F4=4, // A0-A5
F22=9,F23=8, // I2C SCL/SDA
F5=12,F18=11,F19=13,F33=10, // SPI SCK/SDO/SDI/CS
@@ -69,4 +72,23 @@
};
#define DEVICE_SUFFIX "-S3"
+#elif defined(ARDUINO_ESP32C6_DEV)
+ enum {
+ F12=9,F27=6,F15=7,F32=10,F14=11,F16=13,F17=12,F21=15, // Digital Only (8 pins)
+ F26=3,F25=2,F34=1,F39=0,F36=5,F4=4, // A0-A5
+ F22=22,F23=23, // I2C SCL/SDA
+ F5=21,F18=19,F19=20,F33=18, // SPI SCK/SDO/SDI/CS
+ BUILTIN_PIXEL=8 // Built-in Neo-Pixel
+ };
+ #define DEVICE_SUFFIX "-C6"
+
+#elif defined(ARDUINO_WESP32)
+ enum {
+ F12=2,F27=32, // Digital w/Touch (2 pins)
+ F26=12,F25=14,F32=18,F14=23,F21=5,F33=13, // Digital Only (6 pins)
+ F34=35,F39=34,F36=36,F4=33, // A2-A5
+ F22=4,F23=15 // I2C SCL/SDA
+ };
+ #define DEVICE_SUFFIX "-WESP32"
+
#endif
diff --git a/ESP32/HomeSpan-master/src/HAP.cpp b/ESP32/HomeSpan-master/src/HAP.cpp
index 102e382..d427240 100644
--- a/ESP32/HomeSpan-master/src/HAP.cpp
+++ b/ESP32/HomeSpan-master/src/HAP.cpp
@@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
- * Copyright (c) 2020-2024 Gregg E. Berman
+ * Copyright (c) 2020-2025 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
@@ -233,6 +233,8 @@ void HAPClient::processRequest(){
} // PUT request
if(!strncmp(body,"GET ",4)){ // this is a GET request
+
+ int refreshTime;
if(!strncmp(body,"GET /accessories ",17)) // GET ACCESSORIES
getAccessoriesURL();
@@ -240,8 +242,8 @@ void HAPClient::processRequest(){
else if(!strncmp(body,"GET /characteristics?",21)) // GET CHARACTERISTICS
getCharacteristicsURL(body+21);
- else if(homeSpan.webLog.isEnabled && !strncmp(body,homeSpan.webLog.statusURL.c_str(),homeSpan.webLog.statusURL.length())) // GET STATUS - AN OPTIONAL, NON-HAP-R2 FEATURE
- getStatusURL(this,NULL,NULL);
+ else if(homeSpan.webLog.isEnabled && (refreshTime=homeSpan.webLog.check(body+4))>=0) // OPTIONAL (NON-HAP) STATUS REQUEST
+ getStatusURL(this,NULL,NULL,refreshTime);
else {
notFoundError();
@@ -914,6 +916,9 @@ int HAPClient::getCharacteristicsURL(char *urlBuf){
LOG1("In Get Characteristics #%d (%s)...\n",clientNumber,client.remoteIP().toString().c_str());
+ if(homeSpan.getCharacteristicsCallback)
+ homeSpan.getCharacteristicsCallback(urlBuf);
+
int len=strlen(urlBuf); // determine number of IDs specified by counting commas in URL
int numIDs=1;
for(int i=0;i>>>>>>>>> %s >>>>>>>>>>\n",client.remoteIP().toString().c_str());
@@ -1005,13 +1007,13 @@ int HAPClient::putCharacteristicsURL(char *json){
} else { // multicast respose is required
- homeSpan.printfAttributes(pObj,n);
+ homeSpan.printfAttributes(pVec);
size_t nBytes=hapOut.getSize();
hapOut.flush();
hapOut.setLogLevel(2).setHapClient(this);
hapOut << "HTTP/1.1 207 Multi-Status\r\nContent-Type: application/hap+json\r\nContent-Length: " << nBytes << "\r\n\r\n";
- homeSpan.printfAttributes(pObj,n);
+ homeSpan.printfAttributes(pVec);
hapOut.flush();
}
@@ -1019,7 +1021,7 @@ int HAPClient::putCharacteristicsURL(char *json){
// Create and send Event Notifications if needed
- eventNotify(pObj,n,this); // transmit EVENT Notification for "n" pObj objects, except DO NOT notify client making request
+ eventNotify(pVec,this); // transmit EVENT Notification for objects, except DO NOT notify client making request
return(1);
}
@@ -1043,7 +1045,7 @@ int HAPClient::putPrepareURL(char *json){
uint64_t pid;
if((cBuf=strstr(json,ttlToken)))
- sscanf(cBuf+strlen(ttlToken),"%u",&ttl);
+ sscanf(cBuf+strlen(ttlToken),"%lu",&ttl);
if((cBuf=strstr(json,pidToken)))
sscanf(cBuf+strlen(ttlToken),"%llu",&pid);
@@ -1074,8 +1076,10 @@ int HAPClient::putPrepareURL(char *json){
//////////////////////////////////////
-void HAPClient::getStatusURL(HAPClient *hapClient, void (*callBack)(const char *, void *), void *user_data){
-
+void HAPClient::getStatusURL(HAPClient *hapClient, void (*callBack)(const char *, void *), void *user_data, int refreshTime){
+
+ std::shared_lock readLock(homeSpan.webLog.mux); // wait for mux to be unlocked, or already locked non-exclusively, and then lock *non-exclusively* to prevent writing in vLog
+
char clocktime[33];
if(homeSpan.webLog.timeInit){
@@ -1100,8 +1104,12 @@ void HAPClient::getStatusURL(HAPClient *hapClient, void (*callBack)(const char *
hapOut.setHapClient(hapClient).setLogLevel(2).setCallback(callBack).setCallbackUserData(user_data);
- if(!callBack)
- hapOut << "HTTP/1.1 200 OK\r\nContent-type: text/html; charset=utf-8\r\n\r\n";
+ if(!callBack){
+ hapOut << "HTTP/1.1 200 OK\r\nContent-type: text/html; charset=utf-8\r\n";
+ if(refreshTime>0)
+ hapOut << "Refresh: " << refreshTime << "\r\n";
+ hapOut << "\r\n";
+ }
hapOut << "" << homeSpan.displayName << "\n";
hapOut << "\n";
@@ -1111,51 +1119,26 @@ void HAPClient::getStatusURL(HAPClient *hapClient, void (*callBack)(const char *
hapOut << "
Up Time:
" << uptime << "
\n";
hapOut << "
Current Time:
" << clocktime << "
\n";
hapOut << "
Boot Time:
" << homeSpan.webLog.bootTime << "
\n";
- hapOut << "
Reset Reason:
";
-
- switch(esp_reset_reason()) {
- case ESP_RST_UNKNOWN:
- hapOut << "Cannot be determined";
- break;
- case ESP_RST_POWERON:
- hapOut << "Power-on event";
- break;
- case ESP_RST_EXT:
- hapOut << "External pin";
- break;
- case ESP_RST_SW:
- hapOut << "Software reboot via esp_restart";
- break;
- case ESP_RST_PANIC:
- hapOut << "Software Exception/Panic";
- break;
- case ESP_RST_INT_WDT:
- hapOut << "Interrupt watchdog";
- break;
- case ESP_RST_TASK_WDT:
- hapOut << "Task watchdog";
- break;
- case ESP_RST_WDT:
- hapOut << "Other watchdogs";
- break;
- case ESP_RST_DEEPSLEEP:
- hapOut << "Exiting deep sleep mode";
- break;
- case ESP_RST_BROWNOUT:
- hapOut << "Brownout";
- break;
- case ESP_RST_SDIO:
- hapOut << "SDIO";
- break;
- default:
- hapOut << "Unknown Reset Code";
+ hapOut << "
\n";
@@ -1218,9 +1201,9 @@ void HAPClient::getStatusURL(HAPClient *hapClient, void (*callBack)(const char *
void HAPClient::checkNotifications(){
- if(!homeSpan.Notifications.empty()){ // if there are Notifications to process
- eventNotify(&homeSpan.Notifications[0],homeSpan.Notifications.size()); // transmit EVENT Notifications
- homeSpan.Notifications.clear(); // clear Notifications vector
+ if(!homeSpan.Notifications.empty()){ // if there are Notifications to process
+ eventNotify(homeSpan.Notifications); // transmit EVENT Notifications
+ homeSpan.Notifications.clear(); // clear Notifications vector
}
}
@@ -1233,7 +1216,7 @@ void HAPClient::checkTimedWrites(){
auto tw=homeSpan.TimedWrites.begin();
while(tw!=homeSpan.TimedWrites.end()){
if(cTime>tw->second){ // timer has expired
- LOG2("Removing PID=%llu ALARM=%u\n",tw->first,tw->second);
+ LOG2("Removing PID=%llu ALARM=%lu\n",tw->first,tw->second);
tw=homeSpan.TimedWrites.erase(tw);
}
else
@@ -1243,22 +1226,22 @@ void HAPClient::checkTimedWrites(){
//////////////////////////////////////
-void HAPClient::eventNotify(SpanBuf *pObj, int nObj, HAPClient *ignore){
+void HAPClient::eventNotify(SpanBufVec &pVec, HAPClient *ignore){
for(auto it=homeSpan.hapList.begin(); it!=homeSpan.hapList.end(); ++it){ // loop over all connection slots
if(&(*it)!=ignore){ // if NOT flagged to be ignored (in cases where it is the client making a PUT request)
- homeSpan.printfNotify(pObj,nObj,&(*it)); // create JSON (which may be of zero length if there are no applicable notifications for this cNum)
+ homeSpan.printfNotify(pVec,&(*it)); // create JSON (which may be of zero length if there are no applicable notifications for this cNum)
size_t nBytes=hapOut.getSize();
hapOut.flush();
- if(nBytes>0){ // if there ARE notifications to send to client cNum
+ if(nBytes>0){ // if there ARE notifications to send to client cNum
LOG2("\n>>>>>>>>>> %s >>>>>>>>>>\n",it->client.remoteIP().toString().c_str());
hapOut.setLogLevel(2).setHapClient(&(*it));
hapOut << "EVENT/1.0 200 OK\r\nContent-Type: application/hap+json\r\nContent-Length: " << nBytes << "\r\n\r\n";
- homeSpan.printfNotify(pObj,nObj,&(*it));
+ homeSpan.printfNotify(pVec,&(*it));
hapOut.flush();
LOG2("\n-------- SENT ENCRYPTED! --------\n");
@@ -1555,7 +1538,7 @@ HapOut::HapStreamBuffer::HapStreamBuffer(){
ctx = (mbedtls_sha512_context *)heap_caps_malloc(sizeof(mbedtls_sha512_context),caps); // space for hash context
mbedtls_sha512_init(ctx); // initialize context
- mbedtls_sha512_starts_ret(ctx,1); // start SHA-384 hash (note second argument=1)
+ mbedtls_sha512_starts(ctx,1); // start SHA-384 hash (note second argument=1)
setp(buffer, buffer+bufSize-1); // assign buffer pointers
}
@@ -1607,7 +1590,7 @@ void HapOut::HapStreamBuffer::flushBuffer(){
delay(1);
}
- mbedtls_sha512_update_ret(ctx,(uint8_t *)buffer,num); // update hash
+ mbedtls_sha512_update(ctx,(uint8_t *)buffer,num); // update hash
pbump(-num); // reset buffer pointers
}
@@ -1643,8 +1626,8 @@ int HapOut::HapStreamBuffer::sync(){
callBackUserData=NULL;
}
- mbedtls_sha512_finish_ret(ctx,hash); // finish SHA-384 and store hash
- mbedtls_sha512_starts_ret(ctx,1); // re-start hash for next time
+ mbedtls_sha512_finish(ctx,hash); // finish SHA-384 and store hash
+ mbedtls_sha512_starts(ctx,1); // re-start hash for next time
return(0);
}
diff --git a/ESP32/HomeSpan-master/src/HAP.h b/ESP32/HomeSpan-master/src/HAP.h
index 9532741..3998cdd 100644
--- a/ESP32/HomeSpan-master/src/HAP.h
+++ b/ESP32/HomeSpan-master/src/HAP.h
@@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
- * Copyright (c) 2020-2024 Gregg E. Berman
+ * Copyright (c) 2020-2025 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
@@ -72,34 +72,6 @@ struct Accessory {
uint8_t LTPK[crypto_sign_PUBLICKEYBYTES]; // Long Term Ed2519 Public Key
};
-//////////////////////////////////////////////////////////
-// Paired Controller Structure for Permanently-Stored Data
-
-class Controller {
- friend class HAPClient;
-
- boolean allocated=false; // DEPRECATED (but needed for backwards compatability with original NVS storage of Controller info)
- boolean admin; // Controller has admin privileges
- uint8_t ID[36]; // Pairing ID
- uint8_t LTPK[32]; // Long Term Ed2519 Public Key
-
- public:
-
- Controller(uint8_t *id, uint8_t *ltpk, boolean ad){
- allocated=true;
- admin=ad;
- memcpy(ID,id,36);
- memcpy(LTPK,ltpk,32);
- }
-
- Controller(){}
-
- const uint8_t *getID() const {return(ID);}
- const uint8_t *getLTPK() const {return(LTPK);}
- boolean isAdmin() const {return(admin);}
-
-};
-
/////////////////////////////////////////////////
// HAPClient Structure
// Reads and Writes from each HAP Client connection
@@ -118,7 +90,7 @@ struct HAPClient {
// individual structures and data defined for each Hap Client connection
- WiFiClient client; // handle to client
+ NetworkClient client; // handle to client
int clientNumber; // client number
Controller *cPair=NULL; // pointer to info on current, session-verified Paired Controller (NULL=un-verified, and therefore un-encrypted, connection)
@@ -173,9 +145,9 @@ struct HAPClient {
static void tearDown(uint8_t *id); // tears down connections using Controller with ID=id; tears down all connections if id=NULL
static void checkNotifications(); // checks for Event Notifications and reports to controllers as needed (HAP Section 6.8)
static void checkTimedWrites(); // checks for expired Timed Write PIDs, and clears any found (HAP Section 6.7.2.4)
- static void eventNotify(SpanBuf *pObj, int nObj, HAPClient *ignore=NULL); // transmits EVENT Notifications for nObj SpanBuf objects, pObj, with optional flag to ignore a specific client
+ static void eventNotify(SpanBufVec &pVec, HAPClient *ignore=NULL); // transmits EVENT Notifications for SpanBuf objects with optional flag to ignore a specific client
- static void getStatusURL(HAPClient *, void (*)(const char *, void *), void *); // GET / status (an optional, non-HAP feature)
+ static void getStatusURL(HAPClient *, void (*)(const char *, void *), void *, int refreshTime=0); // GET / status (an optional, non-HAP feature)
class HAPTLV : public TLV8 { // dedicated class for HAP TLV8 records
public:
diff --git a/ESP32/HomeSpan-master/src/HAPConstants.h b/ESP32/HomeSpan-master/src/HAPConstants.h
index 2bbf02c..5aa28f9 100644
--- a/ESP32/HomeSpan-master/src/HAPConstants.h
+++ b/ESP32/HomeSpan-master/src/HAPConstants.h
@@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
- * Copyright (c) 2020-2024 Gregg E. Berman
+ * Copyright (c) 2020-2025 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
diff --git a/ESP32/HomeSpan-master/src/HKDF.cpp b/ESP32/HomeSpan-master/src/HKDF.cpp
index 4398070..c3a9331 100644
--- a/ESP32/HomeSpan-master/src/HKDF.cpp
+++ b/ESP32/HomeSpan-master/src/HKDF.cpp
@@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
- * Copyright (c) 2020-2024 Gregg E. Berman
+ * Copyright (c) 2020-2025 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
diff --git a/ESP32/HomeSpan-master/src/HKDF.h b/ESP32/HomeSpan-master/src/HKDF.h
index 2db1820..004670d 100644
--- a/ESP32/HomeSpan-master/src/HKDF.h
+++ b/ESP32/HomeSpan-master/src/HKDF.h
@@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
- * Copyright (c) 2020-2024 Gregg E. Berman
+ * Copyright (c) 2020-2025 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
diff --git a/ESP32/HomeSpan-master/src/HapQR.h b/ESP32/HomeSpan-master/src/HapQR.h
index 3ff15f2..d534cba 100644
--- a/ESP32/HomeSpan-master/src/HapQR.h
+++ b/ESP32/HomeSpan-master/src/HapQR.h
@@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
- * Copyright (c) 2020-2024 Gregg E. Berman
+ * Copyright (c) 2020-2025 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
diff --git a/ESP32/HomeSpan-master/src/HomeSpan.cpp b/ESP32/HomeSpan-master/src/HomeSpan.cpp
index 3ed0421..dc822cf 100644
--- a/ESP32/HomeSpan-master/src/HomeSpan.cpp
+++ b/ESP32/HomeSpan-master/src/HomeSpan.cpp
@@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
- * Copyright (c) 2020-2024 Gregg E. Berman
+ * Copyright (c) 2020-2025 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
@@ -36,11 +36,13 @@
#include
#include
#include
-#include
#include
+#include
+#include
#include "HomeSpan.h"
#include "HAP.h"
+#include
#include
const __attribute__((section(".rodata_custom_desc"))) SpanPartition spanPartition = {HOMESPAN_MAGIC_COOKIE,0};
@@ -51,6 +53,22 @@ HapOut hapOut; // Specialized output stream that can both p
Span homeSpan; // HAP Attributes database and all related control functions for this Accessory (global-scoped variable)
HapCharacteristics hapChars; // Instantiation of all HAP Characteristics used to create SpanCharacteristics (global-scoped variable)
+///////////////////////////////
+// init() //
+///////////////////////////////
+
+// init() is a global "weak" function pre-defined in the Arduino-ESP32 library.
+// It gets called at the end of initArduino(), which in turn is called just before the loopTask that then calls setup() and loop().
+// Defining init() here allows HomeSpan to perform late-stage initializations immediately before setup() is called
+
+extern "C" void init(){
+ static boolean initialized=false; // ensure this function is only called once
+ if(initialized)
+ return;
+ initialized=true;
+ homeSpan.init(); // call the init() method in homeSpan
+}
+
///////////////////////////////
// Span //
///////////////////////////////
@@ -74,6 +92,22 @@ Span::Span(){
///////////////////////////////
+void Span::init(){
+
+ log_i("Initializing Span");
+
+ WiFi.setAutoReconnect(false); // allow HomeSpan to handle disconnect/reconnect logic
+ WiFi.persistent(false); // do not permanently store WiFi configuration data
+ WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN); // scan ALL channels - do NOT stop at first SSID match, else you could connect to weaker BSSID
+ WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL); // sort scan data by RSSI and connect to strongest BSSID with matching SSID
+
+ networkEventQueue=xQueueCreate(10,sizeof(arduino_event_id_t)); // queue to transmit network events
+ Network.onEvent([](arduino_event_id_t event){xQueueSend(homeSpan.networkEventQueue, &event, (TickType_t) 0);});
+ Network.onEvent([](arduino_event_id_t event){homeSpan.useEthernet();},arduino_event_id_t::ARDUINO_EVENT_ETH_START);
+}
+
+///////////////////////////////
+
void Span::begin(Category catID, const char *_displayName, const char *_hostNameBase, const char *_modelName){
loopTaskHandle=xTaskGetCurrentTaskHandle(); // a roundabout way of getting the current task handle
@@ -87,9 +121,7 @@ void Span::begin(Category catID, const char *_displayName, const char *_hostName
statusLED=new Blinker(statusDevice,autoOffLED); // create Status LED, even is statusDevice is NULL
- esp_task_wdt_delete(xTaskGetIdleTaskHandleForCPU(0)); // required to avoid watchdog timeout messages from ESP32-C3
-
- hapServer=new WiFiServer(tcpPortNum); // create HAP WIFI SERVER
+ hapServer=new NetworkServer(tcpPortNum); // create HAP Server (can be WiFi or Ethernet)
size_t len;
@@ -105,7 +137,7 @@ void Span::begin(Category catID, const char *_displayName, const char *_hostName
LOG0("\n************************************************************\n"
"Welcome to HomeSpan!\n"
- "Apple HomeKit for the Espressif ESP-32 WROOM and Arduino IDE\n"
+ "Apple HomeKit for the Espressif ESP-32/S2/S3/C3/C6 chips\n"
"************************************************************\n\n"
"** Please ensure serial monitor is set to transmit \n\n");
@@ -129,7 +161,7 @@ void Span::begin(Category catID, const char *_displayName, const char *_hostName
LOG0("\nHomeSpan Version: %s",HOMESPAN_VERSION);
LOG0("\nArduino-ESP Ver.: %s",ARDUINO_ESP_VERSION);
LOG0("\nESP-IDF Version: %d.%d.%d",ESP_IDF_VERSION_MAJOR,ESP_IDF_VERSION_MINOR,ESP_IDF_VERSION_PATCH);
- LOG0("\nESP32 Chip: %s Rev %d %s-core %dMB Flash", ESP.getChipModel(),ESP.getChipRevision(),
+ LOG0("\nESP32 Chip: %s Rev %d %s-core %luMB Flash", ESP.getChipModel(),ESP.getChipRevision(),
ESP.getChipCores()==1?"single":"dual",ESP.getFlashChipSize()/1024/1024);
#ifdef ARDUINO_VARIANT
@@ -138,16 +170,33 @@ void Span::begin(Category catID, const char *_displayName, const char *_hostName
#endif
LOG0("\nPWM Resources: %d channels, %d timers, max %d-bit duty resolution",
- LEDC_SPEED_MODE_MAX*LEDC_CHANNEL_MAX,LEDC_SPEED_MODE_MAX*LEDC_TIMER_MAX,LEDC_TIMER_BIT_MAX-1);
+ (int)LEDC_SPEED_MODE_MAX*(int)LEDC_CHANNEL_MAX,(int)LEDC_SPEED_MODE_MAX*(int)LEDC_TIMER_MAX,LEDC_TIMER_BIT_MAX-1);
+ LOG0("\nRMT Resources: %d transmission channels of %d symbols each",SOC_RMT_TX_CANDIDATES_PER_GROUP,SOC_RMT_MEM_WORDS_PER_CHANNEL);
+
+ #ifdef SOC_TOUCH_SENSOR_NUM
+ LOG0("\nTouch Sensors: %d pins",SOC_TOUCH_SENSOR_NUM);
+ #else
+ LOG0("\nTouch Sensors: none");
+ #endif
LOG0("\nSodium Version: %s Lib %d.%d",sodium_version_string(),sodium_library_version_major(),sodium_library_version_minor());
char mbtlsv[64];
mbedtls_version_get_string_full(mbtlsv);
LOG0("\nMbedTLS Version: %s",mbtlsv);
- LOG0("\nSketch Compiled: %s %s",__DATE__,__TIME__);
+ LOG0("\nSketch Compiled: %s",compileTime?compileTime:"N/A");
LOG0("\nPartition: %s",esp_ota_get_running_partition()->label);
- LOG0("\nMAC Address: %s",WiFi.macAddress().c_str());
+ if(hsWDT.getSeconds())
+ LOG0("\nHS Watchdog: %d seconds",hsWDT.getSeconds());
+ else
+ LOG0("\nHS Watchdog: DISABLED");
+
+ for(int i=0;i'.\n\n");
- STATUS_UPDATE(start(LED_WIFI_NEEDED),HS_WIFI_NEEDED)
- }
- } else {
- STATUS_UPDATE(start(LED_WIFI_CONNECTING),HS_WIFI_CONNECTING)
+ }
}
-
+
if(controlButton)
- controlButton->reset();
+ controlButton->reset();
+ resetStatus();
+
LOG0("%s is READY!\n\n",displayName);
isInitialized=true;
} // isInitialized
- if(strlen(network.wifiData.ssid)>0){
- checkConnect();
+ if(!ethernetEnabled && strlen(network.wifiData.ssid) && !(connected%2) && millis()>alarmConnect){
+ if(verboseWifiReconnect)
+ addWebLog(true,"Trying to connect to %s. Waiting %ld sec...",network.wifiData.ssid,wifiTimeCounter/1000);
+
+ alarmConnect=millis()+(wifiTimeCounter++);
+ wifiBegin(network.wifiData.ssid,network.wifiData.pwd);
}
- char cBuf[65]="?";
+ if(rescanStatus==RESCAN_PENDING && millis()>rescanAlarm){
+ rescanStatus=RESCAN_RUNNING;
+ LOG2("Rescanning %s for potentially better BSSID...\n",network.wifiData.ssid);
+ WiFi.scanDelete();
+ STATUS_UPDATE(start(LED_WIFI_SCANNING),HS_WIFI_SCANNING)
+ WiFi.scanNetworks(true, false, false, 300, 0, network.wifiData.ssid, nullptr); // start scan in background
+ }
+
+ arduino_event_id_t event;
+ if(xQueueReceive(networkEventQueue, &event, (TickType_t)0))
+ networkCallback(event);
+
+ char cBuf[65]="-";
if(!serialInputDisabled && Serial.available()){
readSerial(cBuf,64);
@@ -230,7 +299,7 @@ void Span::pollTask() {
if(hapServer->hasClient()){
auto it=hapList.emplace(hapList.begin()); // create new HAPClient connection
- it->client=hapServer->available();
+ it->client=hapServer->accept();
it->clientNumber=it->client.fd()-LWIP_SOCKET_OFFSET;
HAPClient::pairStatus=pairState_M1; // reset starting PAIR STATE (which may be needed if Accessory failed in middle of pair-setup)
@@ -293,7 +362,14 @@ void Span::pollTask() {
nvs_set_u8(wifiNVS,"REBOOTS",rebootCount);
nvs_commit(wifiNVS);
}
-
+
+ if(!initialPollingCompleted && pollingCallback){
+ initialPollingCompleted=true;
+ pollingCallback();
+ }
+
+ pollLock.unlock();
+ resetWatchdog(); // reset watchdog timer
} // poll
//////////////////////////////////////
@@ -327,6 +403,8 @@ void Span::commandMode(){
done=true;
}
} // button press
+
+ resetWatchdog();
} // while
STATUS_UPDATE(start(LED_ALERT),static_cast(HS_ENTERING_CONFIG_MODE+mode+5))
@@ -335,7 +413,6 @@ void Span::commandMode(){
switch(mode){
case 1:
- resetStatus();
break;
case 2:
@@ -357,57 +434,100 @@ void Span::commandMode(){
} // switch
LOG0("*** EXITING COMMAND MODE ***\n\n");
+ resetStatus();
}
//////////////////////////////////////
-void Span::checkConnect(){
+Span& Span::setConnectionTimes(uint32_t minTime, uint32_t maxTime, uint8_t nSteps){
+
+ if(minTime==0 || maxTime==0 || nSteps==0)
+ LOG0("\n*** WARNING! Call to setConnectionTimes(%ld,%ld,%d) ignored: illegal parameters\n",minTime,maxTime,nSteps);
+ else
+ wifiTimeCounter.config(minTime*1000,maxTime*1000,nSteps);
+ return(*this);
+}
- if(connected%2){
- if(WiFi.status()==WL_CONNECTED)
- return;
+//////////////////////////////////////
+
+void Span::networkCallback(arduino_event_id_t event){
+
+ switch (event) {
- addWebLog(true,"*** WiFi Connection Lost!");
- connected++;
- waitTime=60000;
- alarmConnect=0;
- STATUS_UPDATE(start(LED_WIFI_CONNECTING),HS_WIFI_CONNECTING)
- }
+ case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
+ if(connected%2){ // we are in a connected state
+ connected++; // move to unconnected state
+ addWebLog(true,"*** WiFi Connection Lost!");
+ wifiTimeCounter.reset();
+ alarmConnect=millis();
+ }
+ resetStatus();
+ break;
- if(WiFi.status()!=WL_CONNECTED){
- if(millis()0){
+ rescanAlarm=millis()+rescanInitialTime;
+ rescanStatus=RESCAN_PENDING;
+ }
+ resetStatus();
+ break;
- if(waitTime==60000)
- waitTime=1000;
- else
- waitTime*=2;
-
- if(waitTime==32000){
- LOG0("\n*** Can't connect to %s. You may type 'W ' to re-configure WiFi, or 'X ' to erase WiFi credentials. Will try connecting again in 60 seconds.\n\n",network.wifiData.ssid);
- waitTime=60000;
- } else {
- if(verboseWifiReconnect)
- addWebLog(true,"Trying to connect to %s. Waiting %d sec...",network.wifiData.ssid,waitTime/1000);
- WiFi.begin(network.wifiData.ssid,network.wifiData.pwd);
- }
+ case ARDUINO_EVENT_WIFI_SCAN_DONE:
+ if(rescanStatus==RESCAN_RUNNING){
+ if(WiFi.scanComplete()>0 && WiFi.BSSIDstr(0)!=WiFi.BSSIDstr() && WiFi.RSSI(0)>=WiFi.RSSI()+rescanThreshold){
+ addWebLog(true,"*** Switching to Access Point with stronger RSSI...");
+ WiFi.disconnect();
+ } else {
+ LOG2("Rescan completed. No stronger signals found.\n");
+ if(rescanPeriodicTime>0){
+ rescanAlarm=millis()+rescanPeriodicTime;
+ rescanStatus=RESCAN_PENDING;
+ } else {
+ rescanStatus=RESCAN_IDLE;
+ }
+ }
+ }
+ resetStatus();
+ break;
- alarmConnect=millis()+waitTime;
+ case ARDUINO_EVENT_ETH_GOT_IP:
+ case ARDUINO_EVENT_ETH_GOT_IP6:
+ addWebLog(true,"Ethernet Connected! IP Address = %s",ETH.localIP().toString().c_str());
+ connected++;
+ if(connected==1)
+ configureNetwork();
+ if(connectionCallback)
+ connectionCallback((connected+1)/2);
+ resetStatus();
+ break;
- return;
+ case ARDUINO_EVENT_ETH_DISCONNECTED:
+ if(connected%2){ // we are in a connected state
+ connected++; // move to unconnected state
+ addWebLog(true,"*** Ethernet Connection Lost!");
+ }
+ resetStatus();
+ break;
+
+ default:
+ break;
}
+}
- resetStatus();
- connected++;
+//////////////////////////////////////
- addWebLog(true,"WiFi Connected! IP Address = %s",WiFi.localIP().toString().c_str());
-
- if(connected>1){ // Do not initialize everything below if this is only a reconnect
- if(wifiCallbackAll)
- wifiCallbackAll((connected+1)/2);
- return;
- }
-
+void Span::configureNetwork(){
+
char id[18]; // create string version of Accessory ID for MDNS broadcast
memcpy(id,HAPClient::accessory.ID,17); // copy ID bytes
id[17]='\0'; // add terminating null
@@ -472,29 +592,29 @@ void Span::checkConnect(){
memcpy(hashInput,qrID,4); // Create the Setup ID for use with optional QR Codes. This is an undocumented feature of HAP R2!
memcpy(hashInput+4,id,17); // Step 1: Concatenate 4-character Setup ID and 17-character Accessory ID into hashInput
- mbedtls_sha512_ret(hashInput,21,hashOutput,0); // Step 2: Perform SHA-512 hash on combined 21-byte hashInput to create 64-byte hashOutput
+ mbedtls_sha512(hashInput,21,hashOutput,0); // Step 2: Perform SHA-512 hash on combined 21-byte hashInput to create 64-byte hashOutput
mbedtls_base64_encode((uint8_t *)setupHash,9,&len,hashOutput,4); // Step 3: Encode the first 4 bytes of hashOutput in base64, which results in an 8-character, null-terminated, setupHash
mdns_service_txt_item_set("_hap","_tcp","sh",setupHash); // Step 4: broadcast the resulting Setup Hash
if(spanOTA.enabled){
ArduinoOTA.setHostname(hostName);
-
if(spanOTA.auth)
ArduinoOTA.setPasswordHash(spanOTA.otaPwd);
ArduinoOTA.onStart(spanOTA.start).onEnd(spanOTA.end).onProgress(spanOTA.progress).onError(spanOTA.error);
ArduinoOTA.begin();
- LOG0("Starting OTA Server: %s at %s\n",displayName,WiFi.localIP().toString().c_str());
- LOG0("Authorization Password: %s",spanOTA.auth?"Enabled\n\n":"DISABLED!\n\n");
+ LOG0("Starting OTA Server: %s at %s\n",displayName,ethernetEnabled?ETH.localIP().toString().c_str():WiFi.localIP().toString().c_str());
+ LOG0("Authorization Password: %s",spanOTA.auth?"Enabled\n":"DISABLED!\n");
+ LOG0("Auto Rollback: %s",verifyRollbackLater()?"Enabled\n\n":"Disabled\n\n");
}
- mdns_service_txt_item_set("_hap","_tcp","ota",spanOTA.enabled?"yes":"no"); // OTA status (info only - NOT used by HAP)
+ mdns_service_txt_item_set("_hap","_tcp","ota",spanOTA.enabled?"yes":"no"); // OTA status (info only - NOT used by HAP)
if(webLog.isEnabled){
- mdns_service_txt_item_set("_hap","_tcp","logURL",webLog.statusURL.c_str()+4); // Web Log status (info only - NOT used by HAP)
+ mdns_service_txt_item_set("_hap","_tcp","logURL",webLog.statusURL); // Web Log status (info only - NOT used by HAP)
- LOG0("Web Logging enabled at http://%s.local:%d%swith max number of entries=%d\n\n",hostName,tcpPortNum,webLog.statusURL.c_str()+4,webLog.maxEntries);
+ LOG0("Web Logging enabled at http://%s.local:%d%s with max number of entries=%d\n\n",hostName,tcpPortNum,webLog.statusURL,webLog.maxEntries);
}
if(webLog.timeServer)
@@ -511,9 +631,6 @@ void Span::checkConnect(){
if(wifiCallback)
wifiCallback();
-
- if(wifiCallbackAll)
- wifiCallbackAll((connected+1)/2);
} // initWiFi
@@ -536,12 +653,80 @@ Span& Span::setQRID(const char *id){
void Span::processSerialCommand(const char *c){
switch(c[0]){
+
+ case '-': {
+ LOG0("Please type '?' for list of commands\n");
+ }
+ break;
+
+ case 'p': {
+ LOG0("\n Partition Address Size OTA State");
+ LOG0("\n ---------------- -------- -------- --------- \n");
+ auto it=esp_partition_find(ESP_PARTITION_TYPE_ANY,ESP_PARTITION_SUBTYPE_ANY,NULL);
+ uint32_t totalSize=0;
+ uint32_t flashSize;
+ while(it){
+ const esp_partition_t *part=esp_partition_get(it);
+ if(totalSize==0){
+ totalSize=part->address; // add in offset of first partition
+ esp_flash_get_physical_size(part->flash_chip,&flashSize);
+ }
+ totalSize+=part->size;
+ LOG0("%1.1s %-16.16s 0x%06lX %8lu ",esp_ota_get_running_partition()==part?"*":" ",part->label,part->address,part->size);
+ esp_ota_img_states_t state;
+ if(ESP_OK==esp_ota_get_state_partition(part,&state)){
+ switch(state){
+ case ESP_OTA_IMG_VALID: LOG0(" VALID"); break;
+ case ESP_OTA_IMG_UNDEFINED: LOG0("UNDEFINED"); break;
+ case ESP_OTA_IMG_INVALID: LOG0(" INVALID"); break;
+ case ESP_OTA_IMG_ABORTED: LOG0(" ABORTED"); break;
+ case ESP_OTA_IMG_PENDING_VERIFY: LOG0(" PENDING"); break;
+ default: LOG0(" ???");
+ }
+ }
+ LOG0("\n");
+ it=esp_partition_next(it);
+ }
+ esp_partition_iterator_release(it);
+ LOG0("\nTotal Size Allocated (including any initial offset): %lu of %lu available\n\n",totalSize,flashSize);
+ }
+ break;
+
+ case 'D': {
+ WiFi.disconnect();
+ }
+ break;
case 'Z': {
- for(auto it=hapList.begin(); it!=hapList.end(); ++it){
- (*it).client.stop();
- delay(5);
-
+ LOG0("Scanning WiFi Networks...\n");
+ WiFi.scanDelete();
+ STATUS_UPDATE(start(LED_WIFI_SCANNING),HS_WIFI_SCANNING)
+ int n=WiFi.scanNetworks();
+ if(n==0){
+ LOG0("No networks found!\n");
+ } else {
+ char d[]="----------------------------------------";
+ LOG0("\n%-32.32s %17.17s %4.4s %12.12s\n","SSID","BSSID","RSSI","ENCRYPTION");
+ LOG0("%-32.32s %17.17s %4.4s %12.12s\n",d,d,d,d);
+ for(int i=0;iend();
MDNS.end();
WiFi.disconnect();
+ delay(1000);
}
network.serialConfigure();
@@ -816,7 +1010,7 @@ void Span::processSerialCommand(const char *c){
char pNames[][7]={"PR","PW","EV","AA","TW","HD","WR"};
for(auto acc=Accessories.begin(); acc!=Accessories.end(); acc++){
- LOG0("\u27a4 Accessory: AID=%u\n",(*acc)->aid);
+ LOG0("\u27a4 Accessory: AID=%lu\n",(*acc)->aid);
boolean foundInfo=false;
if(acc==Accessories.begin() && (*acc)->aid!=1)
@@ -829,7 +1023,10 @@ void Span::processSerialCommand(const char *c){
vector> iidValues;
for(auto svc=(*acc)->Services.begin(); svc!=(*acc)->Services.end(); svc++){
- LOG0(" \u279f Service %s: IID=%u, %sUUID=\"%s\"\n",(*svc)->hapName,(*svc)->iid,(*svc)->isCustom?"Custom-":"",(*svc)->type);
+ LOG0(" \u279f Service %s: IID=%lu, %sUUID=\"%s\"\n",(*svc)->hapName,(*svc)->iid,(*svc)->isCustom?"Custom-":"",(*svc)->type);
+
+ if(invalidUUID((*svc)->type))
+ LOG0(" *** ERROR #%d! Format of UUID is invalid ***\n",++nErrors);
if(!strcmp((*svc)->type,"3E")){
foundInfo=true;
@@ -840,12 +1037,12 @@ void Span::processSerialCommand(const char *c){
isBridge=false; // ...this is not a bridge device
if(std::find(iidValues.begin(),iidValues.end(),(*svc)->iid)!=iidValues.end())
- LOG0(" *** ERROR #%d! IID already in use for another Service or Characteristic within this Accessory ***\n",++nErrors);
+ LOG0(" *** ERROR #%d! IID already in use for another Service or Characteristic within this Accessory ***\n",++nErrors);
iidValues.push_back((*svc)->iid);
for(auto chr=(*svc)->Characteristics.begin(); chr!=(*svc)->Characteristics.end(); chr++){
- LOG0(" \u21e8 Characteristic %s(%.33s%s): IID=%u, %sUUID=\"%s\", %sPerms=",
+ LOG0(" \u21e8 Characteristic %s(%.33s%s): IID=%lu, %sUUID=\"%s\", %sPerms=",
(*chr)->hapName,(*chr)->uvPrint((*chr)->value).c_str(),strlen((*chr)->uvPrint((*chr)->value).c_str())>33?"...\"":"",(*chr)->iid,(*chr)->isCustom?"Custom-":"",(*chr)->type,(*chr)->perms!=(*chr)->hapChar->perms?"Custom-":"");
int foundPerms=0;
@@ -946,7 +1143,7 @@ void Span::processSerialCommand(const char *c){
for(int i=0;iServices.size();j++){
SpanService *s=Accessories[i]->Services[j];
- LOG0("%-30s %8.8s %10u %3u %6s %4s %6s ",s->hapName,s->type,Accessories[i]->aid,s->iid,
+ LOG0("%-30s %8.8s %10lu %3lu %6s %4s %6s ",s->hapName,s->type,Accessories[i]->aid,s->iid,
(void(*)())(s->*(&SpanService::update))!=(void(*)())(&SpanService::update)?"YES":"NO",
(void(*)())(s->*(&SpanService::loop))!=(void(*)())(&SpanService::loop)?"YES":"NO",
(void(*)(int,boolean))(s->*(&SpanService::button))!=(void(*)(int,boolean))(&SpanService::button)?"YES":"NO"
@@ -954,7 +1151,7 @@ void Span::processSerialCommand(const char *c){
if(s->linkedServices.empty())
LOG0("-");
for(int k=0;klinkedServices.size();k++){
- LOG0("%u",s->linkedServices[k]->iid);
+ LOG0("%lu",s->linkedServices[k]->iid);
if(klinkedServices.size()-1)
LOG0(",");
}
@@ -1075,6 +1272,7 @@ void Span::processSerialCommand(const char *c){
LOG0(" i - print summary information about the HAP Database\n");
LOG0(" d - print the full HAP Accessory Attributes Database in JSON format\n");
LOG0(" m - print free heap memory\n");
+ LOG0(" p - print flash partition table\n");
LOG0("\n");
LOG0(" W - configure WiFi Credentials and restart\n");
LOG0(" X - delete WiFi Credentials and restart\n");
@@ -1090,6 +1288,9 @@ void Span::processSerialCommand(const char *c){
LOG0(" P - output Pairing Data that can be saved offline to clone a new device\n");
LOG0(" C - clone Pairing Data previously saved offline from another device\n");
LOG0("\n");
+ LOG0(" D - disconnect/reconnect to WiFi\n");
+ LOG0(" Z - scan for available WiFi networks\n");
+ LOG0("\n");
LOG0(" R - restart device\n");
LOG0(" F - factory reset and restart\n");
LOG0(" E - erase ALL stored data and restart\n");
@@ -1139,10 +1340,10 @@ void Span::getWebLog(void (*f)(const char *, void *), void *user_data){
///////////////////////////////
void Span::resetStatus(){
- if(strlen(network.wifiData.ssid)==0)
+ if(!ethernetEnabled && strlen(network.wifiData.ssid)==0)
STATUS_UPDATE(start(LED_WIFI_NEEDED),HS_WIFI_NEEDED)
- else if(WiFi.status()!=WL_CONNECTED)
- STATUS_UPDATE(start(LED_WIFI_CONNECTING),HS_WIFI_CONNECTING)
+ else if(!(connected%2))
+ STATUS_UPDATE(start(LED_WIFI_CONNECTING),ethernetEnabled?HS_ETH_CONNECTING:HS_WIFI_CONNECTING)
else if(!HAPClient::nAdminControllers())
STATUS_UPDATE(start(LED_PAIRING_NEEDED),HS_PAIRING_NEEDED)
else
@@ -1163,6 +1364,7 @@ const char* Span::statusString(HS_STATUS s){
switch(s){
case HS_WIFI_NEEDED: return("WiFi Credentials Needed");
case HS_WIFI_CONNECTING: return("WiFi Connecting");
+ case HS_ETH_CONNECTING: return("Ethernet Connecting");
case HS_PAIRING_NEEDED: return("Device not yet Paired");
case HS_PAIRED: return("Device Paired");
case HS_ENTERING_CONFIG_MODE: return("Entering Command Mode");
@@ -1182,6 +1384,7 @@ const char* Span::statusString(HS_STATUS s){
case HS_AP_CONNECTED: return("Access Point Connected");
case HS_AP_TERMINATED: return("Access Point Terminated");
case HS_OTA_STARTED: return("OTA Update Started");
+ case HS_WIFI_SCANNING: return("WiFi Scanning Started");
default: return("Unknown");
}
}
@@ -1299,67 +1502,118 @@ SpanCharacteristic *Span::find(uint32_t aid, uint32_t iid){
///////////////////////////////
-int Span::countCharacteristics(char *buf){
-
- int nObj=0;
+char *Span::escapeJSON(char *jObj){
+
+ boolean quoting=false;
+ char *p;
- const char tag[]="\"aid\"";
- while((buf=strstr(buf,tag))){ // count number of characteristic objects in PUT JSON request
- nObj++;
- buf+=strlen(tag);
- }
+ for(int i=0,k=0;;i++){
+
+ if(jObj[i]=='\0'){
+ jObj[k]='\0';
+ break;
+ }
- return(nObj);
+ if(!quoting){
+ if(strchr(" \t\n\r",jObj[i]))
+ continue;
+ if(jObj[i]=='"')
+ quoting=true;
+ } else {
+ if(jObj[i]=='"' && i>0 && jObj[i-1]!='\\')
+ quoting=false;
+ else if((p=strchr(delims,jObj[i])))
+ jObj[i]=DELIM+p-delims;
+ }
+
+ jObj[k++]=jObj[i];
+ }
+
+ return(jObj);
}
///////////////////////////////
-int Span::updateCharacteristics(char *buf, SpanBuf *pObj){
+char *Span::unEscapeJSON(char *jObj){
- int nObj=0;
- char *p1;
- int cFound=0;
- boolean twFail=false;
-
- while(char *t1=strtok_r(buf,"{",&p1)){ // parse 'buf' and extract objects into 'pObj' unless NULL
- buf=NULL;
- char *p2;
- int okay=0;
+ for(int i=0;;i++){
- while(char *t2=strtok_r(t1,"}[]:, \"\t\n\r",&p2)){
+ if(jObj[i]=='\0')
+ break;
- if(!cFound){ // first token found
- if(strcmp(t2,"characteristics")){
- LOG0("\n*** ERROR: Problems parsing JSON - initial \"characteristics\" tag not found\n\n");
- return(0);
- }
- cFound=1;
- break;
+ if(jObj[i]>=DELIM && jObj[i]<=END_DELIM)
+ jObj[i]=delims[jObj[i]-DELIM];
+ }
+
+ return(jObj);
+}
+
+///////////////////////////////
+
+boolean Span::updateCharacteristics(char *buf, SpanBufVec &pVec){
+
+ boolean twFail=false;
+ char *jObj=escapeJSON(buf);
+ size_t end=0;
+
+ if(sscanf(jObj,"{\"characteristics\":[%[^]]]}%n",jObj,&end)!=1 || strlen(jObj+end)){
+ LOG0("\n*** ERROR: Cannot extract properly-formatted \"characteristics\" array from JSON text\n\n");
+ return(false);
+ }
+
+ for(;;){ // loop over objects in characteristics array
+ int okay=0;
+ end=0;
+ if(!sscanf(jObj,"{%[^}]}%n",jObj,&end) || end==0){
+ LOG0("\n*** ERROR: Cannot extract properly-formatted object from \"characteristics\" array\n\n");
+ return(false);
+ }
+
+ SpanBuf sBuf;
+
+ for(;;){ // loop over all name-value pairs in the object
+ end=0;
+ char *name=jObj;
+ if(sscanf(name,"\"%[^\"]\"%n",name,&end)!=1 || end==0){
+ LOG0("\n*** ERROR: Cannot extract name from \"characteristics\" object\n\n");
+ return(false);
+ }
+ char *value=name+end;
+ if(sscanf(value,":%[^,]%n",value,&end)!=1){
+ LOG0("\n*** ERROR: Cannot extract value from \"characteristics\" object\n\n");
+ return(false);
+ }
+
+ if(*value=='\"'){
+ value++;
+ end--;
+ if(value[end-2]=='\"'){
+ value[end-2]='\0';
+ unEscapeJSON(value);
+ }
}
- t1=NULL;
- char *t3;
- if(!strcmp(t2,"aid") && (t3=strtok_r(t1,"}[]:, \"\t\n\r",&p2))){
- sscanf(t3,"%u",&pObj[nObj].aid);
+ if(!strcmp(name,"aid")){
+ sscanf(value,"%lu",&sBuf.aid);
okay|=1;
} else
- if(!strcmp(t2,"iid") && (t3=strtok_r(t1,"}[]:, \"\t\n\r",&p2))){
- sscanf(t3,"%u",&pObj[nObj].iid);
+ if(!strcmp(name,"iid")){
+ sscanf(value,"%lu",&sBuf.iid);
okay|=2;
} else
- if(!strcmp(t2,"value") && (t3=strtok_r(t1,"}[]:,\"",&p2))){
- pObj[nObj].val=t3;
+ if(!strcmp(name,"value")){
+ sBuf.val=value;
okay|=4;
} else
- if(!strcmp(t2,"ev") && (t3=strtok_r(t1,"}[]:, \"\t\n\r",&p2))){
- pObj[nObj].ev=t3;
+ if(!strcmp(name,"ev")){
+ sBuf.ev=value;
okay|=8;
} else
- if(!strcmp(t2,"r") && (t3=strtok_r(t1,"}[]:, \"\t\n\r",&p2))){
- pObj[nObj].wr=(t3 && (!strcmp(t3,"1") || !strcmp(t3,"true")));
+ if(!strcmp(name,"r")){
+ sBuf.wr=(!strcmp(value,"1") || !strcmp(value,"true"));
} else
- if(!strcmp(t2,"pid") && (t3=strtok_r(t1,"}[]:, \"\t\n\r",&p2))){
- uint64_t pid=strtoull(t3,NULL,0);
+ if(!strcmp(name,"pid")){
+ uint64_t pid=strtoull(value,NULL,0);
if(!TimedWrites.count(pid)){
LOG0("\n*** ERROR: Timed Write PID not found\n\n");
twFail=true;
@@ -1369,74 +1623,82 @@ int Span::updateCharacteristics(char *buf, SpanBuf *pObj){
twFail=true;
}
} else {
- LOG0("\n*** ERROR: Problems parsing JSON characteristics object - unexpected property \"%s\"\n\n",t2);
- return(0);
+ LOG0("\n*** ERROR: Problems parsing JSON characteristics object - unexpected property \"%s\"\n\n",name);
+ return(false);
}
- } // parse property tokens
-
- if(!t1){ // at least one token was found that was not initial "characteristics"
- if(okay==7 || okay==11 || okay==15){ // all required properties found
- if(!pObj[nObj].val) // if value is NOT being updated
- pObj[nObj].wr=false; // ignore any request for write-response
- nObj++; // increment number of characteristic objects found
- } else {
- LOG0("\n*** ERROR: Problems parsing JSON characteristics object - missing required properties\n\n");
- return(0);
- }
- }
- } // parse objects
+ jObj=value+end;
+ if(*jObj++=='\0')
+ break;
+ }
+
+ if(okay==7 || okay==11 || okay==15){ // all required properties found
+ if(!sBuf.val) // if value is NOT being updated
+ sBuf.wr=false; // ignore any request for write-response
+ pVec.push_back(sBuf); // add sBuf to pVec vector
+ } else {
+ LOG0("\n*** ERROR: Problems parsing JSON characteristics object - missing required properties\n\n");
+ return(false);
+ }
+
+ if(*++jObj=='\0')
+ break;
+ else if(*jObj++!=','){
+ LOG0("\n*** ERROR: Unexpected characters trailing last object in \"characteristics\" array\n\n");
+ return(false);
+ }
+ }
snapTime=millis(); // timestamp for this series of updates, assigned to each characteristic in loadUpdate()
- for(int i=0;iloadUpdate(pObj[i].val,pObj[i].ev,pObj[i].wr); // save status code, which is either an error, or TBD (in which case updateFlag for the characteristic has been set to either 1 or 2)
+ if((*it).characteristic) // if found, initialize characteristic update with new val/ev
+ (*it).status=(*it).characteristic->loadUpdate((*it).val,(*it).ev,(*it).wr); // save status code, which is either an error, or TBD (in which case updateFlag for the characteristic has been set to either 1 or 2)
else
- pObj[i].status=StatusCode::UnknownResource; // if not found, set HAP error
+ (*it).status=StatusCode::UnknownResource; // if not found, set HAP error
}
} // first pass
- for(int i=0;iservice->update()?StatusCode::OK:StatusCode::Unable; // update service and save statusCode as OK or Unable depending on whether return is true or false
+ StatusCode status=(*it).characteristic->service->update()?StatusCode::OK:StatusCode::Unable; // update service and save statusCode as OK or Unable depending on whether return is true or false
- for(int j=i;jservice==pObj[i].characteristic->service){ // if service of this characteristic matches service that was updated
- pObj[j].status=status; // save statusCode for this object
- LOG1("Updating aid=%u iid=%u",pObj[j].characteristic->aid,pObj[j].characteristic->iid);
+ if((*jt).characteristic->service==(*it).characteristic->service){ // if service of this characteristic matches service that was updated
+ (*jt).status=status; // save statusCode for this object
+ LOG1("Updating aid=%lu iid=%lu",(*jt).characteristic->aid,(*jt).characteristic->iid);
if(status==StatusCode::OK){ // if status is okay
- pObj[j].characteristic->uvSet(pObj[j].characteristic->value,pObj[j].characteristic->newValue); // update characteristic value with new value
- if(pObj[j].characteristic->nvsKey){ // if storage key found
- if(pObj[j].characteristic->formatnvsKey,pObj[j].characteristic->value.UINT64); // store data as uint64_t regardless of actual type (it will be read correctly when access through uvGet())
+ (*jt).characteristic->uvSet((*jt).characteristic->value,(*jt).characteristic->newValue); // update characteristic value with new value
+ if((*jt).characteristic->nvsKey){ // if storage key found
+ if((*jt).characteristic->formatnvsKey,(*jt).characteristic->value.UINT64); // store data as uint64_t regardless of actual type (it will be read correctly when access through uvGet())
else
- nvs_set_str(charNVS,pObj[j].characteristic->nvsKey,pObj[j].characteristic->value.STRING); // store data
+ nvs_set_str(charNVS,(*jt).characteristic->nvsKey,(*jt).characteristic->value.STRING); // store data
nvs_commit(charNVS);
}
LOG1(" (okay)\n");
} else { // if status not okay
- pObj[j].characteristic->uvSet(pObj[j].characteristic->newValue,pObj[j].characteristic->value); // replace characteristic new value with original value
+ (*jt).characteristic->uvSet((*jt).characteristic->newValue,(*jt).characteristic->value); // replace characteristic new value with original value
LOG1(" (failed)\n");
}
- pObj[j].characteristic->updateFlag=0; // reset updateFlag for characteristic
+ (*jt).characteristic->updateFlag=0; // reset updateFlag for characteristic
}
}
} // object had TBD status
} // loop over all objects
- return(1);
+ return(true);
}
///////////////////////////////
@@ -1451,21 +1713,21 @@ void Span::clearNotify(HAPClient *hc){
///////////////////////////////
-void Span::printfNotify(SpanBuf *pObj, int nObj, HAPClient *hc){
+void Span::printfNotify(SpanBufVec &pVec, HAPClient *hc){
boolean notifyFlag=false;
- for(int i=0;ievList.has(hc)){ // if connection hc is subscribed to EV notifications for this characteristic
+ if((*it).status==StatusCode::OK && (*it).val){ // characteristic was successfully updated with a new value (i.e. not just an EV request)
+ if((*it).characteristic->evList.has(hc)){ // if connection hc is subscribed to EV notifications for this characteristic
if(!notifyFlag) // this is first notification for any characteristic
hapOut << "{\"characteristics\":["; // print start of JSON array
else // else already printed at least one other characteristic
hapOut << ","; // add preceeding comma before printing this characteristic
- pObj[i].characteristic->printfAttributes(GET_VALUE|GET_AID|GET_NV); // print JSON attributes for this characteristic
+ (*it).characteristic->printfAttributes(GET_VALUE|GET_AID|GET_NV); // print JSON attributes for this characteristic
notifyFlag=true;
}
}
@@ -1477,17 +1739,17 @@ void Span::printfNotify(SpanBuf *pObj, int nObj, HAPClient *hc){
///////////////////////////////
-void Span::printfAttributes(SpanBuf *pObj, int nObj){
+void Span::printfAttributes(SpanBufVec &pVec){
hapOut << "{\"characteristics\":[";
- for(int i=0;iuvPrint(pObj[i].characteristic->value).c_str();
- hapOut << "}";
- if(i+1uvPrint((*it).characteristic->value).c_str();
+ hapOut << "}";
}
hapOut << "]}";
@@ -1504,7 +1766,7 @@ boolean Span::printfAttributes(char **ids, int numIDs, int flags){
StatusCode status[numIDs];
for(int i=0;iprintfAttributes(flags); // get JSON attributes for characteristic (may or may not include status=0 attribute)
else{ // else create JSON status attribute based on requested aid/iid
- sscanf(ids[i],"%u.%u",&aid,&iid);
+ sscanf(ids[i],"%lu.%lu",&aid,&iid);
hapOut << "{\"iid\":" << iid << ",\"aid\":" << aid << ",\"status\":" << (int)status[i] << "}";
}
@@ -1654,7 +1916,7 @@ SpanAccessory::~SpanAccessory(){
while((*acc)!=this)
acc++;
homeSpan.Accessories.erase(acc);
- LOG1("Deleted Accessory AID=%d\n",aid);
+ LOG1("Deleted Accessory AID=%lu\n",aid);
}
///////////////////////////////
@@ -1722,7 +1984,7 @@ SpanService::~SpanService(){
}
}
- LOG1("Deleted Service AID=%u IID=%u\n",accessory->aid,iid);
+ LOG1("Deleted Service AID=%lu IID=%lu\n",accessory->aid,iid);
}
///////////////////////////////
@@ -1824,7 +2086,7 @@ SpanCharacteristic::~SpanCharacteristic(){
free(newValue.STRING);
}
- LOG1("Deleted Characteristic AID=%u IID=%u\n",aid,iid);
+ LOG1("Deleted Characteristic AID=%lu IID=%lu\n",aid,iid);
}
///////////////////////////////
@@ -2077,6 +2339,10 @@ void SpanCharacteristic::printfAttributes(int flags){
if(validValues){
hapOut << ",\"valid-values\":" << validValues;
}
+
+ if(maxLen){
+ hapOut << ",\"maxLen\":" << (int)maxLen;
+ }
}
if(desc && (flags&GET_DESC)){
@@ -2126,7 +2392,7 @@ StatusCode SpanCharacteristic::loadUpdate(char *val, char *ev, boolean wr){
if(evFlag && !(perms&EV)) // notification is not supported for characteristic
return(StatusCode::NotifyNotAllowed);
- LOG1("Notification Request for aid=%u iid=%u: %s\n",aid,iid,evFlag?"true":"false");
+ LOG1("Notification Request for aid=%lu iid=%lu: %s\n",aid,iid,evFlag?"true":"false");
HAPClient *hc=&(*(homeSpan.currentClient));
if(evFlag)
@@ -2157,7 +2423,7 @@ StatusCode SpanCharacteristic::loadUpdate(char *val, char *ev, boolean wr){
newValue.INT=0;
else if(!strcmp(val,"true"))
newValue.INT=1;
- else if(!sscanf(val,"%d",&newValue.INT))
+ else if(!sscanf(val,"%ld",&newValue.INT))
return(StatusCode::InvalidValue);
break;
@@ -2184,7 +2450,7 @@ StatusCode SpanCharacteristic::loadUpdate(char *val, char *ev, boolean wr){
newValue.UINT32=0;
else if(!strcmp(val,"true"))
newValue.UINT32=1;
- else if(!sscanf(val,"%u",&newValue.UINT32))
+ else if(!sscanf(val,"%lu",&newValue.UINT32))
return(StatusCode::InvalidValue);
break;
@@ -2203,6 +2469,9 @@ StatusCode SpanCharacteristic::loadUpdate(char *val, char *ev, boolean wr){
break;
case STRING:
+ uvSet(newValue,(const char *)val);
+ break;
+
case DATA:
case TLV_ENC:
uvSet(newValue,(const char *)stripBackslash(val));
@@ -2241,6 +2510,25 @@ uint32_t SpanCharacteristic::getIID(){
///////////////////////////////
+uint32_t SpanCharacteristic::getAID(){
+
+ return(aid);
+}
+
+///////////////////////////////
+
+boolean SpanCharacteristic::foundIn(const char *getCharList){
+
+ char *charID;
+
+ asprintf(&charID,"%lu.%lu",getAID(),getIID());
+ boolean res=strstr(getCharList,charID);
+ free(charID);
+ return(res);
+}
+
+///////////////////////////////
+
SpanCharacteristic *SpanCharacteristic::setPerms(uint8_t perms){
perms&=0x7F;
if(perms>0)
@@ -2278,6 +2566,14 @@ SpanCharacteristic *SpanCharacteristic::setUnit(const char *c){
///////////////////////////////
+SpanCharacteristic *SpanCharacteristic::setMaxStringLength(uint8_t n){
+ if(format==FORMAT::STRING)
+ maxLen=n;
+ return(this);
+}
+
+///////////////////////////////
+
SpanCharacteristic *SpanCharacteristic::setValidValues(int n, ...){
String s="[";
@@ -2396,7 +2692,7 @@ void SpanWebLog::init(uint16_t maxEntries, const char *serv, const char *tz, con
timeServer=serv;
timeZone=tz;
if(url){
- statusURL="GET /" + String(url) + " ";
+ asprintf(&statusURL,"/%s",url);
isEnabled=true;
}
log = (log_t *)HS_CALLOC(maxEntries,sizeof(log_t));
@@ -2424,8 +2720,28 @@ void SpanWebLog::initTime(void *args){
///////////////////////////////
+int SpanWebLog::check(const char *uri){
+
+ size_t n=strlen(statusURL);
+
+ if(strncasecmp(uri,statusURL,n)!=0) // no partial match of statusURL
+ return(-1);
+ if(uri[n]==' ') // match without query string
+ return(0);
+ if(uri[n]=='?'){ // match with query string
+ char val[6]="0";
+ Network_HS::getFormValue(uri+n+1,"refresh",val,5);
+ return(atoi(val)>0?atoi(val):0);
+ }
+ return(-1); // no match
+}
+
+///////////////////////////////
+
void SpanWebLog::vLog(boolean sysMsg, const char *fmt, va_list ap){
+ std::unique_lock writeLock(mux); // wait for mux to be unlocked and then lock *exclusively* so write can proceed uninterrupted
+
char *buf;
vasprintf(&buf,fmt,ap);
@@ -2508,18 +2824,23 @@ void SpanOTA::end(){
///////////////////////////////
void SpanOTA::progress(uint32_t progress, uint32_t total){
+
+ homeSpan.resetWatchdog();
+
int percent=progress*100/total;
if(percent/10 != otaPercent/10){
otaPercent=percent;
- LOG0("%d%%..",progress*100/total);
+ LOG0("%d%%..",percent);
}
- if(safeLoad && progress==total){
+ if(safeLoad && progress==total && ArduinoOTA.getCommand() == U_FLASH){
SpanPartition newSpanPartition;
esp_partition_read(esp_ota_get_next_update_partition(NULL), sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t), &newSpanPartition, sizeof(newSpanPartition));
- LOG0("Checking for HomeSpan Magic Cookie: %s..",newSpanPartition.magicCookie);
- if(strcmp(newSpanPartition.magicCookie,spanPartition.magicCookie))
+ LOG0("Checking for HomeSpan Magic Cookie: %s..",spanPartition.magicCookie);
+ if(strcmp(newSpanPartition.magicCookie,spanPartition.magicCookie)){
+ LOG0(" *** NOT FOUND! ABORTING\n");
Update.abort();
+ }
}
}
@@ -2598,7 +2919,7 @@ void SpanPoint::init(const char *password){
esp_wifi_set_config(WIFI_IF_AP,&conf);
uint8_t hash[32];
- mbedtls_sha256_ret((const unsigned char *)password,strlen(password),hash,0); // produce 256-bit bit hash from password
+ mbedtls_sha256((const unsigned char *)password,strlen(password),hash,0); // produce 256-bit bit hash from password
esp_now_init(); // initialize ESP-NOW
memcpy(lmk, hash, 16); // store first 16 bytes of hash for later use as local key
@@ -2711,7 +3032,9 @@ boolean SpanPoint::send(const void *data){
///////////////////////////////
-void SpanPoint::dataReceived(const uint8_t *mac, const uint8_t *incomingData, int len){
+void SpanPoint::dataReceived(const esp_now_recv_info *info, const uint8_t *incomingData, int len){
+
+ const uint8_t *mac=info->src_addr;
auto it=SpanPoints.begin();
for(;it!=SpanPoints.end() && memcmp((*it)->peerInfo.peer_addr,mac,6)!=0; it++);
@@ -2742,12 +3065,12 @@ uint16_t SpanPoint::channelMask=0x3FFE;
QueueHandle_t SpanPoint::statusQueue;
nvs_handle SpanPoint::pointNVS;
-
///////////////////////////////
// MISC //
///////////////////////////////
void __attribute__((weak)) loop(){
+ vTaskDelay(1);
}
///////////////////////////////
diff --git a/ESP32/HomeSpan-master/src/HomeSpan.h b/ESP32/HomeSpan-master/src/HomeSpan.h
index f0376f1..25615b8 100644
--- a/ESP32/HomeSpan-master/src/HomeSpan.h
+++ b/ESP32/HomeSpan-master/src/HomeSpan.h
@@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
- * Copyright (c) 2020-2024 Gregg E. Berman
+ * Copyright (c) 2020-2025 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
@@ -36,10 +36,13 @@
#include
#include
#include
+#include
#include
#include
+#include
#include
#include
+#include
#include "src/extras/Blinker.h"
#include "src/extras/Pixel.h"
@@ -49,7 +52,7 @@
#include "Settings.h"
#include "Utils.h"
-#include "Network.h"
+#include "Network_HS.h"
#include "HAPConstants.h"
#include "HapQR.h"
#include "Characteristics.h"
@@ -58,6 +61,7 @@
using std::vector;
using std::unordered_map;
using std::list;
+using std::string;
enum {
GET_AID=1,
@@ -85,6 +89,16 @@ typedef std::pair DATA_t;
static DATA_t NULL_DATA={NULL,0};
static TLV8 NULL_TLV{};
+///////////////////////////////
+// Macros to lock/unlock poll() mutex
+
+#define homeSpanPAUSE std::shared_lock pollLock(homeSpan.getMutex());
+#define homeSpanRESUME if(pollLock.owns_lock()){pollLock.unlock();}
+
+///////////////////////////////
+
+extern "C" bool verifyRollbackLater(); // declare pre-defined Arduino-ESP32 version, unless over-ridden in user sketch with #include "SpanRollback.h"
+
///////////////////////////////
#define STATUS_UPDATE(LED_UPDATE,MESSAGE_UPDATE) {homeSpan.statusLED->LED_UPDATE;if(homeSpan.statusCallback)homeSpan.statusCallback(MESSAGE_UPDATE);}
@@ -93,7 +107,7 @@ enum HS_STATUS {
HS_WIFI_NEEDED, // WiFi Credentials have not yet been set/stored
HS_WIFI_CONNECTING, // HomeSpan is trying to connect to the network specified in the stored WiFi Credentials
HS_PAIRING_NEEDED, // HomeSpan is connected to central WiFi network, but device has not yet been paired to HomeKit
- HS_PAIRED, // HomeSpan is connected to central WiFi network and ther device has been paired to HomeKit
+ HS_PAIRED, // HomeSpan is connected to central WiFi network and the device has been paired to HomeKit
HS_ENTERING_CONFIG_MODE, // User has requested the device to enter into Command Mode
HS_CONFIG_MODE_EXIT, // HomeSpan is in Command Mode with "Exit Command Mode" specified as choice
HS_CONFIG_MODE_REBOOT, // HomeSpan is in Command Mode with "Reboot" specified as choice
@@ -110,7 +124,37 @@ enum HS_STATUS {
HS_AP_STARTED, // HomeSpan has started the Access Point but no one has yet connected
HS_AP_CONNECTED, // The Access Point is started and a user device has been connected
HS_AP_TERMINATED, // HomeSpan has terminated the Access Point
- HS_OTA_STARTED // HomeSpan is in the process of recveived an Over-the-Air software update
+ HS_OTA_STARTED, // HomeSpan is in the process of receiving an Over-the-Air software update
+ HS_WIFI_SCANNING, // HomeSpan is in the process of scanning for WiFi networks
+ HS_ETH_CONNECTING // HomeSpan is trying to connect to an Ethernet network
+};
+
+//////////////////////////////////////////////////////////
+// Paired Controller Structure for Permanently-Stored Data
+
+class Controller {
+ friend class HAPClient;
+
+ boolean allocated=false; // DEPRECATED (but needed for backwards compatability with original NVS storage of Controller info)
+ boolean admin; // Controller has admin privileges
+ uint8_t ID[36]; // Pairing ID
+ uint8_t LTPK[32]; // Long Term Ed2519 Public Key
+
+ public:
+
+ Controller(uint8_t *id, uint8_t *ltpk, boolean ad){
+ allocated=true;
+ admin=ad;
+ memcpy(ID,id,36);
+ memcpy(LTPK,ltpk,32);
+ }
+
+ Controller(){}
+
+ const uint8_t *getID() const {return(ID);}
+ const uint8_t *getLTPK() const {return(LTPK);}
+ boolean isAdmin() const {return(admin);}
+
};
///////////////////////////////
@@ -126,7 +170,6 @@ struct SpanButton;
struct SpanUserCommand;
struct HAPClient;
-class Controller;
extern Span homeSpan;
@@ -157,6 +200,8 @@ struct SpanBuf{ // temporary storage buffer for us
StatusCode status; // return status (HAP Table 6-11)
SpanCharacteristic *characteristic=NULL; // Characteristic to update (NULL if not found)
};
+
+typedef vector> SpanBufVec;
///////////////////////////////
@@ -168,9 +213,10 @@ struct SpanWebLog{ // optional web status/log data
const char *timeZone; // optional time-zone specification
boolean timeInit=false; // flag to indicate time has been initialized
char bootTime[33]="Unknown"; // boot time
- String statusURL; // URL of status log
+ char *statusURL=NULL; // URL of status log
uint32_t waitTime=120000; // number of milliseconds to wait for initial connection to time server
String css=""; // optional user-defined style sheet for web log
+ std::shared_mutex mux; // shared read/write lock
struct log_t { // log entry type
uint64_t upTime; // number of seconds since booting
@@ -182,6 +228,7 @@ struct SpanWebLog{ // optional web status/log data
void init(uint16_t maxEntries, const char *serv, const char *tz, const char *url);
static void initTime(void *args);
void vLog(boolean sysMsg, const char *fmr, va_list ap);
+ int check(const char *uri);
};
///////////////////////////////
@@ -216,8 +263,9 @@ class Span{
friend class SpanButton;
friend class SpanWebLog;
friend class SpanOTA;
- friend class Network;
+ friend class Network_HS;
friend class HAPClient;
+ friend void init();
char *displayName; // display name for this device - broadcast as part of Bonjour MDNS
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
@@ -236,7 +284,10 @@ class Span{
boolean serialInputDisabled=false; // flag indiating that serial input is disabled
uint8_t rebootCount=0; // counts number of times device was rebooted (used in optional Reboot callback)
uint32_t rebootCallbackTime; // length of time to wait (in milliseconds) before calling optional Reboot callback
-
+ boolean ethernetEnabled=false; // flag to indicate whether Ethernet is being used instead of WiFi
+ boolean initialPollingCompleted=false; // flag to indicate whether polling task has initially completed
+ char *compileTime=NULL; // optional compile time string --- can be set with call to setCompileTime()
+
nvs_handle charNVS; // handle for non-volatile-storage of Characteristics data
nvs_handle wifiNVS=0; // handle for non-volatile-storage of WiFi data
nvs_handle otaNVS; // handle for non-volatile storage of OTA data
@@ -244,8 +295,21 @@ class Span{
nvs_handle hapNVS; // handle for non-volatile-storage of HAP data
int connected=0; // WiFi connection status (increments upon each connect and disconnect)
- unsigned long waitTime=60000; // time to wait (in milliseconds) between WiFi connection attempts
+ HS_ExpCounter wifiTimeCounter; // exponentially-increasing wait time counter between WiFi connection attempts
unsigned long alarmConnect=0; // time after which WiFi connection attempt should be tried again
+
+ static constexpr char delims[]="\"{[:,]}";
+ static const uint8_t DELIM = 0xF5;
+ static const uint8_t END_DELIM = DELIM+strlen(delims)-1;
+
+ void (*wifiBegin)(const char *s, const char *p)=[](const char *s, const char *p){WiFi.begin(s,p);}; // default call to WiFi.begin()
+
+ uint32_t rescanInitialTime=0;
+ uint32_t rescanPeriodicTime=0;
+ int rescanThreshold;
+ unsigned long rescanAlarm;
+ enum {RESCAN_IDLE, RESCAN_PENDING, RESCAN_RUNNING} rescanStatus=RESCAN_IDLE;
+ unordered_map bssidNames;
const char *defaultSetupCode=DEFAULT_SETUP_CODE; // Setup Code used for pairing
uint16_t autoOffLED=0; // automatic turn-off duration (in seconds) for Status LED
@@ -253,8 +317,8 @@ class Span{
unsigned long comModeLife=DEFAULT_COMMAND_TIMEOUT*1000; // length of time (in milliseconds) to keep Command Mode alive before resuming normal operations
uint16_t tcpPortNum=DEFAULT_TCP_PORT; // port for TCP communications between HomeKit and HomeSpan
char qrID[5]=""; // Setup ID used for pairing with QR Code
- void (*wifiCallback)()=NULL; // optional callback function to invoke once WiFi connectivity is initially established
- void (*wifiCallbackAll)(int)=NULL; // optional callback function to invoke every time WiFi connectivity is established or re-established
+ void (*wifiCallback)()=NULL; // optional callback function to invoke once WiFi connectivity is initially established *** TO BE DEPRECATED ***
+ void (*connectionCallback)(int)=NULL; // optional callback function to invoke every time WiFi or Ethernet connectivity is established or re-established
void (*weblogCallback)(String &)=NULL; // optional callback function to invoke after header table in Web Log is produced
void (*pairCallback)(boolean isPaired)=NULL; // optional callback function to invoke when pairing is established (true) or lost (false)
boolean autoStartAPEnabled=false; // enables auto start-up of Access Point when WiFi Credentials not found
@@ -262,16 +326,20 @@ class Span{
void (*statusCallback)(HS_STATUS status)=NULL; // optional callback when HomeSpan status changes
void (*rebootCallback)(uint8_t)=NULL; // optional callback when device reboots
void (*controllerCallback)()=NULL; // optional callback when Controller is added/removed/changed
+ void (*pollingCallback)()=NULL; // optional callback when polling task reaching initial completion (only called once)
+ void (*getCharacteristicsCallback)(const char *)=NULL; // optional callback function to invoke every time HomeKit sends a getCharacteristics request
- WiFiServer *hapServer; // pointer to the HAP Server connection
+ NetworkServer *hapServer; // pointer to the HAP Server connection
Blinker *statusLED; // indicates HomeSpan status
Blinkable *statusDevice = NULL; // the device used for the Blinker
PushButton *controlButton = NULL; // controls HomeSpan configuration and resets
- Network network; // configures WiFi and Setup Code via either serial monitor or temporary Access Point
+ Network_HS network; // configures WiFi and Setup Code via either serial monitor or temporary Access Point
SpanWebLog webLog; // optional web status/log
TaskHandle_t pollTaskHandle = NULL; // optional task handle to use for poll() function
TaskHandle_t loopTaskHandle; // Arduino Loop Task handle
boolean verboseWifiReconnect = true; // set to false to not print WiFi reconnect attempts messages
+ std::shared_mutex pollMutex; // mutex lock for poll task
+ hsWatchdogTimer hsWDT; // general homeSpan watchdog timer
SpanOTA spanOTA; // manages OTA process
SpanConfig hapConfig; // track configuration changes to the HAP Accessory database; used to increment the configuration number (c#) when changes found
@@ -280,26 +348,27 @@ class Span{
list>::iterator currentClient; // iterator to current client
vector> Accessories; // vector of pointers to all Accessories
vector> Loops; // vector of pointer to all Services that have over-ridden loop() methods
- vector> Notifications; // vector of SpanBuf objects that store info for Characteristics that are updated with setVal() and require a Notification Event
+ SpanBufVec Notifications; // vector of SpanBuf objects that store info for Characteristics that are updated with setVal() and require a Notification Event
vector> PushButtons; // vector of pointer to all PushButtons
unordered_map TimedWrites; // map of timed-write PIDs and Alarm Times (based on TTLs)
unordered_map UserCommands; // map of pointers to all UserCommands
- void pollTask(); // poll HAP Clients and process any new HAP requests
- void checkConnect(); // check WiFi connection; connect if needed
- void commandMode(); // allows user to control and reset HomeSpan settings with the control button
- void resetStatus(); // resets statusLED and calls statusCallback based on current HomeSpan status
- void reboot(); // reboots device
+ void pollTask(); // poll HAP Clients and process any new HAP requests
+ void configureNetwork(); // configure Network services (MDNS, WebLog, OTA, etc.) and start HAP Server
+ void commandMode(); // allows user to control and reset HomeSpan settings with the control button
+ void resetStatus(); // resets statusLED and calls statusCallback based on current HomeSpan status
+ void reboot(); // reboots device
void printfAttributes(int flags=GET_VALUE|GET_META|GET_PERMS|GET_TYPE|GET_DESC); // writes Attributes JSON database to hapOut stream
- SpanCharacteristic *find(uint32_t aid, uint32_t iid); // return Characteristic with matching aid and iid (else NULL if not found)
- int countCharacteristics(char *buf); // return number of characteristic objects referenced in PUT /characteristics JSON request
- int updateCharacteristics(char *buf, SpanBuf *pObj); // parses PUT /characteristics JSON request 'buf into 'pObj' and updates referenced characteristics; returns 1 on success, 0 on fail
- void printfAttributes(SpanBuf *pObj, int nObj); // writes SpanBuf objects to hapOut stream
- boolean printfAttributes(char **ids, int numIDs, int flags); // writes accessory requested characteristic ids to hapOut stream - returns true if all characteristics are found and readable, else returns false
- void clearNotify(HAPClient *hc); // clear all notifications related to specific client connection
- void printfNotify(SpanBuf *pObj, int nObj, HAPClient *hc); // writes notification JSON to hapOut stream based on SpanBuf objects and specified connection
+ SpanCharacteristic *find(uint32_t aid, uint32_t iid); // return Characteristic with matching aid and iid (else NULL if not found)
+ void printfAttributes(SpanBufVec &pVec); // writes SpanBuf objects to hapOut stream
+ boolean printfAttributes(char **ids, int numIDs, int flags); // writes accessory requested characteristic ids to hapOut stream - returns true if all characteristics are found and readable, else returns false
+ void clearNotify(HAPClient *hc); // clear all notifications related to specific client connection
+ void printfNotify(SpanBufVec &pVec, HAPClient *hc); // writes notification JSON to hapOut stream based on SpanBuf objects and specified connection
+ char *escapeJSON(char *jObj); // remove all whitespace not within double-quotes, and converts special characters to unused UTF-8 bytes as a placeholder
+ char *unEscapeJSON(char *jObj); // converts UTF-8 placeholder bytes back to original special characters
+ boolean updateCharacteristics(char *buf, SpanBufVec &pVec); // parses PUT /characteristics JSON request and updates referenced characteristics; returns true on success, false on fail
static boolean invalidUUID(const char *uuid){
int x=0;
@@ -309,10 +378,15 @@ class Span{
sscanf(uuid,"%*8[0-9a-fA-F]-%*4[0-9a-fA-F]-%*4[0-9a-fA-F]-%*4[0-9a-fA-F]-%*12[0-9a-fA-F]%n",&x);
return(strlen(uuid)!=36 || x!=36);
}
+
+ QueueHandle_t networkEventQueue; // queue to transmit network events from callback thread to HomeSpan thread
+ void networkCallback(WiFiEvent_t event); // network event handler (works for WiFi as well as Ethernet)
+
+ void init(); // performs all late-stage initializations needed
public:
- Span(); // constructor
+ Span(); // constructor
void begin(Category catID=DEFAULT_CATEGORY,
const char *displayName=DEFAULT_DISPLAY_NAME,
@@ -333,10 +407,10 @@ class Span{
int getControlPin(){return(controlButton?controlButton->getPin():-1);} // get Control Pin (returns -1 if undefined)
Span& setStatusPin(uint8_t pin){statusDevice=new GenericLED(pin);return(*this);} // sets Status Device to a simple LED 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);
- }
+ 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);
+ }
Span& setStatusDevice(Blinkable *sDev){statusDevice=sDev;return(*this);} // sets Status Device to a generic Blinkable object
Span& setStatusAutoOff(uint16_t duration){autoOffLED=duration;return(*this);} // sets Status LED auto off (seconds)
@@ -355,24 +429,31 @@ class Span{
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 initially established
- Span& setWifiCallbackAll(void (*f)(int)){wifiCallbackAll=f;return(*this);} // sets an optional user-defined function to call every time WiFi connectivity is established or re-established
+ Span& setConnectionCallback(void (*f)(int)){connectionCallback=f;return(*this);} // sets an optional user-defined function to call every time WiFi or Ethernet connectivity is established or re-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& setConnectionTimes(uint32_t minTime, uint32_t maxTime, uint8_t nSteps); // sets min/max WiFi connection times (in seconds) and number of steps
Span& setStatusCallback(void (*f)(HS_STATUS status)){statusCallback=f;return(*this);} // sets an optional user-defined function to call when HomeSpan status changes
const char* statusString(HS_STATUS s); // returns char string for HomeSpan status change messages
Span& setPairingCode(const char *s, boolean progCall=true); // sets the Pairing Code - use is NOT recommended. Use 'S' from CLI instead
void deleteStoredValues(){processSerialCommand("V");} // deletes stored Characteristic values from NVS
Span& resetIID(uint32_t newIID); // resets the IID count for the current Accessory to start at newIID
- Span& setControllerCallback(void (*f)()){controllerCallback=f;return(*this);} // sets an optional user-defined function to call whenever a Controller is added/removed/changed
-
- Span& setHostNameSuffix(const char *suffix){asprintf(&hostNameSuffix,"%s",suffix);return(*this);} // sets the hostName suffix to be used instead of the 6-byte AccessoryID
+ Span& setControllerCallback(void (*f)()){controllerCallback=f;return(*this);} // sets an optional user-defined function to call whenever a Controller is added/removed
+ Span& setWifiBegin(void (*f)(const char *, const char *)){wifiBegin=f;return(*this);} // sets an optional user-defined function to over-ride WiFi.begin() with additional logic
+ Span& setPollingCallback(void (*f)()){pollingCallback=f;return(*this);} // sets an optional user-defined function to call upon INITIAL completion of the polling task (only called once)
+ Span& useEthernet(){ethernetEnabled=true;return(*this);} // force use of Ethernet instead of WiFi, even if ETH not called or Ethernet card not detected
+ Span& setGetCharacteristicsCallback(void (*f)(const char *)){getCharacteristicsCallback=f;return(*this);} // sets an optional callback called whenever HomeKit sends a getCharacteristics request
+ Span& setHostNameSuffix(const char *suffix){asprintf(&hostNameSuffix,"%s",suffix);return(*this);} // sets the hostName suffix to be used instead of the 6-byte AccessoryID
+ Span& setCompileTime(const char *compTime=__DATE__ " " __TIME__){asprintf(&compileTime,"%s",compTime);return(*this);} // sets the compile time to compTime; default is to use compiler-provided date/time
+
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 markSketchOK(){esp_ota_mark_app_valid_cancel_rollback();}
+
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);
@@ -388,32 +469,43 @@ class Span{
Span& setWebLogCSS(const char *css){webLog.css="\n" + String(css) + "\n";return(*this);}
Span& setWebLogCallback(void (*f)(String &)){weblogCallback=f;return(*this);}
void getWebLog(void (*f)(const char *, void *), void *);
+ void assumeTimeAcquired(){webLog.timeInit=true;}
Span& setVerboseWifiReconnect(bool verbose=true){verboseWifiReconnect=verbose;return(*this);}
Span& setRebootCallback(void (*f)(uint8_t),uint32_t t=DEFAULT_REBOOT_CALLBACK_TIME){rebootCallback=f;rebootCallbackTime=t;return(*this);}
- void autoPoll(uint32_t stackSize=8192, uint32_t priority=1, uint32_t cpu=0){ // start pollTask()
- xTaskCreateUniversal([](void *parms){
- for(;;){
- homeSpan.pollTask();
- vTaskDelay(5);
- }
- },
- "pollTask", stackSize, NULL, priority, &pollTaskHandle, cpu);
- LOG0("\n*** AutoPolling Task started with priority=%d\n\n",uxTaskPriorityGet(pollTaskHandle));
+ std::shared_mutex& getMutex(){return(pollMutex);}
+
+ void autoPoll(uint32_t stackSize=8192, uint32_t priority=1, uint32_t core=0){
+ xTaskCreateUniversal( [](void *parms){for(;;)homeSpan.pollTask();}, "pollTask", stackSize, NULL, priority, &pollTaskHandle, core);
}
TaskHandle_t getAutoPollTask(){return(pollTaskHandle);}
Span& setTimeServerTimeout(uint32_t tSec){webLog.waitTime=tSec*1000;return(*this);} // sets wait time (in seconds) for optional web log time server to connect
+
+ Span& enableWiFiRescan(uint32_t iTime=1, uint32_t pTime=0, int thresh=3){ // enables periodic WiFi rescan to search for stronger BSSID
+ rescanInitialTime=iTime*60000;
+ rescanPeriodicTime=pTime*60000;
+ rescanThreshold=thresh;
+ return(*this);
+ }
+
+ Span& enableWatchdog(uint16_t nSeconds=CONFIG_ESP_TASK_WDT_TIMEOUT_S){hsWDT.enable(nSeconds);return(*this);} // enables HomeSpan watchdog with timeout of nSeconds
+ void disableWatchdog(){hsWDT.disable();} // disables HomeSpan watchdog
+ void resetWatchdog(){hsWDT.reset();} // resets HomeSpan watchdog
+
+ Span& addBssidName(String bssid, string name){bssid.toUpperCase();bssidNames[bssid.c_str()]=name;return(*this);}
list>::const_iterator controllerListBegin();
list>::const_iterator controllerListEnd();
- [[deprecated("This function has been deprecated (it is not needed) and no longer does anything. Please remove from sketch to ensure backwards compatilibilty with future versions.")]]
- Span& reserveSocketConnections(uint8_t n){return(*this);}
-
+ [[deprecated("This homeSpan method has been deprecated and will be removed in a future version. Please use the more generic setConnectionCallback() method instead.")]]
+ Span& setWifiCallback(void (*f)()){wifiCallback=f;return(*this);} // sets an optional user-defined function to call once WiFi connectivity is initially established
+
+ [[deprecated("This homeSpan method has been deprecated and will be removed in a future version. Please use the more generic setConnectionCallback() method instead.")]]
+ Span& setWifiCallbackAll(void (*f)(int)){connectionCallback=f;return(*this);} // sets an optional user-defined function to call every time WiFi connectivity is established or re-established
};
///////////////////////////////
@@ -438,7 +530,10 @@ class SpanAccessory{
public:
void *operator new(size_t size){return(HS_MALLOC(size));} // override new operator to use PSRAM when available
+ void operator delete(void *p){free(p);}
+
SpanAccessory(uint32_t aid=0); // constructor
+ uint32_t getAID(){return(aid);}
};
///////////////////////////////
@@ -470,6 +565,8 @@ class SpanService{
public:
void *operator new(size_t size){return(HS_MALLOC(size));} // override new operator to use PSRAM when available
+ void operator delete(void *p){free(p);}
+
SpanService(const char *type, const char *hapName, boolean isCustom=false); // constructor
SpanService *setPrimary(); // sets the Service Type to be primary and returns pointer to self
SpanService *setHidden(); // sets the Service Type to be hidden and returns pointer to self
@@ -485,6 +582,7 @@ class SpanService{
}
uint32_t getIID(){return(iid);} // returns IID of Service
+ uint32_t getAID(){return(accessory->aid);} // returns AID of enclosing Accessory
virtual boolean update() {return(true);} // placeholder for code that is called when a Service is updated via a Controller. Must return true/false depending on success of update
virtual void loop(){} // loops for each Service - called every cycle if over-ridden with user-defined code
@@ -517,6 +615,7 @@ class SpanCharacteristic{
};
uint32_t iid=0; // Instance ID (HAP Table 6-3)
+ uint32_t aid=0; // AID for the enclosing Accessory
HapChar *hapChar; // pointer to HAP Characteristic structure
const char *type; // Characteristic Type
const char *hapName; // HAP Name
@@ -528,6 +627,7 @@ class SpanCharacteristic{
UVal minValue; // Characteristic minimum (not applicable for STRING)
UVal maxValue; // Characteristic maximum (not applicable for STRING)
UVal stepValue; // Characteristic step size (not applicable for STRING)
+ uint8_t maxLen=0; // Characteristic maximum length (only applicable for STRING, 0=default)
boolean staticRange; // Flag that indicates whether Range is static and cannot be changed with setRange()
boolean customRange=false; // Flag for custom ranges
char *validValues=NULL; // Optional JSON array of valid values. Applicable only to uint8 Characteristics
@@ -536,7 +636,6 @@ class SpanCharacteristic{
boolean setRangeError=false; // flag to indicate attempt to set Range on Characteristic that does not support changes to Range
boolean setValidValuesError=false; // flag to indicate attempt to set Valid Values on Characteristic that does not support changes to Valid Values
- uint32_t aid=0; // Accessory ID - passed through from Service containing this Characteristic
uint8_t updateFlag=0; // set to either 1 (for normal write) or 2 (for write-response) inside update() when Characteristic is successfully updated via Home App
unsigned long updateTime=0; // last time value was updated (in millis) either by PUT /characteristic OR by setVal()
UVal newValue; // the updated value requested by PUT /characteristic
@@ -622,7 +721,7 @@ class SpanCharacteristic{
nvsKey=(char *)HS_MALLOC(16);
uint16_t t;
sscanf(type,"%hx",&t);
- sprintf(nvsKey,"%04X%08X%03X",t,aid,iid&0xFFF);
+ sprintf(nvsKey,"%04X%08lX%03lX",t,aid,iid&0xFFF);
size_t len;
if(format T getVal(){return(uvGet(value));} // gets the value for numeric-based Characteristics
char *getString(){return(getStringGeneric(value));} // gets the value for string-based Characteristics
@@ -706,6 +806,8 @@ class SpanCharacteristic{
boolean updated(); // returns true within update() if Characteristic was updated by Home App
unsigned long timeVal(); // returns time elapsed (in millis) since value was last updated, either by Home App or by using setVal()
uint32_t getIID(); // returns IID of Characteristic
+ uint32_t getAID(); // returns AID of enclosing Accessory
+ boolean foundIn(const char *getCharList); // returns true if Characteristics is found in getCharList, else returns false
SpanCharacteristic *setPerms(uint8_t perms); // sets permissions of a Characteristic
SpanCharacteristic *addPerms(uint8_t dPerms); // add permissions of a Characteristic
@@ -713,6 +815,7 @@ class SpanCharacteristic{
SpanCharacteristic *setDescription(const char *c); // sets description of a Characteristic
SpanCharacteristic *setUnit(const char *c); // set unit of a Characteristic
SpanCharacteristic *setValidValues(int n, ...); // sets a list of 'n' valid values allowed for a Characteristic - only applicable if format=INT, UINT8, UINT16, or UINT32
+ SpanCharacteristic *setMaxStringLength(uint8_t n); // sets maximum length of STRING Characteristics
template SpanCharacteristic *setRange(A min, B max, S step=0){ // sets the allowed range of a Characteristic
@@ -807,7 +910,7 @@ class SpanPoint {
static QueueHandle_t statusQueue; // queue for communication between SpanPoint::dataSend and SpanPoint::send
static nvs_handle pointNVS; // NVS storage for channel number (only used for remote devices)
- static void dataReceived(const uint8_t *mac, const uint8_t *incomingData, int len);
+ static void dataReceived(const esp_now_recv_info *info, const uint8_t *incomingData, int len);
static void init(const char *password="HomeSpan");
static void setAsHub(){isHub=true;}
static uint8_t nextChannel();
diff --git a/ESP32/HomeSpan-master/src/Network.cpp b/ESP32/HomeSpan-master/src/Network.cpp
index bf9ee39..7dce6ca 100644
--- a/ESP32/HomeSpan-master/src/Network.cpp
+++ b/ESP32/HomeSpan-master/src/Network.cpp
@@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
- * Copyright (c) 2020-2024 Gregg E. Berman
+ * Copyright (c) 2020-2025 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
@@ -29,7 +29,7 @@
#include
-#include "Network.h"
+#include "Network_HS.h"
#include "HomeSpan.h"
#include "Utils.h"
@@ -37,8 +37,10 @@ using namespace Utils;
///////////////////////////////
-void Network::scan(){
+void Network_HS::scan(){
+ WiFi.scanDelete();
+ STATUS_UPDATE(start(LED_WIFI_SCANNING),HS_WIFI_SCANNING)
int n=WiFi.scanNetworks();
free(ssidList);
@@ -62,7 +64,7 @@ void Network::scan(){
///////////////////////////////
-void Network::serialConfigure(){
+void Network_HS::serialConfigure(){
wifiData.ssid[0]='\0';
wifiData.pwd[0]='\0';
@@ -94,7 +96,7 @@ void Network::serialConfigure(){
///////////////////////////////
-boolean Network::allowedCode(char *s){
+boolean Network_HS::allowedCode(char *s){
return(
strcmp(s,"00000000") && strcmp(s,"11111111") && strcmp(s,"22222222") && strcmp(s,"33333333") &&
strcmp(s,"44444444") && strcmp(s,"55555555") && strcmp(s,"66666666") && strcmp(s,"77777777") &&
@@ -103,12 +105,10 @@ boolean Network::allowedCode(char *s){
///////////////////////////////
-void Network::apConfigure(){
+void Network_HS::apConfigure(){
LOG0("*** Starting Access Point: %s / %s\n",apSSID,apPassword);
-
- STATUS_UPDATE(start(LED_AP_STARTED),HS_AP_STARTED)
-
+
LOG0("\nScanning for Networks...\n\n");
scan(); // scan for networks
@@ -116,7 +116,9 @@ void Network::apConfigure(){
for(int i=0;i"
- "
Initiating WiFi connection to:
" + String(wifiData.ssid) + "
";
-
+ responseBody+=""
+ "
Initiating WiFi connection to:
" + String(wifiData.ssid) + "
"
+ "
(waiting " + String((homeSpan.wifiTimeCounter++)/1000) + " seconds to check for response)