diff --git a/Other Examples/RemoteSensors/MainDevice/MainDevice.ino b/Other Examples/RemoteSensors/MainDevice/MainDevice.ino new file mode 100644 index 0000000..270f4e4 --- /dev/null +++ b/Other Examples/RemoteSensors/MainDevice/MainDevice.ino @@ -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() + +////////////////////////////////////// diff --git a/Other Examples/RemoteSensors/RemoteDevice/RemoteDevice.ino b/Other Examples/RemoteSensors/RemoteDevice/RemoteDevice.ino new file mode 100644 index 0000000..04b9dca --- /dev/null +++ b/Other Examples/RemoteSensors/RemoteDevice/RemoteDevice.ino @@ -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); +} diff --git a/Other Examples/RemoteSensors/RemoteTempSensor/RemoteTempSensor.ino b/Other Examples/RemoteSensors/RemoteTempSensor/RemoteTempSensor.ino new file mode 100644 index 0000000..4157707 --- /dev/null +++ b/Other Examples/RemoteSensors/RemoteTempSensor/RemoteTempSensor.ino @@ -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 // 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 +} diff --git a/docs/HS_STATUS.md b/docs/HS_STATUS.md new file mode 100644 index 0000000..7f528df --- /dev/null +++ b/docs/HS_STATUS.md @@ -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 diff --git a/docs/NOW.md b/docs/NOW.md new file mode 100644 index 0000000..5d7d632 --- /dev/null +++ b/docs/NOW.md @@ -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 diff --git a/docs/README.md b/docs/README.md index 9635e1e..fe12d5b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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 - -* **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 - -* **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 +* **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 + +* **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 -* **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 - -* **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 - -* **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 +* **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 + +* **Important Bug Fixes** + + * 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 + + * 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. diff --git a/docs/Reference.md b/docs/Reference.md index 8f7348c..6ea8f8d 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -30,9 +30,26 @@ 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) diff --git a/docs/Tutorials.md b/docs/Tutorials.md index 6ea8fd0..d321446 100644 --- a/docs/Tutorials.md +++ b/docs/Tutorials.md @@ -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 diff --git a/library.properties b/library.properties index 0a523e6..9dc7fbd 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=HomeSpan -version=1.6.0 +version=1.7.0 author=Gregg maintainer=Gregg sentence=A robust and extremely easy-to-use HomeKit implementation for the Espressif ESP32 running on the Arduino IDE. diff --git a/src/FeatherPins.h b/src/FeatherPins.h index 7072d86..b41aa7e 100644 --- a/src/FeatherPins.h +++ b/src/FeatherPins.h @@ -29,7 +29,7 @@ // Facilitates the testing of identical code on an ESP32, ESP32-S2, and ESP32-C3 using a common jig without rewiring #pragma once - + #if defined(ARDUINO_FEATHER_ESP32) enum { F13=13,F12=12,F27=27,F15=15,F32=32,F14=14,F16=16,F17=17,F21=21, // Digital Only (9 pins) @@ -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" diff --git a/src/HAP.cpp b/src/HAP.cpp index c4b2844..6505307 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -637,8 +637,9 @@ 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= - 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,32 +927,27 @@ 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= - 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= - 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= - tlv8.val(kTLVType_Error,tagError_MaxPeers); // set Error=Unknown (there is no specific error type for missing/bad TLV data) - break; + 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= @@ -1537,16 +1533,10 @@ void HAPClient::charPrintRow(uint8_t *buf, int n){ Controller *HAPClient::findController(uint8_t *id){ - for(int i=0;i1) - 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;iLTPK,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); } diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index bba3091..2c3e9b5 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -31,9 +31,11 @@ #include #include #include +#include #include #include #include +#include #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=0){ - Serial.print(statusPin); + if(getStatusPin()>=0){ + Serial.print(getStatusPin()); if(autoOffLED>0) Serial.printf(" (Auto Off=%d sec)",autoOffLED); } @@ -137,7 +141,8 @@ 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); Serial.print("\n\n"); @@ -151,7 +156,7 @@ void Span::begin(Category catID, const char *displayName, const char *hostNameBa delay(100); esp_ota_mark_app_invalid_rollback_and_reboot(); } - + } // begin /////////////////////////////// @@ -189,10 +194,10 @@ void Span::pollTask() { processSerialCommand("A"); } else { Serial.print("YOU MAY CONFIGURE BY TYPING 'W '.\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 @@ -323,12 +327,16 @@ int Span::getFreeSlot(){ ////////////////////////////////////// void Span::commandMode(){ + + 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("*** ENTERING COMMAND MODE ***\n\n"); + 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_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_ENTERING_CONFIG_MODE+mode)) } else { done=true; } } // button press } // while - statusLED.start(LED_ALERT); + STATUS_UPDATE(start(LED_ALERT),static_cast(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()1) // Do not initialize everything below if this is only a reconnect return; @@ -756,11 +751,11 @@ 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 + nvs_commit(wifiNVS); + 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); @@ -865,20 +850,18 @@ void Span::processSerialCommand(const char *c){ nvs_erase_all(charNVS); nvs_commit(charNVS); nvs_erase_all(otaNVS); - nvs_commit(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<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::SpanPoints; +uint16_t SpanPoint::channelMask=0x3FFE; +QueueHandle_t SpanPoint::statusQueue; + +/////////////////////////////// +// MISC // +/////////////////////////////// + +void __attribute__((weak)) loop(){ +} + +/////////////////////////////// + + + diff --git a/src/HomeSpan.h b/src/HomeSpan.h index 3bac4d5..ec02347 100644 --- a/src/HomeSpan.h +++ b/src/HomeSpan.h @@ -39,7 +39,10 @@ #include #include #include +#include +#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 @@ -254,11 +288,16 @@ class Span{ boolean updateDatabase(boolean updateMDNS=true); // updates HAP Configuration Number and Loop vector; if updateMDNS=true and config number has changed, re-broadcasts MDNS 'c#' record; returns true if config number changed 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 setControlPin(uint8_t pin){controlButton=new PushButton(pin);} // sets Control 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,7 +315,9 @@ 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 @@ -296,7 +337,7 @@ class Span{ void autoPoll(uint32_t stackSize=CONFIG_ARDUINO_LOOP_STACK_SIZE){xTaskCreateUniversal([](void *parms){for(;;)homeSpan.pollTask();}, "pollTask", stackSize, NULL, 1, &pollTaskHandle, 0);} // start pollTask() void setTimeServerTimeout(uint32_t tSec){webLog.waitTime=tSec*1000;} // sets wait time (in seconds) for optional web log time server to connect - + [[deprecated("Please use reserveSocketConnections(n) method instead.")]] void setMaxConnections(uint8_t n){requestedMaxCon=n;} // sets maximum number of simultaneous HAP connections }; @@ -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 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" diff --git a/src/Network.cpp b/src/Network.cpp index cc1c102..7cbaae1 100644 --- a/src/Network.cpp +++ b/src/Network.cpp @@ -116,8 +116,8 @@ 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"); scan(); // scan for networks @@ -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(); } } @@ -272,9 +268,9 @@ 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+="" "

