Update libki
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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**<sup>*</sup> | *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.
|
||||
<sup>*</sup>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 <mutex>` 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?
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
///////////////////////////////
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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<MAX_BRIGHTNESS;i++){
|
||||
testPixel.set(Pixel::RGB(i*r,i*g,i*b,i*w),NPIXELS);
|
||||
delay(4);
|
||||
Serial.printf("\n\n*************** PIXEL TESTER **********************\n\n");
|
||||
Serial.printf("This sketch helps you identity your Pixel Type\n\n");
|
||||
|
||||
while(pin<0){
|
||||
Serial.printf("Enter PIN number to which NeoPixel is connected: ");
|
||||
sscanf(getSerial(),"%d",&pin);
|
||||
if(pin<0)
|
||||
Serial.printf("(invalid entry)\n");
|
||||
else
|
||||
Serial.printf("%d\n",pin);
|
||||
}
|
||||
|
||||
for(int i=MAX_BRIGHTNESS;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;v<MAX_BRIGHTNESS;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);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
//////////////////////////////////////
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 <ESP8266WiFi.h>
|
||||
#include <espnow.h>
|
||||
#include <Crypto.h> // 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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
name=HomeSpan
|
||||
version=1.9.1
|
||||
version=2.1.2
|
||||
author=Gregg <homespan@icloud.com>
|
||||
maintainer=Gregg <homespan@icloud.com>
|
||||
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
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<len;i++)
|
||||
@@ -982,17 +987,14 @@ int HAPClient::putCharacteristicsURL(char *json){
|
||||
|
||||
LOG1("In Put Characteristics #%d (%s)...\n",clientNumber,client.remoteIP().toString().c_str());
|
||||
|
||||
int n=homeSpan.countCharacteristics(json); // count number of objects in JSON request
|
||||
if(n==0) // if no objects found, return
|
||||
return(0);
|
||||
|
||||
SpanBuf pObj[n]; // reserve space for objects
|
||||
if(!homeSpan.updateCharacteristics(json, pObj)) // perform update
|
||||
SpanBufVec pVec;
|
||||
|
||||
if(!homeSpan.updateCharacteristics(json, pVec)) // perform update and check for success
|
||||
return(0); // return if failed to update (error message will have been printed in update)
|
||||
|
||||
boolean multiCast=false;
|
||||
for(int i=0;i<n && !multiCast;i++) // for each characterstic, check if any status is either NOT OKAY, or if WRITE-RESPONSE is requested
|
||||
if(pObj[i].status!=StatusCode::OK || pObj[i].wr) // if so, to use multicast response
|
||||
boolean multiCast=false;
|
||||
for(auto it=pVec.begin();it!=pVec.end() && !multiCast;it++) // for each characteristic, check if any status is either NOT OKAY, or if WRITE-RESPONSE is requested
|
||||
if((*it).status!=StatusCode::OK || (*it).wr) // if so, must use multicast response
|
||||
multiCast=true;
|
||||
|
||||
LOG2("\n>>>>>>>>>> %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 << "<html><head><title>" << homeSpan.displayName << "</title>\n";
|
||||
hapOut << "<style>body {background-color:lightblue;} th, td {padding-right: 10px; padding-left: 10px; border:1px solid black;}" << homeSpan.webLog.css.c_str() << "</style></head>\n";
|
||||
@@ -1111,51 +1119,26 @@ void HAPClient::getStatusURL(HAPClient *hapClient, void (*callBack)(const char *
|
||||
hapOut << "<tr><td>Up Time:</td><td>" << uptime << "</td></tr>\n";
|
||||
hapOut << "<tr><td>Current Time:</td><td>" << clocktime << "</td></tr>\n";
|
||||
hapOut << "<tr><td>Boot Time:</td><td>" << homeSpan.webLog.bootTime << "</td></tr>\n";
|
||||
hapOut << "<tr><td>Reset Reason:</td><td>";
|
||||
|
||||
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 << "<tr><td>Reset Reason:</td><td>" << Utils::resetReason() << " (" << esp_reset_reason() << ")</td></tr>\n";
|
||||
|
||||
if(homeSpan.compileTime)
|
||||
hapOut << "<tr><td>Compile Time:</td><td>" << homeSpan.compileTime << "</td></tr>\n";
|
||||
|
||||
if(!homeSpan.ethernetEnabled){
|
||||
hapOut << "<tr><td>WiFi Disconnects:</td><td>" << homeSpan.connected/2 << "</td></tr>\n";
|
||||
hapOut << "<tr><td>WiFi Signal:</td><td>" << (int)WiFi.RSSI() << " dBm</td></tr>\n";
|
||||
if(homeSpan.bssidNames.count(WiFi.BSSIDstr().c_str()))
|
||||
hapOut << "<tr><td>BSSID:</td><td>" << WiFi.BSSIDstr().c_str() << " \"" << homeSpan.bssidNames[WiFi.BSSIDstr().c_str()].c_str() << "\"" << "</td></tr>\n";
|
||||
else
|
||||
hapOut << "<tr><td>BSSID:</td><td>" << WiFi.BSSIDstr().c_str() << "</td></tr>\n";
|
||||
hapOut << "<tr><td>WiFi Local IP:</td><td>" << WiFi.localIP().toString().c_str() << "</td></tr>\n";
|
||||
hapOut << "<tr><td>WiFi Gateway:</td><td>" << WiFi.gatewayIP().toString().c_str() << "</td></tr>\n";
|
||||
} else {
|
||||
hapOut << "<tr><td>Ethernet Disconnects:</td><td>" << homeSpan.connected/2 << "</td></tr>\n";
|
||||
hapOut << "<tr><td>Ethernet Local IP:</td><td>" << ETH.localIP().toString().c_str() << "</td></tr>\n";
|
||||
hapOut << "<tr><td>Ethernet Gateway:</td><td>" << ETH.gatewayIP().toString().c_str() << "</td></tr>\n";
|
||||
}
|
||||
|
||||
hapOut << " (" << esp_reset_reason() << ")</td></tr>\n";
|
||||
|
||||
hapOut << "<tr><td>WiFi Disconnects:</td><td>" << homeSpan.connected/2 << "</td></tr>\n";
|
||||
hapOut << "<tr><td>WiFi Signal:</td><td>" << (int)WiFi.RSSI() << " dBm</td></tr>\n";
|
||||
hapOut << "<tr><td>WiFi Gateway:</td><td>" << WiFi.gatewayIP().toString().c_str() << "</td></tr>\n";
|
||||
hapOut << "<tr><td>ESP32 Board:</td><td>" << ARDUINO_BOARD << "</td></tr>\n";
|
||||
hapOut << "<tr><td>Arduino-ESP Version:</td><td>" << ARDUINO_ESP_VERSION << "</td></tr>\n";
|
||||
hapOut << "<tr><td>ESP-IDF Version:</td><td>" << ESP_IDF_VERSION_MAJOR << "." << ESP_IDF_VERSION_MINOR << "." << ESP_IDF_VERSION_PATCH << "</td></tr>\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);
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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 <unordered_map>
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <shared_mutex>
|
||||
#include <nvs.h>
|
||||
#include <ArduinoOTA.h>
|
||||
#include <ETH.h>
|
||||
#include <esp_now.h>
|
||||
#include <mbedtls/base64.h>
|
||||
#include <esp_ota_ops.h>
|
||||
|
||||
#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<const uint8_t *, size_t> 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<SpanBuf, Mallocator<SpanBuf>> 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<string, string> 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<HAPClient, Mallocator<HAPClient>>::iterator currentClient; // iterator to current client
|
||||
vector<SpanAccessory *, Mallocator<SpanAccessory *>> Accessories; // vector of pointers to all Accessories
|
||||
vector<SpanService *, Mallocator<SpanService *>> Loops; // vector of pointer to all Services that have over-ridden loop() methods
|
||||
vector<SpanBuf, Mallocator<SpanBuf>> 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<SpanButton *, Mallocator<SpanButton *>> PushButtons; // vector of pointer to all PushButtons
|
||||
unordered_map<uint64_t, uint32_t> TimedWrites; // map of timed-write PIDs and Alarm Times (based on TTLs)
|
||||
unordered_map<char, SpanUserCommand *> 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<Controller, Mallocator<Controller>>::const_iterator controllerListBegin();
|
||||
list<Controller, Mallocator<Controller>>::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<FORMAT::STRING){
|
||||
@@ -656,6 +755,7 @@ class SpanCharacteristic{
|
||||
|
||||
SpanCharacteristic(HapChar *hapChar, boolean isCustom=false); // SpanCharacteristic constructor
|
||||
void *operator new(size_t size){return(HS_MALLOC(size));} // override new operator to use PSRAM when available
|
||||
void operator delete(void *p){free(p);}
|
||||
|
||||
template <class T=int> T getVal(){return(uvGet<T>(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 <typename A, typename B, typename S=int> 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();
|
||||
|
||||
@@ -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 <DNSServer.h>
|
||||
|
||||
#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<numSSID;i++)
|
||||
LOG0(" %d) %s\n",i+1,ssidList[i]);
|
||||
|
||||
WiFiServer apServer(80);
|
||||
STATUS_UPDATE(start(LED_AP_STARTED),HS_AP_STARTED)
|
||||
|
||||
NetworkServer apServer(80);
|
||||
client=0;
|
||||
|
||||
const byte DNS_PORT = 53;
|
||||
@@ -161,7 +163,7 @@ void Network::apConfigure(){
|
||||
|
||||
dnsServer.processNextRequest();
|
||||
|
||||
if(client=apServer.available()){ // found a new HTTP client
|
||||
if((client=apServer.accept())){ // found a new HTTP client
|
||||
LOG2("=======================================\n");
|
||||
LOG1("** Access Point Client Connected: (");
|
||||
LOG1(millis()/1000);
|
||||
@@ -229,13 +231,14 @@ void Network::apConfigure(){
|
||||
|
||||
} // process Client
|
||||
|
||||
homeSpan.resetWatchdog();
|
||||
} // while 1
|
||||
|
||||
}
|
||||
|
||||
///////////////////////////////
|
||||
|
||||
void Network::processRequest(char *body, char *formData){
|
||||
void Network_HS::processRequest(char *body, char *formData){
|
||||
|
||||
String responseHead="HTTP/1.1 200 OK\r\nContent-type: text/html\r\n";
|
||||
|
||||
@@ -261,9 +264,10 @@ void Network::processRequest(char *body, char *formData){
|
||||
|
||||
STATUS_UPDATE(start(LED_WIFI_CONNECTING),HS_WIFI_CONNECTING)
|
||||
|
||||
responseBody+="<meta http-equiv = \"refresh\" content = \"" + String(waitTime) + "; url = /wifi-status\" />"
|
||||
"<p>Initiating WiFi connection to:</p><p><b>" + String(wifiData.ssid) + "</p>";
|
||||
|
||||
responseBody+="<meta http-equiv = \"refresh\" content = \"" + String(homeSpan.wifiTimeCounter/1000) + "; url = /wifi-status\" />"
|
||||
"<p>Initiating WiFi connection to:</p><p><b>" + String(wifiData.ssid) + "</b></p>"
|
||||
"<p>(waiting " + String((homeSpan.wifiTimeCounter++)/1000) + " seconds to check for response)</p>";
|
||||
|
||||
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+="<p>Re-initiating connection to:</p><p><b>" + String(wifiData.ssid) + "</b></p>";
|
||||
responseBody+="<p>(waiting " + String(waitTime) + " seconds to check for response)</p>";
|
||||
responseBody+="<p>(waiting " + String((homeSpan.wifiTimeCounter++)/1000) + " seconds to check for response)</p>";
|
||||
responseBody+="<p>Access Point termination in " + String((alarmTimeOut-millis())/1000) + " seconds.</p>";
|
||||
responseBody+="<center><button onclick=\"document.location='/hotspot-detect.html'\">Cancel</button></center>";
|
||||
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+="<p>Welcome to HomeSpan! This page allows you to configure the above HomeSpan device to connect to your WiFi network.</p>"
|
||||
"<p>The LED on this device should be <em>double-blinking</em> during this configuration.</p>"
|
||||
@@ -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<maxSize){ // copy the value until null, '&', or maxSize is reached
|
||||
@@ -402,7 +403,7 @@ int Network::getFormValue(char *formData, const char *tag, char *value, int maxS
|
||||
|
||||
//////////////////////////////////////
|
||||
|
||||
int Network::badRequestError(){
|
||||
int Network_HS::badRequestError(){
|
||||
|
||||
char s[]="HTTP/1.1 400 Bad Request\r\n\r\n";
|
||||
LOG2("\n>>>>>>>>>> ");
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
//////////////////////////////////////
|
||||
|
||||
90
ESP32/HomeSpan-master/src/Network_HS.h
Normal file
90
ESP32/HomeSpan-master/src/Network_HS.h
Normal file
@@ -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 <Arduino.h>
|
||||
#include <WiFi.h>
|
||||
#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);
|
||||
};
|
||||
|
||||
///////////////////////////////
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 //
|
||||
|
||||
@@ -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<TYPE>(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<TYPE>(val,nvsStore,MINVAL,MAXVAL); } };
|
||||
|
||||
namespace Characteristic {
|
||||
|
||||
@@ -525,7 +525,7 @@ namespace Characteristic {
|
||||
CREATE_CHAR(UINT32_t,Identifier,0,0,255); // numerical Identifer of the <b>InputSource</b>.
|
||||
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
|
||||
|
||||
32
ESP32/HomeSpan-master/src/SpanRollback.h
Normal file
32
ESP32/HomeSpan-master/src/SpanRollback.h
Normal file
@@ -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;}
|
||||
@@ -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<uint64_t>());
|
||||
Serial.printf("\n");
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -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<calibCount;i++)
|
||||
threshold+=touchRead(pin);
|
||||
threshold/=calibCount;
|
||||
#if SOC_TOUCH_VERSION_1
|
||||
#if defined(SOC_TOUCH_VERSION_1) || SOC_TOUCH_SENSOR_VERSION==1
|
||||
threshold/=2;
|
||||
LOG0("Touch Sensor at pin=%d used for calibration. Triggers when sensor reading < %d.\n",pin,threshold);
|
||||
#elif SOC_TOUCH_VERSION_2
|
||||
LOG0("Touch Sensor at pin=%d used for calibration. Triggers when sensor reading < %u.\n",pin,threshold);
|
||||
#else
|
||||
threshold*=2;
|
||||
LOG0("Touch Sensor at pin=%d used for calibration. Triggers when sensor reading > %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(nSeconds<CONFIG_ESP_TASK_WDT_TIMEOUT_S) // minimum allowed value is CONFIG_ESP_TASK_WDT_TIMEOUT_S
|
||||
nSeconds=CONFIG_ESP_TASK_WDT_TIMEOUT_S;
|
||||
|
||||
this->nSeconds=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<CONFIG_FREERTOS_NUMBER_OF_CORES;i++)
|
||||
twdtConfig.idle_core_mask |= (ESP_OK==esp_task_wdt_status(xTaskGetIdleTaskHandleForCore(i))) << i; // replicate existing idle task subscriptions to task watchdog
|
||||
|
||||
esp_task_wdt_reconfigure(&twdtConfig); // reconfigure task watchdog with new time=nSeconds but DO NOT alter state of idle task subscriptions on either core
|
||||
|
||||
if(!wdtHandle)
|
||||
esp_task_wdt_add_user(WATCHDOG_TAG,&wdtHandle);
|
||||
|
||||
ESP_LOGI(WATCHDOG_TAG,"Enabled with %d-second timeout. Idle Task Mask = %d",nSeconds,twdtConfig.idle_core_mask);
|
||||
}
|
||||
|
||||
//////////////////////////////////////
|
||||
|
||||
void hsWatchdogTimer::disable(){
|
||||
|
||||
if(wdtHandle)
|
||||
esp_task_wdt_delete_user(wdtHandle);
|
||||
wdtHandle=NULL;
|
||||
|
||||
ESP_LOGI(WATCHDOG_TAG,"Disabled");
|
||||
}
|
||||
|
||||
//////////////////////////////////////
|
||||
|
||||
void hsWatchdogTimer::reset(){
|
||||
|
||||
vTaskDelay(1);
|
||||
if(wdtHandle)
|
||||
esp_task_wdt_reset_user(wdtHandle);
|
||||
}
|
||||
|
||||
//////////////////////////////////////
|
||||
|
||||
uint16_t hsWatchdogTimer::getSeconds(){
|
||||
return(nSeconds);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*
|
||||
@@ -28,14 +28,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <esp_task_wdt.h>
|
||||
|
||||
#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 <newline>, 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);}
|
||||
#else
|
||||
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();
|
||||
};
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
#include "Blinker.h"
|
||||
|
||||
////////////////////////////////
|
||||
////////////////////////////////
|
||||
// Blinker //
|
||||
////////////////////////////////
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <driver/timer.h>
|
||||
|
||||
[[maybe_unused]] static const char* BLINKER_TAG = "Blinker";
|
||||
|
||||
|
||||
@@ -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;i<len && i<5;i++){ // parse and then validate pixelType
|
||||
int index=strchrnul(v,toupper(pixelType[i]))-v;
|
||||
if(index==strlen(v)) // invalid mapping character found
|
||||
invalidMap=true;
|
||||
map[bytesPerPixel++]=index%5; // create pixel map and compute number of bytes per pixel
|
||||
}
|
||||
|
||||
map=pixelType;
|
||||
if(bytesPerPixel<3 || len>5 || 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;i<bytesPerPixel;i++) // remap colors into ping/pong structure
|
||||
data[index].col[i]=c->col[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);
|
||||
}
|
||||
|
||||
///////////////////
|
||||
|
||||
@@ -31,30 +31,43 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "RFControl.h"
|
||||
#include "PwmPin.h"
|
||||
#include "Blinker.h"
|
||||
|
||||
#pragma GCC diagnostic ignored "-Wvolatile"
|
||||
|
||||
#include <driver/rmt_tx.h> // IDF 5 RMT driver
|
||||
#include <soc/rmt_struct.h> // where RMT register structure is defined
|
||||
#include <hal/rmt_ll.h> // where low-level RMT calls are defined
|
||||
|
||||
#include <soc/gpio_struct.h>
|
||||
|
||||
[[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(temp<wTemp)
|
||||
temp=wTemp;
|
||||
else if(temp>cTemp)
|
||||
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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;i<nCycles;i++) // loop over nCycles
|
||||
rmt_write_items(config->channel, (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;i<nCycles;i++){
|
||||
rmt_transmit(tx_chan, encoder, data, nData*4, &tx_config);
|
||||
rmt_tx_wait_all_done(tx_chan,-1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
///////////////////
|
||||
@@ -128,37 +134,44 @@ void RFControl::phase(uint32_t nTicks, uint8_t phase){
|
||||
|
||||
void RFControl::enableCarrier(uint32_t freq, float duty){
|
||||
|
||||
if(channel<0)
|
||||
return;
|
||||
|
||||
if(freq==0){
|
||||
rmt_ll_tx_enable_carrier_modulation(&RMT, channel, 0); // disable carrier wave
|
||||
return;
|
||||
}
|
||||
|
||||
if(duty<0)
|
||||
duty=0;
|
||||
if(duty>1)
|
||||
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;
|
||||
|
||||
@@ -31,9 +31,15 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "driver/gpio.h"
|
||||
|
||||
#pragma GCC diagnostic ignored "-Wvolatile"
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <soc/rmt_reg.h>
|
||||
#include "driver/rmt.h"
|
||||
#include <driver/rmt_tx.h> // IDF 5 RMT driver
|
||||
#include <soc/rmt_struct.h> // where RMT register structure is defined
|
||||
#include <hal/rmt_ll.h> // where low-level RMT calls are defined
|
||||
|
||||
#include <vector>
|
||||
|
||||
[[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<uint32_t> 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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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<NCYCLES;i++){
|
||||
px.set(c,8);
|
||||
delay(1000);
|
||||
px.set(d,8);
|
||||
delay(1000);
|
||||
}
|
||||
|
||||
delay(5000);
|
||||
px.set(Pixel::RGB(0,0,0,255,0),100);
|
||||
delay(2000);
|
||||
px.set(Pixel::RGB(0,0,0,0,255),100);
|
||||
delay(2000);
|
||||
px.set(Pixel::RGB(0,0,0,0,0),100);
|
||||
while(1);
|
||||
|
||||
rf.clear(); // clear the pulse train memory buffer
|
||||
|
||||
servo.set(NAN);
|
||||
for(int i=0;i<COUNT;i++)
|
||||
rf.add(ONTIME,OFFTIME);
|
||||
rf.phase(OFFTIME,LOW);
|
||||
|
||||
delay(10000);
|
||||
long int x,y;
|
||||
|
||||
Serial.printf("Starting cycles of pulses without carrier...\n");
|
||||
x=millis();
|
||||
rf.start(NCYCLES,100);
|
||||
y=millis();
|
||||
Serial.printf("Total time: %ld ms\n",y-x);
|
||||
|
||||
servo.set(0);
|
||||
rf.enableCarrier(10,0.4);
|
||||
|
||||
Serial.printf("Starting cycles of pulses with 10 Hz carrier...\n");
|
||||
x=millis();
|
||||
rf.start(NCYCLES,100);
|
||||
y=millis();
|
||||
Serial.printf("Total time: %ld ms\n",y-x);
|
||||
|
||||
Serial.printf("Turning off RGB LEDs.\n");
|
||||
|
||||
px.set(Pixel::RGB(0,0,0),8);
|
||||
|
||||
Serial.print("Done!\n");
|
||||
}
|
||||
|
||||
//////////////////////////////////////
|
||||
|
||||
void loop(){
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*
|
||||
@@ -27,12 +27,15 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
// 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 <FATAL_ERROR>
|
||||
#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 <FATAL_ERROR>
|
||||
#endif
|
||||
|
||||
#include <esp_arduino_version.h>
|
||||
|
||||
#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 <FATAL_ERROR>
|
||||
#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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user