From a9fba3267d9e26f909b0c305bd2013ad6d78db60 Mon Sep 17 00:00:00 2001 From: sieja Date: Mon, 21 Jul 2025 21:31:40 +0200 Subject: [PATCH] Update libki --- ESP32/HomeSpan-master/LICENSE | 2 +- ESP32/HomeSpan-master/README.md | 137 ++-- .../07-AccessoryNames/07-AccessoryNames.ino | 2 +- .../CustomNVSPartition/CustomNVSPartition.ino | 10 +- .../MultiThreading/MultiThreading.ino | 75 ++ .../Pixel-RGBWC/Pixel-RGBWC.ino | 121 +++ .../examples/Other Examples/Pixel/Pixel.ino | 2 +- .../PixelTester/PixelTester.ino | 187 +++-- .../ProgrammableHub/ProgrammableHub.ino | 11 +- .../RemoteSensors/MainDevice/MainDevice.ino | 8 +- .../RemoteDevice/RemoteDevice.ino | 4 +- .../RemoteDevice8266/RemoteDevice8266.ino | 129 ++++ .../RemoteTempSensor/RemoteTempSensor.ino | 2 +- .../ServoControl/ServoControl.ino | 5 +- ESP32/HomeSpan-master/library.properties | 4 +- ESP32/HomeSpan-master/src/Characteristics.h | 2 +- ESP32/HomeSpan-master/src/FeatherPins.h | 34 +- ESP32/HomeSpan-master/src/HAP.cpp | 133 ++-- ESP32/HomeSpan-master/src/HAP.h | 36 +- ESP32/HomeSpan-master/src/HAPConstants.h | 2 +- ESP32/HomeSpan-master/src/HKDF.cpp | 2 +- ESP32/HomeSpan-master/src/HKDF.h | 2 +- ESP32/HomeSpan-master/src/HapQR.h | 2 +- ESP32/HomeSpan-master/src/HomeSpan.cpp | 709 +++++++++++++----- ESP32/HomeSpan-master/src/HomeSpan.h | 205 +++-- ESP32/HomeSpan-master/src/Network.cpp | 89 ++- ESP32/HomeSpan-master/src/Network_HS.h | 90 +++ ESP32/HomeSpan-master/src/PSRAM.h | 2 +- ESP32/HomeSpan-master/src/SRP.cpp | 26 +- ESP32/HomeSpan-master/src/SRP.h | 3 +- ESP32/HomeSpan-master/src/Settings.h | 5 +- ESP32/HomeSpan-master/src/Span.h | 6 +- ESP32/HomeSpan-master/src/SpanRollback.h | 32 + ESP32/HomeSpan-master/src/TLV8.cpp | 4 +- ESP32/HomeSpan-master/src/TLV8.h | 2 +- ESP32/HomeSpan-master/src/Utils.cpp | 97 ++- ESP32/HomeSpan-master/src/Utils.h | 40 +- .../src/src/extras/Blinker.cpp | 2 +- .../HomeSpan-master/src/src/extras/Blinker.h | 1 - .../HomeSpan-master/src/src/extras/Pixel.cpp | 199 ++--- ESP32/HomeSpan-master/src/src/extras/Pixel.h | 217 +++--- .../HomeSpan-master/src/src/extras/PwmPin.cpp | 21 +- ESP32/HomeSpan-master/src/src/extras/PwmPin.h | 3 +- .../src/src/extras/RFControl.cpp | 153 ++-- .../src/src/extras/RFControl.h | 29 +- .../HomeSpan-master/src/src/extras/extras.ino | 117 ++- ESP32/HomeSpan-master/src/version.h | 22 +- 47 files changed, 2072 insertions(+), 914 deletions(-) create mode 100644 ESP32/HomeSpan-master/examples/Other Examples/MultiThreading/MultiThreading.ino create mode 100644 ESP32/HomeSpan-master/examples/Other Examples/Pixel-RGBWC/Pixel-RGBWC.ino create mode 100644 ESP32/HomeSpan-master/examples/Other Examples/RemoteSensors/RemoteDevice8266/RemoteDevice8266.ino create mode 100644 ESP32/HomeSpan-master/src/Network_HS.h create mode 100644 ESP32/HomeSpan-master/src/SpanRollback.h 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 << "Reset Reason:" << Utils::resetReason() << " (" << esp_reset_reason() << ")\n"; + + if(homeSpan.compileTime) + hapOut << "Compile Time:" << homeSpan.compileTime << "\n"; + + if(!homeSpan.ethernetEnabled){ + hapOut << "WiFi Disconnects:" << homeSpan.connected/2 << "\n"; + hapOut << "WiFi Signal:" << (int)WiFi.RSSI() << " dBm\n"; + if(homeSpan.bssidNames.count(WiFi.BSSIDstr().c_str())) + hapOut << "BSSID:" << WiFi.BSSIDstr().c_str() << " \"" << homeSpan.bssidNames[WiFi.BSSIDstr().c_str()].c_str() << "\"" << "\n"; + else + hapOut << "BSSID:" << WiFi.BSSIDstr().c_str() << "\n"; + hapOut << "WiFi Local IP:" << WiFi.localIP().toString().c_str() << "\n"; + hapOut << "WiFi Gateway:" << WiFi.gatewayIP().toString().c_str() << "\n"; + } else { + hapOut << "Ethernet Disconnects:" << homeSpan.connected/2 << "\n"; + hapOut << "Ethernet Local IP:" << ETH.localIP().toString().c_str() << "\n"; + hapOut << "Ethernet Gateway:" << ETH.gatewayIP().toString().c_str() << "\n"; } - hapOut << " (" << esp_reset_reason() << ")\n"; - - hapOut << "WiFi Disconnects:" << homeSpan.connected/2 << "\n"; - hapOut << "WiFi Signal:" << (int)WiFi.RSSI() << " dBm\n"; - hapOut << "WiFi Gateway:" << WiFi.gatewayIP().toString().c_str() << "\n"; hapOut << "ESP32 Board:" << ARDUINO_BOARD << "\n"; hapOut << "Arduino-ESP Version:" << ARDUINO_ESP_VERSION << "\n"; hapOut << "ESP-IDF Version:" << ESP_IDF_VERSION_MAJOR << "." << ESP_IDF_VERSION_MINOR << "." << ESP_IDF_VERSION_PATCH << "\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)

"; + WiFi.begin(wifiData.ssid,wifiData.pwd); } else @@ -294,12 +298,9 @@ void Network::processRequest(char *body, char *formData){ LOG1("In Get WiFi Status...\n"); if(WiFi.status()!=WL_CONNECTED){ - waitTime+=2; - if(waitTime==12) - waitTime=2; - responseHead+="Refresh: " + String(waitTime) + "\r\n"; + responseHead+="Refresh: " + String(homeSpan.wifiTimeCounter/1000) + "\r\n"; responseBody+="

Re-initiating connection to:

" + String(wifiData.ssid) + "

"; - responseBody+="

(waiting " + String(waitTime) + " seconds to check for response)

"; + responseBody+="

(waiting " + String((homeSpan.wifiTimeCounter++)/1000) + " seconds to check for response)

"; responseBody+="

Access Point termination in " + String((alarmTimeOut-millis())/1000) + " seconds.

"; responseBody+="
"; WiFi.begin(wifiData.ssid,wifiData.pwd); @@ -327,7 +328,7 @@ void Network::processRequest(char *body, char *formData){ LOG1("In Landing Page...\n"); STATUS_UPDATE(start(LED_AP_CONNECTED),HS_AP_CONNECTED) - waitTime=2; + homeSpan.wifiTimeCounter.reset(); responseBody+="

Welcome to HomeSpan! This page allows you to configure the above HomeSpan device to connect to your WiFi network.

" "

The LED on this device should be double-blinking during this configuration.