Initiating WiFi connection to:

" + String(wifiData.ssid) + "

"; @@ -319,9 +315,9 @@ void Network::processRequest(char *body, char *formData){ WiFi.begin(wifiData.ssid,wifiData.pwd); } else { - - homeSpan.statusLED.start(LED_AP_CONNECTED); // slow double-blink - + + STATUS_UPDATE(start(LED_AP_CONNECTED),HS_AP_CONNECTED) + responseBody+="

SUCCESS! Connected to:

" + String(wifiData.ssid) + "

"; responseBody+="

You may enter new 8-digit Setup Code below, or leave blank to retain existing code.

"; @@ -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+="

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

" diff --git a/src/Network.h b/src/Network.h index 663f744..8f43653 100644 --- a/src/Network.h +++ b/src/Network.h @@ -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; diff --git a/src/Settings.h b/src/Settings.h index 73225ad..40d6bd8 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -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 diff --git a/src/Utils.cpp b/src/Utils.cpp index 5997e28..4fd4c68 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -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) 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) 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)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 - -}; - -//////////////////////////////////////////// diff --git a/src/extras/PwmPin.h b/src/extras/PwmPin.h index c3d3f4c..f012652 100644 --- a/src/extras/PwmPin.h +++ b/src/extras/PwmPin.h @@ -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 -#include - -#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] -}; diff --git a/src/extras/RFControl.h b/src/extras/RFControl.h index 34cceb2..bfbcaea 100644 --- a/src/extras/RFControl.h +++ b/src/extras/RFControl.h @@ -24,53 +24,6 @@ * SOFTWARE. * ********************************************************************************/ + +#include "../src/extras/RFControl.h" -//////////////////////////////////// -// RF Control Module // -//////////////////////////////////// - -#pragma once - -#include -#include -#include "driver/rmt.h" -#include - -using std::vector; - -class RFControl { - friend class Pixel; - - private: - rmt_config_t *config=NULL; - vector data; - boolean lowWord=true; - boolean refClock; - static uint8_t nChannels; - - RFControl(uint8_t pin, boolean refClock, boolean installDriver); // private constructor (only used by Pixel class) - - 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) - diff --git a/src/extras/extras.ino b/src/extras/extras.ino deleted file mode 100644 index 8d47d19..0000000 --- a/src/extras/extras.ino +++ /dev/null @@ -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(){ -} diff --git a/src/src.ino b/src/src.ino index 19e4ae3..abf2d6b 100644 --- a/src/src.ino +++ b/src/src.ino @@ -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)); +} diff --git a/src/src/extras/Blinker.cpp b/src/src/extras/Blinker.cpp new file mode 100644 index 0000000..e97ee34 --- /dev/null +++ b/src/src/extras/Blinker.cpp @@ -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;inBlinks;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)getPin()); +} diff --git a/src/src/extras/Blinker.h b/src/src/extras/Blinker.h new file mode 100644 index 0000000..633bc19 --- /dev/null +++ b/src/src/extras/Blinker.h @@ -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 +#include + +//////////////////////////////// +// 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);} +}; diff --git a/src/extras/Pixel.cpp b/src/src/extras/Pixel.cpp similarity index 99% rename from src/extras/Pixel.cpp rename to src/src/extras/Pixel.cpp index 479d115..1e87187 100644 --- a/src/extras/Pixel.cpp +++ b/src/src/extras/Pixel.cpp @@ -58,7 +58,9 @@ Pixel::Pixel(int pin, boolean isRGBW){ rmt_set_tx_intr_en(rf->getChannel(),false); // disable end-of-transmission interrupt txEndMask=RMT.int_ena.val; // save interrupt enable vector rmt_set_tx_intr_en(rf->getChannel(),true); // enable end-of-transmission interrupt - txEndMask^=RMT.int_ena.val; // find bit that flipped and save as end-of-transmission mask for this channel + 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); } /////////////////// diff --git a/src/src/extras/Pixel.h b/src/src/extras/Pixel.h new file mode 100644 index 0000000..e781129 --- /dev/null +++ b/src/src/extras/Pixel.h @@ -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 + +}; + +//////////////////////////////////////////// diff --git a/src/extras/PwmPin.cpp b/src/src/extras/PwmPin.cpp similarity index 100% rename from src/extras/PwmPin.cpp rename to src/src/extras/PwmPin.cpp diff --git a/src/src/extras/PwmPin.h b/src/src/extras/PwmPin.h new file mode 100644 index 0000000..d9506ec --- /dev/null +++ b/src/src/extras/PwmPin.h @@ -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 +#include +#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] +}; diff --git a/src/extras/RFControl.cpp b/src/src/extras/RFControl.cpp similarity index 100% rename from src/extras/RFControl.cpp rename to src/src/extras/RFControl.cpp diff --git a/src/src/extras/RFControl.h b/src/src/extras/RFControl.h new file mode 100644 index 0000000..34cceb2 --- /dev/null +++ b/src/src/extras/RFControl.h @@ -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 +#include +#include "driver/rmt.h" +#include + +using std::vector; + +class RFControl { + friend class Pixel; + + private: + rmt_config_t *config=NULL; + vector data; + boolean lowWord=true; + boolean refClock; + static uint8_t nChannels; + + RFControl(uint8_t pin, boolean refClock, boolean installDriver); // private constructor (only used by Pixel class) + + 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) + diff --git a/src/src/extras/extras.ino b/src/src/extras/extras.ino new file mode 100644 index 0000000..b179ae1 --- /dev/null +++ b/src/src/extras/extras.ino @@ -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 + +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(); +}