Merge pull request #422 from HomeSpan/dev

Create HomeSpan 1.7.0
This commit is contained in:
HomeSpan 2022-11-11 18:05:28 -06:00 committed by GitHub
commit 7b457bbbb8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 1830 additions and 869 deletions

View File

@ -0,0 +1,121 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2022 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: A HomeKit implementation for the ESP32 //
// ------------------------------------------------ //
// //
// Demonstrates how to use SpanPoint() to implement //
// two remote temperature sensors on separate ESP32 //
// devices. //
// //
// This sketch is for the MAIN DEVICE that contains //
// all the usual HomeSpan logic, plus two instances //
// of SpanPoint to read temperatures from two other //
// remote devices. //
// //
////////////////////////////////////////////////////////////
#include "HomeSpan.h"
struct RemoteTempSensor : Service::TemperatureSensor {
SpanCharacteristic *temp;
SpanCharacteristic *fault;
SpanPoint *remoteTemp;
const char *name;
float temperature;
RemoteTempSensor(const char *name, const char*macAddress) : Service::TemperatureSensor(){
this->name=name;
temp=new Characteristic::CurrentTemperature(-10.0); // set initial temperature
temp->setRange(-50,100); // expand temperature range to allow negative values
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)
} // end constructor
void loop(){
if(remoteTemp->get(&temperature)){ // if there is data from the remote sensor
temp->setVal(temperature); // update temperature
fault->setVal(0); // clear fault
LOG1("Sensor %s update: Temperature=%0.2f\n",name,temperature*9/5+32);
} else if(remoteTemp->time()>60000 && !fault->getVal()){ // else if it has been a while since last update (60 seconds), and there is no current fault
fault->setVal(1); // set fault state
LOG1("Sensor %s update: FAULT\n",name);
}
} // loop
};
//////////////////////////////////////
void setup() {
Serial.begin(115200);
homeSpan.setLogLevel(1);
homeSpan.begin(Category::Bridges,"Sensor Hub");
new SpanAccessory();
new Service::AccessoryInformation();
new Characteristic::Identify();
new SpanAccessory();
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 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
} // end of setup()
//////////////////////////////////////
void loop(){
homeSpan.poll();
} // end of loop()
//////////////////////////////////////

View File

@ -0,0 +1,78 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2022 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: A HomeKit implementation for the ESP32 //
// ------------------------------------------------ //
// //
// Demonstrates how to use SpanPoint() to implement //
// two remote temperature sensors on separate ESP32 //
// devices. //
// //
// This sketch is for the REMOTE DEVICES. They are //
// very simple and don't need any of the normal //
// HomeSpan logic (except for SpanPoint). //
// //
// Note this sketch only SIMULATES a temperature //
// sensor by slowly setting the temperature from //
// -30.0 to 35.0 C in steps of 0.5 C. The sketch //
// does not contain logic for an actual physical //
// temperature sensor. //
// //
////////////////////////////////////////////////////////////
#include "HomeSpan.h"
float temperature=-10.0;
SpanPoint *mainDevice;
void setup() {
Serial.begin(115200);
delay(1000);
Serial.printf("Starting\n\n");
// In the line below, replace the MAC Address with that of your MAIN HOMESPAN DEVICE
mainDevice=new SpanPoint("7C:DF:A1:61:E4:A8",sizeof(float),0); // create a SpanPoint with send size=sizeof(float) and receive size=0
homeSpan.setLogLevel(1);
}
void loop() {
boolean success = mainDevice->send(&temperature); // this will show as success as long as the MAIN DEVICE is running
Serial.printf("Send %s\n",success?"Succeded":"Failed");
temperature+=0.5;
if(temperature>35.0)
temperature=-30.0;
delay(20000);
}

View File

@ -0,0 +1,109 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2022 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: A HomeKit implementation for the ESP32 //
// ------------------------------------------------ //
// //
// This sketch is for a Remote Temperature Sensor to //
// be used in conjunction with the "MainDevice.ino" //
// sketch running on a separate ESP32 //
// //
// The purpose of these sketches is to demonstrate //
// how to use SpanPoint() to communication between //
// a remote ESP32 device that takes measurements from //
// a sensor, and a separate "main" ESP32 device that //
// is running the full HomeSpan code, and thus //
// connects to HomeKit. //
// //
// This sketch implements an Adafruit ADT7410 I2C //
// Temperature Sensor. If you don't have such a //
// device, please use the sketch "RemoteDevice.ino" //
// instead. That sketch SIMULATES a temperature //
// sensor and therefore allows you to learn how //
// SpanPoint() works even though the temperature data //
// itself isn't real. //
// //
////////////////////////////////////////////////////////////
#include "HomeSpan.h"
#include <Wire.h> // include the I2C library
#define DIAGNOSTIC_MODE
#define SAMPLE_TIME 30000 // Time between temperature samples (in milliseconds)
#define I2C_ADD 0x48 // ICS Address to use for the Adafruit ADT7410
SpanPoint *mainDevice;
void setup() {
setCpuFrequencyMhz(80); // reduce CPU frequency to save battery power
#if defined(DIAGNOSTIC_MODE)
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());
#endif
// In the line below, replace the MAC Address with that of your MAIN HOMESPAN DEVICE
mainDevice=new SpanPoint("7C:DF:A1:61:E4:A8",sizeof(float),0); // create a SpanPoint with send size=sizeof(float) and receive size=0
Wire.begin(); // start I2C in Controller Mode
#if defined(DIAGNOSTIC_MODE)
Wire.beginTransmission(I2C_ADD); // setup transmission
Wire.write(0x0B); // ADT7410 Identification Register
Wire.endTransmission(0); // transmit and leave in restart mode to allow reading
Wire.requestFrom(I2C_ADD,1); // request read of single byte
uint8_t id = Wire.read(); // receive a byte
LOG1("Configuring Temperature Sensor ADT7410 version 0x%02X with address 0x%02X.\n",id,I2C_ADD); // initialization message
#endif
Wire.beginTransmission(I2C_ADD); // setup transmission
Wire.write(0x03); // ADT740 Configuration Register
Wire.write(0xC0); // set 16-bit temperature resolution, 1 sample per second
Wire.endTransmission(); // transmit
Wire.beginTransmission(I2C_ADD); // setup transmission
Wire.write(0x00); // ADT7410 2-byte Temperature
Wire.endTransmission(0); // transmit and leave in restart mode to allow reading
Wire.requestFrom(I2C_ADD,2); // request read of two bytes
int16_t iTemp = ((int16_t)Wire.read()<<8)+Wire.read();
float temperature = iTemp/128.0;
boolean success = mainDevice->send(&temperature); // send temperature to main device
LOG1("Send temp update of %0.2f F: %s\n",temperature*9/5+32,success?"Succeded":"Failed");
esp_deep_sleep(SAMPLE_TIME*1000); // enter deep sleep mode -- reboot after resuming
}

85
docs/HS_STATUS.md Normal file
View File

@ -0,0 +1,85 @@
# HomeSpan Status
The optional ***homeSpan*** method, `void setStatusCallback(void (*func)(HS_STATUS status))`, can be used to create a callback function, *func*, that HomeSpan calls whenever its status changes. HomeSpan passes *func* a single argument, *status*, of type *HS_STATUS*, defined as follows:
```C++
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_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
HS_CONFIG_MODE_LAUNCH_AP, // HomeSpan is in Command Mode with "Launch Access Point" specified as choice
HS_CONFIG_MODE_UNPAIR, // HomeSpan is in Command Mode with "Unpair Device" specified as choice
HS_CONFIG_MODE_ERASE_WIFI, // HomeSpan is in Command Mode with "Erase WiFi Credentials" specified as choice
HS_CONFIG_MODE_EXIT_SELECTED, // User has selected "Exit Command Mode"
HS_CONFIG_MODE_REBOOT_SELECTED, // User has select "Reboot" from the Command Mode
HS_CONFIG_MODE_LAUNCH_AP_SELECTED, // User has selected "Launch AP Access" from the Command Mode
HS_CONFIG_MODE_UNPAIR_SELECTED, // User has seleected "Unpair Device" from the Command Mode
HS_CONFIG_MODE_ERASE_WIFI_SELECTED, // User has selected "Erase WiFi Credentials" from the Command Mode
HS_REBOOTING, // HomeSpan is in the process of rebooting the device
HS_FACTORY_RESET, // HomeSpan is in the process of performing a Factory Reset of device
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
};
```
The ***homeSpan*** method `char* statusString(HS_STATUS s)`, is a convenience function for converting any of the above enumerations to short, pre-defined character string messages as follows:
```C++
const char* Span::statusString(HS_STATUS s){
switch(s){
case HS_WIFI_NEEDED: return("WiFi Credentials Needed");
case HS_WIFI_CONNECTING: return("WiFi Connecting");
case HS_PAIRING_NEEDED: return("Device not yet Paired");
case HS_PAIRED: return("Device Paired");
case HS_ENTERING_CONFIG_MODE: return("Entering Command Mode");
case HS_CONFIG_MODE_EXIT: return("1. Exit Command Mode");
case HS_CONFIG_MODE_REBOOT: return("2. Reboot Device");
case HS_CONFIG_MODE_LAUNCH_AP: return("3. Launch Access Point");
case HS_CONFIG_MODE_UNPAIR: return("4. Unpair Device");
case HS_CONFIG_MODE_ERASE_WIFI: return("5. Erase WiFi Credentials");
case HS_CONFIG_MODE_EXIT_SELECTED: return("Exiting Command Mode...");
case HS_CONFIG_MODE_REBOOT_SELECTED: return("Rebooting Device...");
case HS_CONFIG_MODE_LAUNCH_AP_SELECTED: return("Launching Access Point...");
case HS_CONFIG_MODE_UNPAIR_SELECTED: return("Unpairing Device...");
case HS_CONFIG_MODE_ERASE_WIFI_SELECTED: return("Erasing WiFi Credentials...");
case HS_REBOOTING: return("REBOOTING!");
case HS_FACTORY_RESET: return("Performing Factory Reset...");
case HS_AP_STARTED: return("Access Point Started");
case HS_AP_CONNECTED: return("Access Point Connected");
case HS_AP_TERMINATED: return("Access Point Terminated");
case HS_OTA_STARTED: return("OTA Update Started");
default: return("Unknown");
}
}
```
### Example:
```C++
#include "HomeSpan.h"
void setup(){
homeSpan.setStatusCallback(statusUpdate); // set callback function
...
homeSpan.begin();
...
}
// create a callback function that simply prints the pre-defined short messages on the Serial Monitor whenever the HomeSpan status changes
void statusUpdate(HS_STATUS status){
Serial.printf("\n*** HOMESPAN STATUS CHANGE: %s\n",homeSpan.statusString(status));
}
```
You can of course create any alternative messsages, or take any actions desired, in *func* and do not need to use the pre-defined strings above.
---
[↩️](Reference.md) Back to the Reference API page

83
docs/NOW.md Normal file
View File

