Update libki

This commit is contained in:
sieja
2025-07-21 21:31:40 +02:00
parent d145590bd3
commit a9fba3267d
47 changed files with 2072 additions and 914 deletions

View File

@@ -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

View File

@@ -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?

View File

@@ -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);

View File

@@ -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.
///////////////////////////////////////////////////////////////////////////////////////////

View 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)

View File

@@ -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();
}
///////////////////////////////

View File

@@ -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
}

View File

@@ -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();
}
//////////////////////////////////////

View File

@@ -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();

View File

@@ -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()

View File

@@ -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);
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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
*

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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:

View File

@@ -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
*

View File

@@ -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
*

View File

@@ -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
*

View File

@@ -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

View File

@@ -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();

View File

@@ -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);
}
//////////////////////////////////////

View 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);
};
///////////////////////////////

View File

@@ -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
*

View File

@@ -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
}

View File

@@ -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

View File

@@ -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 //

View File

@@ -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

View 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;}

View File

@@ -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");

View File

@@ -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
*

View File

@@ -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);
}

View File

@@ -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();
};

View File

@@ -27,7 +27,7 @@
#include "Blinker.h"
////////////////////////////////
////////////////////////////////
// Blinker //
////////////////////////////////

View File

@@ -28,7 +28,6 @@
#pragma once
#include <Arduino.h>
#include <driver/timer.h>
[[maybe_unused]] static const char* BLINKER_TAG = "Blinker";

View File

@@ -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);
}
///////////////////

View File

@@ -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;

View File

@@ -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;

View File

@@ -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

View File

@@ -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(&copy_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;

View File

@@ -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);
}
};

View File

@@ -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(){
}

View File

@@ -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