" @@ -368,9 +369,9 @@ void Network::processRequest(char *body, char *formData){ ////////////////////////////////////// -int Network::getFormValue(char *formData, const char *tag, char *value, int maxSize){ +int Network_HS::getFormValue(const char *formData, const char *tag, char *value, int maxSize){ - char *s=strstr(formData,tag); // find start of tag + char *s=strcasestr(formData,tag); // find start of tag if(!s) // if not found, return -1 return(-1); @@ -380,7 +381,7 @@ int Network::getFormValue(char *formData, const char *tag, char *value, int maxS if(!v) // if not found, return -1 (this should not happen) return(-1); - v++; // point to begining of value + v++; // point to beginning of value int len=0; // track length of value while(*v!='\0' && *v!='&' && len>>>>>>>>> "); @@ -419,3 +420,41 @@ int Network::badRequestError(){ } ////////////////////////////////////// + +HS_ExpCounter::HS_ExpCounter(uint32_t _minCount, uint32_t _maxCount, uint8_t _totalSteps){ + config(_minCount,_maxCount,_totalSteps); +} + +void HS_ExpCounter::config(uint32_t _minCount, uint32_t _maxCount, uint8_t _totalSteps){ + if(_minCount==0 || _maxCount==0 || _totalSteps==0){ + ESP_LOGE("HS_Counter","call to config(%ld,%ld,%d) ignored: all parameters must be non-zero\n",_minCount,_maxCount,_totalSteps); + } else { + minCount=_minCount; + maxCount=_maxCount; + totalSteps=_totalSteps; + } + reset(); +} + +void HS_ExpCounter::reset(){ + nStep=0; +} + +HS_ExpCounter::operator uint32_t(){ + return(minCount*pow((double)maxCount/(double)minCount,(double)nStep/(double)totalSteps)); +} + +HS_ExpCounter& HS_ExpCounter::operator++(){ + nStep++; + if(nStep>totalSteps) + nStep=0; + return(*this); +} + +HS_ExpCounter HS_ExpCounter::operator++(int){ + HS_ExpCounter temp=*this; + operator++(); + return(temp); +} + +////////////////////////////////////// diff --git a/ESP32/HomeSpan-master/src/Network_HS.h b/ESP32/HomeSpan-master/src/Network_HS.h new file mode 100644 index 0000000..725ccf6 --- /dev/null +++ b/ESP32/HomeSpan-master/src/Network_HS.h @@ -0,0 +1,90 @@ +/********************************************************************************* + * 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. + * + ********************************************************************************/ + +#pragma once + +#include +#include +#include "Settings.h" + +const int MAX_SSID=32; // max number of characters in WiFi SSID +const int MAX_PWD=64; // max number of characters in WiFi Password + +/////////////////////////////// + +struct Network_HS { + + const int MAX_HTTP=4095; // max number of bytes in HTTP message + + const char *apSSID=DEFAULT_AP_SSID; // Access Point SSID + const char *apPassword=DEFAULT_AP_PASSWORD; // Access Point password (does not need to be secret - only used to ensure encrypted WiFi connection) + unsigned long lifetime=DEFAULT_AP_TIMEOUT*1000; // length of time (in milliseconds) to keep Access Point alive before shutting down and restarting + + char **ssidList=NULL; + int numSSID; + + NetworkClient client; // client used for HTTP calls + unsigned long alarmTimeOut; // alarm time after which access point is shut down and HomeSpan is re-started + int apStatus; // tracks access point status (0=timed-out, -1=cancel, 1=save) + + struct { + char ssid[MAX_SSID+1]=""; + char pwd[MAX_PWD+1]=""; + } wifiData; + + char setupCode[8+1]; + + void scan(); // scan for WiFi networks and save only those with unique SSIDs + void serialConfigure(); // configure homeSpan WiFi from serial monitor + boolean allowedCode(char *s); // checks if Setup Code is allowed (HAP defines a list of disallowed codes) + void apConfigure(); // configure homeSpan WiFi and Setup Code using temporary Captive Access Point; only returns if sucessful, else ESP restarts + void processRequest(char *body, char *formData); // process the HTTP request + int badRequestError(); // return 400 error + + static int getFormValue(const char *formData, const char *tag, char *value, int maxSize); // search for 'tag' in 'formData' and copy result into 'value' up to 'maxSize' characters; returns number of characters, else -1 if 'tag' not found + +}; + +/////////////////////////////// + +class HS_ExpCounter{ + uint8_t nStep; + uint32_t minCount; + uint32_t maxCount; + uint8_t totalSteps; + + public: + + HS_ExpCounter(uint32_t _minCount=5000, uint32_t _maxCount=60000, uint8_t _totalSteps=5); + void config(uint32_t _minCount, uint32_t _maxCount, uint8_t _totalSteps); + void reset(); + operator uint32_t(); + HS_ExpCounter& operator++(); + HS_ExpCounter operator++(int); +}; + +/////////////////////////////// diff --git a/ESP32/HomeSpan-master/src/PSRAM.h b/ESP32/HomeSpan-master/src/PSRAM.h index d5b0cb6..341ba3d 100644 --- a/ESP32/HomeSpan-master/src/PSRAM.h +++ b/ESP32/HomeSpan-master/src/PSRAM.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/SRP.cpp b/ESP32/HomeSpan-master/src/SRP.cpp index 93ec8dc..33bb00d 100644 --- a/ESP32/HomeSpan-master/src/SRP.cpp +++ b/ESP32/HomeSpan-master/src/SRP.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 * @@ -100,10 +100,10 @@ void SRP6A::createVerifyCode(const char *setupCode, Verification *vData){ // compute x = SHA512( s | SHA512( I | ":" | P ) ) - memcpy(tBuf,vData->salt,16); // write salt into first 16 bytes of staging buffer - mbedtls_sha512_ret((uint8_t *)icp,strlen(icp),tBuf+16,0); // create hash of username:password and write into last 64 bytes of staging buffer - mbedtls_sha512_ret(tBuf,80,tHash,0); // create second hash of salted, hashed username:password - mbedtls_mpi_read_binary(&x,tHash,64); // load hash result into x + memcpy(tBuf,vData->salt,16); // write salt into first 16 bytes of staging buffer + mbedtls_sha512((uint8_t *)icp,strlen(icp),tBuf+16,0); // create hash of username:password and write into last 64 bytes of staging buffer + mbedtls_sha512(tBuf,80,tHash,0); // create second hash of salted, hashed username:password + mbedtls_mpi_read_binary(&x,tHash,64); // load hash result into x // compute v = g^x %N @@ -136,7 +136,7 @@ void SRP6A::createPublicKey(const Verification *vData, uint8_t *publicKey){ mbedtls_mpi_write_binary(&N,tBuf,384); // write N into first half of staging buffer mbedtls_mpi_write_binary(&g,tBuf+384,384); // write g into second half of staging buffer (fully padded with leading zeros) - mbedtls_sha512_ret(tBuf,768,tHash,0); // create hash of data + mbedtls_sha512(tBuf,768,tHash,0); // create hash of data mbedtls_mpi_read_binary(&k,tHash,64); // load hash result into k // compute B = (k*v + g^b) %N @@ -163,7 +163,7 @@ void SRP6A::createSessionKey(const uint8_t *publicKey, size_t len){ mbedtls_mpi_write_binary(&A,tBuf,384); // write A into first half of staging buffer (padding with initial zeros is less than 384 bytes) mbedtls_mpi_write_binary(&B,tBuf+384,384); // write B into second half of staging buffer (padding with initial zeros is less than 384 bytes) - mbedtls_sha512_ret(tBuf,768,tHash,0); // create hash of data + mbedtls_sha512(tBuf,768,tHash,0); // create hash of data mbedtls_mpi_read_binary(&u,tHash,64); // load hash result into mpi structure u // compute S = (A * v^u)^b %N @@ -176,7 +176,7 @@ void SRP6A::createSessionKey(const uint8_t *publicKey, size_t len){ // compute K = SHA512( PAD(S) ) mbedtls_mpi_write_binary(&S,tBuf,384); // write S into staging buffer (only first half of buffer will be used) - mbedtls_sha512_ret(tBuf,384,K,0); // create hash of data - this is the SRP SHARED SESSION KEY, K + mbedtls_sha512(tBuf,384,K,0); // create hash of data - this is the SRP SHARED SESSION KEY, K } @@ -196,13 +196,13 @@ int SRP6A::verifyClientProof(const uint8_t *proof){ // compute M1V = SHA512( SHA512(N) xor SHA512(g) | SHA512(I) | s | A | B | K ) mbedtls_mpi_write_binary(&N,tBuf,384); // write N into staging buffer - mbedtls_sha512_ret(tBuf,384,tHash,0); // create hash of data - mbedtls_sha512_ret(&g3072,1,tBuf,0); // create hash of g, but place output directly into staging buffer + mbedtls_sha512(tBuf,384,tHash,0); // create hash of data + mbedtls_sha512(&g3072,1,tBuf,0); // create hash of g, but place output directly into staging buffer for(int i=0;i<64;i++) // H(g) -> H(g) XOR H(N), with results in first 64 bytes of staging buffer tBuf[i]^=tHash[i]; - mbedtls_sha512_ret((uint8_t *)I,strlen(I),tBuf+64,0); // create hash of userName and concatenate result to end of staging buffer + mbedtls_sha512((uint8_t *)I,strlen(I),tBuf+64,0); // create hash of userName and concatenate result to end of staging buffer mbedtls_mpi_write_binary(&s,tBuf+128,16); // concatenate s to staging buffer @@ -217,7 +217,7 @@ int SRP6A::verifyClientProof(const uint8_t *proof){ memcpy(tBuf+count,K,64); // concatenate K to staging buffer (should always be 64 bytes since it is a hashed value) count+=64; // final total of bytes written to staging buffer - mbedtls_sha512_ret(tBuf,count,tHash,0); // create hash of data - this is M1V + mbedtls_sha512(tBuf,count,tHash,0); // create hash of data - this is M1V if(!memcmp(M1,tHash,64)) // check that client Proof M1 matches M1V return(1); // success - proof from HAP Client is verified @@ -236,7 +236,7 @@ void SRP6A::createAccProof(uint8_t *proof){ mbedtls_mpi_write_binary(&A,tBuf,384); // write A into staging buffer memcpy(tBuf+384,M1,64); // concatenate M1 (now verified) to staging buffer memcpy(tBuf+448,K,64); // concatenate K to staging buffer - mbedtls_sha512_ret(tBuf,512,proof,0); // create hash of data writing directly to proof - this is M2 + mbedtls_sha512(tBuf,512,proof,0); // create hash of data writing directly to proof - this is M2 } diff --git a/ESP32/HomeSpan-master/src/SRP.h b/ESP32/HomeSpan-master/src/SRP.h index 24aece6..f703c1c 100644 --- a/ESP32/HomeSpan-master/src/SRP.h +++ b/ESP32/HomeSpan-master/src/SRP.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 * @@ -90,6 +90,7 @@ struct SRP6A { ~SRP6A(); 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);} void createVerifyCode(const char *setupCode, Verification *vData); // generates random s and computes v; writes back resulting Verification Data void createPublicKey(const Verification *vData, uint8_t *publicKey); // generates random b and computes k and B; writes back resulting Accessory Public Key diff --git a/ESP32/HomeSpan-master/src/Settings.h b/ESP32/HomeSpan-master/src/Settings.h index 56e96a6..0286ab5 100644 --- a/ESP32/HomeSpan-master/src/Settings.h +++ b/ESP32/HomeSpan-master/src/Settings.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 * @@ -69,8 +69,9 @@ #define LED_ALERT 100 // rapid flashing #define LED_WIFI_CONNECTING 2000 // slow flashing #define LED_AP_STARTED 100,0.5,2,300 // rapid double-blink -#define LED_AP_CONNECTED 300,0.5,2,400 // medium double-blink +#define LED_AP_CONNECTED 300,0.5,2,400 // medium double-blink #define LED_OTA_STARTED 300,0.5,3,400 // medium triple-blink +#define LED_WIFI_SCANNING 300,0.8,3,400 // medium inverted triple-blink ///////////////////////////////////////////////////// // Message Log Level Control Macros // diff --git a/ESP32/HomeSpan-master/src/Span.h b/ESP32/HomeSpan-master/src/Span.h index bf2892a..5a6703a 100644 --- a/ESP32/HomeSpan-master/src/Span.h +++ b/ESP32/HomeSpan-master/src/Span.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 * @@ -475,7 +475,7 @@ namespace Service { // Macro to define Span Characteristic structures based on name of HAP Characteristic, default value, and min/max value (not applicable for STRING or BOOL which default to min=0, max=1) #define CREATE_CHAR(TYPE,HAPCHAR,DEFVAL,MINVAL,MAXVAL,...) \ - struct HAPCHAR : SpanCharacteristic { __VA_OPT__(enum{) __VA_ARGS__ __VA_OPT__(};) HAPCHAR(TYPE val=DEFVAL, boolean nvsStore=false) : SpanCharacteristic {&hapChars.HAPCHAR} { init(val,nvsStore,MINVAL,MAXVAL); } }; + struct HAPCHAR : SpanCharacteristic { __VA_OPT__(enum Value_t {) __VA_ARGS__ __VA_OPT__(};) HAPCHAR(TYPE val=DEFVAL, boolean nvsStore=false) : SpanCharacteristic {&hapChars.HAPCHAR} { init(val,nvsStore,MINVAL,MAXVAL); } }; namespace Characteristic { @@ -525,7 +525,7 @@ namespace Characteristic { CREATE_CHAR(UINT32_t,Identifier,0,0,255); // numerical Identifer of the InputSource. CREATE_CHAR(UINT8_t,InputDeviceType,0,0,6); // not used CREATE_CHAR(UINT8_t,InputSourceType,0,0,10); // not used - CREATE_CHAR(UINT8_t,InUse,0,0,1,NOT_IN_USE,IN_USE); // if Service is set to active, this indictes whether it is currently in use + CREATE_CHAR(UINT8_t,InUse,0,0,1,NOT_IN_USE,IN_USE); // if Service is set to active, this indicates whether it is currently in use CREATE_CHAR(UINT8_t,IsConfigured,0,0,1,NOT_CONFIGURED,CONFIGURED); // indicates if a predefined Service has been configured CREATE_CHAR(UINT8_t,LeakDetected,0,0,1,NOT_DETECTED,DETECTED); // indictates if a leak is detected CREATE_CHAR(UINT8_t,LockCurrentState,0,0,3,UNLOCKED,LOCKED,JAMMED,UNKNOWN); // indicates state of a lock diff --git a/ESP32/HomeSpan-master/src/SpanRollback.h b/ESP32/HomeSpan-master/src/SpanRollback.h new file mode 100644 index 0000000..d61412b --- /dev/null +++ b/ESP32/HomeSpan-master/src/SpanRollback.h @@ -0,0 +1,32 @@ +/********************************************************************************* + * 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. + * + ********************************************************************************/ + +#pragma once + +// Override of weakly-defined Arduino-ESP32 function to enable auto rollback + +extern "C" bool verifyRollbackLater() {return true;} diff --git a/ESP32/HomeSpan-master/src/TLV8.cpp b/ESP32/HomeSpan-master/src/TLV8.cpp index e700946..d7c4c75 100644 --- a/ESP32/HomeSpan-master/src/TLV8.cpp +++ b/ESP32/HomeSpan-master/src/TLV8.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 * @@ -266,7 +266,7 @@ void TLV8::print(TLV8_itc it1, TLV8_itc it2) const { if(it1->getLen()==0) Serial.printf(" [null]"); else if(it1->getLen()<=4) - Serial.printf(" [%u]",it1->getVal()); + Serial.printf(" [%lu]",it1->getVal()); else if(it1->getLen()<=8) Serial.printf(" [%llu]",it1->getVal()); Serial.printf("\n"); diff --git a/ESP32/HomeSpan-master/src/TLV8.h b/ESP32/HomeSpan-master/src/TLV8.h index e51dc62..923e90b 100644 --- a/ESP32/HomeSpan-master/src/TLV8.h +++ b/ESP32/HomeSpan-master/src/TLV8.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/Utils.cpp b/ESP32/HomeSpan-master/src/Utils.cpp index d3711f0..52b93d7 100644 --- a/ESP32/HomeSpan-master/src/Utils.cpp +++ b/ESP32/HomeSpan-master/src/Utils.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 * @@ -34,8 +34,10 @@ // // Utils::readSerial - reads all characters from Serial port and saves only up to max specified // Utils::mask - masks a string with asterisks (good for displaying passwords) +// Utils::resetReason - returns literal string description of esp_reset_reason() // // class PushButton - tracks Single, Double, and Long Presses of a pushbutton that connects a specified pin to ground +// class hsWatchdogTimer - a generic watchdog timer that reboots the ESP32 device if not reset periodically // /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -51,7 +53,8 @@ char *Utils::readSerial(char *c, int max){ while(1){ - while(!Serial.available()); // wait until there is a new character + while(!Serial.available()) // wait until there is a new character + homeSpan.resetWatchdog(); buf=Serial.read(); @@ -101,6 +104,32 @@ String Utils::mask(char *c, int n){ return(s); } // mask +////////////////////////////////////// + +const char *Utils::resetReason(){ + + switch(esp_reset_reason()) { + case ESP_RST_UNKNOWN: return "Cannot be determined"; break; + case ESP_RST_POWERON: return "Power-on event"; break; + case ESP_RST_EXT: return "External pin"; break; + case ESP_RST_SW: return "Software reboot via esp_restart"; break; + case ESP_RST_PANIC: return "Software Exception/Panic"; break; + case ESP_RST_INT_WDT: return "Interrupt watchdog"; break; + case ESP_RST_TASK_WDT: return "Task watchdog"; break; + case ESP_RST_WDT: return "Other watchdogs"; break; + case ESP_RST_DEEPSLEEP: return "Exiting deep sleep mode"; break; + case ESP_RST_BROWNOUT: return "Brownout"; break; + case ESP_RST_SDIO: return "SDIO"; break; + case ESP_RST_USB: return "USB peripheral"; break; + case ESP_RST_JTAG: return "JTAG"; break; + case ESP_RST_EFUSE: return "Efuse error"; break; + case ESP_RST_PWR_GLITCH: return "Power glitch"; break; + case ESP_RST_CPU_LOCKUP: return "CPU Lockup"; break; + default: break; + } + return "Unknown Reset Code"; +} + //////////////////////////////// // PushButton // //////////////////////////////// @@ -123,12 +152,12 @@ PushButton::PushButton(int pin, triggerType_t triggerType){ for(int i=0;i %d.\n",pin,threshold); + LOG0("Touch Sensor at pin=%d used for calibration. Triggers when sensor reading > %lu.\n",pin,threshold); #endif } #endif @@ -281,7 +310,8 @@ int PushButton::type(){ ////////////////////////////////////// void PushButton::wait(){ - while(triggerType(pin)); + while(triggerType(pin)) + homeSpan.resetWatchdog(); } ////////////////////////////////////// @@ -293,3 +323,56 @@ void PushButton::reset(){ ////////////////////////////////////// PushButton::touch_value_t PushButton::threshold=0; + +//////////////////////////////// +// hsWatchdogTimer // +//////////////////////////////// + +void hsWatchdogTimer::enable(uint16_t nSeconds){ + + if(nSecondsnSeconds=nSeconds; + esp_task_wdt_config_t twdtConfig; + + twdtConfig.timeout_ms=nSeconds*1000; + twdtConfig.trigger_panic=true; + twdtConfig.idle_core_mask=0; + + for(int i=0;i +#include #include "PSRAM.h" +[[maybe_unused]] static const char* WATCHDOG_TAG = "HomeSpan Watchdog"; + namespace Utils { char *readSerial(char *c, int max); // read serial port into 'c' until , but storing only first 'max' characters (the rest are discarded) String mask(char *c, int n); // simply utility that creates a String from 'c' with all except the first and last 'n' characters replaced by '*' -char *stripBackslash(char *c); // strips backslashes out of c (Apple unecessesarily "escapes" forward slashes in JSON) +char *stripBackslash(char *c); // strips backslashes out of c (Apple unecessesarily "escapes" forward slashes in JSON) +const char *resetReason(); // returns literal string description of esp_reset_reason() } ///////////////////////////////////////////////// @@ -112,11 +116,11 @@ class PushButton{ uint32_t doubleAlarm; uint32_t longAlarm; -#if SOC_TOUCH_VERSION_2 - typedef uint32_t touch_value_t; -#else +#if defined(SOC_TOUCH_VERSION_1) || SOC_TOUCH_SENSOR_VERSION==1 typedef uint16_t touch_value_t; -#endif +#else + typedef uint32_t touch_value_t; +#endif static touch_value_t threshold; static const int calibCount=20; @@ -145,10 +149,10 @@ class PushButton{ static boolean TRIGGER_ON_HIGH(int pin){return(digitalRead(pin));} #if SOC_TOUCH_SENSOR_NUM > 0 -#if SOC_TOUCH_VERSION_2 - static boolean TRIGGER_ON_TOUCH(int pin){return(touchRead(pin)>threshold);} -#else +#if defined(SOC_TOUCH_VERSION_1) || SOC_TOUCH_SENSOR_VERSION==1 static boolean TRIGGER_ON_TOUCH(int pin){return(touchRead(pin)threshold);} #endif #endif @@ -235,3 +239,21 @@ class PushButton{ #endif }; + +//////////////////////////////// +// hsWatchdogTimer // +//////////////////////////////// + +class hsWatchdogTimer { + + uint16_t nSeconds=0; + esp_task_wdt_user_handle_t wdtHandle=NULL; + + public: + + hsWatchdogTimer(){}; + void enable(uint16_t nSeconds); + void disable(); + void reset(); + uint16_t getSeconds(); +}; diff --git a/ESP32/HomeSpan-master/src/src/extras/Blinker.cpp b/ESP32/HomeSpan-master/src/src/extras/Blinker.cpp index a398730..dc52ca7 100644 --- a/ESP32/HomeSpan-master/src/src/extras/Blinker.cpp +++ b/ESP32/HomeSpan-master/src/src/extras/Blinker.cpp @@ -27,7 +27,7 @@ #include "Blinker.h" - //////////////////////////////// +//////////////////////////////// // Blinker // //////////////////////////////// diff --git a/ESP32/HomeSpan-master/src/src/extras/Blinker.h b/ESP32/HomeSpan-master/src/src/extras/Blinker.h index 49a8f0d..291cb89 100644 --- a/ESP32/HomeSpan-master/src/src/extras/Blinker.h +++ b/ESP32/HomeSpan-master/src/src/extras/Blinker.h @@ -28,7 +28,6 @@ #pragma once #include -#include [[maybe_unused]] static const char* BLINKER_TAG = "Blinker"; diff --git a/ESP32/HomeSpan-master/src/src/extras/Pixel.cpp b/ESP32/HomeSpan-master/src/src/extras/Pixel.cpp index 24be2ce..8f72716 100644 --- a/ESP32/HomeSpan-master/src/src/extras/Pixel.cpp +++ b/ESP32/HomeSpan-master/src/src/extras/Pixel.cpp @@ -35,105 +35,120 @@ // Single-Wire RGB/RGBW NeoPixels // //////////////////////////////////////////// -Pixel::Pixel(int pin, pixelType_t pixelType){ +Pixel::Pixel(int pin, const char *pixelType){ - rf=new RFControl(pin,false,false); // set clock to 1/80 usec, no default driver - if(!*rf) + this->pin=pin; + + rmt_tx_channel_config_t tx_chan_config; + tx_chan_config.clk_src = RMT_CLK_SRC_DEFAULT; // always use 80MHz clock source + tx_chan_config.gpio_num = (gpio_num_t)pin; // GPIO number + tx_chan_config.mem_block_symbols = SOC_RMT_MEM_WORDS_PER_CHANNEL; // set number of symbols to match those in a single channel block + tx_chan_config.resolution_hz = 80 * 1000 * 1000; // set to 80MHz + tx_chan_config.intr_priority = 3; // medium interrupt priority + tx_chan_config.trans_queue_depth = 4; // set the number of transactions that can pend in the background + tx_chan_config.flags.invert_out = false; // do not invert output signal + tx_chan_config.flags.with_dma = false; // use RMT channel memory, not DMA (most chips do not support use of DMA anyway) + tx_chan_config.flags.io_loop_back = false; // do not use loop-back mode + tx_chan_config.flags.io_od_mode = false; // do not use open-drain output + + if(!GPIO_IS_VALID_OUTPUT_GPIO(pin)){ + ESP_LOGE(PIXEL_TAG,"Can't create Pixel(%d) - invalid output pin",pin); + return; + } + + if(rmt_new_tx_channel(&tx_chan_config, &tx_chan)!=ESP_OK){ + ESP_LOGE(PIXEL_TAG,"Can't create Pixel(%d) - no open channels",pin); return; + } + + bytesPerPixel=0; + size_t len=strlen(pixelType); + boolean invalidMap=false; + char v[]="RGBWC01234-"; // list of valid mapping characters for pixelType + + for(int i=0;i5 || invalidMap){ + ESP_LOGE(PIXEL_TAG,"Can't create Pixel(%d, \"%s\") - invalid pixelType",pin,pixelType); + return; + } + + sscanf(pixelType,"%ms",&pType); // save pixelType for later use with hasColor() - if(map[3]) - bytesPerPixel=4; - else - bytesPerPixel=3; + rmt_enable(tx_chan); // enable channel + channel=((int *)tx_chan)[0]; // get channel number + + tx_config.loop_count=0; // populate tx_config structure + tx_config.flags.eot_level=0; + tx_config.flags.queue_nonblocking=0; + rmt_bytes_encoder_config_t encoder_config; // can leave blank for now - will populate from within setTiming() below + rmt_new_bytes_encoder(&encoder_config, &encoder); // create byte encoder + setTiming(0.32, 0.88, 0.64, 0.56, 80.0); // set default timing parameters (suitable for most SK68 and WS28 RGB pixels) - rmt_isr_register(loadData,NULL,0,NULL); // set custom interrupt handler - - rmt_set_tx_thr_intr_en(rf->getChannel(),false,8); // disable threshold interrupt - txThrMask=RMT.int_ena.val; // save interrupt enable vector - rmt_set_tx_thr_intr_en(rf->getChannel(),true,8); // enable threshold interrupt to trigger every 8 pulses - txThrMask^=RMT.int_ena.val; // find bit that flipped and save as threshold mask for this channel - - rmt_set_tx_intr_en(rf->getChannel(),false); // disable end-of-transmission interrupt - txEndMask=RMT.int_ena.val; // save interrupt enable vector - rmt_set_tx_intr_en(rf->getChannel(),true); // enable end-of-transmission interrupt - txEndMask^=RMT.int_ena.val; // find bit that flipped and save as end-of-transmission mask for this channel - onColor.HSV(0,100,100,0); } /////////////////// -void Pixel::setTiming(float high0, float low0, float high1, float low1, uint32_t lowReset){ +Pixel *Pixel::setTiming(float high0, float low0, float high1, float low1, uint32_t lowReset){ + + if(channel<0) + return(this); + + rmt_bytes_encoder_config_t encoder_config; + + encoder_config.bit0.level0=1; + encoder_config.bit0.duration0=high0*80+0.5; + encoder_config.bit0.level1=0; + encoder_config.bit0.duration1=low0*80+0.5; + + encoder_config.bit1.level0=1; + encoder_config.bit1.duration0=high1*80+0.5; + encoder_config.bit1.level1=0; + encoder_config.bit1.duration1=low1*80+0.5; + + encoder_config.flags.msb_first=1; // MSB of data bytes should be converted and transmitted first + + rmt_bytes_encoder_update_config(encoder,&encoder_config); // update config - pattern[0]=RF_PULSE(high0*80+0.5,low0*80+0.5); - pattern[1]=RF_PULSE(high1*80+0.5,low1*80+0.5); resetTime=lowReset; + return(this); } /////////////////// void Pixel::set(Color *c, int nPixels, boolean multiColor){ - - if(!*rf || nPixels==0) + + if(channel<0 || nPixels==0) return; - status.nPixels=nPixels; - status.color=c; - status.iMem=0; - status.started=true; - status.px=this; - status.multiColor=multiColor; - status.iByte=0; + Color data[2]; // temp ping/pong structure to store re-mapped color bytes + int index=0; // points to current slot in ping/pong structure - loadData(this); // load first two bytes of data to get started - loadData(this); + rmt_ll_set_group_clock_src(&RMT, channel, RMT_CLK_SRC_DEFAULT, 1, 0, 0); // ensure use of DEFAULT CLOCK, which is always 80 MHz, without any scaling + + do { + for(int i=0;icol[map[i]]; - rmt_tx_start(rf->getChannel(),true); + rmt_tx_wait_all_done(tx_chan,-1); // wait until any outstanding data is transmitted + rmt_transmit(tx_chan, encoder, data[index].col, bytesPerPixel, &tx_config); // transmit data + + index=1-index; // flips index to second data structure + c+=multiColor; + } while(--nPixels>0); - while(status.started); // wait for transmission to be complete - delayMicroseconds(resetTime); // end-of-marker delay + rmt_tx_wait_all_done(tx_chan,-1); // wait until final data is transmitted + delayMicroseconds(resetTime); // end-of-marker delay } -/////////////////// - -void IRAM_ATTR Pixel::loadData(void *arg){ - - if(RMT.int_st.val & status.px->txEndMask){ - RMT.int_clr.val=status.px->txEndMask; - status.started=false; - return; - } - - RMT.int_clr.val=status.px->txThrMask; // if loadData() is called and it is NOT because of an END interrupt (above) then must either be a pre-load, or a threshold trigger - - if(status.nPixels==0){ - RMTMEM.chan[status.px->rf->getChannel()].data32[status.iMem].val=0; - return; - } - - int startBit=status.px->map[status.iByte]; - int endBit=startBit-8; - - for(int iBit=startBit;iBit>endBit;iBit--) - RMTMEM.chan[status.px->rf->getChannel()].data32[status.iMem++].val=status.px->pattern[(status.color->val>>iBit)&1]; - - if(++status.iByte==status.px->bytesPerPixel){ - status.iByte=0; - status.color+=status.multiColor; - status.nPixels--; - } - - status.iMem%=status.px->memSize; -} - -/////////////////// - -volatile Pixel::pixel_status_t Pixel::status; - //////////////////////////////////////////// // Two-Wire RGB DotStars // //////////////////////////////////////////// @@ -148,29 +163,27 @@ Dot::Dot(uint8_t dataPin, uint8_t clockPin){ dataMask=1<<(dataPin%32); clockMask=1<<(clockPin%32); -#ifdef CONFIG_IDF_TARGET_ESP32C3 - dataSetReg=&GPIO.out_w1ts.val; - dataClearReg=&GPIO.out_w1tc.val; - clockSetReg=&GPIO.out_w1ts.val; - clockClearReg=&GPIO.out_w1tc.val; +#if defined(CONFIG_IDF_TARGET_ESP32C3) + #define OUT_W1TS &GPIO.out_w1ts.val + #define OUT_W1TC &GPIO.out_w1tc.val + #define OUT1_W1TS NULL + #define OUT1_W1TC NULL +#elif defined(CONFIG_IDF_TARGET_ESP32C6) + #define OUT_W1TS &GPIO.out_w1ts.val + #define OUT_W1TC &GPIO.out_w1tc.val + #define OUT1_W1TS &GPIO.out1_w1ts.val + #define OUT1_W1TC &GPIO.out1_w1tc.val #else - if(dataPin<32){ - dataSetReg=&GPIO.out_w1ts; - dataClearReg=&GPIO.out_w1tc; - } else { - dataSetReg=&GPIO.out1_w1ts.val; - dataClearReg=&GPIO.out1_w1tc.val; - } - - if(clockPin<32){ - clockSetReg=&GPIO.out_w1ts; - clockClearReg=&GPIO.out_w1tc; - } else { - clockSetReg=&GPIO.out1_w1ts.val; - clockClearReg=&GPIO.out1_w1tc.val; - } + #define OUT_W1TS &GPIO.out_w1ts + #define OUT_W1TC &GPIO.out_w1tc + #define OUT1_W1TS &GPIO.out1_w1ts.val + #define OUT1_W1TC &GPIO.out1_w1tc.val #endif + dataSetReg= dataPin<32 ? (OUT_W1TS) : (OUT1_W1TS); + dataClearReg= dataPin<32 ? (OUT_W1TC) : (OUT1_W1TC); + clockSetReg= clockPin<32 ? (OUT_W1TS) : (OUT1_W1TS); + clockClearReg= clockPin<32 ? (OUT_W1TC) : (OUT1_W1TC); } /////////////////// diff --git a/ESP32/HomeSpan-master/src/src/extras/Pixel.h b/ESP32/HomeSpan-master/src/src/extras/Pixel.h index 1cd37a1..91850a7 100644 --- a/ESP32/HomeSpan-master/src/src/extras/Pixel.h +++ b/ESP32/HomeSpan-master/src/src/extras/Pixel.h @@ -31,30 +31,43 @@ #pragma once -#include "RFControl.h" #include "PwmPin.h" #include "Blinker.h" +#pragma GCC diagnostic ignored "-Wvolatile" + +#include // IDF 5 RMT driver +#include // where RMT register structure is defined +#include // where low-level RMT calls are defined + +#include + [[maybe_unused]] static const char* PIXEL_TAG = "Pixel"; -typedef const uint8_t pixelType_t[]; + +/***********************************/ +/* TO BE DEPRECATED IN 2.X RELEASE */ + +typedef const String pixelType_t; namespace PixelType { - pixelType_t RGB={31,23,15,0}; - pixelType_t RBG={31,15,23,0}; - pixelType_t BRG={23,15,31,0}; - pixelType_t BGR={15,23,31,0}; - pixelType_t GBR={15,31,23,0}; - pixelType_t GRB={23,31,15,0}; - pixelType_t RGBW={31,23,15,7}; - pixelType_t RBGW={31,15,23,7}; - pixelType_t BRGW={23,15,31,7}; - pixelType_t BGRW={15,23,31,7}; - pixelType_t GBRW={15,31,23,7}; - pixelType_t GRBW={23,31,15,7}; + pixelType_t RGB="rgb"; + pixelType_t RBG="rbg"; + pixelType_t BRG="brg"; + pixelType_t BGR="bgr"; + pixelType_t GBR="gbr"; + pixelType_t GRB="grb"; + pixelType_t RGBW="rgbw"; + pixelType_t RBGW="rbgw"; + pixelType_t BRGW="brgw"; + pixelType_t BGRW="bgrw"; + pixelType_t GBRW="gbrw"; + pixelType_t GRBW="grbw"; }; +/***********************************/ + //////////////////////////////////////////// // Single-Wire RGB/RGBW NeoPixels // //////////////////////////////////////////// @@ -63,127 +76,133 @@ class Pixel : public Blinkable { public: struct Color { - union{ - struct { - uint8_t white:8; - uint8_t blue:8; - uint8_t green:8; - uint8_t red:8; - }; - uint32_t val; - }; + uint8_t col[5]; - Color RGB(uint8_t r, uint8_t g, uint8_t b, uint8_t w=0){ // returns Color based on provided RGB(W) values where r/g/b/w=[0-255] - this->red=r; - this->green=g; - this->blue=b; - this->white=w; + Color(){ + col[0]=0; + col[1]=0; + col[2]=0; + col[3]=0; + col[4]=0; + } + + Color RGB(uint8_t r, uint8_t g, uint8_t b, uint8_t w=0, uint8_t c=0){ // returns Color based on provided RGB(WC) values where r/g/b/w/c=[0-255] + col[0]=r; + col[1]=g; + col[2]=b; + col[3]=w; + col[4]=c; return(*this); } - Color HSV(float h, float s, float v, double w=0){ // returns Color based on provided HSV(W) values where h=[0,360] and s/v/w=[0,100] - float r,g,b; - LedPin::HSVtoRGB(h,s/100.0,v/100.0,&r,&g,&b); - this->red=r*255; - this->green=g*255; - this->blue=b*255; - this->white=w*2.555; + Color WC(uint8_t w, uint8_t c=0){ // returns Color based on provided RGB(WC) values where r/g/b/w/c=[0-255] + col[0]=0; + col[1]=0; + col[2]=0; + col[3]=w; + col[4]=c; return(*this); } + Color HSV(float h, float s, float v, double w=0, double c=0){ // returns Color based on provided HSV(WC) values where h=[0,360] and s/v/w/c=[0,100] + float r,g,b; + LedPin::HSVtoRGB(h,s/100.0,v/100.0,&r,&g,&b); + col[0]=r*255; + col[1]=g*255; + col[2]=b*255; + col[3]=w*2.555; + col[4]=c*2.555; + return(*this); + } + + Color CCT(float temp, float v, float wTemp, float cTemp){ + col[0]=0; + col[1]=0; + col[2]=0; + if(tempcTemp) + temp=cTemp; + col[4]=(temp-wTemp)/(cTemp-wTemp)*255.0; + col[3]=255-col[4]; + col[3]*=v/100.0; + col[4]*=v/100.0; + return(*this); + } + bool operator==(const Color& color){ - return(val==color.val); + boolean eq=true; + for(int i=0;i<5;i++) + eq&=(col[i]==color.col[i]); + return(eq); } bool operator!=(const Color& color){ - return(val!=color.val); + return(!(*this==color)); } Color operator+(const Color& color){ Color newColor; - newColor.white=white+color.white; - newColor.blue=blue+color.blue; - newColor.red=red+color.red; - newColor.green=green+color.green; + for(int i=0;i<5;i++) + newColor.col[i]=col[i]+color.col[i]; return(newColor); } Color& operator+=(const Color& color){ - white+=color.white; - red+=color.red; - blue+=color.blue; - green+=color.green; + for(int i=0;i<5;i++) + col[i]+=color.col[i]; return(*this); - } - - Color operator-(const Color& color){ - Color newColor; - newColor.white=white-color.white; - newColor.blue=blue-color.blue; - newColor.red=red-color.red; - newColor.green=green-color.green; - return(newColor); - } - - Color& operator-=(const Color& color){ - white-=color.white; - red-=color.red; - blue-=color.blue; - green-=color.green; - return(*this); - } - + } }; // Color private: - struct pixel_status_t { - int nPixels; - Color *color; - int iMem; - boolean started; - Pixel *px; - boolean multiColor; - int iByte; - }; + uint8_t pin; + int channel=-1; + char *pType=NULL; + rmt_channel_handle_t tx_chan = NULL; + rmt_encoder_handle_t encoder; + rmt_transmit_config_t tx_config; - RFControl *rf; // Pixel utilizes RFControl - uint32_t pattern[2]; // storage for zero-bit and one-bit pulses uint32_t resetTime; // minimum time (in usec) between pulse trains - uint32_t txEndMask; // mask for end-of-transmission interrupt - uint32_t txThrMask; // mask for threshold interrupt - uint8_t bytesPerPixel; // RGBW=4; RGB=3 - const uint8_t *map; // color map representing order in which color bytes are transmitted + uint8_t bytesPerPixel; // WC=2, RGB=3, RGBW=4, RGBWC=5 + float warmTemp=2000; // default temperature (in Kelvin) of warm-white LED + float coolTemp=7000; // defult temperature (in Kelvin) of cool-white LED + uint8_t map[5]; // color map representing order in which color bytes are transmitted Color onColor; // color used for on() command - - const int memSize=sizeof(RMTMEM.chan[0].data32)/4; // determine size (in pulses) of one channel - - static void loadData(void *arg); // interrupt handler - volatile static pixel_status_t status; // storage for volatile information modified in interupt handler public: - - Pixel(int pin, pixelType_t pixelType=PixelType::GRB); // creates addressable single-wire LED of pixelType connected to pin (such as the SK68 or WS28) + Pixel(int pin, const char *pixelType="GRB"); // creates addressable single-wire LED of pixelType connected to pin (such as the SK68 or WS28) void set(Color *c, int nPixels, boolean multiColor=true); // sets colors of nPixels based on array of Colors c; setting multiColor to false repeats Color in c[0] for all nPixels void set(Color c, int nPixels=1){set(&c,nPixels,false);} // sets color of nPixels to be equal to specific Color c - static Color RGB(uint8_t r, uint8_t g, uint8_t b, uint8_t w=0){return(Color().RGB(r,g,b,w));} // an alternative method for returning an RGB Color - static Color HSV(float h, float s, float v, double w=0){return(Color().HSV(h,s,v,w));} // an alternative method for returning an HSV Color + static Color RGB(uint8_t r, uint8_t g, uint8_t b, uint8_t w=0, uint8_t c=0){return(Color().RGB(r,g,b,w,c));} // a static method for returning an RGB(WC) Color + static Color HSV(float h, float s, float v, double w=0, double c=0){return(Color().HSV(h,s,v,w,c));} // a static method for returning an HSV(WC) Color + static Color WC(uint8_t w, uint8_t c=0){return(Color().WC(w,c));} // a static method for returning an Warm-White/Cold-White (WC) Color + static Color CCT(float temp, float v, float wTemp, float cTemp){return(Color().CCT(temp,v,wTemp,cTemp));} // a static method for returning a CCT Color + Color CCT(float temp, float v){return(Color().CCT(temp,v,warmTemp,coolTemp));} // a member function for returning a CCT Color using pixel-specific temperatures - int getPin(){return(rf->getPin());} // returns pixel pin if valid, else returns -1 - boolean isRGBW(){return(bytesPerPixel==4);} // returns true if RGBW LED, else false if RGB LED - void setTiming(float high0, float low0, float high1, float low1, uint32_t lowReset); // changes default timings for bit pulse - note parameters are in MICROSECONDS + int getPin(){return(channel>=0?pin:-1);} // returns pixel pin (=-1 if channel is not valid) + Pixel *setTiming(float high0, float low0, float high1, float low1, uint32_t lowReset); // changes default timings for bit pulse - note parameters are in MICROSECONDS + Pixel *setTemperatures(float wTemp, float cTemp){warmTemp=wTemp;coolTemp=cTemp;return(this);} // changes default warm-white and cool-white LED temperatures (in Kelvin) + boolean hasColor(char c){return(strchr(pType,toupper(c))!=NULL || strchr(pType,tolower(c))!=NULL);} // returns true if pixelType includes c (case-insensitive) + operator bool(){ // override boolean operator to return true/false if creation succeeded/failed - return(*rf); + return(channel>=0); } void on() {set(onColor);} void off() {set(RGB(0,0,0,0));} Pixel *setOnColor(Color c){onColor=c;return(this);} - [[deprecated("Please use Pixel(int pin, pixelType_t pixelType) constructor instead.")]] - Pixel(int pin, boolean isRGBW):Pixel(pin,isRGBW?PixelType::GRBW:PixelType::GRB){}; + [[deprecated("*** Please use Pixel(int pin, const char *pixelType) constructor instead to ensure future compatibility.")]] + Pixel(int pin, boolean isRGBW) : Pixel(pin,isRGBW?"GRBW":"GRB"){} + + [[deprecated("*** Please use Pixel(int pin, const char *pixelType) constructor instead to ensure future compatibility.")]] + Pixel(int pin, pixelType_t pixelType) : Pixel(pin, pixelType.c_str()){} + [[deprecated("*** This method will be deprecated in a future release.")]] + boolean isRGBW(){return(bytesPerPixel==4);} }; //////////////////////////////////////////// @@ -205,6 +224,14 @@ class Dot { uint32_t val; }; + Color(){ + this->red=0; + this->green=0; + this->blue=0; + this->drive=31; + this->flags=7; + } + Color RGB(uint8_t r, uint8_t g, uint8_t b, uint8_t driveLevel=31){ // returns Color based on provided RGB values where r/g/b=[0-255] and current-limiting drive level=[0,31] this->red=r; this->green=g; @@ -214,7 +241,7 @@ class Dot { return(*this); } - Color HSV(float h, float s, float v, double drivePercent=100){ // returns Color based on provided HSV values where h=[0,360], s/v=[0,100], and current-limiting drive percent=[0,100] + Color HSV(float h, float s, float v, double drivePercent=100){ // returns Color based on provided HSV values where h=[0,360], s/v=[0,100], and current-limiting drive percent=[0,100] float r,g,b; LedPin::HSVtoRGB(h,s/100.0,v/100.0,&r,&g,&b); this->red=r*255; diff --git a/ESP32/HomeSpan-master/src/src/extras/PwmPin.cpp b/ESP32/HomeSpan-master/src/src/extras/PwmPin.cpp index 703e142..07761a4 100644 --- a/ESP32/HomeSpan-master/src/src/extras/PwmPin.cpp +++ b/ESP32/HomeSpan-master/src/src/extras/PwmPin.cpp @@ -44,10 +44,19 @@ LedC::LedC(uint8_t pin, uint16_t freq, boolean invert){ timerList[nTimer][nMode]->speed_mode=(ledc_mode_t)nMode; timerList[nTimer][nMode]->timer_num=(ledc_timer_t)nTimer; timerList[nTimer][nMode]->freq_hz=freq; +#if defined(SOC_LEDC_SUPPORT_APB_CLOCK) timerList[nTimer][nMode]->clk_cfg=LEDC_USE_APB_CLK; +#elif defined(SOC_LEDC_SUPPORT_PLL_DIV_CLOCK) + timerList[nTimer][nMode]->clk_cfg=LEDC_USE_PLL_DIV_CLK; +#endif + +#if ESP_IDF_VERSION > ESP_IDF_VERSION_VAL(5, 1, 5) + timerList[nTimer][nMode]->deconfigure=false; +#endif + int res=LEDC_TIMER_BIT_MAX-1; // find the maximum possible resolution - while(getApbFrequency()/(freq*pow(2,res))<1) + while(80.0e6/(freq*pow(2,res))<1) res--; timerList[nTimer][nMode]->duty_resolution=(ledc_timer_bit_t)res; @@ -60,7 +69,7 @@ LedC::LedC(uint8_t pin, uint16_t freq, boolean invert){ } if(timerList[nTimer][nMode]->freq_hz==freq){ // if timer matches desired frequency (always true if newly-created above) - channelList[nChannel][nMode]=new ledc_channel_config_t; // create new channel instance + channelList[nChannel][nMode]=new ledc_channel_config_t(); // create new channel instance channelList[nChannel][nMode]->speed_mode=(ledc_mode_t)nMode; channelList[nChannel][nMode]->channel=(ledc_channel_t)nChannel; channelList[nChannel][nMode]->timer_sel=(ledc_timer_t)nTimer; @@ -96,8 +105,11 @@ LedPin::LedPin(uint8_t pin, float level, uint16_t freq, boolean invert) : LedC(p timer->duty_resolution, channel->flags.output_invert?"(inverted)":"" ); - - ledc_fade_func_install(0); + + if(!fadeInitialized){ + ledc_fade_func_install(0); + fadeInitialized=true; + } ledc_cbs_t fadeCallbackList = {.fade_cb = fadeCallback}; // for some reason, ledc_cb_register requires the function to be wrapped in a structure ledc_cb_register(channel->speed_mode,channel->channel,&fadeCallbackList,this); @@ -273,3 +285,4 @@ void ServoPin::set(double degrees){ ledc_channel_config_t *LedC::channelList[LEDC_CHANNEL_MAX][LEDC_SPEED_MODE_MAX]={}; ledc_timer_config_t *LedC::timerList[LEDC_TIMER_MAX][LEDC_SPEED_MODE_MAX]={}; +boolean LedPin::fadeInitialized=false; diff --git a/ESP32/HomeSpan-master/src/src/extras/PwmPin.h b/ESP32/HomeSpan-master/src/src/extras/PwmPin.h index eedce3a..901c0e6 100644 --- a/ESP32/HomeSpan-master/src/src/extras/PwmPin.h +++ b/ESP32/HomeSpan-master/src/src/extras/PwmPin.h @@ -91,9 +91,10 @@ class LedPin : public LedC { private: int fadeState=NOT_FADING; static bool fadeCallback(const ledc_cb_param_t *param, void *arg); + static boolean fadeInitialized; public: - LedPin(uint8_t pin, float level=0, uint16_t freq=DEFAULT_PWM_FREQ, boolean invert=false); // assigns pin to be output of one of 16 PWM channels initial level and frequency + LedPin(uint8_t pin, float level=0, uint16_t freq=DEFAULT_PWM_FREQ, boolean invert=false); // assigns LED pin void set(float level); // sets the PWM duty to level (0-100) int fade(float level, uint32_t fadeTime, int fadeType=ABSOLUTE); // sets the PWM duty to level (0-100) within fadeTime in milliseconds, returns success (0) or fail (1) int fadeStatus(); // returns fading state diff --git a/ESP32/HomeSpan-master/src/src/extras/RFControl.cpp b/ESP32/HomeSpan-master/src/src/extras/RFControl.cpp index 1266263..e6b5e0e 100644 --- a/ESP32/HomeSpan-master/src/src/extras/RFControl.cpp +++ b/ESP32/HomeSpan-master/src/src/extras/RFControl.cpp @@ -29,67 +29,73 @@ /////////////////// -RFControl::RFControl(uint8_t pin, boolean refClock, boolean installDriver){ +RFControl::RFControl(uint8_t pin, boolean refClock){ -#if defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3) - if(nChannels==RMT_CHANNEL_MAX/2){ -#else - if(nChannels==RMT_CHANNEL_MAX){ -#endif + this->refClock=refClock; + this->pin=pin; + + rmt_tx_channel_config_t tx_chan_config; + tx_chan_config.clk_src = RMT_CLK_SRC_DEFAULT; // use default as a placeholder - will be reset to required clock before each transmission + tx_chan_config.gpio_num = (gpio_num_t)pin; // GPIO number + tx_chan_config.mem_block_symbols = SOC_RMT_MEM_WORDS_PER_CHANNEL; // set number of symbols to match those in a single channel block + tx_chan_config.resolution_hz = 1 * 1000 * 1000; // use 1MHz as a placeholder - will be reset to required clock before each transmission + tx_chan_config.intr_priority = 3; // medium interrupt priority + tx_chan_config.trans_queue_depth = 4; // set the number of transactions that can pend in the background + tx_chan_config.flags.invert_out = false; // do not invert output signal + tx_chan_config.flags.with_dma = false; // use RMT channel memory, not DMA (most chips do not support use of DMA anyway) + tx_chan_config.flags.io_loop_back = false; // do not use loop-back mode + tx_chan_config.flags.io_od_mode = false; // do not use open-drain output + + if(!GPIO_IS_VALID_OUTPUT_GPIO(pin)){ + ESP_LOGE(RFControl_TAG,"Can't create RFControl(%d) - invalid output pin",pin); + return; + } + + if(rmt_new_tx_channel(&tx_chan_config, &tx_chan)!=ESP_OK){ ESP_LOGE(RFControl_TAG,"Can't create RFControl(%d) - no open channels",pin); return; } - config=new rmt_config_t; + rmt_enable(tx_chan); // enable channel + channel=((int *)tx_chan)[0]; // get channel number + + tx_config.loop_count=0; // populate tx_config structure + tx_config.flags.eot_level=0; + tx_config.flags.queue_nonblocking=0; - config->rmt_mode=RMT_MODE_TX; - config->tx_config.carrier_en=false; - config->channel=(rmt_channel_t)nChannels; - config->flags=0; - config->clk_div = 1; - config->mem_block_num=1; - config->gpio_num=(gpio_num_t)pin; - config->tx_config.idle_output_en=false; - config->tx_config.idle_level=RMT_IDLE_LEVEL_LOW; - config->tx_config.loop_en=false; - - rmt_config(config); - - if(installDriver) - rmt_driver_install(config->channel,0,0); - - // If specified, set the base clock to 1 MHz so tick-units are in microseconds (before any CLK_DIV is applied), otherwise default will be 80 MHz APB clock - - this->refClock=refClock; - - if(refClock) -#ifdef RMT_SYS_CONF_REG - REG_SET_FIELD(RMT_SYS_CONF_REG,RMT_SCLK_DIV_NUM,79); // ESP32-C3 and ESP32-S3 do not have a 1 MHz REF Tick Clock, but allows the 80 MHz APB clock to be scaled by an additional RMT-specific divider -#else - rmt_set_source_clk(config->channel,RMT_BASECLK_REF); // use 1 MHz REF Tick Clock for ESP32 and ESP32-S2 -#endif - - nChannels++; - + rmt_copy_encoder_config_t copy_config; // required, though nothing to populate + rmt_new_copy_encoder(©_config, &encoder); // create copy encoder } /////////////////// -void RFControl::start(uint8_t nCycles, uint8_t tickTime){ // starts transmission of pulses from internal data structure, repeated for nCycles, where each tick in pulse is tickTime microseconds long +void RFControl::start(uint8_t nCycles, uint8_t tickTime){ // starts transmission of pulses from internal data structure, repeated for nCycles start(data.data(), data.size(), nCycles, tickTime); } /////////////////// -void RFControl::start(uint32_t *data, int nData, uint8_t nCycles, uint8_t tickTime){ // starts transmission of pulses from specified data pointer, repeated for nCycles, where each tick in pulse is tickTime microseconds long +void RFControl::start(uint32_t *data, size_t nData, uint8_t nCycles, uint8_t tickTime){ // starts transmission of pulses from specified data pointer, repeated for nCycles - if(!config || nData==0) + if(channel<0 || nData==0) return; - - rmt_set_clk_div(config->channel,tickTime); // set clock divider - for(int i=0;ichannel, (rmt_item32_t *) data, nData, true); // start transmission and wait until completed before returning + rmt_ll_tx_set_channel_clock_div(&RMT, channel, tickTime); // set clock divider + + if(refClock) +#if defined(SOC_RMT_SUPPORT_REF_TICK) + rmt_ll_set_group_clock_src(&RMT, channel, RMT_CLK_SRC_REF_TICK, 0, 0, 0); // use REF_TICK, which is 1 MHz +#else + rmt_ll_set_group_clock_src(&RMT, channel, RMT_CLK_SRC_DEFAULT, 80, 0, 0); // use DEFAULT CLOCK, which is always 80 MHz, and scale by 80 to get 1 MHz +#endif + else + rmt_ll_set_group_clock_src(&RMT, channel, RMT_CLK_SRC_DEFAULT, 1, 0, 0); // use DEFAULT CLOCK, which is always 80 MHz, without any scaling + + for(int i=0;i1) duty=1; + + float period=(refClock?1.0e6:80.0e6)/freq; + uint32_t highTime=period*duty+0.5; + uint32_t lowTime=period*(1.0-duty)+0.5; - if(freq>0){ - float period=1.0e6/freq*(refClock?1:80); - uint32_t highTime=period*duty+0.5; - uint32_t lowTime=period*(1.0-duty)+0.5; - - if(highTime>0xFFFF || lowTime>0xFFFF){ - ESP_LOGE(RFControl_TAG,"Can't enable carrier frequency=%d Hz for RF Control pin=%d, duty=%0.2f. Frequency is too low!",freq,config->gpio_num,duty); - return; - } - - if(highTime==0){ - ESP_LOGE(RFControl_TAG,"Can't enable carrier frequency=%d Hz for RF Control pin=%d, duty=%0.2f. Duty is too low or frequency is too high!",freq,config->gpio_num,duty); - return; - } - - if(lowTime==0){ - ESP_LOGE(RFControl_TAG,"Can't enable carrier frequency=%d Hz for RF Control pin=%d, duty=%0.2f. Duty is too high or frequency is too high!",freq,config->gpio_num,duty); - return; - } - - rmt_set_tx_carrier(config->channel,true,highTime,lowTime,RMT_CARRIER_LEVEL_HIGH); - } else { - rmt_set_tx_carrier(config->channel,false,0,0,RMT_CARRIER_LEVEL_HIGH); + if(highTime>0xFFFF || lowTime>0xFFFF){ + ESP_LOGE(RFControl_TAG,"Can't enable carrier frequency=%d Hz for RF Control pin=%d, duty=%0.2f. Frequency is too low!",freq,pin,duty); + return; } + + if(highTime==0){ + ESP_LOGE(RFControl_TAG,"Can't enable carrier frequency=%d Hz for RF Control pin=%d, duty=%0.2f. Duty is too low or frequency is too high!",freq,pin,duty); + return; + } + + if(lowTime==0){ + ESP_LOGE(RFControl_TAG,"Can't enable carrier frequency=%d Hz for RF Control pin=%d, duty=%0.2f. Duty is too high or frequency is too high!",freq,pin,duty); + return; + } + +#if SOC_RMT_SUPPORT_TX_CARRIER_DATA_ONLY + rmt_ll_tx_enable_carrier_always_on(&RMT, channel, 0); // for chips that support carrier is always on, disable it +#endif + + rmt_ll_tx_set_carrier_level(&RMT, channel, 1); // turn on carrier wave when signal is HIGH + rmt_ll_tx_set_carrier_high_low_ticks(&RMT, channel, highTime, lowTime); // set high/low ticks for carrier wave frequency and duty cycle + rmt_ll_tx_enable_carrier_modulation(&RMT, channel, 1); // enable carrier wave + } - -/////////////////// - -uint8_t RFControl::nChannels=0; diff --git a/ESP32/HomeSpan-master/src/src/extras/RFControl.h b/ESP32/HomeSpan-master/src/src/extras/RFControl.h index d80d22d..757b5e2 100644 --- a/ESP32/HomeSpan-master/src/src/extras/RFControl.h +++ b/ESP32/HomeSpan-master/src/src/extras/RFControl.h @@ -31,9 +31,15 @@ #pragma once +#include "driver/gpio.h" + +#pragma GCC diagnostic ignored "-Wvolatile" + #include -#include -#include "driver/rmt.h" +#include // IDF 5 RMT driver +#include // where RMT register structure is defined +#include // where low-level RMT calls are defined + #include [[maybe_unused]] static const char* RFControl_TAG = "RFControl"; @@ -41,21 +47,21 @@ using std::vector; class RFControl { - friend class Pixel; private: - rmt_config_t *config=NULL; vector data; boolean lowWord=true; boolean refClock; - static uint8_t nChannels; - - RFControl(uint8_t pin, boolean refClock, boolean installDriver); // private constructor (only used by Pixel class) + uint8_t pin; + int channel=-1; + rmt_channel_handle_t tx_chan = NULL; + rmt_encoder_handle_t encoder; + rmt_transmit_config_t tx_config; public: - RFControl(uint8_t pin, boolean refClock=true):RFControl(pin,refClock,true){}; // public constructor to create transmitter on pin, using 1-MHz Ref Tick clock or 80-MHz APB clock + RFControl(uint8_t pin, boolean refClock=true); // public constructor to create transmitter on pin, using 1-MHz Ref Tick clock or 80-MHz APB clock - void start(uint32_t *data, int nData, uint8_t nCycles=1, uint8_t tickTime=1); // starts transmission of pulses from specified data pointer, repeated for numCycles, where each tick in pulse is tickTime microseconds long + void start(uint32_t *data, size_t nData, uint8_t nCycles=1, uint8_t tickTime=1); // starts transmission of pulses from specified data pointer, repeated for numCycles, where each tick in pulse is tickTime microseconds long void start(uint8_t nCycles=1, uint8_t tickTime=1); // starts transmission of pulses from internal data structure, repeated for numCycles, where each tick in pulse is tickTime microseconds long void clear(); // clears transmitter memory @@ -64,11 +70,8 @@ class RFControl { void enableCarrier(uint32_t freq, float duty=0.5); // enables carrier wave if freq>0, else disables carrier wave; duty is a fraction from 0-1 void disableCarrier(){enableCarrier(0);} // disables carrier wave - int getPin(){return(config?config->gpio_num:-1);} // returns the pin number, or -1 if no channel defined - rmt_channel_t getChannel(){return(config?config->channel:RMT_CHANNEL_0);} // returns channel, or channel_0 is no channel defined - operator bool(){ // override boolean operator to return true/false if creation succeeded/failed - return(config); + return(channel>=0); } }; diff --git a/ESP32/HomeSpan-master/src/src/extras/extras.ino b/ESP32/HomeSpan-master/src/src/extras/extras.ino index 0e6d435..b7b0aa5 100644 --- a/ESP32/HomeSpan-master/src/src/extras/extras.ino +++ b/ESP32/HomeSpan-master/src/src/extras/extras.ino @@ -24,41 +24,116 @@ * SOFTWARE. * ********************************************************************************/ - -#include "PwmPin.h" -ServoPin servo(21,0,500,2200,-60,60); +#include "Pixel.h" +#include "RFControl.h" -void setup() { +#define PIXEL_PIN 26 +#define LED_PIN 15 + +#define NCYCLES 4 +#define COUNT 5 +#define ONTIME 5000 +#define OFFTIME 5000 + +void setup(){ - Serial.begin(115200); + Serial.begin(115200); // start the Serial interface delay(1000); - Serial.print("\n\nReady\n\n"); + Serial.print("\n\nHomeSpan Pixel+RF Example\n\n"); - for(int count=0;count<3;count++){ - for(int i=-60;i<61;i++){ - servo.set(i); - delay(10); - } + Pixel px(PIXEL_PIN,"G-B"); + px.setOnColor(Pixel::RGB(0,255,0))->setTemperatures(2000,6000)->setTiming(0.32, 0.88, 0.64, 0.56, 80.0); + RFControl rf(LED_PIN); - for(int i=60;i>-61;i--){ - servo.set(i); - delay(10); - } + Pixel::Color q; + + q.CCT(4000,25,3000,6500); + Serial.printf("%d %d %d %d %d\n",q.col[0],q.col[1],q.col[2],q.col[3],q.col[4]); + q.CCT(4000,100,3000,6500); + Serial.printf("%d %d %d %d %d\n",q.col[0],q.col[1],q.col[2],q.col[3],q.col[4]); + q.CCT(3000,100,2000,7000); + Serial.printf("%d %d %d %d %d\n",q.col[0],q.col[1],q.col[2],q.col[3],q.col[4]); + q.CCT(4500,100,2000,7000); + Serial.printf("%d %d %d %d %d\n",q.col[0],q.col[1],q.col[2],q.col[3],q.col[4]); + q.CCT(6000,10,2000,7000); + Serial.printf("%d %d %d %d %d\n",q.col[0],q.col[1],q.col[2],q.col[3],q.col[4]); + q=px.CCT(6000,10,2000,7000); + Serial.printf("%d %d %d %d %d\n",q.col[0],q.col[1],q.col[2],q.col[3],q.col[4]); + q=px.CCT(6000,10); + Serial.printf("%d %d %d %d %d\n",q.col[0],q.col[1],q.col[2],q.col[3],q.col[4]); + q.CCT(7000,50,2000,7000); + Serial.printf("%d %d %d %d %d\n",q.col[0],q.col[1],q.col[2],q.col[3],q.col[4]); + q=Pixel::CCT(8000,25,2000,7000); + Serial.printf("%d %d %d %d %d\n",q.col[0],q.col[1],q.col[2],q.col[3],q.col[4]); + + Pixel::Color c[8]={ + Pixel::RGB(255,0,0), + Pixel::RGB(255,0,0), + Pixel::RGB(255,0,0), + px.RGB(0,255,0), + px.RGB(0,255,0), + Pixel::RGB(0,0,255), + Pixel::RGB(0,0,255), + Pixel::RGB(0,0,255) + }; + + Pixel::Color d[8]={ + Pixel::HSV(0,100,10), + Pixel::HSV(0,100,10), + Pixel::HSV(0,100,10), + Pixel::HSV(120,100,10), + Pixel::HSV(120,100,10), + Pixel::HSV(240,100,10), + Pixel::HSV(240,100,10), + Pixel::HSV(240,100,10) + }; + + Serial.printf("Starting cycles of RGB pattern...\n"); + + for(int i=0;i + ////////////////////////////////////////////////////// // HomeSpan Version // -#define HS_MAJOR 1 -#define HS_MINOR 9 -#define HS_PATCH 1 +#define HS_MAJOR 2 +#define HS_MINOR 1 +#define HS_PATCH 2 +#define HS_EXTENSION "" ////////////////////////////////////////////////////// @@ -41,17 +44,22 @@ #include #endif +#if !(defined(CONFIG_IDF_TARGET_ESP32) || defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32C6)) + #error ERROR: SELECTED MICROCONTROLLER NOT SUPPORTED. HOMESPAN SUPPORTS THE FOLLOWING CHIPS: ESP32, ESP32-S2, ESP32-S3, ESP32-C3, AND ESP32-C6 + #include +#endif + #include -#if ESP_ARDUINO_VERSION_MAJOR!=2 - #error ERROR: HOMESPAN REQUIRES VERSION 2 OF THE ARDUINO ESP32 LIBRARY. HOMESPAN IS NOT COMPATIBLE WITH VERSION 1 OR VERSION 3 +#if ESP_ARDUINO_VERSION < ESP_ARDUINO_VERSION_VAL(3, 0, 2) + #error ERROR: THIS VERSION OF HOMESPAN REQUIRES VERSION 3.0.2 OR GREATER OF THE ARDUINO-ESP32 BOARD MANAGER #include #endif #define STRINGIFY(x) _STR(x) #define _STR(x) #x -#define HOMESPAN_VERSION STRINGIFY(HS_MAJOR) "." STRINGIFY(HS_MINOR) "." STRINGIFY(HS_PATCH) +#define HOMESPAN_VERSION STRINGIFY(HS_MAJOR) "." STRINGIFY(HS_MINOR) "." STRINGIFY(HS_PATCH) HS_EXTENSION #define VERSION(major,minor,patch) major*10000+minor*100+patch