@ -0,0 +1,83 @@
# SpanPoint: Point-to-Point Communication between ESP32 Devices
SpanPoint is HomeSpan's easy-to-use implementation of the Espressif ESP-NOW protocol. SpanPoint provides bi-directional, point-to-point communication of small, fixed-size messages directly between ESP32 devices based on their MAC Addresses without the need for a central WiFi network.
To establish connectivity between any two devices simply instantiate a SpanPoint object on each device that references the MAC Address of the other device, as well as specifies the (potentially different) sizes of the messages that each device is expected to send to, and receive from, the other.
SpanPoint creates all the internal data queues needed to manage message flow, configures ESP-NOW to encrypt all message traffic, and auto-sets the WiFi channel used by ESP-NOW for transmission to match whatever is needed by any devices that are also connected to HomeKit through your central WiFi network.
SpanPoint is part of the main HomeSpan library and is accessible by adding `#include "HomeSpan.h"` near the top of your sketch. Detailed descriptions of the SpanPoint class and all of its methods are provided below.
## *SpanPoint(const char \*macAddress, int sendSize, int receiveSize, int queueDepth=1)*
Creating an instance of this **class** enables the device to send messages to, and/or receive messages from, a "complementary" instance of *SpanPoint* on another ESP32 device. Arguments, along with their defaults if left unspecified, are as follows:
* *macAddress* - the MAC Address of the *other* device to which you want to send data to, and/or receive data from, in the standard 6-byte format "XX:XX:XX:XX:XX:XX", where each XX represents a single 2-digit hexidecimal byte from 00 to FF
* *sendSize* - the size, in bytes, of any messages that will be sent from this device to the *other* device. Allowed range is 0 to 200, where a value of 0 is used to indicate to SpanPoint that you will **not** be using `send()` to transmit any messages from this device to the *other* device
* *receiveSize* - the size, in bytes, of any messages that will be received by this device from the *other* device. Allowed range is 0 to 200, where a value of 0 is used to indicate to SpanPoint that you will **not** be using `get()` to retreive any messages transmitted by the *other* device to this device
* *queueDepth* - the depth of the queue reserved to hold messages of *receiveSize* bytes that were received by this device from the *other* device, but not yet retreived using `get()`. Default=1 if left unspecified, which should be sufficient for most applications. See `get()` below for further details.
> SpanPoint objects created on two separate devices are considered "complementary" if the MAC Addresses specified in each SpanPoint object references each other's devices, and the *sendSize* and *receiveSize* of the SpanPoint object on one device matches, respectively, the *receiveSize* and *sendSize* of the SpanPoint object on the *other* device, with the exception that it is always okay to set either the *sendSize* or *receiveSize* to zero regardless of the value set on the *other* device.
SpanPoint will throw a fatal error during instantiation and halt the sketch if:
* the *macAddress* specified is ill-formed, or
* either *sendSize* or *receiveSize* is set to greater than 200, or
* both *sendSize* and *receiveSize* are set to 0, since there is no purpose for a SpanPoint that will neither transmit nor receive data
**The following SpanPoint methods are used to transmit and receive messages from a SpanPoint object on one device to a complementary SpanPoint object on the *other* device:**
* `boolean send(const void *data)`
* transmits a message using data pointed to by *data* (which may be a standard data type, such as *uint16_t*, or a user-defined *struct*) to the *other* device
* the size of the *data* element to be transmitted much match the *sendSize* parameter specified when the SpanPoint object was created
* returns **true** if transmission was successful, otherwise **false** if transmission failed. Note that a transmission is considered successful as long as the device can find and connect to the *other* device based on its MAC Address, regardless of whether or not the other device has a corresponding SpanPoint object
* `boolean get(void *dataBuf)`
* checks to see if a message has been received into SpanPoint's internal message queue from the *other* device
* if no message is available, the method returns **false** and *dataBuf* is unmodified
* if a message is available, it is **moved** from the internal message queue into *dataBuf* and the method returns **true**
* the size of the message will always be equal to the *receiveSize* parameter specified when the SpanPoint object was created, so make sure *dataBuf* is sufficiently sized to store such a message
Note that whether or or not you call the `get()` method, SpanPoint is configured to store (in an internal queue) any SpanPoint messages it receives from *other* devices, provided that (a) there is room in the internal queue, and (b) the size of the message received matches the *receiveSize* parameter specified when the relevant SpanPoint object was instantiated. If the internal queue is full when a message is received, the message is *not* moved to the queue and is instead discarded before it can ever be retreived by the `get()` method. To avoid this, make sure you call `get()` more frequently than you expect to receive messages, or set the *queueDepth* parameter to something greater than 1 when instantating the SpanPoint object.
Also note that regardless of whether or not the queue if full, if the size of a received message does not match the *receiveSize* parameter specified for this instance of the SpanPoint object, the message will be discarded. If *receiveSize* is greater than zero, a non-fatal run-time warning about size mismatches will also be output on the Serial Monitor.
**Other methods supported by SpanPoint are as follows:**
* `uint32_t time()`
* returns the time elapsed (in millis) since a SpanPoint object last received a valid message
* valid messages are those that can be properly decrypted and whose size matches the *receiveSize* parameter, regardless of whether or not there is room in the queue to store the message
* reading a message in the queue with `get()` has no impact on the elapsed time calculation
* this method is typically used to check whether messages from a transmitting device are overdue (suggesting a potential problem with that device)
* `static void setPassword(const char *pwd)`
* this *optional* **class-level** method changes the default passphrase used to generate ESP-NOW encryption keys for all SpanPoint objects from the default passphrase ("HomeSpan") to *pwd*, which can be a character string of any length
* if used, this method must be called *before* the instantiation of any SpanPoint objects. Example: `SpanPoint::setPassword("MyPassword");`
* the same passphrase must be used among all devices that are communicating via SpanPoint, else the receiving device will not be able to decrypt messages it receives
* `static void setChannelMask(uint16_t mask)`
* this *optional* **class-level** method changes the default channel bitmask from 0x3FFE (i.e. 0011 1111 1111 1110) to *mask*
* the channel bitmask is used to limit which of the standard channels (1-13) supported by the ESP32 WiFi radio should be tried whenever SpanPoint needs to reset the ESP-NOW channel after a transmission failure
* setting bit number *N* to 1 in the bitmask, where N=[1,13], enables the use of WiFi channel number *N*
* setting bit number *N* to 0 in the bitmask, where N=[1,13], disables the use of WiFi channel number *N*
* example: `SpanPoint::setChannelMask(1<<1 | 1<<6 | 1<<11);` causes SpanPoint to try only WiFi channels 1, 6, and 11 when transmitting messages
* this method will throw a fatal error and halt the sketch if called with a *mask* that does not enable at least one channel
* this method has no effect on SpanPoint if used within a full HomeSpan sketch that connects to HomeKit via a central WiFi network, since under these conditions the WiFi channel must remain set to whatever the central WiFi network requires
## Typical Use Cases
One of the primary reasons for using SpanPoint is to enable the deployement of battery-powered devices. Since HomeKit requires an always-on WiFi connection, wall-power is a must. But ESP-NOW does not require always-on connectivity to a central WiFi network, which makes it possible to power things like remote-sensor devices with just a battery. Such battery-powered "Remote Devices" can take periodic local measurements and transmit them via SpanPoint messages to a wall-powered "Main Device" that is running a full HomeSpan sketch connected to HomeKit via a central WiFi network.
Examples showing such a configuration can be found in the Arduino IDE under [*File → Examples → HomeSpan → Other Examples → RemoteSensors*](../Other%20Examples/RemoteSensors). This folder contains three sketches:
* *MainDevice.ino* - a full HomeSpan sketch that implements two Temperature Sensor Accessories, but instead of taking its own temperature measurements, it uses SpanPoint to read messages containing temperature updates from other Remote Devices
* *RemoteDevice.ino* - a lightweight sketch that simulates taking periodic temperature measurements, which are then transmitted to the Main Device via SpanPoint
* *RemoteTempSensor.ino* - a lightweight sketch that is similar to *RemoteDevice.ino*, except that instead of simulating a temperature sensor, it implements an actual Adafruit ADT7410 I2C-based temperature sensor. This sketch also uses some power-management techniques to extend battery life, such as lowering the CPU frequency and entering into deep-sleep after each measurement is taken
---
[↩️](README.md) Back to the Welcome page

View File

@ -4,7 +4,7 @@ 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)](https://developer.apple.com/homekit/specification/) 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.0.0 or later of the [Arduino-ESP32 Board Manager](https://github.com/espressif/arduino-esp32), and has been tested up through version 2.0.4 (recommended). HomeSpan can be run on the original ESP32 as well as Espressif's ESP32-S2, ESP32-C3, and ESP32-S3 chips.
HomeSpan requires version 2.0.0 or later of the [Arduino-ESP32 Board Manager](https://github.com/espressif/arduino-esp32), and has been tested up through version 2.0.5 (recommended). HomeSpan can be run on the original ESP32 as well as Espressif's ESP32-S2, ESP32-C3, and ESP32-S3 chips.
### HomeSpan Highlights
@ -31,6 +31,7 @@ HomeSpan requires version 2.0.0 or later of the [Arduino-ESP32 Board Manager](ht
* 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 class that faciliates seemless point-to-point communication between ESP32 devices using ESP-NOW
* Integrated Web Log for user-defined log messages
* Extensively-commented Tutorial Sketches taking you from the very basics of HomeSpan through advanced HomeKit topics
* Additional examples and projects showcasing real-world implementations of HomeSpan
@ -47,39 +48,29 @@ HomeSpan requires version 2.0.0 or later of the [Arduino-ESP32 Board Manager](ht
* Launch the WiFi Access Point
* A standalone, detailed End-User Guide
## ❗Latest Update - HomeSpan 1.6.0 (8/20/2022)
## ❗Latest Update - HomeSpan 1.7.0 (11/6/2022)
* **Support for ESP32-S3 devices**
* Requires Arduino-ESP32 Board Manager version 2.0.4 or later
* **ESP-NOW is now fully integrated into HomeSpan!**
* New dedicated class, **SpanPoint**, that facilitates bi-directional device-to-device communication between multiple ESP32 devices
* Provides automatic calibration of WiFi channels to ensure compatibility with HomeSpan's normal WiFi connectivity
* Includes detailed [Example Sketches](../Other%20Examples/RemoteSensors) demonstrating how SpanPoint can be used to implement a battery-powered Remote Sensor
* See the dedicated [SpanPoint Tutorial Page](NOW.md) for full details
* **New functionality to *dynamically* add/delete Accessories on the fly without rebooting the device**
* Adds `homeSpan.deleteAccessory()` and `homeSpan.updateDatabase()` methods
* Includes new [Example 20 - AdvancedTechniques](https://github.com/HomeSpan/HomeSpan/blob/master/examples/20-AdvancedTechniques) to demonstrate how these methods can be used to create a dynamic bridge
* See [HomeSpan API Reference](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Reference.md) for details
* **NeoPixels can now be used as a Status LED**
* Adds`homeSpan.setStatusPixel()` method
* Works well with ESP32 boards that have a built-in NeoPixel LED
* See the [API Reference](Reference.md) for details
* **New support for Touch Pads**
* `SpanButton()` now supports three pin trigger methods: `TRIGGER_ON_LOW`, `TRIGGER_ON_HIGH`, and `TRIGGER_ON_TOUCH`
* Also allows users to add their own trigger methods so `SpanButton()` can monitor pushbuttons attached to pin extenders, pin multiplexers, or any other device that requires calling third-party library functions
* See [HomeSpan API Reference](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Reference.md) for details
* **New functionality to track HomeSpan run-time status**
* Adds `homeSpan.setStatusCallback()` method providing users with a callback function whenever the internal state of HomeSpan changes, such as from *WiFi Needed* to *WiFi Connecting...*
* Tracks changes to the run-time state of HomeSpan that would normally trigger a change in the blinking pattern of the (optional) Status LED
* See the [API Reference](Reference.md) for details
* **Improved WiFi disconnect/reconnect handling**
* Fixes code that may, under certain circumstances, have prevented HomeSpan from reconnecting to WiFi after signal is lost
* Adds WiFi diagnostics to Web Logs to monitor for disconnects and WiFi signal strength
* **Important Bug Fixes**
* **New option to run HomeSpan as a separate task in its own thread**
* Adds `homeSpan.autoPoll()` method
* Works with both single- and dual-core processors
* For dual-core processors, polling task will be created on the "free" core not being used for other Arduino functions
* Allows user to add time-sensitive code the the main Arduino `loop()` function without delaying, or being dalyed by, HomeSpan polling
* See [HomeSpan API Reference](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Reference.md) for details
* Fixed bug in controller-update logic associated with changes to the way Apple now handles HomeKit Hubs that was producing *ERROR: Device not yet paired!* messages
* **New [Programmable Hub](https://github.com/HomeSpan/HomeSpan/blob/master/Other%20Examples/ProgrammableHub) Example**
* Demonstrates how to implement a Web Interface that allows users to dynamically add, delete, and configure up to 12 separate Light Accessories on a bridge device
* Expands upon many of the methods used in [Example 20](https://github.com/HomeSpan/HomeSpan/blob/master/examples/20-AdvancedTechniques)
* **Additional updates include:**
* New 'm' CLI command that prints free bytes remaining in heap memory - useful for monitoring memory usage when dynamically adding Accessories
* New `homeSpan.getLogLevel()` method that returns the current log level
* Fixed bug in touch sensor logic that would cause compile failure when using Arduino-ESP32 versions 2.0.0-2.0.2
See [Releases](https://github.com/HomeSpan/HomeSpan/releases) for details on all changes and bug fixes included in this update.
@ -87,23 +78,24 @@ See [Releases](https://github.com/HomeSpan/HomeSpan/releases) for details on all
HomeSpan includes the following documentation:
* [Getting Started with HomeSpan](https://github.com/HomeSpan/HomeSpan/blob/master/docs/GettingStarted.md) - setting up the software and the hardware needed to develop HomeSpan devices
* [HomeSpan API Overview](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Overview.md) - an overview of the HomeSpan API, including a step-by-step guide to developing your first HomeSpan Sketch
* [HomeSpan Tutorials](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Tutorials.md) - a guide to HomeSpan's tutorial-sketches
* [HomeSpan Services and Characteristics](https://github.com/HomeSpan/HomeSpan/blob/master/docs/ServiceList.md) - a list of all HAP Services and Characterstics supported by HomeSpan
* [HomeSpan Accessory Categories](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Categories.md) - a list of all HAP Accessory Categories defined by HomeSpan
* [HomeSpan Command-Line Interface (CLI)](https://github.com/HomeSpan/HomeSpan/blob/master/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 User Guide](https://github.com/HomeSpan/HomeSpan/blob/master/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](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Reference.md) - a complete guide to the HomeSpan Library API
* [HomeSpan QR Codes](https://github.com/HomeSpan/HomeSpan/blob/master/docs/QRCodes.md) - create and use QR Codes for pairing HomeSpan devices
* [HomeSpan OTA](https://github.com/HomeSpan/HomeSpan/blob/master/docs/OTA.md) - update your sketches Over-the-Air directly from the Arduino IDE without a serial connection
* [HomeSpan PWM](https://github.com/HomeSpan/HomeSpan/blob/master/docs/PWM.md) - integrated control of standard LEDs and Servo Motors using the ESP32's on-chip PWM peripheral
* [HomeSpan RFControl](https://github.com/HomeSpan/HomeSpan/blob/master/docs/RMT.md) - easy generation of RF and IR Remote Control signals using the ESP32's on-chip RMT peripheral
* [HomeSpan Pixels](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Pixels.md) - integrated control of addressable one- and two-wire RGB and RGBW LEDs and LED strips
* [HomeSpan Television Services](https://github.com/HomeSpan/HomeSpan/blob/master/docs/TVServices.md) - how to use HomeKit's undocumented Television Services and Characteristics
* [HomeSpan Message Logging](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Logging.md) - how to generate log messages for display on the Arduino Serial Monitor as well as optionally posted to an integrated Web Log page
* [Getting Started with HomeSpan](GettingStarted.md) - setting up the software and the hardware needed to develop HomeSpan devices
* [HomeSpan API Overview](Overview.md) - an overview of the HomeSpan API, including a step-by-step guide to developing your first HomeSpan Sketch
* [HomeSpan Tutorials](Tutorials.md) - a guide to HomeSpan's tutorial-sketches
* [HomeSpan Services and Characteristics](ServiceList.md) - a list of all HAP Services and Characterstics supported by HomeSpan
* [HomeSpan Accessory Categories](Categories.md) - a list of all HAP Accessory Categories defined by HomeSpan
* [HomeSpan Command-Line Interface (CLI)](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 User Guide](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](Reference.md) - a complete guide to the HomeSpan Library API
* [HomeSpan QR Codes](QRCodes.md) - create and use QR Codes for pairing HomeSpan devices
* [HomeSpan OTA](OTA.md) - update your sketches Over-the-Air directly from the Arduino IDE without a serial connection
* [HomeSpan PWM](PWM.md) - integrated control of standard LEDs and Servo Motors using the ESP32's on-chip PWM peripheral
* [HomeSpan RFControl](RMT.md) - easy generation of RF and IR Remote Control signals using the ESP32's on-chip RMT peripheral
* [HomeSpan Pixels](Pixels.md) - integrated control of addressable one- and two-wire RGB and RGBW LEDs and LED strips
* [HomeSpan SpanPoint](NOW.md) - facilitates point-to-point, bi-directional communication between ESP32 Devices using ESP-NOW
* [HomeSpan Television Services](TVServices.md) - how to use HomeKit's undocumented Television Services and Characteristics
* [HomeSpan Message Logging](Logging.md) - how to generate log messages for display on the Arduino Serial Monitor as well as optionally posted to an integrated Web Log page
* [HomeSpan Projects](https://github.com/topics/homespan) - real-world applications of the HomeSpan Library
* [HomeSpan FAQ](https://github.com/HomeSpan/HomeSpan/blob/master/docs/FAQ.md) - answers to frequently-asked questions
* [HomeSpan FAQ](FAQ.md) - answers to frequently-asked questions
Note that all documentation is version-controlled and tied to each branch. The *master* branch generally points to the latest release. The *dev* branch, when available, will contain code under active development.

View File

@ -31,8 +31,25 @@ The following **optional** `homeSpan` methods override various HomeSpan initiali
* `void setControlPin(uint8_t pin)`
* sets the ESP32 pin to use for the HomeSpan Control Button. If not specified, HomeSpan will assume there is no Control Button
* `int getControlPin()`
* returns the pin number of the HomeSpan Control Button as set by `setControlPin(pin)`, or -1 if no pin has been set
* `void setStatusPin(uint8_t pin)`
* sets the ESP32 pin to use for the HomeSpan Status LED. If not specified, HomeSpan will assume there is no Status LED
* sets the ESP32 pin to use for the HomeSpan Status LED
* assumes a standard LED will be connected to *pin*
* if neither this method nor `setStatusPixel()` is called, HomeSpan will assume there is no Status LED
* `void setStatusPixel(uint8_t pin, float h=0, float s=100, float v=100)`
* sets the ESP32 pin to use for the HomeSpan Status LED
* assumes an RGB NeoPixel (or equivalent) will be connected to *pin*
* works well with ESP32 boards that have a built-in NeoPixel LED, though adding an external NeoPixel is fine
* users can optionally specify the color HomeSpan will use with the NeoPixel by providing the following HSV values:
* h = Hue from 0-360
* s = Saturation percentage from 0-100
* v = Brightness percentage from 0-100
* color defaults to *red* if unspecified
* example: `homeSpan.setStatusPixel(8,120,100,20)` sets the Status LED to light green using a NeoPixel attached to pin 8
* if neither this method nor `setStatusPin()` is called, HomeSpan will assume there is no Status LED
* `void setStatusAutoOff(uint16_t duration)`
* sets Status LED to automatically turn off after *duration* seconds
@ -127,7 +144,17 @@ The following **optional** `homeSpan` methods enable additional features and pro
* sets an optional user-defined callback function, *func*, to be called by HomeSpan upon completion of pairing to a controller (*status=true*) or unpairing from a controller (*status=false*)
* this one-time call to *func* is provided for users that would like to trigger additional actions when the device is first paired, or the device is later unpaired
* note this *func* is **not** called upon start-up and should not be used to simply check whether a device is paired or unpaired. It is only called when pairing status changes
* the function *func* must be of type *void* and have one *boolean* argument
* the function *func* must be of type *void* and accept one *boolean* argument
* `void setStatusCallback(void (*func)(HS_STATUS status))`
* sets an optional user-defined callback function, *func*, to be called by HomeSpan whenever its running state (e.g. WiFi Connecting, Pairing Needed...) changes in way that would alter the blinking pattern of the (optional) Status LED
* if *func* is set, it will be called regardless of whether or not a Status LED has actually been defined
* this allows users to reflect changes to the current state of HomeSpan using alternative methods, such as outputting messages to an embedded LCD or E-Ink display
* the function *func* must be of type *void* and accept one argument of enum type [HS_STATUS](HS_STATUS.md)
* `char* statusString(HS_STATUS s)`
* returns a pre-defined character string message representing *s*, which must be of enum type [HS_STATUS](HS_STATUS.md)
* typically used in conjunction with `setStatusCallback()` above
* `void setPairingCode(const char *s)`
* sets the Setup Pairing Code to *s*, which **must** be exactly eight numerical digits (no dashes)

View File

@ -132,6 +132,9 @@ Demonstrates how to create Custom Services and Custom Characteristics in HomeSpa
### [ProgrammableHub](../Other%20Examples/ProgrammableHub)
Demonstrates how to implement a fully programmable Light Accessory Hub that allows the user to *dynamically* add/delete up to 12 Light Accessories directly through a device-hosted *web interface* or via HomeSpan's *command-line inteface*. Each light can be configured as dimmable/non-dimmable with either no color control, full RGB color control, or color-temperature control. Builds upon many of the techniques used in [Example 20](../examples/20-AdvancedTechniques)
### [RemoteSensors](../Other%20Examples/RemoteSensors)
Demonstrates how SpanPoint can be used to transmit messages from battery-powered Remote Devices running light-weight sketches that measure the local temperature, to a wall-powered Main Device running a full HomeSpan sketch implementing Temperature Sensor Accessories. See [SpanPoint: Point-to-Point Communication between ESP32 Devices](NOW.md) for full details regarding the SpanPoint class and all of its methods.
---
[↩️](README.md) Back to the Welcome page

View File

@ -1,5 +1,5 @@
name=HomeSpan
version=1.6.0
version=1.7.0
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.

View File

@ -44,7 +44,8 @@
F13=1,F12=3,F27=7,F15=10,F32=42,F14=11,F16=20,F17=21,F21=16, // Digital Only (9 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
F5=36,F18=35,F19=37,F33=34, // SPI SCK/SDO/SDI/CS
BUILTIN_PIXEL=18 // Built-in Neo-Pixel
};
#define DEVICE_SUFFIX "-S2"
@ -53,7 +54,8 @@
F27=2,F32=3,F14=10,F16=20,F17=21,F21=19, // Digital Only (6 pins)
F26=0,F25=1,F4=18, // A0/A1/A5
F22=9,F23=8, // I2C SCL/SDA
F5=4,F18=6,F19=5,F33=7 // SPI SCK/SDO/SDI/CS
F5=4,F18=6,F19=5,F33=7, // SPI SCK/SDO/SDI/CS
BUILTIN_PIXEL=8 // Built-in Neo-Pixel
};
#define DEVICE_SUFFIX "-C3"
@ -62,7 +64,8 @@
F13=5,F12=6,F27=7,F15=16,F32=17,F14=18,F16=37,F17=36,F21=35, // Digital Only (9 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
F5=12,F18=11,F19=13,F33=10, // SPI SCK/SDO/SDI/CS
BUILTIN_PIXEL=48 // Built-in Neo-Pixel
};
#define DEVICE_SUFFIX "-S3"

View File

@ -637,7 +637,8 @@ int HAPClient::postPairSetupURL(){
mdns_service_txt_item_set("_hap","_tcp","sf","0"); // broadcast new status
LOG1("\n*** ACCESSORY PAIRED! ***\n");
homeSpan.statusLED.on();
STATUS_UPDATE(on(),HS_PAIRED)
if(homeSpan.pairCallback) // if set, invoke user-defined Pairing Callback to indicate device has been paired
homeSpan.pairCallback(true);
@ -918,7 +919,7 @@ int HAPClient::postPairingsURL(){
Serial.print("\n*** ERROR: One or more of required 'Identifier,' 'PublicKey,' and 'Permissions' TLV records for this step is bad or missing\n\n");
tlv8.clear(); // clear TLV records
tlv8.val(kTLVType_State,pairState_M2); // set State=<M2>
tlv8.val(kTLVType_Error,tagError_Unknown); // set Error=Unknown (there is no specific error type for missing/bad TLV data)
tlv8.val(kTLVType_Error,tagError_Unknown); // set Error=Unknown (there is no specific error type for missing/bad TLV data)
break;
}
@ -926,33 +927,28 @@ int HAPClient::postPairingsURL(){
Serial.print("\n*** ERROR: Controller making request does not have admin privileges to add/update other Controllers\n\n");
tlv8.clear(); // clear TLV records
tlv8.val(kTLVType_State,pairState_M2); // set State=<M2>
tlv8.val(kTLVType_Error,tagError_Authentication); // set Error=Authentication
tlv8.val(kTLVType_Error,tagError_Authentication); // set Error=Authentication
break;
}
if((newCont=findController(tlv8.buf(kTLVType_Identifier)))){
if((newCont=findController(tlv8.buf(kTLVType_Identifier))) && memcmp(tlv8.buf(kTLVType_PublicKey),newCont->LTPK,32)){ // requested Controller already exists, but LTPKs don't match
Serial.print("\n*** ERROR: Invalid request to update the LTPK of an exsiting Controller\n\n");
tlv8.clear(); // clear TLV records
tlv8.val(kTLVType_State,pairState_M2); // set State=<M2>
if(!memcmp(cPair->LTPK,newCont->LTPK,32)){ // requested Controller already exists and LTPK matches
newCont->admin=tlv8.val(kTLVType_Permissions)==1?true:false; // update permission of matching Controller
} else {
tlv8.val(kTLVType_Error,tagError_Unknown); // set Error=Unknown
}
tlv8.val(kTLVType_Error,tagError_Unknown); // set Error=Unknown
break;
}
if(!(newCont=getFreeController())){
if(!addController(tlv8.buf(kTLVType_Identifier),tlv8.buf(kTLVType_PublicKey),tlv8.val(kTLVType_Permissions)==1?true:false)){
Serial.print("\n*** ERROR: Can't pair more than ");
Serial.print(MAX_CONTROLLERS);
Serial.print(" Controllers\n\n");
tlv8.clear(); // clear TLV records
tlv8.val(kTLVType_State,pairState_M2); // set State=<M2>
tlv8.val(kTLVType_Error,tagError_MaxPeers); // set Error=Unknown (there is no specific error type for missing/bad TLV data)
tlv8.val(kTLVType_Error,tagError_MaxPeers); // set Error=MaxPeers
break;
}
addController(tlv8.buf(kTLVType_Identifier),tlv8.buf(kTLVType_PublicKey),tlv8.val(kTLVType_Permissions)==1?true:false);
tlv8.clear(); // clear TLV records
tlv8.val(kTLVType_State,pairState_M2); // set State=<M2>
break;
@ -1537,16 +1533,10 @@ void HAPClient::charPrintRow(uint8_t *buf, int n){
Controller *HAPClient::findController(uint8_t *id){
for(int i=0;i<MAX_CONTROLLERS;i++){ // loop over all controller slots
if(controllers[i].allocated && !memcmp(controllers[i].ID,id,36)){ // found matching ID
LOG2("Found Controller: ");
if(homeSpan.logLevel>1)
charPrintRow(id,36);
LOG2(controllers[i].admin?" (admin)\n":" (regular)\n");
return(controllers+i); // return with pointer to matching controller
}
} // loop
for(int i=0;i<MAX_CONTROLLERS;i++){ // loop over all controller slots
if(controllers[i].allocated && !memcmp(controllers[i].ID,id,36)) // found matching ID
return(controllers+i); // return with pointer to matching controller
}
return(NULL); // no match
}
@ -1556,7 +1546,6 @@ Controller *HAPClient::findController(uint8_t *id){
Controller *HAPClient::getFreeController(){
for(int i=0;i<MAX_CONTROLLERS;i++){ // loop over all controller slots
if(!controllers[i].allocated) // found free slot
return(controllers+i); // return with pointer to free slot
}
@ -1570,7 +1559,7 @@ Controller *HAPClient::addController(uint8_t *id, uint8_t *ltpk, boolean admin){
Controller *slot;
if((slot=findController(id))){
if((slot=findController(id))){ // found existing controller
memcpy(slot->LTPK,ltpk,32);
slot->admin=admin;
LOG2("\n*** Updated Controller: ");
@ -1580,7 +1569,7 @@ Controller *HAPClient::addController(uint8_t *id, uint8_t *ltpk, boolean admin){
return(slot);
}
if((slot=getFreeController())){
if((slot=getFreeController())){ // get slot for new controller, if available
slot->allocated=true;
memcpy(slot->ID,id,36);
memcpy(slot->LTPK,ltpk,32);
@ -1592,9 +1581,6 @@ Controller *HAPClient::addController(uint8_t *id, uint8_t *ltpk, boolean admin){
return(slot);
}
Serial.print("\n*** WARNING: No open slots. Can't add Controller: ");
hexPrintRow(id,36);
Serial.print(admin?" (admin)\n\n":" (regular)\n\n\n");
return(NULL);
}
@ -1636,7 +1622,9 @@ void HAPClient::removeController(uint8_t *id){
removeControllers();
LOG1("That was last Admin Controller! Removing any remaining Regular Controllers and unpairing Accessory\n");
mdns_service_txt_item_set("_hap","_tcp","sf","1"); // set Status Flag = 1 (Table 6-8)
homeSpan.statusLED.start(LED_PAIRING_NEEDED);
STATUS_UPDATE(start(LED_PAIRING_NEEDED),HS_PAIRING_NEEDED)
if(homeSpan.pairCallback) // if set, invoke user-defined Pairing Callback to indicate device has been paired
homeSpan.pairCallback(false);
}

View File

@ -31,9 +31,11 @@
#include <WiFi.h>
#include <driver/ledc.h>
#include <mbedtls/version.h>
#include <mbedtls/sha256.h>
#include <esp_task_wdt.h>
#include <esp_sntp.h>
#include <esp_ota_ops.h>
#include <esp_wifi.h>
#include "HomeSpan.h"
#include "HAP.h"
@ -57,9 +59,11 @@ void Span::begin(Category catID, const char *displayName, const char *hostNameBa
this->modelName=modelName;
sprintf(this->category,"%d",(int)catID);
esp_task_wdt_delete(xTaskGetIdleTaskHandleForCPU(0)); // required to avoid watchdog timeout messages from ESP32-C3
SpanPoint::setAsHub();
statusLED.init(statusPin,0,autoOffLED);
statusLED=new Blinker(statusDevice,autoOffLED); // create Status LED, even is statusDevice is NULL
esp_task_wdt_delete(xTaskGetIdleTaskHandleForCPU(0)); // required to avoid watchdog timeout messages from ESP32-C3
if(requestedMaxCon<maxConnections) // if specific request for max connections is less than computed max connections
maxConnections=requestedMaxCon; // over-ride max connections with requested value
@ -96,8 +100,8 @@ void Span::begin(Category catID, const char *displayName, const char *hostNameBa
Serial.print("Message Logs: Level ");
Serial.print(logLevel);
Serial.print("\nStatus LED: Pin ");
if(statusPin>=0){
Serial.print(statusPin);
if(getStatusPin()>=0){
Serial.print(getStatusPin());
if(autoOffLED>0)
Serial.printf(" (Auto Off=%d sec)",autoOffLED);
}
@ -137,6 +141,7 @@ void Span::begin(Category catID, const char *displayName, const char *hostNameBa
Serial.print(__TIME__);
Serial.printf("\nPartition: %s",esp_ota_get_running_partition()->label);
Serial.printf("\nMAC Address: %s",WiFi.macAddress().c_str());
Serial.print("\n\nDevice Name: ");
Serial.print(displayName);
@ -189,10 +194,10 @@ void Span::pollTask() {
processSerialCommand("A");
} else {
Serial.print("YOU MAY CONFIGURE BY TYPING 'W <RETURN>'.\n\n");
statusLED.start(LED_WIFI_NEEDED);
STATUS_UPDATE(start(LED_WIFI_NEEDED),HS_WIFI_NEEDED)
}
} else {
homeSpan.statusLED.start(LED_WIFI_CONNECTING);
STATUS_UPDATE(start(LED_WIFI_CONNECTING),HS_WIFI_CONNECTING)
}
if(controlButton)
@ -290,13 +295,12 @@ void Span::pollTask() {
if(spanOTA.enabled)
ArduinoOTA.handle();
if(controlButton && controlButton->primed()){
statusLED.start(LED_ALERT);
}
if(controlButton && controlButton->primed())
STATUS_UPDATE(start(LED_ALERT),HS_ENTERING_CONFIG_MODE)
if(controlButton && controlButton->triggered(3000,10000)){
statusLED.off();
if(controlButton->type()==PushButton::LONG){
STATUS_UPDATE(off(),HS_FACTORY_RESET)
controlButton->wait();
processSerialCommand("F"); // FACTORY RESET
} else {
@ -304,7 +308,7 @@ void Span::pollTask() {
}
}
statusLED.check();
statusLED->check();
} // poll
@ -324,11 +328,15 @@ int Span::getFreeSlot(){
void Span::commandMode(){
Serial.print("*** ENTERING COMMAND MODE ***\n\n");
if(!statusDevice && !statusCallback){
Serial.print("*** ERROR: CAN'T ENTER COMMAND MODE WITHOUT A DEFINED STATUS LED OR EVENT HANDLER CALLBACK***\n\n");
return;
}
Serial.print("*** COMMAND MODE ***\n\n");
int mode=1;
boolean done=false;
statusLED.start(500,0.3,mode,1000);
STATUS_UPDATE(start(500,0.3,mode,1000),static_cast<HS_STATUS>(HS_ENTERING_CONFIG_MODE+mode))
unsigned long alarmTime=millis()+comModeLife;
while(!done){
@ -338,35 +346,26 @@ void Span::commandMode(){
Serial.print(" seconds).\n\n");
mode=1;
done=true;
statusLED.start(LED_ALERT);
delay(2000);
} else
if(controlButton->triggered(10,3000)){
if(controlButton->type()==PushButton::SINGLE){
mode++;
if(mode==6)
mode=1;
statusLED.start(500,0.3,mode,1000);
STATUS_UPDATE(start(500,0.3,mode,1000),static_cast<HS_STATUS>(HS_ENTERING_CONFIG_MODE+mode))
} else {
done=true;
}
} // button press
} // while
statusLED.start(LED_ALERT);
STATUS_UPDATE(start(LED_ALERT),static_cast<HS_STATUS>(HS_ENTERING_CONFIG_MODE+mode+5))
controlButton->wait();
switch(mode){
case 1:
Serial.print("*** NO ACTION\n\n");
if(strlen(network.wifiData.ssid)==0)
statusLED.start(LED_WIFI_NEEDED);
else
if(!HAPClient::nAdminControllers())
statusLED.start(LED_PAIRING_NEEDED);
else
statusLED.on();
resetStatus();
break;
case 2:
@ -398,12 +397,12 @@ void Span::checkConnect(){
if(WiFi.status()==WL_CONNECTED)
return;
addWebLog(true,"*** WiFi Connection Lost!"); // losing and re-establishing connection has not been tested
addWebLog(true,"*** WiFi Connection Lost!");
connected++;
waitTime=60000;
alarmConnect=0;
homeSpan.statusLED.start(LED_WIFI_CONNECTING);
}
STATUS_UPDATE(start(LED_WIFI_CONNECTING),HS_WIFI_CONNECTING)
}
if(WiFi.status()!=WL_CONNECTED){
if(millis()<alarmConnect) // not yet time to try to try connecting
@ -429,14 +428,10 @@ void Span::checkConnect(){
return;
}
if(!HAPClient::nAdminControllers())
statusLED.start(LED_PAIRING_NEEDED);
else
statusLED.on();
resetStatus();
connected++;
addWebLog(true,"WiFi Connected! IP Address = %s\n",WiFi.localIP().toString().c_str());
addWebLog(true,"WiFi Connected! IP Address = %s",WiFi.localIP().toString().c_str());
if(connected>1) // Do not initialize everything below if this is only a reconnect
return;
@ -757,10 +752,10 @@ void Span::processSerialCommand(const char *c){
Serial.print("\nDEVICE NOT YET PAIRED -- PLEASE PAIR WITH HOMEKIT APP\n\n");
mdns_service_txt_item_set("_hap","_tcp","sf","1"); // set Status Flag = 1 (Table 6-8)
if(strlen(network.wifiData.ssid)==0)
statusLED.start(LED_WIFI_NEEDED);
else
statusLED.start(LED_PAIRING_NEEDED);
if(homeSpan.pairCallback)
homeSpan.pairCallback(false);
resetStatus();
}
break;
@ -776,10 +771,8 @@ void Span::processSerialCommand(const char *c){
network.serialConfigure();
nvs_set_blob(wifiNVS,"WIFIDATA",&network.wifiData,sizeof(network.wifiData)); // update data
nvs_commit(wifiNVS); // commit to NVS
Serial.print("\n*** WiFi Credentials SAVED! Re-starting ***\n\n");
statusLED.off();
delay(1000);
ESP.restart();
Serial.print("\n*** WiFi Credentials SAVED! Restarting ***\n\n");
reboot();
}
break;
@ -800,7 +793,7 @@ void Span::processSerialCommand(const char *c){
network.apConfigure();
nvs_set_blob(wifiNVS,"WIFIDATA",&network.wifiData,sizeof(network.wifiData)); // update data
nvs_commit(wifiNVS); // commit to NVS
Serial.print("\n*** Credentials saved!\n\n");
Serial.print("\n*** Credentials saved!\n");
if(strlen(network.setupCode)){
char s[10];
sprintf(s,"S%s",network.setupCode);
@ -809,21 +802,19 @@ void Span::processSerialCommand(const char *c){
Serial.print("*** Setup Code Unchanged\n");
}
Serial.print("\n*** Re-starting ***\n\n");
statusLED.off();
delay(1000);
ESP.restart(); // re-start device
Serial.print("\n*** Restarting...\n\n");
STATUS_UPDATE(start(LED_ALERT),HS_AP_TERMINATED)
reboot();
}
break;
case 'X': {
statusLED.off();
nvs_erase_all(wifiNVS);
nvs_commit(wifiNVS);
Serial.print("\n*** WiFi Credentials ERASED! Re-starting...\n\n");
delay(1000);
ESP.restart(); // re-start device
WiFi.begin("none");
Serial.print("\n*** WiFi Credentials ERASED! Restarting...\n\n");
reboot();
}
break;
@ -837,27 +828,21 @@ void Span::processSerialCommand(const char *c){
case 'H': {
statusLED.off();
nvs_erase_all(HAPClient::hapNVS);
nvs_commit(HAPClient::hapNVS);
Serial.print("\n*** HomeSpan Device ID and Pairing Data DELETED! Restarting...\n\n");
delay(1000);
ESP.restart();
reboot();
}
break;
case 'R': {
statusLED.off();
Serial.print("\n*** Restarting...\n\n");
delay(1000);
ESP.restart();
reboot();
}
break;
case 'F': {
statusLED.off();
nvs_erase_all(HAPClient::hapNVS);
nvs_commit(HAPClient::hapNVS);
nvs_erase_all(wifiNVS);
@ -866,19 +851,17 @@ void Span::processSerialCommand(const char *c){
nvs_commit(charNVS);
nvs_erase_all(otaNVS);
nvs_commit(otaNVS);
WiFi.begin("none");
Serial.print("\n*** FACTORY RESET! Restarting...\n\n");
delay(1000);
ESP.restart();
reboot();
}
break;
case 'E': {
statusLED.off();
nvs_flash_erase();
Serial.print("\n*** ALL DATA ERASED! Restarting...\n\n");
delay(1000);
ESP.restart();
reboot();
}
break;
@ -1109,6 +1092,56 @@ void Span::processSerialCommand(const char *c){
///////////////////////////////
void Span::resetStatus(){
if(strlen(network.wifiData.ssid)==0)
STATUS_UPDATE(start(LED_WIFI_NEEDED),HS_WIFI_NEEDED)
else if(WiFi.status()!=WL_CONNECTED)
STATUS_UPDATE(start(LED_WIFI_CONNECTING),HS_WIFI_CONNECTING)
else if(!HAPClient::nAdminControllers())
STATUS_UPDATE(start(LED_PAIRING_NEEDED),HS_PAIRING_NEEDED)
else
STATUS_UPDATE(on(),HS_PAIRED)
}
///////////////////////////////
void Span::reboot(){
STATUS_UPDATE(off(),HS_REBOOTING)
delay(1000);
ESP.restart();
}
///////////////////////////////
const char* Span::statusString(HS_STATUS s){
switch(s){
case HS_WIFI_NEEDED: return("WiFi Credentials Needed");
case HS_WIFI_CONNECTING: return("WiFi Connecting");
case HS_PAIRING_NEEDED: return("Device not yet Paired");
case HS_PAIRED: return("Device Paired");
case HS_ENTERING_CONFIG_MODE: return("Entering Command Mode");
case HS_CONFIG_MODE_EXIT: return("1. Exit Command Mode");
case HS_CONFIG_MODE_REBOOT: return("2. Reboot Device");
case HS_CONFIG_MODE_LAUNCH_AP: return("3. Launch Access Point");
case HS_CONFIG_MODE_UNPAIR: return("4. Unpair Device");
case HS_CONFIG_MODE_ERASE_WIFI: return("5. Erase WiFi Credentials");
case HS_CONFIG_MODE_EXIT_SELECTED: return("Exiting Command Mode...");
case HS_CONFIG_MODE_REBOOT_SELECTED: return("Rebooting Device...");
case HS_CONFIG_MODE_LAUNCH_AP_SELECTED: return("Launching Access Point...");
case HS_CONFIG_MODE_UNPAIR_SELECTED: return("Unpairing Device...");
case HS_CONFIG_MODE_ERASE_WIFI_SELECTED: return("Erasing WiFi Credentials...");
case HS_REBOOTING: return("REBOOTING!");
case HS_FACTORY_RESET: return("Performing Factory Reset...");
case HS_AP_STARTED: return("Access Point Started");
case HS_AP_CONNECTED: return("Access Point Connected");
case HS_AP_TERMINATED: return("Access Point Terminated");
case HS_OTA_STARTED: return("OTA Update Started");
default: return("Unknown");
}
}
///////////////////////////////
void Span::setWifiCredentials(const char *ssid, const char *pwd){
sprintf(network.wifiData.ssid,"%.*s",MAX_SSID,ssid);
sprintf(network.wifiData.pwd,"%.*s",MAX_PWD,pwd);
@ -2096,9 +2129,9 @@ void SpanOTA::init(boolean _auth, boolean _safeLoad){
void SpanOTA::start(){
Serial.printf("\n*** Current Partition: %s\n*** New Partition: %s\n*** OTA Starting..",
esp_ota_get_running_partition()->label,esp_ota_get_next_update_partition(NULL)->label);
esp_ota_get_running_partition()->label,esp_ota_get_next_update_partition(NULL)->label);
otaPercent=0;
homeSpan.statusLED.start(LED_OTA_STARTED);
STATUS_UPDATE(start(LED_OTA_STARTED),HS_OTA_STARTED)
}
///////////////////////////////
@ -2107,8 +2140,7 @@ void SpanOTA::end(){
nvs_set_u8(homeSpan.otaNVS,"OTA_REQUIRED",safeLoad);
nvs_commit(homeSpan.otaNVS);
Serial.printf(" DONE! Rebooting...\n");
homeSpan.statusLED.off();
delay(100); // make sure commit it finished before reboot
homeSpan.reboot();
}
///////////////////////////////
@ -2142,14 +2174,202 @@ void SpanOTA::error(ota_error_t err){
///////////////////////////////
void __attribute__((weak)) loop(){
}
///////////////////////////////
int SpanOTA::otaPercent;
boolean SpanOTA::safeLoad;
boolean SpanOTA::enabled=false;
boolean SpanOTA::auth;
///////////////////////////////
// SpanPoint //
///////////////////////////////
SpanPoint::SpanPoint(const char *macAddress, int sendSize, int receiveSize, int queueDepth){
if(sscanf(macAddress,"%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",peerInfo.peer_addr,peerInfo.peer_addr+1,peerInfo.peer_addr+2,peerInfo.peer_addr+3,peerInfo.peer_addr+4,peerInfo.peer_addr+5)!=6){
Serial.printf("\nFATAL ERROR! Can't create new SpanPoint(\"%s\") - Invalid MAC Address ***\n",macAddress);
Serial.printf("\n=== PROGRAM HALTED ===");
while(1);
}
if(sendSize<0 || sendSize>200 || receiveSize<0 || receiveSize>200 || queueDepth<1 || (sendSize==0 && receiveSize==0)){
Serial.printf("\nFATAL ERROR! Can't create new SpanPoint(\"%s\",%d,%d,%d) - one or more invalid parameters ***\n",macAddress,sendSize,receiveSize,queueDepth);
Serial.printf("\n=== PROGRAM HALTED ===");
while(1);
}
this->sendSize=sendSize;
this->receiveSize=receiveSize;
Serial.printf("SpanPoint: Created link to device with MAC Address %02X:%02X:%02X:%02X:%02X:%02X. Send size=%d bytes, Receive size=%d bytes with queue depth=%d.\n",
peerInfo.peer_addr[0],peerInfo.peer_addr[1],peerInfo.peer_addr[2],peerInfo.peer_addr[3],peerInfo.peer_addr[4],peerInfo.peer_addr[5],sendSize,receiveSize,queueDepth);
if(receiveSize>0)
WiFi.mode(WIFI_AP_STA);
else if(WiFi.getMode()==WIFI_OFF)
WiFi.mode(WIFI_STA);
init(); // initialize SpanPoint
peerInfo.channel=0; // 0 = matches current WiFi channel
peerInfo.ifidx=WIFI_IF_STA; // must specify interface
peerInfo.encrypt=true; // turn on encryption for this peer
memcpy(peerInfo.lmk, lmk, 16); // set local key
esp_now_add_peer(&peerInfo); // add peer to ESP-NOW
if(receiveSize>0)
receiveQueue = xQueueCreate(queueDepth,receiveSize);
SpanPoints.push_back(this);
}
///////////////////////////////
void SpanPoint::init(const char *password){
if(initialized)
return;
if(WiFi.getMode()==WIFI_OFF)
WiFi.mode(WIFI_STA);
uint8_t hash[32];
mbedtls_sha256_ret((const unsigned char *)password,strlen(password),hash,0); // produce 256-bit bit hash from password
esp_now_init(); // initialize ESP-NOW
memcpy(lmk, hash, 16); // store first 16 bytes of hash for later use as local key
esp_now_set_pmk(hash+16); // set hash for primary key using last 16 bytes of hash
esp_now_register_recv_cb(dataReceived); // set callback for receiving data
esp_now_register_send_cb(dataSent); // set callback for sending data
statusQueue = xQueueCreate(1,sizeof(esp_now_send_status_t)); // create statusQueue even if not needed
setChannelMask(channelMask); // default channel mask at start-up uses channels 1-13
initialized=true;
}
///////////////////////////////
void SpanPoint::setChannelMask(uint16_t mask){
channelMask = mask & 0x3FFE;
if(isHub)
return;
uint8_t channel=0;
for(int i=1;i<=13 && channel==0;i++) // find first "allowed" channel based on mask
channel=(channelMask & (1<<i))?i:0;
if(channel==0){
Serial.printf("\nFATAL ERROR! SpanPoint::setChannelMask(0x%04X) - mask must allow for at least one channel ***\n",mask);
Serial.printf("\n=== PROGRAM HALTED ===");
while(1);
}
esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE);
}
///////////////////////////////
uint8_t SpanPoint::nextChannel(){
uint8_t channel;
wifi_second_chan_t channel2;
esp_wifi_get_channel(&channel,&channel2); // get current channel
if(isHub || channelMask==(1<<channel)) // do not change channel if device is either a hub, or channel mask does not allow for any other channels
return(channel);
do {
channel=(channel<13)?channel+1:1; // advance to next channel
} while(!(channelMask & (1<<channel))); // until we find next valid one
esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE); // set the WiFi channel
return(channel);
}
///////////////////////////////
boolean SpanPoint::get(void *dataBuf){
if(receiveSize==0)
return(false);
return(xQueueReceive(receiveQueue, dataBuf, 0));
}
///////////////////////////////
boolean SpanPoint::send(const void *data){
if(sendSize==0)
return(false);
uint8_t channel;
wifi_second_chan_t channel2;
esp_wifi_get_channel(&channel,&channel2); // get current channel
uint8_t startingChannel=channel; // set starting channel to current channel
esp_now_send_status_t status = ESP_NOW_SEND_FAIL;
do {
for(int i=1;i<=3;i++){
LOG1("SpanPoint: Sending %d bytes to MAC Address %02X:%02X:%02X:%02X:%02X:%02X using channel %hhu...\n",
sendSize,peerInfo.peer_addr[0],peerInfo.peer_addr[1],peerInfo.peer_addr[2],peerInfo.peer_addr[3],peerInfo.peer_addr[4],peerInfo.peer_addr[5],channel);
esp_now_send(peerInfo.peer_addr, (uint8_t *) data, sendSize);
xQueueReceive(statusQueue, &status, pdMS_TO_TICKS(2000));
if(status==ESP_NOW_SEND_SUCCESS)
return(true);
delay(10);
}
channel=nextChannel();
} while(channel!=startingChannel);
return(false);
}
///////////////////////////////
void SpanPoint::dataReceived(const uint8_t *mac, const uint8_t *incomingData, int len){
auto it=SpanPoints.begin();
for(;it!=SpanPoints.end() && memcmp((*it)->peerInfo.peer_addr,mac,6)!=0; it++);
if(it==SpanPoints.end())
return;
if((*it)->receiveSize==0)
return;
if(len!=(*it)->receiveSize){
Serial.printf("SpanPoint Warning! %d bytes received from %02X:%02X:%02X:%02X:%02X:%02X does not match %d-byte queue size\n",len,mac[0],mac[1],mac[2],mac[3],mac[4],mac[5],(*it)->receiveSize);
return;
}
(*it)->receiveTime=millis(); // set time of receive
xQueueSend((*it)->receiveQueue, incomingData, 0); // send to queue - do not wait if queue is full and instead fail immediately since we need to return from this function ASAP
}
///////////////////////////////
uint8_t SpanPoint::lmk[16];
boolean SpanPoint::initialized=false;
boolean SpanPoint::isHub=false;
vector<SpanPoint *> SpanPoint::SpanPoints;
uint16_t SpanPoint::channelMask=0x3FFE;
QueueHandle_t SpanPoint::statusQueue;
///////////////////////////////
// MISC //
///////////////////////////////
void __attribute__((weak)) loop(){
}
///////////////////////////////

View File

@ -39,7 +39,10 @@
#include <unordered_set>
#include <nvs.h>
#include <ArduinoOTA.h>
#include <esp_now.h>
#include "extras/Blinker.h"
#include "extras/Pixel.h"
#include "Settings.h"
#include "Utils.h"
#include "Network.h"
@ -64,6 +67,34 @@ enum {
///////////////////////////////
#define STATUS_UPDATE(LED_UPDATE,MESSAGE_UPDATE) {homeSpan.statusLED->LED_UPDATE;if(homeSpan.statusCallback)homeSpan.statusCallback(MESSAGE_UPDATE);}
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_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
HS_CONFIG_MODE_LAUNCH_AP, // HomeSpan is in Command Mode with "Launch Access Point" specified as choice
HS_CONFIG_MODE_UNPAIR, // HomeSpan is in Command Mode with "Unpair Device" specified as choice
HS_CONFIG_MODE_ERASE_WIFI, // HomeSpan is in Command Mode with "Erase WiFi Credentials" specified as choice
HS_CONFIG_MODE_EXIT_SELECTED, // User has selected "Exit Command Mode"
HS_CONFIG_MODE_REBOOT_SELECTED, // User has select "Reboot" from the Command Mode
HS_CONFIG_MODE_LAUNCH_AP_SELECTED, // User has selected "Launch AP Access" from the Command Mode
HS_CONFIG_MODE_UNPAIR_SELECTED, // User has seleected "Unpair Device" from the Command Mode
HS_CONFIG_MODE_ERASE_WIFI_SELECTED, // User has selected "Erase WiFi Credentials" from the Command Mode
HS_REBOOTING, // HomeSpan is in the process of rebooting the device
HS_FACTORY_RESET, // HomeSpan is in the process of performing a Factory Reset of device
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
};
///////////////////////////////
// Forward-Declarations
struct Span;
@ -189,7 +220,6 @@ class Span{
unsigned long alarmConnect=0; // time after which WiFi connection attempt should be tried again
const char *defaultSetupCode=DEFAULT_SETUP_CODE; // Setup Code used for pairing
int statusPin=DEFAULT_STATUS_PIN; // pin for Status LED
uint16_t autoOffLED=0; // automatic turn-off duration (in seconds) for Status LED
uint8_t logLevel=DEFAULT_LOG_LEVEL; // level for writing out log messages to serial monitor
uint8_t maxConnections=CONFIG_LWIP_MAX_SOCKETS-2; // maximum number of allowed simultaneous HAP connections
@ -201,9 +231,11 @@ class Span{
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
void (*apFunction)()=NULL; // optional function to invoke when starting Access Point
void (*statusCallback)(HS_STATUS status)=NULL; // optional callback when HomeSpan status changes
WiFiServer *hapServer; // pointer to the HAP Server connection
Blinker statusLED; // indicates HomeSpan status
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
SpanWebLog webLog; // optional web status/log
@ -223,6 +255,8 @@ class Span{
int getFreeSlot(); // returns free HAPClient slot number. HAPClients slot keep track of each active HAPClient connection
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
int sprintfAttributes(char *cBuf, int flags=GET_VALUE|GET_META|GET_PERMS|GET_TYPE|GET_DESC); // prints Attributes JSON database into buf, unless buf=NULL; return number of characters printed, excluding null terminator
@ -255,10 +289,15 @@ class Span{
boolean deleteAccessory(uint32_t aid); // deletes Accessory with matching aid; returns true if found, else returns false
void setControlPin(uint8_t pin){controlButton=new PushButton(pin);} // sets Control Pin
void setStatusPin(uint8_t pin){statusPin=pin;} // sets Status Pin
void setStatusPin(uint8_t pin){statusDevice=new GenericLED(pin);} // sets Status Device to a simple LED on specified pin
void setStatusAutoOff(uint16_t duration){autoOffLED=duration;} // sets Status LED auto off (seconds)
int getStatusPin(){return(statusPin);} // get Status Pin
int getStatusPin(){return(statusLED->getPin());} // get Status Pin (getPin will return -1 if underlying statusDevice is undefined)
int getControlPin(){return(controlButton?controlButton->getPin():-1);} // get Control Pin (returns -1 if undefined)
void setStatusPixel(uint8_t pin,float h=0,float s=100,float v=100){ // sets Status Device to an RGB Pixel on specified pin
statusDevice=((new Pixel(pin))->setOnColor(Pixel::HSV(h,s,v)));
}
void setApSSID(const char *ssid){network.apSSID=ssid;} // sets Access Point SSID
void setApPassword(const char *pwd){network.apPassword=pwd;} // sets Access Point Password
void setApTimeout(uint16_t nSec){network.lifetime=nSec*1000;} // sets Access Point Timeout (seconds)
@ -276,6 +315,8 @@ class Span{
void setApFunction(void (*f)()){apFunction=f;} // sets an optional user-defined function to call when activating the WiFi Access Point
void enableAutoStartAP(){autoStartAPEnabled=true;} // enables auto start-up of Access Point when WiFi Credentials not found
void setWifiCredentials(const char *ssid, const char *pwd); // sets WiFi Credentials
void setStatusCallback(void (*f)(HS_STATUS status)){statusCallback=f;} // sets an optional user-defined function to call when HomeSpan status changes
const char* statusString(HS_STATUS s); // returns char string for HomeSpan status change messages
void setPairingCode(const char *s){sprintf(pairingCodeCommand,"S %9s",s);} // sets the Pairing Code - use is NOT recommended. Use 'S' from CLI instead
void deleteStoredValues(){processSerialCommand("V");} // deletes stored Characteristic values from NVS
@ -742,6 +783,44 @@ class SpanUserCommand {
SpanUserCommand(char c, const char *s, void (*f)(const char *, void *), void *arg);
};
///////////////////////////////
class SpanPoint {
friend class Span;
int receiveSize; // size (in bytes) of messages to receive
int sendSize; // size (in bytes) of messages to send
esp_now_peer_info_t peerInfo; // structure for all ESP-NOW peer data
QueueHandle_t receiveQueue; // queue to store data after it is received
uint32_t receiveTime=0; // time (in millis) of most recent data received
static uint8_t lmk[16];
static boolean initialized;
static boolean isHub;
static vector<SpanPoint *> SpanPoints;
static uint16_t channelMask; // channel mask
static QueueHandle_t statusQueue; // queue for communication between SpanPoint::dataSend and SpanPoint::send
static void dataReceived(const uint8_t *mac, const uint8_t *incomingData, int len);
static void init(const char *password="HomeSpan");
static void setAsHub(){isHub=true;}
static uint8_t nextChannel();
static void dataSent(const uint8_t *mac, esp_now_send_status_t status) {
xQueueOverwrite( statusQueue, &status );
}
public:
SpanPoint(const char *macAddress, int sendSize, int receiveSize, int queueDepth=1);
static void setPassword(const char *pwd){init(pwd);};
static void setChannelMask(uint16_t mask);
boolean get(void *dataBuf);
boolean send(const void *data);
uint32_t time(){return(millis()-receiveTime);}
};
/////////////////////////////////////////////////
#include "Span.h"

View File

@ -116,7 +116,7 @@ void Network::apConfigure(){
Serial.print(apPassword);
Serial.print("\n");
homeSpan.statusLED.start(LED_AP_STARTED);
STATUS_UPDATE(start(LED_AP_STARTED),HS_AP_STARTED)
Serial.print("\nScanning for Networks...\n\n");
@ -153,12 +153,10 @@ void Network::apConfigure(){
while(1){ // loop until we get timed out (which will be accelerated if save/cancel selected)
if(homeSpan.controlButton && homeSpan.controlButton->triggered(9999,3000)){
Serial.print("\n*** Access Point Terminated.");
homeSpan.statusLED.start(LED_ALERT);
Serial.print("\n*** Access Point Terminated. Restarting...\n\n");
STATUS_UPDATE(start(LED_ALERT),HS_AP_TERMINATED)
homeSpan.controlButton->wait();
Serial.print(" Restarting... \n\n");
homeSpan.statusLED.off();
ESP.restart();
homeSpan.reboot();
}
if(millis()>alarmTimeOut){
@ -173,13 +171,11 @@ void Network::apConfigure(){
Serial.print(lifetime/1000);
Serial.print(" seconds).");
} else {
Serial.print("\n*** Access Point: Configuration Canceled.");
Serial.print("\n*** Access Point: Configuration Cancelled.");
}
Serial.print(" Restarting...\n\n");
homeSpan.statusLED.start(LED_ALERT);
delay(1000);
homeSpan.statusLED.off();
ESP.restart();
STATUS_UPDATE(start(LED_ALERT),HS_AP_TERMINATED)
homeSpan.reboot();
}
}
@ -273,7 +269,7 @@ void Network::processRequest(char *body, char *formData){
getFormValue(formData,"network",wifiData.ssid,MAX_SSID);
getFormValue(formData,"pwd",wifiData.pwd,MAX_PWD);
homeSpan.statusLED.start(LED_WIFI_CONNECTING);
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>";
@ -320,7 +316,7 @@ void Network::processRequest(char *body, char *formData){
} else {
homeSpan.statusLED.start(LED_AP_CONNECTED); // slow double-blink
STATUS_UPDATE(start(LED_AP_CONNECTED),HS_AP_CONNECTED)
responseBody+="<p>SUCCESS! Connected to:</p><p><b>" + String(wifiData.ssid) + "</b></p>";
responseBody+="<p>You may enter new 8-digit Setup Code below, or leave blank to retain existing code.</p>";
@ -340,7 +336,7 @@ void Network::processRequest(char *body, char *formData){
LOG1("In Landing Page...\n");
homeSpan.statusLED.start(LED_AP_CONNECTED);
STATUS_UPDATE(start(LED_AP_CONNECTED),HS_AP_CONNECTED)
waitTime=2;
responseBody+="<p>Welcome to HomeSpan! This page allows you to configure the above HomeSpan device to connect to your WiFi network.</p>"

View File

@ -44,7 +44,7 @@ struct Network {
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 excrypted WiFi connection)
unsigned long lifetime=DEFAULT_AP_TIMEOUT*1000; // length of time (in milliseconds) to keep Access Point alive before shutting down and re-starting
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;

View File

@ -35,7 +35,7 @@
// HomeSpan Version //
#define HS_MAJOR 1
#define HS_MINOR 6
#define HS_MINOR 7
#define HS_PATCH 0
#define STRINGIFY(x) _STR(x)
@ -71,8 +71,6 @@
#define DEFAULT_QR_ID "HSPN" // change with homeSpan.setQRID(qrID);
#define DEFAULT_STATUS_PIN -1 // change with homeSpan.setStatusPin(pin)
#define DEFAULT_AP_SSID "HomeSpan-Setup" // change with homeSpan.setApSSID(ssid)
#define DEFAULT_AP_PASSWORD "homespan" // change with homeSpan.setApPassword(pwd)
#define DEFAULT_OTA_PASSWORD "homespan-ota" // change with 'O' command

View File

@ -35,7 +35,6 @@
// Utils::mask - masks a string with asterisks (good for displaying passwords)
//
// class PushButton - tracks Single, Double, and Long Presses of a pushbutton that connects a specified pin to ground
// class Blinker - creates customized blinking patterns on an LED connected to a specified pin
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@ -223,182 +222,4 @@ void PushButton::reset(){
//////////////////////////////////////
#if SOC_TOUCH_SENSOR_NUM > 0
touch_value_t PushButton::threshold=0;
#endif
////////////////////////////////
// Blinker //
////////////////////////////////
Blinker::Blinker(){
}
//////////////////////////////////////
Blinker::Blinker(int pin, int timerNum, uint16_t autoOffDuration){
init(pin, timerNum, autoOffDuration);
}
//////////////////////////////////////
void Blinker::init(int pin, int timerNum, uint16_t autoOffDuration){
this->pin=pin;
if(pin<0)
return;
pinMode(pin,OUTPUT);
digitalWrite(pin,0);
pauseDuration=autoOffDuration*1000;
#if SOC_TIMER_GROUP_TIMERS_PER_GROUP>1 // ESP32 and ESP32-S2 contains two timers per timer group
group=((timerNum/2)%2==0)?TIMER_GROUP_0:TIMER_GROUP_1;
idx=(timerNum%2==0)?TIMER_0:TIMER_1; // ESP32-C3 only contains one timer per timer group
#else
group=(timerNum%2==0)?TIMER_GROUP_0:TIMER_GROUP_1;
idx=TIMER_0;
#endif
timer_config_t conf;
conf.alarm_en=TIMER_ALARM_EN;
conf.counter_en=TIMER_PAUSE;
conf.intr_type=TIMER_INTR_LEVEL;
conf.counter_dir=TIMER_COUNT_UP;
conf.auto_reload=TIMER_AUTORELOAD_EN;
conf.divider=getApbFrequency()/10000; // set divider to yield 10 kHz clock (0.1 ms pulses)
#ifdef SOC_TIMER_GROUP_SUPPORT_XTAL // set clock to APB (default is XTAL!) if clk_src is defined in conf structure
conf.clk_src=TIMER_SRC_CLK_APB;
#endif
timer_init(group,idx,&conf);
timer_isr_register(group,idx,Blinker::isrTimer,(void *)this,0,NULL);
timer_enable_intr(group,idx);
}
//////////////////////////////////////
void Blinker::isrTimer(void *arg){
Blinker *b=(Blinker *)arg;
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0) // use new method that is generic to ESP32, S2, and C3
timer_group_clr_intr_status_in_isr(b->group,b->idx);
#else // use older method that is only for ESP32
if(b->group){
if(b->idx)
TIMERG1.int_clr_timers.t1=1;
else
TIMERG1.int_clr_timers.t0=1;
} else {
if(b->idx)
TIMERG0.int_clr_timers.t1=1;
else
TIMERG0.int_clr_timers.t0=1;
}
#endif
if(!digitalRead(b->pin)){
digitalWrite(b->pin,1);
timer_set_alarm_value(b->group,b->idx,b->onTime);
b->count--;
} else {
digitalWrite(b->pin,0);
if(b->count){
timer_set_alarm_value(b->group,b->idx,b->offTime);
} else {
timer_set_alarm_value(b->group,b->idx,b->delayTime);
b->count=b->nBlinks;
}
}
timer_set_alarm(b->group,b->idx,TIMER_ALARM_EN);
}
//////////////////////////////////////
void Blinker::start(int period, float dutyCycle){
start(period, dutyCycle, 1, 0);
}
//////////////////////////////////////
void Blinker::start(int period, float dutyCycle, int nBlinks, int delayTime){
if(pin<0)
return;
pauseTime=millis();
isPaused=false;
gpio_set_direction((gpio_num_t)pin, GPIO_MODE_INPUT_OUTPUT); // needed to ensure digitalRead() functions correctly on ESP32-C3; also needed to re-enable after pause()
period*=10;
onTime=dutyCycle*period;
offTime=period-onTime;
this->delayTime=delayTime*10+offTime;
this->nBlinks=nBlinks;
count=nBlinks;
timer_set_counter_value(group,idx,0);
timer_set_alarm_value(group,idx,0);
timer_start(group,idx);
}
//////////////////////////////////////
void Blinker::stop(){
if(pin<0)
return;
timer_pause(group,idx);
}
//////////////////////////////////////
void Blinker::on(){
if(pin<0)
return;
pauseTime=millis();
isPaused=false;
gpio_set_direction((gpio_num_t)pin, GPIO_MODE_INPUT_OUTPUT);
stop();
digitalWrite(pin,1);
}
//////////////////////////////////////
void Blinker::off(){
if(pin<0)
return;
pauseTime=millis();
isPaused=false;
gpio_set_direction((gpio_num_t)pin, GPIO_MODE_INPUT_OUTPUT);
stop();
digitalWrite(pin,0);
}
//////////////////////////////////////
void Blinker::check(){
if(pin<0)
return;
if(pauseDuration==0 || isPaused || (millis()-pauseTime)<pauseDuration)
return;
Serial.print("Pausing Status LED\n");
isPaused=true;
gpio_set_direction((gpio_num_t)pin, GPIO_MODE_DISABLE);
}
touch_value_t PushButton::threshold=0;

View File

@ -71,6 +71,12 @@ struct TempBuffer {
// PushButton //
////////////////////////////////
#if SOC_TOUCH_VERSION_2
typedef uint32_t touch_value_t;
#else
typedef uint16_t touch_value_t;
#endif
class PushButton{
int status;
@ -80,10 +86,8 @@ class PushButton{
uint32_t longAlarm;
int pressType;
#if SOC_TOUCH_SENSOR_NUM > 0
static touch_value_t threshold;
static const int calibCount=20;
#endif
protected:
@ -103,10 +107,12 @@ class PushButton{
static boolean TRIGGER_ON_LOW(int pin){return(!digitalRead(pin));}
static boolean TRIGGER_ON_HIGH(int pin){return(digitalRead(pin));}
#if SOC_TOUCH_VERSION_1 // ESP32
static boolean TRIGGER_ON_TOUCH(int pin){return(touchRead(pin)<threshold);}
#elif SOC_TOUCH_VERSION_2 // ESP32S2 ESP32S3
#if SOC_TOUCH_SENSOR_NUM > 0
#if SOC_TOUCH_VERSION_2
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
PushButton(int pin, triggerType_t triggerType=TRIGGER_ON_LOW);
@ -180,100 +186,4 @@ class PushButton{
#endif
};
////////////////////////////////
// Blinker //
////////////////////////////////
class Blinker {
timer_group_t group;
timer_idx_t idx;
int pin;
int nBlinks;
int onTime;
int offTime;
int delayTime;
int count;
unsigned long pauseDuration;
unsigned long pauseTime;
boolean isPaused=false;
static void isrTimer(void *arg);
public:
Blinker();
Blinker(int pin, int timerNum=0, uint16_t autoOffDuration=0);
// Creates a generic blinking LED on specified pin controlled
// in background via interrupts generated by an ESP32 Alarm Timer.
//
// In the first form, a Blinker is instantiated without specifying
// the pin. In this case the pin must be specified in a subsequent call
// to init() before the Blinker can be used.
//
// In the second form, a Blinker is instantiated and initialized with
// the specified pin, obviating the need for a separate call to init().
//
// pin: Pin mumber to control. Blinker will set pinMode to OUTPUT automatically
//
// timerNum: ESP32 Alarm Timer to use.
// For ESP32 and ESP32-S2: 0=Group0/Timer0, 1=Group0/Timer1, 2=Group1/Timer0, 3=Group1/Timer1
// For ESP32-C3: 0=Group0/Timer0, 1=Group1/Timer0
//
// autoOffDuration: If greater than zero, Blinker will automatically turn off after autoOffDuration (in seconds) has elapsed
// Blinker will resume normal operation upon next call to start(), on(), or off()
// Program must periodically call check() for auto-off functionality to work
void init(int pin, int timerNum=0, uint16_t autoOffDuration=0);
// Initializes Blinker, if not configured during instantiation.
//
// pin: Pin mumber to control. Blinker will set pinMode to OUTPUT automatically
//
// timerNum: ESP32 Alarm Timer to use.
// For ESP32 and ESP32-S2: 0=Group0/Timer0, 1=Group0/Timer1, 2=Group1/Timer0, 3=Group1/Timer1
// For ESP32-C3: 0=Group0/Timer0, 1=Group1/Timer0
//
// autoOffDuration: If greater than zero, Blinker will automatically turn off after autoOffDuration (in seconds) has elapsed
// Blinker will resume normal operation upon next call to start(), on(), or off()
// Program must periodically call check() for auto-off functionality to work
void start(int period, float dutyCycle=0.5);
// Starts simple ON/OFF blinking.
//
// period: ON/OFF blinking period, in milliseconds
// dutyCycle: Fraction of period that LED is ON (default=50%)
void start(int period, float dutyCycle, int nBlinks, int delayTime);
// Starts ON/OFF blinking pattern.
//
// period: ON/OFF blinking period, in milliseconds, used for blinking portion of pattern
// dutyCycle: Fraction of period that LED is ON (default=50%)
// nBlinks: Number of blinks in blinking portion of pattern
// delayTime: delay, in milliseconds, during which LED is off before restarting blinking pattern
void stop();
// Stops current blinking pattern.
void on();
// Stops current blinking pattern and turns on LED
void off();
// Stops current blinking pattern and turns off LED
void check();
// Optional check to see if LED output should be paused (check is bypassed if pauseDuration=0)
};

30
src/extras/Blinker.h Normal file
View File

@ -0,0 +1,30 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2022 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.
*
********************************************************************************/
#include "../src/extras/Blinker.h"

View File

@ -25,229 +25,5 @@
*
********************************************************************************/
////////////////////////////////////////////
// Addressable LEDs //
////////////////////////////////////////////
#include "../src/extras/Pixel.h"
#pragma once
#include "RFControl.h"
#include "PwmPin.h"
////////////////////////////////////////////
// Single-Wire RGB/RGBW NeoPixels //
////////////////////////////////////////////
class Pixel {
public:
struct Color {
union{
struct {
uint8_t white:8;
uint8_t blue:8;
uint8_t red:8;
uint8_t green:8;
};
uint32_t val;
};
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;
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;
return(*this);
}
bool operator==(const Color& color){
return(val==color.val);
}
bool operator!=(const Color& color){
return(val!=color.val);
}
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 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 iBit;
int iMem;
boolean started;
Pixel *px;
boolean multiColor;
};
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
uint32_t lastBit; // 0=RGBW; 8=RGB
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, boolean isRGBW=false); // creates addressable single-wire RGB (false) or RGBW (true) LED 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
int getPin(){return(rf->getPin());} // returns pixel pin if valid, else returns -1
void setTiming(float high0, float low0, float high1, float low1, uint32_t lowReset); // changes default timings for bit pulse - note parameters are in MICROSECONDS
operator bool(){ // override boolean operator to return true/false if creation succeeded/failed
return(*rf);
}
};
////////////////////////////////////////////
// Two-Wire RGB DotStars //
////////////////////////////////////////////
class Dot {
public:
struct Color {
union{
struct {
uint8_t red:8;
uint8_t green:8;
uint8_t blue:8;
uint8_t drive:5;
uint8_t flags:3;
};
uint32_t val;
};
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;
this->blue=b;
this->drive=driveLevel;
this->flags=7;
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]
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->drive=drivePercent*0.315;
this->flags=7;
return(*this);
}
bool operator==(const Color& color){
return(val==color.val);
}
bool operator!=(const Color& color){
return(val!=color.val);
}
Color operator+(const Color& color){
Color newColor;
newColor.blue=blue+color.blue;
newColor.red=red+color.red;
newColor.green=green+color.green;
return(newColor);
}
Color& operator+=(const Color& color){
red+=color.red;
blue+=color.blue;
green+=color.green;
return(*this);
}
Color operator-(const Color& color){
Color newColor;
newColor.blue=blue-color.blue;
newColor.red=red-color.red;
newColor.green=green-color.green;
return(newColor);
}
Color& operator-=(const Color& color){
red-=color.red;
blue-=color.blue;
green-=color.green;
return(*this);
}
};
private:
uint32_t dataMask;
uint32_t clockMask;
volatile uint32_t *dataSetReg;
volatile uint32_t *dataClearReg;
volatile uint32_t *clockSetReg;
volatile uint32_t *clockClearReg;
public:
Dot(uint8_t dataPin, uint8_t clockPin); // creates addressable two-wire RGB LED connected to dataPin and clockPin (such as the DotStar SK9822 or APA102)
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 driveLevel=31){return(Color().RGB(r,g,b,driveLevel));} // an alternative method for returning an RGB Color
static Color HSV(float h, float s, float v, double drivePercent=100){return(Color().HSV(h,s,v,drivePercent));} // an alternative method for returning an HSV Color
};
////////////////////////////////////////////

View File

@ -25,72 +25,5 @@
*
********************************************************************************/
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// ----- PWM Pin Control -----
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Wrappers around the ESP-IDF ledc library to control PWM-based devices:
//
// LedPin(pin) - controls a Dimmable LED on specified pin with frequency=5000 Hz
// - use set(level) to set brightness from 0-100%
//
// ServoPin(pin) - controls a Servo Motor on specified pin with frequency=50 Hz
// - use set(degrees) to set position to degrees
//
//
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
#include "../src/extras/PwmPin.h"
#pragma once
#include <Arduino.h>
#include <driver/ledc.h>
#define DEFAULT_PWM_FREQ 5000
/////////////////////////////////////
class LedC {
protected:
static ledc_channel_config_t *channelList[LEDC_CHANNEL_MAX][LEDC_SPEED_MODE_MAX];
static ledc_timer_config_t *timerList[LEDC_TIMER_MAX][LEDC_SPEED_MODE_MAX];
ledc_channel_config_t *channel=NULL;
ledc_timer_config_t *timer;
LedC(uint8_t pin, uint16_t freq);
public:
int getPin(){return(channel?channel->gpio_num:-1);} // returns the pin number
operator bool(){ // override boolean operator to return true/false if creation succeeded/failed
return(channel);
}
};
/////////////////////////////////////
class LedPin : public LedC {
public:
LedPin(uint8_t pin, float level=0, uint16_t freq=DEFAULT_PWM_FREQ); // assigns pin to be output of one of 16 PWM channels initial level and frequency
void set(float level); // sets the PWM duty to level (0-100)
static void HSVtoRGB(float h, float s, float v, float *r, float *g, float *b ); // converts Hue/Saturation/Brightness to R/G/B
};
/////////////////////////////////////
class ServoPin : public LedC {
uint16_t minMicros;
uint16_t maxMicros;
double minDegrees;
double microsPerDegree;
public:
ServoPin(uint8_t pin, double initDegrees, uint16_t minMicros, uint16_t maxMicros, double minDegrees, double maxDegrees);
ServoPin(uint8_t pin, double initDegrees=0) : ServoPin(pin,initDegrees,1000,2000,-90,90) {};
void set(double degrees); // sets the Servo to degrees, where degrees is bounded by [minDegrees,maxDegrees]
};

View File

@ -25,52 +25,5 @@
*
********************************************************************************/
////////////////////////////////////
// RF Control Module //
////////////////////////////////////
#pragma once
#include <Arduino.h>
#include <soc/rmt_reg.h>
#include "driver/rmt.h"
#include <vector>
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)
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
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(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
void add(uint32_t onTime, uint32_t offTime); // adds pulse of onTime ticks HIGH followed by offTime ticks LOW
void phase(uint32_t nTicks, uint8_t phase); // adds either a HIGH phase or LOW phase lasting numTicks ticks
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);
}
};
// Helper macro for creating your own storage of uint32_t data array elements - used with first variation of start() above
#define RF_PULSE(highTicks,lowTicks) (1 << 15 | uint32_t(highTicks) | uint32_t(lowTicks) << 16)
#include "../src/extras/RFControl.h"

View File

@ -1,43 +0,0 @@
// This is a placeholder .ino file that allows you to easily edit the contents of this files using the Arduino IDE,
// as well as compile and test from this point. This file is ignored when the library is included in other sketches.
#include "Pixel.h"
//#define PixelType Pixel
#define PixelType Dot
//Pixel p(8);
Dot p(0,1);
void setup() {
Serial.begin(115200); // start the Serial interface
Serial.flush();
delay(1000); // wait for interface to flush
Serial.println("\n\nHomeSpan Pixel Example\n");
PixelType::Color off=PixelType::RGB(0,0,0);
p.set(PixelType::RGB(0,0,255),3);
delay(1000);
p.set(off,3);
delay(1000);
PixelType::Color c[]={p.HSV(120,100,30),p.HSV(0,0,0),p.HSV(0,0,0)};
p.set(c,3);
delay(1000);
c[0].HSV(0,0,0);
c[1].HSV(60,100,30);
p.set(c,3);
delay(1000);
c[1].HSV(0,0,0);
c[2].HSV(0,100,30);
p.set(c,3);
}
void loop(){
}

View File

@ -3,19 +3,38 @@
// as well as compile and test from this point. This file is ignored when the library is included in other sketches.
#include "HomeSpan.h"
#include "FeatherPins.h"
#include "extras/Pixel.h"
#include "extras/RFControl.h"
#include "extras/Blinker.h"
#include "extras/PwmPin.h"
#define STRING_t const char * // WORK-AROUND
CUSTOM_CHAR(LightMode, AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA, PR, STRING, "ANY_VALUE", NULL, NULL, true);
CUSTOM_CHAR_STRING(DarkMode, AAAAAAAA-BBBB-AAAA-AAAA-AAAAAAAAAAAA, PR, "MY_VALUE");
SpanPoint *dev1;
SpanPoint *dev2;
struct message_t {
char a[32];
int b;
float c;
bool d;
} message;
void setup() {
Serial.begin(115200);
// homeSpan.setLogLevel(2);
// homeSpan.setStatusPin(13);
homeSpan.setControlPin(33);
homeSpan.setControlPin(F25);
homeSpan.setStatusPin(F26);
// homeSpan.setStatusPin(new LED(F26));
// new Pixel(F27);
homeSpan.setHostNameSuffix("-lamp1");
homeSpan.setPortNum(1201);
@ -25,14 +44,29 @@ void setup() {
homeSpan.setSketchVersion("OTA Test 8");
homeSpan.setWifiCallback(wifiEstablished);
homeSpan.setStatusCallback(statusUpdate);
new SpanUserCommand('d',"- My Description",userCom1);
new SpanUserCommand('e',"- My second Description",userCom2);
// homeSpan.enableAutoStartAP();
// homeSpan.setApFunction(myWiFiAP);
homeSpan.enableWebLog(10,"pool.ntp.org","UTC","myLog"); // creates a web log on the URL /HomeSpan-[DEVICE-ID].local:[TCP-PORT]/myLog
SpanPoint::setChannelMask(1<<13);
SpanPoint::setPassword("Hello Thert");
homeSpan.setLogLevel(1);
dev2=new SpanPoint("7C:DF:A1:61:E4:A8",sizeof(int),sizeof(message_t));
dev1=new SpanPoint("AC:67:B2:77:42:20",sizeof(int),0);
homeSpan.begin(Category::Lighting,"HomeSpan Lamp Server","homespan");
new SpanAccessory(); // Begin by creating a new Accessory using SpanAccessory(), which takes no arguments
new Service::AccessoryInformation(); // HAP requires every Accessory to implement an AccessoryInformation Service, which has 6 required Characteristics
@ -54,6 +88,8 @@ void setup() {
new Characteristic::Name("Light 1");
new Characteristic::ColorTemperature();
new Characteristic::Active();
new Service::LightBulb();
new Characteristic::On(0,true);
(new Characteristic::Brightness(50,false))->setRange(10,100,5);
@ -82,11 +118,23 @@ void setup() {
} // end of setup()
unsigned long alarmTime=0;
//////////////////////////////////////
void loop(){
homeSpan.poll();
// if(dev1->get(&message))
// Serial.printf("DEV1: '%s' %d %f %d\n",message.a,message.b,message.c,message.d);
// if(dev2->get(&message))
// Serial.printf("DEV2: '%s' %d %f %d\n",message.a,message.b,message.c,message.d);
//
// if(millis()-alarmTime>5000){
// alarmTime=millis();
// boolean success = dev2->send(&alarmTime);
// Serial.printf("Success = %d\n",success);
// }
} // end of loop()
@ -101,6 +149,8 @@ void myWiFiAP(){
void wifiEstablished(){
Serial.print("IN CALLBACK FUNCTION\n\n");
Serial.printf("MODE = %d\n",WiFi.getMode());
}
//////////////////////////////////////
@ -114,3 +164,9 @@ void userCom1(const char *v){
void userCom2(const char *v){
Serial.printf("In User Command 2: '%s'\n\n",v);
}
//////////////////////////////////////
void statusUpdate(HS_STATUS status){
Serial.printf("\n*** HOMESPAN STATUS CHANGE: %s\n",homeSpan.statusString(status));
}

145
src/src/extras/Blinker.cpp Normal file
View File

@ -0,0 +1,145 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2022 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.
*
********************************************************************************/
#include "Blinker.h"
////////////////////////////////
// Blinker //
////////////////////////////////
Blinker::Blinker(Blinkable *led, uint16_t autoOffDuration){
this->led=led;
pauseDuration=autoOffDuration*1000;
}
//////////////////////////////////////
void Blinker::blinkTask(void *arg){
Blinker *b=(Blinker *)arg;
for(;;){
for(int i=0;i<b->nBlinks;i++){
b->led->on();
delay(b->onTime);
b->led->off();
delay(b->offTime);
}
delay(b->delayTime);
}
}
//////////////////////////////////////
void Blinker::start(int period, float dutyCycle){
start(period, dutyCycle, 1, 0);
}
//////////////////////////////////////
void Blinker::start(int period, float dutyCycle, int nBlinks, int delayTime){
if(!led)
return;
onTime=dutyCycle*period;
offTime=period-onTime;
this->delayTime=delayTime+offTime;
this->nBlinks=nBlinks;
stop();
xTaskCreate( blinkTask, "BlinkTask", 1024, (void *)this, 2, &blinkHandle );
pauseTime=millis();
isPaused=false;
}
//////////////////////////////////////
void Blinker::stop(){
if(!led)
return;
if(blinkHandle!=NULL){
vTaskDelete(blinkHandle);
blinkHandle=NULL;
}
isPaused=true;
}
//////////////////////////////////////
void Blinker::on(){
if(!led)
return;
stop();
led->on();
pauseTime=millis();
isPaused=false;
}
//////////////////////////////////////
void Blinker::off(){
if(!led)
return;
stop();
led->off();
}
//////////////////////////////////////
void Blinker::check(){
if(!led)
return;
if(pauseDuration==0 || isPaused || (millis()-pauseTime)<pauseDuration)
return;
Serial.print("Pausing LED\n");
off();
}
//////////////////////////////////////
int Blinker::getPin(){
if(!led)
return(-1);
return(led->getPin());
}

128
src/src/extras/Blinker.h Normal file
View File

@ -0,0 +1,128 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2022 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 <driver/timer.h>
////////////////////////////////
// Blinkable Interface //
////////////////////////////////
class Blinkable {
public:
virtual void on()=0;
virtual void off()=0;
virtual int getPin()=0;
};
////////////////////////////////
// Blinker //
////////////////////////////////
class Blinker {
TaskHandle_t blinkHandle = NULL;
Blinkable *led;
int nBlinks;
int onTime;
int offTime;
int delayTime;
unsigned long pauseDuration;
unsigned long pauseTime;
boolean isPaused=false;
static void blinkTask(void *arg);
public:
Blinker(Blinkable *led, uint16_t autoOffDuration=0);
// Creates a generic blinking LED in a separate task thread
//
// led: An initialized LED device that implements the Blinkable Interface
////
// autoOffDuration: If greater than zero, Blinker will automatically turn off after autoOffDuration (in seconds) has elapsed
// Blinker will resume normal operation upon next call to start(), on(), or off()
// Program must periodically call check() for auto-off functionality to work
void start(int period, float dutyCycle=0.5);
// Starts simple ON/OFF blinking.
//
// period: ON/OFF blinking period, in milliseconds
// dutyCycle: Fraction of period that LED is ON (default=50%)
void start(int period, float dutyCycle, int nBlinks, int delayTime);
// Starts ON/OFF blinking pattern.
//
// period: ON/OFF blinking period, in milliseconds, used for blinking portion of pattern
// dutyCycle: Fraction of period that LED is ON (default=50%)
// nBlinks: Number of blinks in blinking portion of pattern
// delayTime: delay, in milliseconds, during which LED is off before restarting blinking pattern
void stop();
// Stops current blinking pattern.
void on();
// Stops current blinking pattern and turns on LED
void off();
// Stops current blinking pattern and turns off LED
void check();
// Optional check to see if LED output should be paused (check is bypassed if pauseDuration=0)
int getPin();
// Returns pin number of connected LED
};
////////////////////////////////
// GenericLED //
////////////////////////////////
class GenericLED : public Blinkable {
int pin;
public:
GenericLED(int pin) : pin{pin} {pinMode(pin,OUTPUT);digitalWrite(pin,0);}
void on() {digitalWrite(pin,HIGH);}
void off() {digitalWrite(pin,LOW);}
int getPin() {return(pin);}
};

View File

@ -59,6 +59,8 @@ Pixel::Pixel(int pin, boolean isRGBW){
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);
}
///////////////////

259
src/src/extras/Pixel.h Normal file
View File

@ -0,0 +1,259 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2022 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.
*
********************************************************************************/
////////////////////////////////////////////
// Addressable LEDs //
////////////////////////////////////////////
#pragma once
#include "RFControl.h"
#include "PwmPin.h"
#include "Blinker.h"
////////////////////////////////////////////
// Single-Wire RGB/RGBW NeoPixels //
////////////////////////////////////////////
class Pixel : public Blinkable {
public:
struct Color {
union{
struct {
uint8_t white:8;
uint8_t blue:8;
uint8_t red:8;
uint8_t green:8;
};
uint32_t val;
};
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;
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;
return(*this);
}
bool operator==(const Color& color){
return(val==color.val);
}
bool operator!=(const Color& color){
return(val!=color.val);
}
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 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 iBit;
int iMem;
boolean started;
Pixel *px;
boolean multiColor;
};
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
uint32_t lastBit; // 0=RGBW; 8=RGB
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, boolean isRGBW=false); // creates addressable single-wire RGB (false) or RGBW (true) LED 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
int getPin(){return(rf->getPin());} // returns pixel pin if valid, else returns -1
void setTiming(float high0, float low0, float high1, float low1, uint32_t lowReset); // changes default timings for bit pulse - note parameters are in MICROSECONDS
operator bool(){ // override boolean operator to return true/false if creation succeeded/failed
return(*rf);
}
void on() {set(onColor);}
void off() {set(RGB(0,0,0,0));}
Pixel *setOnColor(Color c){onColor=c;return(this);}
};
////////////////////////////////////////////
// Two-Wire RGB DotStars //
////////////////////////////////////////////
class Dot {
public:
struct Color {
union{
struct {
uint8_t red:8;
uint8_t green:8;
uint8_t blue:8;
uint8_t drive:5;
uint8_t flags:3;
};
uint32_t val;
};
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;
this->blue=b;
this->drive=driveLevel;
this->flags=7;
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]
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->drive=drivePercent*0.315;
this->flags=7;
return(*this);
}
bool operator==(const Color& color){
return(val==color.val);
}
bool operator!=(const Color& color){
return(val!=color.val);
}
Color operator+(const Color& color){
Color newColor;
newColor.blue=blue+color.blue;
newColor.red=red+color.red;
newColor.green=green+color.green;
return(newColor);
}
Color& operator+=(const Color& color){
red+=color.red;
blue+=color.blue;
green+=color.green;
return(*this);
}
Color operator-(const Color& color){
Color newColor;
newColor.blue=blue-color.blue;
newColor.red=red-color.red;
newColor.green=green-color.green;
return(newColor);
}
Color& operator-=(const Color& color){
red-=color.red;
blue-=color.blue;
green-=color.green;
return(*this);
}
};
private:
uint32_t dataMask;
uint32_t clockMask;
volatile uint32_t *dataSetReg;
volatile uint32_t *dataClearReg;
volatile uint32_t *clockSetReg;
volatile uint32_t *clockClearReg;
public:
Dot(uint8_t dataPin, uint8_t clockPin); // creates addressable two-wire RGB LED connected to dataPin and clockPin (such as the DotStar SK9822 or APA102)
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 driveLevel=31){return(Color().RGB(r,g,b,driveLevel));} // an alternative method for returning an RGB Color
static Color HSV(float h, float s, float v, double drivePercent=100){return(Color().HSV(h,s,v,drivePercent));} // an alternative method for returning an HSV Color
};
////////////////////////////////////////////

101
src/src/extras/PwmPin.h Normal file
View File

@ -0,0 +1,101 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2022 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.
*
********************************************************************************/
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// ----- PWM Pin Control -----
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Wrappers around the ESP-IDF ledc library to control PWM-based devices:
//
// LedPin(pin) - controls a Dimmable LED on specified pin with frequency=5000 Hz
// - use set(level) to set brightness from 0-100%
//
// ServoPin(pin) - controls a Servo Motor on specified pin with frequency=50 Hz
// - use set(degrees) to set position to degrees
//
//
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma once
#include <Arduino.h>
#include <driver/ledc.h>
#include "Blinker.h"
#define DEFAULT_PWM_FREQ 5000
/////////////////////////////////////
class LedC {
protected:
static ledc_channel_config_t *channelList[LEDC_CHANNEL_MAX][LEDC_SPEED_MODE_MAX];
static ledc_timer_config_t *timerList[LEDC_TIMER_MAX][LEDC_SPEED_MODE_MAX];
ledc_channel_config_t *channel=NULL;
ledc_timer_config_t *timer;
LedC(uint8_t pin, uint16_t freq);
public:
int getPin(){return(channel?channel->gpio_num:-1);} // returns the pin number
operator bool(){ // override boolean operator to return true/false if creation succeeded/failed
return(channel);
}
};
/////////////////////////////////////
class LedPin : public LedC {
public:
LedPin(uint8_t pin, float level=0, uint16_t freq=DEFAULT_PWM_FREQ); // assigns pin to be output of one of 16 PWM channels initial level and frequency
void set(float level); // sets the PWM duty to level (0-100)
static void HSVtoRGB(float h, float s, float v, float *r, float *g, float *b ); // converts Hue/Saturation/Brightness to R/G/B
};
////////////////////////////////
// ServoPin //
////////////////////////////////
class ServoPin : public LedC {
uint16_t minMicros;
uint16_t maxMicros;
double minDegrees;
double microsPerDegree;
public:
ServoPin(uint8_t pin, double initDegrees, uint16_t minMicros, uint16_t maxMicros, double minDegrees, double maxDegrees);
ServoPin(uint8_t pin, double initDegrees=0) : ServoPin(pin,initDegrees,1000,2000,-90,90) {};
void set(double degrees); // sets the Servo to degrees, where degrees is bounded by [minDegrees,maxDegrees]
};

View File

@ -0,0 +1,76 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2022 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.
*
********************************************************************************/
////////////////////////////////////
// RF Control Module //
////////////////////////////////////
#pragma once
#include <Arduino.h>
#include <soc/rmt_reg.h>
#include "driver/rmt.h"
#include <vector>
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)
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
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(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
void add(uint32_t onTime, uint32_t offTime); // adds pulse of onTime ticks HIGH followed by offTime ticks LOW
void phase(uint32_t nTicks, uint8_t phase); // adds either a HIGH phase or LOW phase lasting numTicks ticks
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);
}
};
// Helper macro for creating your own storage of uint32_t data array elements - used with first variation of start() above
#define RF_PULSE(highTicks,lowTicks) (1 << 15 | uint32_t(highTicks) | uint32_t(lowTicks) << 16)

32
src/src/extras/extras.ino Normal file
View File

@ -0,0 +1,32 @@
// This is a placeholder .ino file that allows you to easily edit the contents of this files using the Arduino IDE,
// as well as compile and test from this point. This file is ignored when the library is included in other sketches.
#include "Blinker.h"
#include "Pixel.h"
#include <Wire.h>
Blinker p(new Pixel(2),10);
//Blinker p(NULL,10);
void setup() {
Serial.begin(115200); // start the Serial interface
Serial.flush();
delay(1000); // wait for interface to flush
Serial.println("\n\nHomeSpan Blinker Example\n");
Serial.printf("Pins = %d\n",p.getPin());
p.on();
delay(2000);
p.off();
delay(2000);
p.start(300,0.25,4,1000);
delay(5000);
Serial.printf("New Pattern\n");
p.start(200,0.2,2,200);
}
void loop(){
p.check();
}