Compare commits

..

No commits in common. "master" and "release-1.5.1" have entirely different histories.

131 changed files with 5094 additions and 11874 deletions

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2020-2023 Gregg E. Berman
Copyright (c) 2020, 2021 Gregg E. Berman
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -61,6 +61,7 @@
#endif
#include "HomeSpan.h"
#include "extras/Pixel.h" // include the HomeSpan Pixel class
///////////////////////////////
@ -71,12 +72,12 @@ struct NeoPixel_RGB : Service::LightBulb { // Addressable single-wire RGB L
Characteristic::Saturation S{0,true};
Characteristic::Brightness V{100,true};
Pixel *pixel;
int nPixels;
uint8_t nPixels;
NeoPixel_RGB(uint8_t pin, int nPixels) : Service::LightBulb(){
NeoPixel_RGB(uint8_t pin, uint8_t nPixels) : Service::LightBulb(){
V.setRange(5,100,1); // sets the range of the Brightness to be from a min of 5%, to a max of 100%, in steps of 1%
pixel=new Pixel(pin); // creates Pixel RGB LED on specified pin
pixel=new Pixel(pin); // creates Pixel LED on specified pin
this->nPixels=nPixels; // save number of Pixels in this LED Strand
update(); // manually call update() to set pixel with restored initial values
}
@ -105,12 +106,12 @@ struct NeoPixel_RGBW : Service::LightBulb { // Addressable single-wire RGBW
Characteristic::Brightness V{100,true};
Characteristic::ColorTemperature T{140,true};
Pixel *pixel;
int nPixels;
uint8_t nPixels;
NeoPixel_RGBW(uint8_t pin, int nPixels) : Service::LightBulb(){
NeoPixel_RGBW(uint8_t pin, uint8_t nPixels) : Service::LightBulb(){
V.setRange(5,100,1); // sets the range of the Brightness to be from a min of 5%, to a max of 100%, in steps of 1%
pixel=new Pixel(pin,PixelType::GRBW); // creates Pixel RGBW LED on specified pin (with order of colors chnanged to reflect this specific NeoPixel device)
pixel=new Pixel(pin,true); // creates Pixel RGBW LED (second parameter set to true for RGBW) on specified pin
this->nPixels=nPixels; // save number of Pixels in this LED Strand
update(); // manually call update() to set pixel with restored initial values
}
@ -141,9 +142,9 @@ struct DotStar_RGB : Service::LightBulb { // Addressable two-wire RGB LED S
Characteristic::Saturation S{0,true};
Characteristic::Brightness V{100,true};
Dot *pixel;
int nPixels;
uint8_t nPixels;
DotStar_RGB(uint8_t dataPin, uint8_t clockPin, int nPixels) : Service::LightBulb(){
DotStar_RGB(uint8_t dataPin, uint8_t clockPin, uint8_t nPixels) : Service::LightBulb(){
V.setRange(5,100,1); // sets the range of the Brightness to be from a min of 5%, to a max of 100%, in steps of 1%
pixel=new Dot(dataPin,clockPin); // creates Dot LED on specified pins

View File

@ -26,6 +26,7 @@
********************************************************************************/
#include "HomeSpan.h" // include the HomeSpan library
#include "extras/RFControl.h" // include RF Control Library
void setup() {

View File

@ -3,6 +3,10 @@
// DEVICE-SPECIFIC LED SERVICES //
////////////////////////////////////
#include <extras/PwmPin.h>
////////////////////////////////////
struct DEV_WindowShade : Service::WindowCovering { // A motorized Window Shade with Hold Feature
SpanCharacteristic *current; // reference to a "generic" Current Position Characteristic (used by a variety of different Service)

View File

@ -189,10 +189,6 @@ void setup() {
new Characteristic::CurrentVisibilityState(0); // ...and included in the Selection List...
new Characteristic::TargetVisibilityState(0); // ...and a "checked" checkbox is provided on the Settings Screen that can be used to toggle CurrentVisibilityState()
SpanService *speaker = new Service::TelevisionSpeaker();
new Characteristic::VolumeSelector();
new Characteristic::VolumeControlType(3);
(new HomeSpanTV("Test TV")) // Define a Television Service. Must link in InputSources!
->addLink(hdmi1)
->addLink(hdmi2)
@ -204,7 +200,6 @@ void setup() {
->addLink(hdmi8)
->addLink(hdmi9)
->addLink(hdmi10)
->addLink(speaker)
;
}

194
README.md
View File

@ -1,194 +0,0 @@
# Welcome!
Welcome to HomeSpan - a robust and extremely easy-to-use Arduino library for creating your own [ESP32-based](https://www.espressif.com/en/products/modules/esp32) HomeKit devices entirely within the [Arduino IDE](http://www.arduino.cc).
HomeSpan provides a microcontroller-focused implementation of Apple's HomeKit Accessory Protocol Specification Release R2 (HAP-R2) designed specifically for the Espressif ESP32 microcontroller running within the Arduino IDE. HomeSpan pairs directly to HomeKit via your home WiFi network without the need for any external bridges or components. With HomeSpan you can use the full power of the ESP32's I/O functionality to create custom control software and/or hardware to automatically operate external devices from the Home App on your iPhone, iPad, or Mac, or with Siri.
HomeSpan requires version 2 of the [Arduino-ESP32 Board Manager](https://github.com/espressif/arduino-esp32). HomeSpan can be run on the original ESP32 as well as Espressif's ESP32-S2, ESP32-C3, and ESP32-S3 chips.
HomeSpan is currently NOT compatible with version 3.X of the Arduino-ESP32 Board Manager, since version 3 contains many breaking changes and is not backwards-compatible with version 2.X of the Arduino-ESP32 Board Manager. At present, HomeSpan can only be compiled under version 2.X of the Board Manager.
> [!NOTE]
> Apple's new HomeKit architecture [requires the use of a Home Hub](https://support.apple.com/en-us/HT207057) (either a HomePod or Apple TV) for full and proper operation of any HomeKit device, including those based on HomeSpan. Without a Home Hub, HomeSpan cannot send notifications to the Home App - things like pushbuttons and temperature sensors will not be able to transmit updates to the Home App. Use of HomeSpan without a Home Hub is NOT recommended.
### HomeSpan Highlights
* Provides a natural, intuitive, and **very** easy-to-use framework
* Utilizes a unique *Service-Centric* approach to creating HomeKit devices
* Takes full advantage of the widely-popular Arduino IDE
* 100% HAP-R2 compliance
* Dozens of integrated HomeKit Services
* Operates in either Accessory or Bridge mode
* Supports pairing with Setup Codes or QR Codes
### For the HomeSpan Developer
* Extensive use of the Arduino Serial Monitor
* Real-time, easy-to-understand diagnostics
* Complete transparency to every underlying HomeKit action, data request, and data response
* Command-line interface with a variety of info, debugging, and configuration commands
* Built-in database validation to ensure your configuration meets all HAP requirements
* Dedicated classes that utilize the ESP32's 16-channel PWM peripheral for easy control of:
* LED Brightness (including auto-fading)
* Servo Motors
* Integrated Push Button and Toggle Switch functionality supporting single, double, and long presses of:
* Physical pushbuttons that connect an ESP32 pin to either ground or VCC
* Touch pads/sensors connected to an ESP32 pin (for ESP32 devices that support touch pads)
* Integrated access to the ESP32's on-chip Remote Control peripheral for easy generation of IR and RF signals
* Dedicated classes to control one- and two-wire addressable RGB and RGBW LEDs and LED strips
* Dedicated classes to control stepper motors that can run smoothly in the background without interfering with HomeSpan
* Dedicated class that faciliates seamless point-to-point communication between ESP32 devices using ESP-NOW
* Integrated Web Log for user-defined log messages
* 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
* A complete set of documentation explaining every aspect of the HomeSpan API
### For the HomeSpan End-User
* Embedded WiFi Access Point and Web Interface to allow end-users (non-developers) to:
* Set up Homespan with their own home WiFi Credentials
* Create their own HomeKit Pairing Setup Code
* Status LED and Control Button to allow end-users to:
* Force-unpair the device from HomeKit
* Perform a Factory Reset
* Launch the WiFi Access Point
* A standalone, detailed End-User Guide
## ❗Latest Update - HomeSpan 1.9.1 (07/03/2024)
* **HomeSpan now supports *Tag-Length-Value ("TLV8")* Characteristics!**
* adds new, fully-integrated `TLV8()` class library for the creation and management of TLV8 objects
* includes methods to handle standard byte-stream VALUES as well as strings, numerical values, zero-length tags, and sub-TLVs
* utilizes standard C++ iterators for easy access to reading and writing TLV8 records
* adds new `Characteristic` methods `getTLV()`, `getNewTLV()`, and `setTLV()`
* adds new `CUSTOM_CHAR_TLV8()` that allows for easy creation of custom TLV8 Characteristics
* includes new [Tutorial Example 22 - TLV8 Characteristics](examples/22-TLV8_Characteristics) demonstrating use of the `TLV8()` class and TLV8 Characteristics
* see the new [TLV8 Characteristics](docs/TLV8.md) page for complete details and documentation
* **New *DisplayOrder* TLV8 Characteristic**
* utlizes HomeSpan's new `TLV8()` library
* allows you to specify the exact order in which the Input Sources for a Television Service are displayed in the Home App
* see [Tutorial Example 22 - TLV8 Characteristics](examples/22-TLV8_Characteristics) for details
* **New *AccessoryIdentifier* Tutorial**
* demonstrates how to trigger an Accessory's Identifier Characteristic, optionally used to help identify a device during initial pairing
* see [Tutorial Example 21 - AccessoryIdentifier](examples/21-AccessoryIdentifier)
* **Added support for customizing Pixel chips**
* new constructor `Pixel(uint8_t pin, [pixelType_t pixelType])` allows your to set the order in which colors are transmitted to the pixel chip, where *pixelType* is one of the following:
* PixelType::RGB, PixelType::RBG, PixelType::BRG, PixelType::BGR, PixelType::GBR, PixelType::GRB
* PixelType::RGBW, PixelType::RBGW, PixelType::BRGW, PixelType::BGRW, PixelType::GBRW, PixelType::GRBW*
* deprecated previous constructor `Pixel(uint8_t pin, boolean isRGBW)`
* this constructor will continue to work, but you will receive a warning during compilation that it has been deprecated
* users should switch to the new constructor to avoid potential compatibility issues with future versions of HomeSpan
* added new method `boolean isRGBW()`
* returns *true* if Pixel was constructed as RGBW, else *false* if constructed as RGB only (i.e. no white LED)
* added new [PixelTester](examples/Other%20Examples/PixelTester) sketch (found under *Other Examples*) to aid in determining the *pixelType* for any LED Strip
* see the [Adressable RGB LEDs](docs/Pixels.md) page for details
* **New ability to read and set the IIDs of Services and Characteristics**
* adds new `SpanService` method `getIID()` that returns the IID of a Service
* adds new `SpanCharacteristic` method `getIID()` that returns the IID of a Characteristic
* adds new `homeSpan` method `resetIID(int newIID)` that resets the IID count for the current Accessory
* see the [API Reference](docs/Reference.md) for details
* **New ability to read Controller pairing data (for advanced use-cases only)**
* adds new `homeSpan` methods `controllerListBegin()` and `controllerListEnd()` that returns iterators to HomeSpan's internal linked-list of Controller data records
* adds new methods to read each Controller's pairing data:
* `getID()` - returns a pointer to the 36-byte Device ID of the controller
* `getLTPK()` - a pointer to the 32-byte Long-Term Public Key of the controller
* `isAdmin()` - returns true if the controller has admin permission, else returns false
* adds new `homeSpan` method `setControllerCallback()` to set optional callback function that HomeSpan calls whenever a controller is added, removed, or updated
* see the [API Reference](docs/Reference.md) for details
* **HomeSpan now supports the *write-response ("WR")* protocol**
* added automated handling of the HomeKits's *write-response ("WR")* protocol*
* not needed for any Characteristics that are currently supported by HomeSpan, but useful for experimentation and work with Custom Characteristics
* added extra checks when using `setVal()`
* a warning message is output on the Serial Monitor if `setVal()` is called to change the value of a Characteristic from within the `update()` method at the same time the Home App is sending an update request for that value
* does not apply if `setVal()` is called from within `update()` to change the value of a Characteristic in response to a *write-response* request from the Home App
* **Converted the `getLinks()` SpanService method to a template function**
* allows user to automatically cast the elements of the returned vector into any specific Service type
* also adds an optional parameter to restrict the elements of the returned vector to match a specified HomeSpan Service
* see the [API Reference](docs/Reference.md) for details
* **New ability to halt the pulse generation for a ServoPin**
* calling `set(NAN)` for a ServoPin halts the pulse generation, which (for most analog servos) allows the motor to be freely rotated
* calling `set(position)`, where *position* equal the desired number of degrees, restarts the pulse generation and sets the servo position accordingly
* **Refactored client/slot management to save memory and prepare for future integration of Ethernet support**
* fixed-array of Client/Socket connections replaced by dynamic linked-list
* serial interface now only shows active client connections (rather than a fixed list of client slots)
* **deprecated** `homeSpan.reserveSocketConnections()` since it is no longer needed
* **Fixed bug introduced in 1.9.0 that prevented `homeSpan.setPairingCode()` from saving (and subsequently using) the request Setup Pairing Code**
* this method now operates silently, unless an invalid pairing code is provided, in which case an error is reported to the Serial Monitor and *the sketch is halted*
* the process for setting the Pairing Code using the CLI 'S' command or via the Access Point are unchanged - confirmation messages are still output to the Serial Monitor and errors do *not* cause the sketch to halt
* **Fixed memory leak introduced in 1.9.0 that would fail to free a small temporary memory block created when verifying a new connection**
* had no practical impact when using a Home Hub since Home Kit only creates a few permanent connections
* had significant impact when not using a Home Hub in cases where the Home App repeatedly drops and re-establishes connections, resulting in slow erosion of heap memory and then out-of-memory failure of the device after a few days (note use of HomeSpan without a Home Hub is not formally supported)
* **Fixed latent bug in SpanPoint**
* HomeSpan would crash when printing **SpanPoint** configuration information to the Serial Monitor (the 'i' CLI command) if any of the instances of SpanPoint had *receiveSize=0*
* this bug never surfaced before since all the **SpanPoint examples** were based on receiving data and therefore had a non-zero *receiveSize*
* **Deleted `homeSpan.setMaxConnections()`, which had been *deprecated* many versions ago**
* **Deleted stand-alone `SpanRange` structure, which had been *deprecated* many versions ago**
* this has no impact on standard use of the Characteristic method `setRange()`
See [Releases](https://github.com/HomeSpan/HomeSpan/releases) for details on all changes and bug fixes included in this update.
# HomeSpan Resources
HomeSpan includes the following documentation:
* [Getting Started with HomeSpan](docs/GettingStarted.md) - setting up the software and the hardware needed to develop HomeSpan devices
* [HomeSpan API Overview](docs/Overview.md) - an overview of the HomeSpan API, including a step-by-step guide to developing your first HomeSpan Sketch
* [HomeSpan Tutorials](docs/Tutorials.md) - a guide to HomeSpan's tutorial-sketches
* [HomeSpan Services and Characteristics](docs/ServiceList.md) - a list of all HAP Services and Characterstics supported by HomeSpan
* [HomeSpan Accessory Categories](docs/Categories.md) - a list of all HAP Accessory Categories defined by HomeSpan
* [HomeSpan Command-Line Interface (CLI)](docs/CLI.md) - configure a HomeSpan device's WiFi Credentials, modify its HomeKit Setup Code, monitor and update its status, and access detailed, real-time device diagnostics from the Arduino IDE Serial Monitor
* [HomeSpan User Guide](docs/UserGuide.md) - turnkey instructions on how to configure an already-programmed HomeSpan device's WiFi Credentials, modify its HomeKit Setup Code, and pair the device to HomeKit. No computer needed!
* [HomeSpan API Reference](docs/Reference.md) - a complete guide to the HomeSpan Library API
* [HomeSpan QR Codes](docs/QRCodes.md) - create and use QR Codes for pairing HomeSpan devices
* [HomeSpan OTA](docs/OTA.md) - update your sketches Over-the-Air directly from the Arduino IDE without a serial connection
* [HomeSpan PWM](docs/PWM.md) - integrated control of standard LEDs and Servo Motors using the ESP32's on-chip PWM peripheral
* [HomeSpan RFControl](docs/RMT.md) - easy generation of RF and IR Remote Control signals using the ESP32's on-chip RMT peripheral
* [HomeSpan Pixels](docs/Pixels.md) - integrated control of addressable one- and two-wire RGB and RGBW LEDs and LED strips
* [HomeSpan Stepper Motor Control](docs/Stepper.md) - integrated control of stepper motors, including PWM microstepping
* [HomeSpan SpanPoint](docs/NOW.md) - facilitates point-to-point, bi-directional communication between ESP32 Devices using ESP-NOW
* [HomeSpan Television Services](docs/TVServices.md) - how to use HomeKit's undocumented Television Services and Characteristics
* [HomeSpan Message Logging](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
* [HomeSpan TLV8 Characteristics](docs/TLV8.md) - classes and methods for creating TLV8 objects to use with TLV8-based Characteristics
* [HomeSpan Device Cloning](docs/Cloning.md) - seamlessly swap a broken device for a new one without needing to re-pair and lose HomeKit automations
* [HomeSpan Projects](https://github.com/topics/homespan) - real-world applications of the HomeSpan Library
* [HomeSpan FAQ](docs/FAQ.md) - answers to frequently-asked questions
* [Solutions to Common Problems](docs/Solutions.md) - resolutions to some common problems when using/compiling HomeSpan
* [HomeSpan Reference Sketches](https://github.com/HomeSpan/HomeSpanReferenceSketches) - a collection of self-contained Reference Sketches showcasing some of the more complex HomeKit Services, such as Thermostats and Irrigation Systems
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.
# External Resources
In addition to HomeSpan resources, developers who are new to HomeKit programming may find useful Chapters 8 and 9 of Apple's HomeKit Accessory Protocol Specification, Non-Commercial Version, Release R2 (HAP-R2). This document is unfortunately no longer available from Apple (perhaps because it was last updated July, 2019, and is now somewhat out-of-date). However, you may be able find copies of this document elsewhere on the web. Note Apple has not replaced the HAP-R2 document with any other versions for non-commercial use, and Apple's open-source [HomeKit ADK](https://github.com/apple/HomeKitADK) only reflects the original HAP-R2 specs (rather than all the latest Services and Characteristics available in HomeKit for commercial devices).
---
### Feedback or Questions?
Please post bug reports or other problems with HomeSpan to the [Issues Board](https://github.com/HomeSpan/HomeSpan/issues). Please post all other questions about HomeSpan (use, functionality, specs, examples, etc.) or any ideas or recommendations you may have for new functionality, or any general feedback about HomeSpan or HomeKit, to the [Discussion Board](https://github.com/HomeSpan/HomeSpan/discussions). For more general questions or feedback not related to a specific HomeSpan issue or function, you can email me directly at [homespan@icloud.com](mailto:homespan@icloud.com).
### About the Author
HomeSpan was developed and continues to be maintained and supported by Gregg Berman. It was originally conceived to solve the pesky problem of not being able to operate an RF-controlled kitchen vent hood with Siri. I hope you find it useful as well as fun to use.
This is my second large-scale open-source project --- my first was the design of an open-source sytem for operating model railroads using nothing more than an Arduino Uno and Arduino Motor Shield to generate digital command and control (DCC) signals. Though I have not been involved with the model railroading hobby for many years, videos showcasing my original system (dubbed DCC++), along with detailed tutorials of how it works, are still available on the [DCC++ YouTube Channel](https://www.youtube.com/@dcc2840/videos).

View File

@ -1,49 +0,0 @@
# Creating a Custom Status LED with the Blinkable Interface
The HomeSpan Status LED conveys information about the state of HomeSpan to the user through different blinking patterns. The *homeSpan* `setStatusPin()` and `setStatusPixel()` methods allow you to choose, respectively, either a standard LED or a NeoPixel LED as the Status LED. However, the Status LED can be set to any object that implements the **Blinkable** interface[^1] using the *homeSpan* method `setStatusDevice(Blinkable *sDev)`, where *sDev* is a Blinkable object.
To create your own Blinkable object, start by creating a child class derived from **Blinkable**. Next, add a constructor that defines the pins and performs any initializations if needed. Finally, define the following *required* methods that **Blinkable** calls to blink the device:
* `void on()` - turns on the device (e.g. causes an LED to light)
* `void off()` - turns off the device (e.g. causes an LED to go dark)
* `int getPin()` - returns the pin number of the device (any number is fine; does not have to be an actual ESP32 pin)
For example, the following defines a Blinkable object for an inverted LED that turns *on* when an ESP32 pin is LOW, and turns *off* when the ESP32 pin is HIGH:
```C++
// CREATE THIS STRUCTURE SOMEWHERE NEAR TOP OF SKETCH
struct invertedLED : Blinkable { // create a child class derived from Blinkable
int pin; // variable to store the pin number
invertedLED(int pin) : pin{pin} { // constructor that initializes the pin parameter
pinMode(pin,OUTPUT); // set the pin to OUTPUT
digitalWrite(pin,HIGH); // set pin HIGH (which is off for an inverted LED)
}
void on() override { digitalWrite(pin,LOW); } // required function on() - sets pin LOW
void off() override { digitalWrite(pin,HIGH); } // required function off() - sets pin HIGH
int getPin() override { return(pin); } // required function getPin() - returns pin number
};
...
// THEN USE THE STRUCTURE IN SETUP() TO SET THE STATUS LED
void setup(){
homeSpan.setStatusDevice(new invertedLED(13)); // set Status LED to be a new Blinkable device attached to pin 13
...
}
```
[^1]: In C++, an *interface* is any abstract class that contains only pure virtual functions. You cannot instantiate an interface, but you can instantiate any derived child classes from the interface provided that you define each of the required virtual functions.
---
[↩️](Reference.md) Back to the Reference API page

View File

@ -35,9 +35,6 @@ In addition to listening for incoming HAP requests, HomeSpan also continuously p
* **d** - print the full HAP Accessory Attributes Database in JSON format
* This outputs the full HAP Database in JSON format, exactly as it is transmitted to any HomeKit device that requests it (with the exception of the newlines and spaces that make it easier to read on the screen). Note that the value tag for each Characteristic will reflect the *current* value on the device for that Characteristic.
* **m** - print free heap memory (in bytes)
* This prints the amount of memory available for use when creating new objects or allocating memory. Useful for developers only.
* **W** - configure WiFi Credentials and restart
* HomeSpan sketches *do not* contain WiFi network names or WiFi passwords. Rather, this information is separately stored in a dedicated Non-Volatile Storage (NVS) partition in the ESP32's flash memory, where it is permanently retained until updated (with this command) or erased (see below). When HomeSpan receives this command it first scans for any local WiFi networks. If your network is found, you can specify it by number when prompted for the WiFi SSID. Otherwise, you can directly type your WiFi network name. After you then type your WiFi Password, HomeSpan updates the NVS with these new WiFi Credentials, and restarts the device.
@ -71,14 +68,8 @@ In addition to listening for incoming HAP requests, HomeSpan also continuously p
* **H** - delete HomeKit Device ID as well as all Controller data and restart
* In addition to deleting all Controller data (as if the 'U' command was run), this command also deletes the device's HomeKit ID. This unique ID is broadcast to all HomeKit Controllers so the device can be uniquely recognized. When HomeSpan first runs on a new device, it creates this unique ID and stores it permanently in an NVS partition. Normally, this ID should not changed once set. However, if you are actively developing and testing a HomeSpan sketch, you may find that HomeKit is cacheing information about your device and the changes you have made to your HAP Accessory Database are not always reflected in the Home App. Sometimes simply unpairing and re-pairing the device solves this HomeKit issue. If not, deleting your device's HomeKit ID with this command forces HomeSpan to generate a new one after restarting, which means HomeKit will think this is a completely different device, thereby ignoring any prior data it had cached.
* This command also restores the device's default Setup ID, which is used for optional pairing with QR codes, to "HSPN".
* This command also restores the device's default Setup ID, which is used for optional pairing with QR codes, to "HSPN".
* **P** - prints the device's Pairing Data in base-64 chunks
* Used for [Cloning](Cloning.md) the Pairing Data from one device to another
* **C** - prompts you to input the Pairing Data from another device in base-64 chunks
* Used for [Cloning](Cloning.md) the Pairing Data from one device to another
* **R** - restart the device
* This command simply reboots HomeSpan.
@ -103,7 +94,7 @@ You can extend the HomeSpan CLI with custom functions using `SpanUserCommand()`.
---
[↩️](../README.md) Back to the Welcome page
[↩️](README.md) Back to the Welcome page

View File

@ -1,6 +1,6 @@
# HomeSpan Accessory Categories
Every HomeSpan device must be assigned a HomeKit Accessory Category. HomeSpan implements these categories as C++ Classes with names that exactly match the spelling and capitalization specified by Apple in Section 13 of HAP-R2, but without any spaces. HomeSpan Accessory Categories are defined in HomeSpan's `Category` namespace. For example, HomeSpan defines the *Garage Door Openers* Category (HAP Category 4) as `Category::GarageDoorOpeners`, which could be used when initializing HomeSpan as follows:
Every HomeSpan device must be assigned a HomeKit Accessory Category. HomeSpan implements these categories as C++ Classes with names that exactly match the spelling and capitalization specified by Apple in Section 13 of [HAP-R2](https://developer.apple.com/homekit/specification/), but without any spaces. HomeSpan Accessory Categories are defined in HomeSpan's `Category` namespace. For example, HomeSpan defines the *Garage Door Openers* Category (HAP Category 4) as `Category::GarageDoorOpeners`, which could be used when initializing HomeSpan as follows:
```C++
homeSpan.begin(Category::GarageDoorOpeners,"Acme Garage Door Lifts");
@ -41,5 +41,5 @@ Note that the HomeKit primarily uses the Accessory Category of a device for dete
---
[↩️](../README.md) Back to the Welcome page
[↩️](README.md) Back to the Welcome page

View File

@ -1,88 +0,0 @@
# Cloning Pairing Data from one Device to another
### HomeSpan Pairing Data
Even though two different ESP32 devices may be running the exact same sketch, they are nevertheless distinct. This is because every HomeSpan Accessory has unique 17-character Device ID, a unique 32-byte long-term public key (LTPK), and a unique 64-byte long-term secret key (LTSK). When HomeSpan is run for the first time on a new device, it looks for these data in the device's non-volatile storage (NVS) memory. If it is found, the data is loaded for use. If not found, HomeSpan generates a new set of random keys, and saves this data in the NVS. The data is permanently stored, though can be erased by typing 'H' into the CLI, which causes HomeSpan to generate a new set of random keys upon the next reboot.
When HomeSpan is initially paired to HomeKit, the 36-character Device ID and 32-byte LTPK for one or more HomeKit Controllers is securely transmitted to the HomeSpan Accessory. These keys are then saved in the device's NVS for permanent retention (though can be erased by the 'H' command).
Collectively, the Accessory Device ID, LTPK and LTSK, along with the Device ID and LTPK for each paired Controller, is known as the device's *Pairing Data*. You can view the Pairing Data (except for the LTSK) for any HomeSpan Accessory by typing 'S' into the CLI. Here is an example:
```
*** HomeSpan Status ***
IP Address: 192.168.1.11
Accessory ID: 77:D2:F6:99:CE:65 LTPK: 346A544A876B124E50F9E3CC276A29D23E8B5DD0590138AA59C833A0D2096E37
Paired Controller: A487DE69-81C3-B5ED-8762-C3B9A987F967 (admin) LTPK: EE12A678DD56C4E9C0D935A341B8E6C6C098A6B3E6D4C5F5F914A54C9E85BA76
Paired Controller: 449AD09E-109D-3EB5-25B4-8A04E5C57D65 (admin) LTPK: 34A6B57DE881A75B647D2C9C68E76745A3B466577D19E4C78A67A68C4ED959B8
Connection #0 192.168.1.29 on Socket 3/16 ID=A487DE69-81C3-B5ED-8762-C3B9A987F967 (admin)
Connection #1 (unconnected)
Connection #2 (unconnected)
Connection #3 (unconnected)
Connection #4 (unconnected)
Connection #5 (unconnected)
Connection #6 (unconnected)
Connection #7 (unconnected)
Connection #8 (unconnected)
Connection #9 (unconnected)
Connection #10 (unconnected)
Connection #11 (unconnected)
*** End Status ***
```
### Cloning a HomeSpan Device
Because every device has a unique set of Pairing Data, it is not possible to simply swap a device that is already paired to HomeKit with another one that is not yet paired - the Device IDs will be different and the second device will need to be separately paired.
This can present a problem if you need to swap out a device (perhaps because it has malfunctioned or was damaged) and you have created a lot of custom automations and scenes for the device from within the Home App. The new device you use to replace the old device will be seen as a completely new Accessory by HomeKit, and will not be connected with any automations or scenes associated with the old device. In fact, if your unpair the old device, automations and scenes specific to that device will be lost.
To solve this problem you need to be able to replace the broken device with a new device, but *without* unpairing the old device or re-pairing the new device. This requires the new device to be initialized not with a new set of randomly-generated Device IDs, LTPKs and LTSKs, but rather with the *same* Pairing Data as the old device.
Fortunately, HomeSpan provides a methods for "cloning" the Pairing Data from one device to another. This means you can swap out a broken device for a new device without HomeKit knowing the difference (provided it is running the same sketch of course). In fact, you can even swap out an ESP32 for an ESP32-S2, or ESP32-C3. As long as the sketch is the same, once you clone the Pairing Data the devices are effectively hot-swappable.
Cloning HomeSpan's Pairing Data is a two-step process. First you output the Pairing Data from one device to the Serial Monitor, then you copy and paste this data into the Serial Monitor of the second device. Of course if the first device is completely broken you will not be able to output its Pairing Data. If you create a lot of automations in HomeKit you may want to output the Pairing Data from each of your devices and save it in a plain text file for later use should any device need to be replaced in the future.
#### Step 1: Type 'P' into the Serial Monitor CLI of the first device to output its Pairing Data
Unlike the 'S' command, the 'P' command compresses all the Pairing Data into *base-64* chunks to make it easier to copy and paste as follows:
```
*** Pairing Data used for Cloning another Device
Accessory data: ZzbH11I8uNx47Y3Bapq3axQfY5uPOrDfC8D2Q6ke2NwWqat/IGa/6ll8xyY8AShMYO2q6h8gZr/qWXzHJjwBKExg7arqFnNsfXUjy43HgNzc6RDI6RjY6OTk6Q0U6NjUb7mHwbmWzrEWca+5frayfmp=
Controller data: YaNJH5JYDAQE4NjI0NTAwNy02Mi1FRUY4ODNENTA2NjdDvTRGLTRBRDEtQjkwRXFM1On32PKvumS+0YgVMaEo53X/TYNzg==
Controller data: MEUwLTREMEUtODk3Ni0yMjBDREQ2RDUxMjjmah3s+Je0GkmAQE0NDQ1NUE2Ni1ExIUkujzeyWfCCRWol/xecsVkjAIYDRQ==
*** End Pairing Data
```
The first line completely encodes the Pairing Data for the HomeSpan Accessory. The second two lines encode the Pairing Data for two Controllers that HomeKit is using to control the HomeSpan device. Note your system may only have one Controller, or it may have more than two. The number of Controllers depends on your HomeKit network, how it is configured, what devices you have (Apple TVs, HomePods, etc.) and what version of iOS you are running.
Copy this data, exactly as is, from the CLI and save it in a text file. Make sure not to lose any trailing equal signs as they are part of the base&#8209;64 data!
Next, power down the first device, or at least remove it from the WiFi network to avoid potential duplications of two devices running on the same network with identical Pairing Data (HomeKit will likely not behave if this occurs). If the second device is not plugged in, do so now and open its Serial Monitor.
#### Step 2: Type 'C' into the Serial Monitor CLI of the second device to input the Pairing Data you just saved from the first device
HomeSpan will begin by asking you for the Accessory Pairing Data. Copy and paste this data (it's the first set of base-64 data output in Step 1 above) directly into the Serial Monitor input window and hit return. If you copied the data correctly it will be accepted and HomeSpan will display the Device ID that was encoded in the data (it does not bother to display the LTPK and LTSK data). The Device ID should match that of the orignal device.
If you copied or pasted the data incorrectly, HomeSpan will let you know there is a problem, cancel the process, and reboot without making any changes. You can also cancel the process by simply hitting return after typing 'P' *without* entering any data (this does not cause a reboot, since no data was changed).
After the Accessory data is accepted, HomeSpan will then ask for Controller data. Copy and paste this base-64 data from one of the Controllers in the saved text file directly into the Serial Monitor input window and hit return. As before, if you copied and pasted correctly, HomeSpan will accept the data and display the Device ID of the Controller. If you copied and pasted incorrectly, HomeSpan will inform you of the error, cancel the process, and reboot without making any changes.
Assuming the data for the first Controller has been accepted, HomeSpan will ask you to repeat the process for any other Controllers you may have. Keep repeating the process for copying and pasting the Pairing Data for each Controller. When you have input the Pairing Data for all Controllers, simply hit return without entering any data when asked for the next Controller. An empty response tells HomeSpan you are done adding Controller data.
Finally, HomeSpan will ask you to confirm saving the new data. Type either 'y' to confirm (yes) or 'n' to cancel (no). If you type 'n', HomeSpan will reboot without saving any of the changes.
If you type 'y', HomeSpan will save all of the new Pairing Data in the device's NVS and reboot. Upon restarting, this second device will be a perfect clone of the first device and HomeKit should recognize it as if it were the original. You will not need to re-pair the device or make any other changes to the Home App.
❗Caution: Do NOT run two devices on the same HomeKit network with the same Pairing Data. If you want to experiment by Cloning a working device onto a second device, make sure to unplug the first device before cloning the data onto the second device. When you are finished experimenting, type 'H' into the CLI of one of the devices so the cloned Pairing Data will be erased and re-generated into something once again unique, allowing you to plug both devices in at the same time without conflict.
---
[↩️](../README.md) Back to the Welcome page

View File

@ -2,29 +2,19 @@
*A list of answers to frequently-asked-questions, as well as discussions of various topics of interest.*
#### How do I set my WiFi SSID and Password (i.e. WiFi Credentials)?
#### How do I set my WiFi SSID and Password?
* Though commercial HomeKit devices can automatically retrieve WiFi Credentials from an iPhone, Apple does not provide this mechanism in the non-commerical version of HomeKit used by HomeSpan. Instead, you need to add your WiFi Credentials to a HomeSpan device using one of four methods:
* In the spirit of promoting good security practices, HomeSpan does not allow you to embed your WiFi SSID and Password (collectively known as WiFi Credentials) into your sketch. This eliminates the possibility of accidentally disclosing this information should you share your sketch with others.
* From within the Arduino IDE, simply use the HomeSpan Command Line Interface and type 'W' into the Serial Monitor. HomeSpan will prompt you to input your WiFi SSID and Password. You will only need to do this once since HomeSpan stores the WiFi Credentials you provide in the device's non-volatile storage (NVS) for use every time the device boots. See the [HomeSpan CLI](CLI.md) for complete details.
* As an alternative, and especially if your HomeSpan device is not connected to a computer, you can launch HomeSpan's Temporary WiFi Setup Network and input your WiFi Credentials directly into the temporary web form served by HomeSpan. Similar to above, this only needs to be done once since HomeSpan likewise stores the WiFi Credentials you input using this method. See the [HomeSpan User Guide](UserGuide.md#setting-homespans-wifi-credentials-and-setup-code) for details.
* If you really want to, you can programmatically set your WiFi Credentials directly in your sketch using HomeSpan's `setWifiCredentials()` method. However, this is **not** recommended since hardcoding sensitive passwords into a sketch poses a security risk, and is generally not considered a good practice. See the [HomeSpan API Reference](Reference.md) for details.
* Finally, for advanced users, HomeSpan provides an API hook `setApFunction()` that allows you to create your own custom method for inputting your WiFi Credentials. See the [HomeSpan API Reference](Reference.md) for details.
* Instead, HomeSpan stores your WiFi Credentials in a dedicated non-volatile storage (NVS) partition of the ESP32. There are two ways of entering this information. If you are developing a sketch within the Arduino IDE, simply use the HomeSpan Command Line Interface and type 'W' into the Serial Monitor. HomeSpan will prompt you to input your WiFi SSID and Password (see [CLI](CLI.md) for details). As an alternative, if your HomeSpan device is not connected to a computer, you can launch HomeSpan's Temporary WiFi Setup Network and input your WiFi Credentials directly into the web forms served by your device (see the [User Guide](UserGuide.md#setting-homespans-wifi-credentials-and-setup-code) for details).
#### What is the Setup Code used for pairing a HomeSpan device to HomeKit?
* The HomeSpan default Setup Code is 466-37-726. You can (and should) use the HomeSpan Command Line Interface to change this default to a secret code unique for each of your devices. Simply type 'S \<code\>' into the Serial Monitor, or specify your desired Setup Code at the outset when configuring your WiFi Credentials using HomeSpan's Temporary WiFi Setup Network. Alternatively, you can programmatically set the Setup Code in your sketch using HomeSpan's `setPairingCode()` method. However, hardcoding your Setup Code in a sketch is generally considered a security risk, as well as being inconsistent with Apple's HomeKit guidelines.
* The HomeSpan default Setup Code is 466-37-726. You can (and should) use the HomeSpan Command Line Interface to change this default to a secret code unique for each of your devices. Simply type 'S \<code\>' into the Serial Monitor, or specify your desired Setup Code at the outset when configuring your WiFi Credentials using HomeSpan's Temporary WiFi Setup Network.
#### Can you use more than one HomeSpan device on the same HomeKit network?
* Yes, multiple ESP32's, each running a separate copy of HomeSpan, can be used on the same HomeKit network, provided that each device has a unique *Device ID*, a unique *Host Name*, and a unique *Display Name*. Normally, the *Device ID* is randomly-generated by HomeSpan at start-up and will therefore automatically be unique across multiple devices. Also, unless you override the suffix of the default *Host Name*, it will also be unique across multiple devices since HomeSpan uses the *Device ID* as the suffix of the *Host Name*. The only thing you need to ensure is that you assign a different *Display Name* to each of your devices. See the [HomeSpan API Reference](Reference.md) for details on how to do this.
#### Does HomeSpan require the use of a HomeKit Hub, such as a HomePod or Apple TV?
* Yes, if you want HomeSpan to be able to send notifications, such as status updates if a contact sensor is opened, or updates from a temperature sensor. If your device can only be operated from the Home App (i.e. it has no local control buttons) and does not generate any status messages, than a HomeKit Hub would not be necessary. Note you will also need a HomeKit Hub if you wish to control your device via the Internet when away from your local WiFi network. These requirements and conditions are not specific to HomeSpan, but apply to all commercial HomeKit devices as a result of Apple updating its HomeKit Architecture (as of iOS 16.4).
* Yes, multiple ESP32's, each running a separate copy of HomeSpan, can be used on the same HomeKit network, provided that each device has a unique *Device ID*, a unique *Host Name*, and a unique *Display Name*. Normally, the *Device ID* is randomly-generated by HomeSpan at start-up and will therefore automatically be unique across multiple devices. Also, unless you override the suffix of the default *Host Name*, it will also be unique across multiple devices since HomeSpan uses the *Device ID* as the suffix of the *Host Name*. The only thing you need to ensure is that you assign a different *Display Name* to each of your devices. See the [HomeSpan API Reference](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Reference.md) for details on how to do this.
#### Does HomeSpan support video and audio streams?
@ -36,7 +26,11 @@
#### Will HomeSpan work on an ESP8266 device?
* No, HomeSpan is coded specifically for the ESP32 and will not operate on an ESP8266 device, though an ESP8266 can be used as a remote [SpanPoint ESP-NOW](NOW.md) Device.
* No, HomeSpan is coded specifically for the ESP32 and will not operate on an ESP8266 device.
#### Will HomeSpan work on an ESP32-S2 or ESP32-C3?
* Yes! Starting with version 1.4.0, HomeSpan is fully compatible with Espressif's ESP32-S2 and ESP32-C3 chips, as well as the original ESP32 chip. Note that to select an ESP32-S2 or ESP32-C3 device from the Arduino IDE you'll need to install Version 2 of the [Arduino-ESP32 Board Manager](https://github.com/espressif/arduino-esp32).
#### How can I read HomeSpan's MDNS broadcast mentioned in the [OTA](OTA.md) documentation?
@ -48,11 +42,7 @@
#### Does HomeSpan support Television Services?
* Yes. Though undocumented by Apple and not officially part of HAP-R2, HomeSpan supports HomeKit Television controls. See [Television Services](TVServices.md) for details.
#### Can you use HomeSpan via Bluetooth?
* No. HomeSpan does not support Apple's HAP-R2 protocol for HomeKit connectivity via Bluetooth. However, you can still use the Bluetooth radio on your device for other connectivity unrelated to HomeKit if needed.
* Yes. Though undocumented by Apple and not officially part of HAP-R2, HomeSpan supports HomeKit Television controls. See [Television Services](../docs/TVServices.md) for details.
#### Can you use HomeSpan with an Ethernet connection instead of a WiFi connection?
@ -64,35 +54,17 @@
#### Can you add a Web Server to HomeSpan?
* Yes, provided you implement your Web Server using standard ESP32-Arduino libraries, such as `WebServer.h`. See [ProgrammableHub](../examples/Other%20Examples/ProgrammableHub) for an illustrative example of how to easily integrate a Web Server into HomeSpan. This project also covers various other advanced topics, including TCP slot management, dynamic creation of Accessories, and saving arbitrary data in the ESP32's NVS.
* Yes, provided you implement your Web Server using standard ESP32-Arduino libraries, such as `WebServer.h`. See [ProgrammableHub](https://github.com/HomeSpan/ProgrammableHub) for an illustrative example of how to easily integrate a Web Server into HomeSpan. This project also covers various other advanced topics, including TCP slot management, dynamic creation of Accessories, and saving arbitrary data in the ESP32's NVS.
#### Can you add *custom* Services and Characteristics to HomeSpan?
* Yes, HomeSpan includes two easy-to-use macros to define your own custom Services and custom Characteristics beyond those specified in HAP-R2. See the [HomeSpan API Reference](Reference.md#custom-characteristics-and-custom-services-macros) for details and examples demonstrating how to do this. Note that any new Characteristics you create will be *completely ignored* by the Home App. Similarly, any new Services you create will be shown in the Home App on a tile labeled "Not Supported". Apple ***does not*** provide any mechanism to extend the functionality of the Home App itself. However, the place where custom Services and Characteristics can be used is in third-party applications designed for these extra features. For example, the *Eve for HomeKit* App properly handles all the Services and Characteristics defined in HAP-R2, *plus* a variety of additional Services and Characteristics designed explictly for use with Eve products. If you know the UUID codes for these extra Services and Characteristics you can add them to HomeKit and use them within the Eve App just as if they were HAP-R2 Services and Characteristics.
* Yes, HomeSpan includes two easy-to-use macros to define your own custom Services and custom Characteristics beyond those specified in HAP-R2. See the [HomeSpan API Reference](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Reference.md) for details and examples demonstrating how to do this. Note that any new Characteristics you create will be *completely ignored* by the Home App. Similarly, any new Services you create will be shown in the Home App on a tile labeled "Not Supported". Apple ***does not*** provide any mechanism to extend the functionality of the Home App itself. However, the place where custom Services and Characteristics can be used is in third-party applications designed for these extra features. For example, the *Eve for HomeKit* App properly handles all the Services and Characteristics defined in HAP-R2, *plus* a variety of additional Services and Characteristics designed explictly for use with Eve products. If you know the UUID codes for these extra Services and Characteristics you can add them to HomeKit and use them within the Eve App just as if they were HAP-R2 Services and Characteristics.
#### Can HomeSpan be used for commercial devices?
* No, a MFi license is needed to create commercial devices. HomeSpan was developed using Apple's HAP-R2 specs, which Apple provides for [non-commercial devices that won't be distributed or sold](https://developers.apple.com/homekit/faq/). Though I believe the commercial specifications are functionally the same, there is a slight, but critical, difference in the pairing protocol between HAP-R2 and MFi. Note that when you pair a HomeSpan device (or any device that is based on HAP-R2, such as Apple's HAP-R2 ADK, Espressif's non-commercial ADK, HomeBridge, etc.) the Home App on your iPhone will flag the device as uncertified and require you to grant it permission to proceed with pairing. This warning message about the device being uncertified does not appear on commercial devices, presumably because Apple provides the licensee with a custom MFi authorization code that is recognized by the iPhone.
#### Why does the Home App indicate the Doorbell Service is unsupported?
* Though not documented in HAP-R2, it appears that the Doorbell Service is designed to be used in conjunction with another service, such as the Lock Mechanism. If you add in a second service, the Home App will show the appropriate Tile (such as a Lock) with the Doorbell being the second service. However, you can still use the Doorbell Service on a standalone basis --- even though the Home App says it is unsupported, a button press on the device will properly trigger a chime on your Home Pods as expected.
#### How can I read the current date and time from within my sketch?
* HomeSpan can automatically acquire the current date and time for your local timezone when you enable the HomeSpan Web Log using `homeSpan.enableWebLog()`. Note this command can be used to set the date and time even without enabling the Web Log itself (see the [HomeSpan API Reference](Reference.md) for details on how to do this). Once the date and time has been acquired, you can use the built-in Arduino-ESP32 `getLocalTime(struct tm *info)` command whenever needed to populate *info* with the current date and time. Note *info* is in [standard Unix](https://man7.org/linux/man-pages/man0/time.h.0p.html) `struct tm` format which is easily parsed as follows:
```C++
struct tm myTime; // create a tm structure
getLocalTime(&myTime); // populate the tm structure with current date and time
// print the individual elements of the tm structure (see standard Unix tm structure for details)
Serial.printf("Current Date = %02d/%02d/%04d\n", myTime.tm_mon+1, myTime.tm_mday, myTime.tm_year+1900);
Serial.printf("Current Time = %02d:%02d:%02d\n", myTime.tm_hour, myTime.tm_min, myTime.tm_sec);
```
---
[↩️](../README.md) Back to the Welcome page
[↩️](README.md) Back to the Welcome page

View File

@ -8,7 +8,7 @@ Since HomeSpan is a library designed for Arduino's Integrated Development Enviro
#### Step 2: Install the Arduino-ESP32 Core
The Arduino IDE comes with built-in compilers and support for a variety of Arduino boards, such as the Arduino Uno and Arduino Nano. However, the IDE does not natively support programming for the ESP32. Fortunately, the IDE allows you to download and install *cores* for other micro-controllers, including the ESP32, so they can be programming from within the Arduino environment. See the [Espressif Arduino-ESP32 GitHub page](https://github.com/espressif/arduino-esp32) for details on the Arduino-ESP32 Core, as well as for instructions on how to install easily this core (and keep it updated) from within the Arduino IDE using the [Arduino IDE Board Manager](https://docs.espressif.com/projects/arduino-esp32/en/latest/installing.html#installing-using-arduino-ide).
The Arduino IDE comes with built-in compilers and support for a variety of Arduino boards, such as the Arduino Uno and Arduino Nano. However, the IDE does not natively support programming for the ESP32. Fortunately, the IDE allows you to download and install *cores* for other micro-controllers, including the ESP32, so they can be programming from within the Arduino environment. See the [Espressif Arduino-ESP32 GitHub page](https://github.com/espressif/arduino-esp32#arduino-core-for-esp32-wifi-chip) for complete instructions on how to install the Arduino-ESP32 core into the Arduino IDE using the [Arduino IDE Board Manager](https://github.com/espressif/arduino-esp32/blob/master/docs/arduino-ide/boards_manager.md).
#### Step 3: Install HomeSpan
@ -20,7 +20,7 @@ HomeSpan is packaged as a standard Arduino library and can be installed either a
#### Step 4: Acquire an ESP32 Board
ESP32 development boards are widely available in different configurations from most electronics hobby sites, such as [Adafruit](https://www.adafruit.com) and [Sparkfun](https://www.sparkfun.com). HomeSpan works with any ESP32 supported by the [Arduino-ESP32 Core](https://docs.espressif.com/projects/arduino-esp32/en/latest/getting_started.html#supported-soc-s), including the original ESP32, the ESP32-S2 and ESP32&#8209;S3, and the ESP32-C3.
ESP32 development boards are widely available in different configurations from most electronics hobby sites, such as [Adafruit](https://www.adafruit.com) and [Sparkfun](https://www.sparkfun.com). HomeSpan was developed using a dual-core ESP32-WROOM-32 (the [Huzzah32 from Adafruit](https://www.adafruit.com/product/3619)) but should work equally well on dual-core ESP32-WROVER chips. HomeSpan has *not* been tested on any single-core ESP32 chips, such as the ESP32-S2.
#### Step 5: Install any required USB Drivers
@ -52,7 +52,7 @@ In addition to being able to configure a HomeSpan device using the [HomeSpan CLI
The Control Button should be installed between ground and any pin on the ESP32 that can serve as an input. To inform HomeSpan of which pin you chose, you must call the method `homeSpan.setControlPin(pin)` near the top of your sketch (see the [HomeSpan API Reference](Reference.md) for details), else HomeSpan will assume a Control Button has **not** be installed.
Similarly, the Status LED can be connected to any pin on the ESP32 that can serve as an output (and grounded through an appropriately-sized current-limiting resistor). To inform HomeSpan of which pin you chose, you must call the method `homeSpan.setStatusPin(pin)` near the top of your sketch, else HomeSpan will assume a Status LED has **not** been installed. Note some ESP32 boards have a built-in LED --- it is fine to use this for the Status LED if it is a simple on/off LED. If your built-in LED is an RGB NeoPixel, or if you would like to use an external RGB NeoPixel for your Status LED, call the method `homeSpan.setStatusPixel(pin)` instead, in which case HomeSpan will use its NeoPixel driver to operate the status LED.
Similarly, the Status LED can be connected to any pin on the ESP32 that can serve as an output (and grounded through an appropriately-sized current-limiting resistor). To inform HomeSpan of which pin you chose, you must call the method `homeSpan.setStatusPin(pin)` near the top of your sketch, else HomeSpan will assume a Status LED has **not** been installed. Note some ESP32 boards have a built-in LED --- it is fine to use this for the Status LED if it is a simple on/off LED, *not* an addressable color LED that requires a special driver.
Using the Control Button and Status LED to configure a standalone HomeSpan device, including starting HomeSpan's temporary WiFi network to configure the device's WiFi Credentials and HomeKit Setup Code, is fully explained in the [HomeSpan User Guide](UserGuide.md).
@ -62,7 +62,7 @@ If you've not yet read through the [HomeSpan API Overview](Overview.md) page, yo
Next, explore the tutorial sketches, upload a few, and see how they work. The examples start simple and grow in complexity, taking you through all the functions and features of HomeSpan. Along the way you'll also learn a lot of HomeKit tips and tricks. See [HomeSpan Tutorials](Tutorials.md) for a summary of all the included examples. Find something in a sketch you don't understand? Visit the [HomeSpan API Reference](Reference.md) for details on all HomeSpan objects, functions, and methods. Have a more general question? See if it's been answered on the [HomeSpan FAQ](FAQ.md) page or any of the [Disussion](https://github.com/HomeSpan/HomeSpan/discussions) or [Issues](https://github.com/HomeSpan/HomeSpan/issues) pages. If not, feel free to join the Discusion by adding a new question.
Ready to start creating your own HomeSpan sketches? Check out the [HomeSpan Services and Characteristics](ServiceList.md) page for a full list of all the HomeKit Services and Characteristics supported by HomeSpan, as well as the [HomeSpan Categories](Categories.md) page for a list of all supported HomeKit Categories. And don't forget to use Apple's HomeKit Accessory Protocol Specification, Release R2 (HAP-R2) as your go-to reference for details on every Service and Characteristic.
Ready to start creating your own HomeSpan sketches? Check out the [HomeSpan Services and Characteristics](ServiceList.md) page for a full list of all the HomeKit Services and Characteristics supported by HomeSpan, as well as the [HomeSpan Categories](Categories.md) page for a list of all supported HomeKit Categories. And don't forget to use Apple's [HomeKit Accessory Protocol Specification, Release R2 (HAP-R2)](https://developer.apple.com/homekit/specification/) as your go-to reference for details on every Service and Characteristic.
While developing your sketch remember to utilize the Arduino Serial Monitor. HomeSpan produces extensive diagnostics that will help you debug your sketches as well as monitor all aspects of the HomeSpan device. You'll also be able to control various aspects of HomeSpan from the Serial Monitor using the [HomeSpan Command-Line Interface (CLI)](CLI.md), including configuring the device's WiFi Credentials and HomeKit Setup Code.
@ -72,4 +72,4 @@ Finally, disconnect your HomeSpan device from the computer and power it directly
---
[↩️](../README.md) Back to the Welcome page
[↩️](README.md) Back to the Welcome page

View File

@ -1,85 +0,0 @@
# 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

10
docs/HomeKitPrimer.md Normal file
View File

@ -0,0 +1,10 @@
# HomeKit Primer
*(editing in progress)*
The standard reference for all HomeKit devices is [Apple's HomeKit Accessory Protocol Specification Release R2 (HAP-R2)](https://developer.apple.com/support/homekit-accessory-protocol/). HomeSpan rigorously implements HAP-R2, so if you are already familiar with HomeKit concepts and terminology, using HomeSpan to create your own HomeKit devices should be extremely easy. However, if this is your first time programming for HomeKit, some of the basic HomeKit terminology can be confusing, and unfortunately Apple's HAP-R2 guide does not provide much in the way of an introduction or overview to HomeKit itself. This page hopefully provides you with the missing "overview" you need to better understand the overall HomeKit ecosystem.
---
[↩️](README.md) Back to the Welcome page

View File

@ -14,10 +14,6 @@ HomeSpan log messages are typically output directly to the Arduino Serial Monito
You can set the *Log Level* in your sketch using the method `homeSpan.setLogLevel(uint8_t level)` as described in the [HomeSpan API Reference](API.md). Level 0 messages are always output; Level 1 messages are only output if the *Log Level* is set to 1 or greater; and Level 2 messages are only output if the *Log Level* is set to to 2. The *Log Level* can also be changed dynamically via the Serial Monitor at any time by typing either the 'L0', 'L1', or 'L2' as described in the [HomeSpan CLI](CLI.md).
You can also completely suppress all Log messages generated by HomeSpan (as well as all user-defined Log messages - see below) by setting the *Log Level* to -1, either by typing 'L-1' into the HomeSpan CLI or by calling `homeSpan.setLogLevel(-1)` in your sketch. Disabling all Log messages from being output to the Serial Monitor may be useful in cases where a separate Serial peripheral is being controlled by the ESP32. In such cases you may want to implement a physical switch on your device that automatically sets the Log Level to 0 or -1 so you don't have to recompile your sketch every time you want to enable/disable HomeSpan Log Messages.
Note that the *Log Level* setting has no impact on messages output by any `Serial.print()` or `Serial.printf()` statements used in a sketch. To ensure you can control such messages via the *Log Level* setting, use the `LOG()` macros below. Also note that the *Log Level* setting has no impact on any ESP32 diagnostic messages produced by the ESP32 operating system itself. These messages are controlled according to the *Core Debug Level* specified at compile time under the Tools menu of the Arduino IDE.
## User-Defined Log Messages
You can add your own log messages to any sketch using HomeSpan's **LOG0()**, **LOG1()**, and **LOG2()** macros. Messages created with these macros will be output to the Arduino Serial Monitor according the *Log Level* setting described above. Each **LOGn()** macro (where n=\[0,2\]) is available in two flavors depending on the number of arguments specified:
@ -53,7 +49,7 @@ To enable Web Logging (it's turned off by default), call the method `homeSpan.en
* the total number of WEBLOG() messages to be stored - older messages are discarded in favor of newer ones once the limit you set is reached
* the URL of an NTP time server - this is optional and only needed if you want to set the clock of the device at start-up
* the time zone for the device - this is only needed if an NTP time server has been specified
* the URL of the Web Log page - if unspecified, HomeSpan will serve the Web Log at a page named "status". If set to NULL, HomeSpan will process Web Log data but will *not* serve any Web Log pages to any HTTP requests. However, Web Log data in the form of a finished HTML page can still be accessed by the user by calling `homeSpan.getWebLog()` as described further below
* the URL of the Web Log page - if unspecified, HomeSpan will serve the Web Log at a page named "status"
Additional notes:
@ -62,85 +58,8 @@ Additional notes:
See [Example 19 - WebLog](Tutorials.md#example-19---weblog) for a tutorial sketch demonstrating the use of `homeSpan.enableWebLog()` and the WEBLOG() macro.
### Custom Style Sheets (CSS)
HomeSpan's Web Log normally consists of black text on a light blue background. However, you can set a Custom Style Sheet (CSS) to change the format by calling `homeSpan.setWebLogCSS(const char *css)`, where *css* is constructed using [HTML classes](https://www.w3schools.com/html/html_classes.asp) containing one or more custom style elements. HomeSpan implements the following three class names for the different parts of the Web Log:
* *bod1* - this class specifies style elements for the main body of the Web Log page, including the background color and the header text at the top (which itself is formatted as \<h2\>)
* *tab1* - this class specifies style elements for the status table at the top of the Web Log page
* *tab2* - this class specifies style elements for the log entry table at the botom of the Web Log page
For example, the following CSS changes the background color of the Web Log page to light yellow, the color of the header text to blue, the color of the cells in the top table to light green, and the color of the cells in the botom table to light blue. It also changes the color of the text in the header row (\<th\>) of the second table to red, the color of the data rows (\<td\>) in the second table to dark blue, and the alignment of the text in the data rows to be centered within each table cell:
```C++
homeSpan.setWebLogCSS(".bod1 {background-color:lightyellow;}"
".bod1 h2 {color:blue;}"
".tab1 {background-color:lightgreen;}"
".tab2 {background-color:lightblue;} .tab2 th {color:red;} .tab2 td {color:darkblue; text-align:center;}"
);
```
Note that HomeSpan outputs the full content of the Web Log HTML, including whatever CSS you may have specified above, to the Serial Monitor whenever the Log Level is set to 1 or greater. Reviewing this output can be helpful when creating your own CSS.
### Adding User-Defined Data and/or Custom HTML
Homespan provides a hook into the text used to generate the Web Log that you can extend to add your own data to the initial table as well as more generally add any custom HTML.
To access this text, set a Web Log callback using `homeSpan.setWebLogCallback(void (*func)(String &htmlText))` where
* *func* is a function of type *void* that takes a single argument of type *String*, and
* *htmlText* will be set by HomeSpan to a String reference containing all the HTML text that the Web Log has already generated to produce the initial table.
To add your own data to the table, simply extend the String *htmlText* by adding as many `<tr>` and `<td>` HTML tags as needed. If you wish to end the table and add any other HTML, simple include the `</table>` tag in *htmlText*, and then add any other custom HTML. For example, the following function could be used to extend the initial Web Log table to show free DRAM, end the table, and provide a hot link to the HomeSpan Repo:
```C++
void extraData(String &r){
r+="<tr><td>Free DRAM:</td><td>" + String(esp_get_free_internal_heap_size()) + " bytes</td></tr>\n";
r+="</table><p><a href=\"https://github.com/HomeSpan/HomeSpan\">Click Here to Access HomeSpan Repo</a></p>";
}
```
To embed this custom HTML text in the Web Log, call `homeSpan.setWebLogCallback(extraData)` in your sketch.
### Accessing Web Log HTML from within your sketch
In addition to (or as an alternative to) having HomeSpan serve HTML Web Log pages in response to HTTP requests, users can directly access the HTML text for a Web Log page from within their sketch for customized processing and handling. Since the HTML for a Web Log page can be very large, HomeSpan only generates the HTML for a Web Log page when the page has been requested, and streams the HTML in sequential chunks of 1024 bytes in response to a Web Log HTTP request. It is therefore not possible for HomeSpan to simply provide the user with a `char *` pointer to the HTML text for a complete Web Log. Instead, HomeSpan provides the user with the following *homeSpan* method to trigger the production of a Web Log page and access the resulting HTML text whenever needed:
`getWebLog(void (*f)(const char *htmlText, void *data), void *userData)`
* *f()* - a user-defined function that returns `void` and takes two arguments:
* *htmlText* - a null-terminated `const char *` pointer to a chunk of HTML text (max 1024 bytes) provided by HomeSpan
* *data* - a `void *` pointer to any user-provided data, *userData*
* *userData* - a `void *` pointer to any optional user-provided data that is passed to *f()* as its second argument, *data*
When the above method is called from a sketch, HomeSpan will repeatedly call the user-defined function *f()* and provide sequential chunks of HTML text for the Web Log page as the first argument, *htmlText*. Once all HTML chunks have been sent to the function *f()*, HomeSpan calls *f()* one final time with *htmlText* set to NULL to indicate there are no more HTML chunks to be sent.
The primary purpose of this function is for the user to provide their own method of serving an HTML Web Log page, such as through a secure HTTPS channel. Note this channel can be in addition to, or instead of, HomeSpan's normal serving of Web Log pages through HTTP requests depending on whether or not the URL argument used in the `homeSpan.enableWebLog()` method was set to NULL (disabling HomeSpan from serving Web Log pages in response to HTTP requests).
The following psuedo-code snippet shows how `getWebLog()` can be used:
```C++
...
homeSpan.enableWebLog(50,"pool.ntp.org","UTC",NULL); // this enables the Web Log for 50 entries and sets the clock, but prevents HomeSpan from responding to any HTTP requests for a Web Log page
...
IF WEBLOG NEEDED THEN{
homeSpan.getWebLog(myWebLogHandler,NULL); // this triggers HomeSpan to produce the HTML text for a Web Log page and stream the data to myWebLogHandler without any extra user data
}
...
void myWebLogHandler(const char *htmlText, void *args){ // this is the user-defined Web Log handler (note the optional *arg parameter is not used in this example)
if(htmlText!=NULL){
DO SOMETHING WITH htmlText (e.g. transmit it to the user via an HTTPS connection)
}
else
PERFORM ANY CLEAN-UP PROCESSING (e.g. close the HTTPS connection)
}
}
```
---
[↩️](../README.md) Back to the Welcome page
[↩️](README.md) Back to the Welcome page

View File

@ -1,99 +0,0 @@
# 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. SpanPoint can also be used on an ESP-8266 when configured as a remote device.
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 [, boolean useAPaddress=false]])*
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
* *useAPaddress* - SpanPoint normally communicates via the ESP32's WiFi Station (STA) Interface using the STA MAC Address. Setting *useAPaddress* to *true* causes SpanPoint to instead communicate via the ESP32's WiFi Access Point (AP) Interface using the AP MAC Address. This is needed when using an ESP-8266 as a Remote Device (see below). Default=*false* if left unspecified
A list of all SpanPoint objects instantiated in a sketch, their parameters as specified above, and the specific MAC Address each Remote Device should use to connect back to the Main HomeSpan Device, is displayed in the Serial Monitor by typing 'i' into the CLI
> 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 setEncryption(boolean encrypt)`
* this *optional* **class-level** method provides the ability to enable or disable encryption according to whether *encrypt* is set to *true* or *false*
* by default, encryption is normally enabled (using the password above)
* if used, this method must be called *before* the instantiation of any SpanPoint objects. Example: `SpanPoint::setEncryption(false);` disables encryption for all SpanPoint connections
* note that this is a global setting - if SpanPoint encryption is disabled on the main device, it must also be disabled on every remote device, else communication between devices will fail
* enabling/disabling encryption impacts that total number of SpanPoint connections that can be supported by the ESP32's ESP-NOW functionality:
* with encryption enabled, the ESP32 can support a maximum of 7 ESP-NOW links (i.e. 7 instances of SpanPoint)
* with encryption disabled, the ESP32 can support a maximum of 20 ESP-NOW links (i.e. 20 instances of SpanPoint)
* `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*](../examples/Other%20Examples/RemoteSensors). This folder contains the following 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
* *RemoteDevice8266.ino* - similar in function to *RemoteDevice.ino*, but implemented to run on an ESP8266 device using native ESP-NOW commands (since neither HomeSpan nor SpanPoint support the ESP8266). Note that the "complementary" SpanPoint object on the ESP32 that receives data from the ESP8266 must be configured to use the ESP32's *AP MAC Address* (instead of the *STA MAC Address*) by setting *useAPaddress* to *true* in the SpanPoint constructor
Please also see the [SpanPointLightSwitch Repository](https://github.com/HomeSpan/SpanPointLightSwitch/tree/main) for a detailed example that shows how to use SpanPoint for *bi-directional communication* between an ESP32 "Central Hub" device implementing two HomeKit Lighbulb Accessories, an remote ESP32 device controlling an LED, and a separate ESP8266 device controlling another LED.
---
[↩️](../README.md) Back to the Welcome page

View File

@ -8,8 +8,6 @@ By default, HomeSpan requires the use of a password whenever you begin an OTA up
You can change the password for a HomeSpan device from the [HomeSpan CLI](CLI.md) with the 'O' command. Similar to a device's Setup Code, HomeSpan saves a non-recoverable hashed version of the OTA password you specify in non-volatile storage (NVS). If you forget the password you specified, you'll need to create a new one using the 'O' command, or you can restore the default OTA password by fully erasing the NVS with the 'E' command.
You can also change the password programmatically from within a sketch by calling `homeSpan.enableOTA(const char *pwd)`. This is not as secure as setting the password using the method above since your sketch will contain a plaintext-version, instead of a hashed-version, or your password. Note that setting your password this way causes HomeSpan to ignore, but does not alter, any password you have saved in NVS using the 'O' command.
> :exclamation: Though not recommended, you can override the requirement for a password when enabling OTA for your sketch by including *false* as a parameter to the enabling method as such: `homeSpan.enableOTA(false)`. Use with caution! Anyone who can access the device over your network will now be able to upload a new sketch.
Note that in in order for OTA to properly operate, your sketch must be compiled with a partition scheme that includes OTA partitions. Partition schemes are found under the *Tools → Partition Scheme* menu of the Arduino IDE. Select a scheme that indicates it supports OTA. Note that schemes labeled "default" usually include OTA partitions. If unsure, try it out. HomeSpan will let you know if it does or does not.
@ -38,6 +36,6 @@ Note that these check are *only* applicable when uploading sketches via OTA. Th
---
[↩️](../README.md) Back to the Welcome page
[↩️](README.md) Back to the Welcome page

View File

@ -42,30 +42,6 @@ void loop(){
} // end of loop()
```
Note that as an *alternative*, you can intruct HomeSpan to create separate task that repeatedly calls `homeSpan.poll()` in the background. To do so, **replace** the call to `homeSpan.poll()` in the main `loop()` with a call to `homeSpan.autoPoll()` at the end of the `setup()` function:
```C++
#include "HomeSpan.h" // include the HomeSpan library
void setup() {
Serial.begin(115200); // start the Serial interface
homeSpan.begin(); // initialize HomeSpan
/// DEFINITION OF HAP ACCESSORY ATTRIBUTE DATABASE GOES HERE ///
homeSpan.autoPoll(); // start a task that repeatedly calls `homeSpan.poll()` in the background
} // end of setup()
void loop(){
} // end of loop()
```
This is particularly efficient when using dual-core processors since HomeSpan will run the polling task on the "free" processor that is otherwise not performing any other Arduino functions.
## Creating the HAP Accessory Attribute Database
The next step is to implement the code that defines the HAP Accessory Attribute Database, which is not really a database but simply a list of all HAP accessory objects, Service objects, and Characteristic objects implemented by this HomeSpan device.
@ -314,5 +290,5 @@ Finally, don't forget to visit the [HomeSpan Command-Line Interface (CLI)](CLI.m
---
[↩️](../README.md) Back to the Welcome page
[↩️](README.md) Back to the Welcome page

View File

@ -1,43 +1,22 @@
# Pulse Width Modulation (PWM)
The ESP32 has up to 16 PWM channels that can be used to drive a variety of devices. HomeSpan includes an integrated PWM library with dedicated classes designed for controlling **Dimmable LEDs** as well as **Servo Motors**.
The ESP32 has up to 16 PWM channels that can be used to drive a variety of devices. HomeSpan includes an integrated PWM library with dedicated classes designed for controlling **Dimmable LEDs** as well as **Servo Motors**. Both classes are provided in a standalone header file that is accessed by placing the following near the top of your sketch:
## *LedPin(uint8_t pin [,float level [,uint16_t frequency [,boolean invert]]])*
`#include "extras/PwmPin.h"`
## *LedPin(uint8_t pin [,float level [,uint16_t frequency]])*
Creating an instance of this **class** configures the specified *pin* to output a PWM signal suitable for a controlling dimmable LED. Arguments, along with their defaults if left unspecified, are as follows:
* *pin* - the pin on which the PWM control signal will be output
* *level* - sets the initial %duty-cycle of the PWM from from 0 (LED completely off) to 100 (LED fully on). Default=0 (LED initially off)
* *frequency* - sets the PWM frequency, in Hz, from 1-65535 (ESP32 only) or 5-65535 (ESP32-S2 and ESP32-C3). Defaults to 5000 Hz if unspecified, or if set to 0
* *boolean* - if true, the output of the PWM signal will be inverted. Default=false
The following methods are supported:
* `void set(float level)`
* sets the PWM %duty-cycle to *level*, where *level* ranges from 0 (LED completely off) to 100 (LED fully on)
* `int fade(float level, uint32_t fadeTime, int fadeType=LedPin::ABSOLUTE)`
* uses the ESP32's PWM hardware to smoothly fade the LED to *level* (from 0-100) over a maximum of *fadeTime* milliseconds
* if *fadeType* is set to **LedPin::ABSOLUTE** (the default), fading will take the full amount of time specified by *fadeTime*
* if *fadeType* is set to **LedPin::PROPORTIONAL**, the fading time will be scaled down proportionally according to the difference between the current level and the level specified. For example, if the current level is set to 30, then
* `fade(20, 1000, LedPin::ABSOLUTE)` sets the level to 20 over the course of 1 second, whereas
* `fade(20, 1000, LedPin::PROPORTIONAL)` sets the level to 20 over the course of 100 milliseconds (since the level only needs to change by 10 out of 100 units)
* this is a **NON-BLOCKING** method and will return immediately. Fading occurs in the background controlled by the ESP32 hardware
* note: once fading begins it CANNOT be stopped or changed until completed (this is a limitation of the ESP32 hardware)
* this method returns 0 if the fading has successfully started, or 1 if fading is already in progress and cannot yet be changed (new requests for fading while fading is already in progress for a specific LedPin are simply ignored)
* use the *fadeStatus* method (below) to determine the current fading status of any given LedPin
* `int fadeStatus()`
* returns the fading status of an LedPin. Return values are as follows:
* **LedPin::NOT_FADING** - the LedPin is not currently fading
* **LedPin::FADING** - fading on LedPin is currently in progress and cannot be changed/stopped
* **LedPin::COMPLETED** - fading has just completed
* once this value is returned, subsequent calls to `fadeStatus()` will return **LedPin::NOT_FADING** (unless you called `fade()` again)
* by checking for `fadeStatus()==LedPin::COMPLETED` in a `loop()` method, you can thus trigger a new action (if desired) once fading is completed
* `int getPin()`
@ -54,7 +33,7 @@ LedPin also includes a static class function that converts Hue/Saturation/Bright
* *g* - output Green value, range 0-1
* *b* - output Blue value, range 0-1
See tutorial sketch [#10 (RGB_LED)](../examples/10-RGB_LED) for an example of using LedPin to control an RGB LED. Also see [*File → Examples → HomeSpan → Other Examples → FadingLED*](../examples/Other%20Examples/FadingLED) for an example of to use the ESP32's built-in fading controls.
See tutorial sketch [#10 (RGB_LED)](../examples/10-RGB_LED) for an example of using LedPin to control an RGB LED.
## *ServoPin(uint8_t pin [,double initDegrees [,uint16_t minMicros, uint16_t maxMicros, double minDegrees, double maxDegrees]])*
@ -71,14 +50,13 @@ The *minMicros* parameter must be less than the *maxMicros* parameter, but setti
* `void set(double position)`
* sets the position of the Servo Motor to *position* (in degrees). In order to protect the Servo Motor, values of *position* less than *minDegrees* are automatically reset to *minDegrees*, and values greater than *maxDegrees* are automatically reset to *maxDegrees*
* if *position* is specified as *NAN* (i.e. the C++ "not-a-number" constant), the duty-cycle is set to zero, which effectively stops the pulse generation --- for most analog servos this means the motor can be freely rotated. Calling `set()` once again with *position* equal to an actual number of degrees restarts the pulse train and sets the servo position accordingly
* sets the position of the Servo Motor to *position* (in degrees). In order to protect the Servo Motor, values of *position* less than *minDegrees* are automatically reset to *minDegrees*, and values greater than *maxDegrees* are automatically reset to *maxDegrees*.
* `int getPin()`
* returns the pin number (or -1 if ServoPin was not successfully initialized)
A worked example showing how ServoPin can be used to control the Horizontal Tilt of a motorized Window Shade can be found in the Arduino IDE under [*File → Examples → HomeSpan → Other Examples → ServoControl*](../examples/Other%20Examples/ServoControl).
A worked example showing how ServoPin can be used to control the Horizontal Tilt of a motorized Window Shade can be found in the Arduino IDE under [*File → Examples → HomeSpan → Other Examples → ServoControl*](../Other%20Examples/ServoControl).
### PWM Resource Allocation and Limitations
@ -87,14 +65,11 @@ The following PWM resources are available:
* ESP32: 16 Channels / 8 Timers (arranged in two distinct sets of 8 Channels and 4 Timers)
* ESP32-S2: 8 Channels / 4 Timers
* ESP32-C3: 6 Channels / 4 Timers
* ESP32-S3: 8 Channels / 4 Timers
HomeSpan *automatically* allocates Channels and Timers to LedPin and ServoPin objects as they are instantiated. Every pin assigned consumes a single Channel; every *unique* frequency specified among all channels (within the same set, for the ESP32) consumes a single Timer. HomeSpan will conserve resources by re-using the same Timer for all Channels operating at the same frequency. *HomeSpan also automatically configures each Timer to support the maximum duty-resolution possible for the frequency specified.*
#### Diagnostic Messages
The **LedPin** and **ServoPin** classes output *Information \[I\]* and *Warning \[W\]* messages to the Serial Monitor based on the *Core Debug Level* selected when compiling the sketch using the Arduino IDE. A non-fatal warning message is produced when insufficient Channel or Timer resources prevent the creation of a new LedPin or ServoPin object. Calls to the `set()` method for objects that failed to be properly created are silently ignored.
HomeSpan will report a non-fatal error message to the Arduino Serial Monitor when insufficient Channel or Timer resources prevent the creation of a new LedPin or ServoPin object. Calls to the `set()` method for objects that failed to be properly created are silently ignored.
---
[↩️](../README.md) Back to the Welcome page
[↩️](README.md) Back to the Welcome page

View File

@ -6,22 +6,16 @@ Both classes allow you to individually set each of the "pixels" in a multi-pixel
The methods for both classes are nearly identical, which allows you to readily interchange code written for single-wire devices to use with two-wire devices (and vice-versa) with only minor modifications.
## *Pixel(uint8_t pin, [pixelType_t pixelType])*
Both classes are provided in a standalone header file that is accessed by placing the following near the top of your sketch:
`#include "extras/Pixel.h"`
## *Pixel(uint8_t pin, [boolean isRGBW])*
Creating an instance of this **class** configures the specified *pin* to output a waveform signal suitable for controlling a single-wire, addressable RGB or RGBW LED device with an arbitrary number of pixels. Such devices typically contain SK6812 or WS2812 LEDs. Arguments, along with their defaults if left unspecified, are as follows:
* *pin* - the pin on which the RGB control signal will be output; normally connected to the "data" input of the addressable LED device
* *pixelType* - controls the order in which color data is transmitted, as well as whether the device contains 3-color (red/green/blue) or&nbsp;4&#8209;color (red/green/blue/white) LEDs. Pre-defined values for *pixelType* are provided in the **PixelType** namespace. Choose from one of the following twelve formats:
* *PixelType::RGB, PixelType::RBG, PixelType::BRG, PixelType::BGR, PixelType::GBR, PixelType::GRB*
* *PixelType::RGBW, PixelType::RBGW, PixelType::BRGW, PixelType::BGRW, PixelType::GBRW, PixelType::GRBW*
* Example: `Pixel myDevice(26, PixelType::BRGW);` creates a 4-color RGBW device attached to pin 26 where the colors are transmitted in the order blue, red, green, and then white
Note that *pixelType* is optional. If left unspecified, the default value is *PixelType::GRB*.
> [!TIP]
> Since it is often not obvious which type of LED your specific device may have, HomeSpan includes a sketch designed to help you determine the correct value of *pixelType*. See [*File → Examples → HomeSpan → Other Examples → PixelTester*](../examples/Other%20Examples/PixelTester) under the Arduino IDE for detailed instructions. Please use this tester sketch if you find the colors of your Pixel device are not matching what appears in the Home App.
* *isRGBW* - set to *true* for RGBW devices that contain 4-color (red/green/blue/white) LEDs; set to *false* for the more typical 3-color RGB devices. Defaults to *false* if unspecified. Note you must set the *isRGBW* flag to *true* if you are using an RGBW device, even if you do not intend on utilizing the white LED
The two main methods to set pixel colors are:
@ -33,7 +27,7 @@ The two main methods to set pixel colors are:
* individually sets the color of each pixel in a multi-pixel device to the color values specified in the **Color** array *\*color*, of *nPixels* size, where the first pixel of the device is set to the value in *color\[0\]*, the second pixel is set to the value in *color\[1\]* ... and the last pixel is set to the value in *color\[nPixels-1\]*. Similar to above, it is not a problem if the value specified for *nPixels* does not match the total number of actual RGB (or RGBW) pixels in your device
In both of the methods above, colors are stored in a 32-bit **Color** object configured to hold four 8-bit RGBW values. **Color** objects can be instantiated as single variables (e.g. `Pixel::Color myColor;`) or as arrays (e.g. `Pixel::Color myColors[8];`). Note that the **Color** object used by the **Pixel** class is scoped to the **Pixel** class itself, so you need to use the fully-qualified class name "Pixel::Color". Once a **Color** object is created, the color it stores can be set using one of the two following methods:
In both of the methods above, colors are stored in a 32-bit **Color** object configured to hold four 8-bit RGBW values. **Color** objects can be instantiated as single variables (e.g. `Pixel::Color myColor;`) or as arrays (e.g. `Pixel::Color myColors\[8\];`). Note that the **Color** object used by the **Pixel** class is scoped to the **Pixel** class itself, so you need to use the fully-qualified class name "Pixel::Color". Once a **Color** object is created, the color it stores can be set using one of the two following methods:
* `Color RGB(uint8_t r, uint8_t g, uint8_t b, uint8_t w=0)`
@ -57,26 +51,19 @@ The **Pixel** class also supports the following class-level methods as a conveni
* equivalent to `return(Color().HSV(h,s,v,w));`
* example: `Pixel::Color c[]={Pixel::HSV(120,100,100),Pixel::HSV(60,100,100),Pixel::HSV(0,100,100)};` to create a red-yellow-green traffic light pattern
Finally, the **Pixel** class supports these additional methods:
Finally, the **Pixel** class supports these two additional, but rarely-needed, methods:
* `int getPin()`
* returns the pin number, or -1 if the instantiation failed due to lack of resources
* `boolean isRGBW()`
* returns *true* if *pixelType* specified an RGBW LED, else *false* if just an RGB LED
* `void setTiming(float high0, float low0, float high1, float low1, uint32_t lowReset)`
* the default timing parameters used by the **Pixel** class to generate the "data" signal needed to set the colors of an RGB LED device should work with most commercial products based on SK6812 or WS2812 driver chips. Use this method **ONLY** if you need to override the class defaults and replace them with your own timing parameters, where
* *high0* and *low0* specify the duration (in microseconds) of the high phase and low phase for a pulse encoding a zero-bit;
* *high1* and *low1* specify the duration (in microseconds) of the high phase and low phase for a pulse encoding a one-bit; and
* *lowReset* specifies the delay (in microseconds) representing the end of a pulse stream
* for reference, the **Pixel** class uses the following defaults: *high0=0.32𝛍s, low0=0.88𝛍s, high1=0.64𝛍s, low1=0.56𝛍s, lowReset=80.0𝛍s*
> [!TIP]
> If your LED colors are flickering, this is likely due to a mismatch in timing parameters. To solve, please search the web for a specifications document for the exact **chip** used by your specific device. These documents generally provide a table listing the durations of the HIGH and LOW periods required to transmit a binary 1 or binary 0. You can then use the `setTiming` method above to set the timing parameters accordingly.
* for reference, the **Pixel** class uses the following defaults: *high0=0.32𝛍s, low0=0.88𝛍s, high1=0.64𝛍s, low1=0.56𝛍s, lowReset=80.0𝛍s*
### Resource Usage and Resource Conflicts
@ -131,10 +118,10 @@ Unlike the **Pixel** class, the **Dot** class does *not* utilize the ESP32's RMT
### Example Sketches
A fully worked example showing how to use the Pixel library within a HomeSpan sketch to control an RGB Pixel Device, an RGBW Pixel Device, and an RGB DotStar Device, all from the Home App on your iPhone, can be found in the Arduino IDE under [*File → Examples → HomeSpan → Other Examples → Pixel*](../examples/Other%20Examples/Pixel).
A fully worked example showing how to use the Pixel library within a HomeSpan sketch to control an RGB Pixel Device, an RGBW Pixel Device, and an RGB DotStar Device, all from the Home App on your iPhone, can be found in the Arduino IDE under [*File → Examples → HomeSpan → Other Examples → Pixel*](../Other%20Examples/Pixel).
For a more complete showcase of the Pixel library , check out [Holiday Lights](https://github.com/HomeSpan/HolidayLights) on the [HomeSpan Projects page](https://github.com/topics/homespan). This sketch demonstrates how the Pixel library can be used to generate a variety of special effects with a 60-pixel RGBW strip. The sketch also showcases the use of HomeSpan's [Custom Characteristic macro](Reference.md#custom-characteristics-and-custom-services-macros) to implement a special-effects "selector" button for use in the Eve for HomeKit App.
For a more complete showcase of the Pixel library , check out [Holiday Lights](https://github.com/HomeSpan/HolidayLights) on the [HomeSpan Projects page](https://github.com/topics/homespan). This sketch demonstrates how the Pixel library can be used to generate a variety of special effects with a 60-pixel RGBW strip. The sketch also showcases the use of HomeSpan's [Custom Characteristic macro](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Reference.md#define-custom_charnameuuidpermsformatdefaultvalueminvaluemaxvaluestaticrange) to implement a special-effects "selector" button for use in the Eve for HomeKit App.
---
[↩️](../README.md) Back to the Welcome page
[↩️](README.md) Back to the Welcome page

View File

@ -63,6 +63,6 @@ The result must be 9 digits. If less, pad with leading zeros.
---
[↩️](../README.md) Back to the Welcome page
[↩️](README.md) Back to the Welcome page

113
docs/README.md Normal file
View File

@ -0,0 +1,113 @@
# Welcome!
Welcome to HomeSpan - a robust and extremely easy-to-use Arduino library for creating your own [ESP32-based](https://www.espressif.com/en/products/modules/esp32) HomeKit devices entirely within the [Arduino IDE](http://www.arduino.cc).
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 is fully compatible with both Versions 1 and 2 of the [Arduino-ESP32 Board Manager](https://github.com/espressif/arduino-esp32). Under Version 1, HomeSpan can be run only on the original ESP32. Under Version 2, HomeSpan can be run on the original ESP32 as well as Espressif's ESP32-S2 and ESP32-C3 chips.
### HomeSpan Highlights
* Provides a natural, intuitive, and **very** easy-to-use framework
* Utilizes a unique *Service-Centric* approach to creating HomeKit devices
* Takes full advantage of the widely-popular Arduino IDE
* 100% HAP-R2 compliance
* 38 integrated HomeKit Services
* Operates in either Accessory or Bridge mode
* Supports pairing with Setup Codes or QR Codes
### For the HomeSpan Developer
* Extensive use of the Arduino Serial Monitor
* Real-time, easy-to-understand diagnostics
* Complete transparency to every underlying HomeKit action, data request, and data response
* Command-line interface with a variety of info, debugging, and configuration commands
* Built-in database validation to ensure your configuration meets all HAP requirements
* Dedicated classes that utilize the ESP32's 16-channel PWM peripheral for easy control of:
* LED Brightness
* Servo Motors
* Integrated Push Button functionality supporting single, double, and long presses
* 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
* 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
* A complete set of documentation explaining every aspect of the HomeSpan API
### For the HomeSpan End-User
* Embedded WiFi Access Point and Web Interface to allow end-users (non-developers) to:
* Set up Homespan with their own home WiFi Credentials
* Create their own HomeKit Pairing Setup Code
* Status LED and Control Button to allow end-users to:
* Force-unpair the device from HomeKit
* Perform a Factory Reset
* Launch the WiFi Access Point
* A standalone, detailed End-User Guide
## ❗Latest Update - HomeSpan 1.5.1 (4/17/2022)
* **New Web Logging functionality**
* HomeSpan can now host a Web Log page for message logging
* New WEBLOG() macro makes is easy to create user-defined log messages
* Provides for the optional use of an NTP Time Server to set the device clock so all messages can be properly timestamped
* See [HomeSpan Message Logging](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Logging.md) for full details
* **New *printf*-style formatting for LOG() macros**
* Adds variadic forms of the LOG0(), LOG1(), and LOG2() macros so they can be used in the same manner as a standard C printf function
* Greatly simplifies the creation of log messages
* See [HomeSpan Message Logging](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Logging.md) for full details
* **New CUSTOM_SERV() macro**
* Allows for the creation of Custom Services
* Can be used in conjunction with the existing CUSTOM_CHAR() macro to produce Services beyond those provided in HAP-R2
* Includes a fully worked example of a custom [Pressure Sensor Accessory](https://github.com/HomeSpan/HomeSpan/blob/master/Other%20Examples/CustomService) that is recognized by *Eve for HomeKit*
* See [HomeSpan API Reference](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Reference.md) for details
* **New "Safe-Load" mode for OTA updates**
* HomeSpan can check to make sure the new sketch being uploaded via OTA is another HomeSpan sketch. If not, the upload fails
* Upon rebooting after an OTA update, HomeSpan checks to ensure that OTA is enabled in the updated sketch. If not, HomeSpan rolls back to the previous version of the sketch
* See [HomeSpan OTA](https://github.com/HomeSpan/HomeSpan/blob/master/docs/OTA.md) for full details
* **Additional updates include:**
* a new (optional) argument to `SpanUserCommand()` that allows for passing a pointer to any arbitrary data structure
* a new SPAN_ACCESSORY() macro that expands to a common snippet of code often used when creating Accessories
* refreshed and streamlined example Tutorials, and fully reworked Examples 7 and 11, to best conform with Home App behavior under iOS 15.4
See [Releases](https://github.com/HomeSpan/HomeSpan/releases) for details on all changes and bug fixes included in this update.
# HomeSpan Resources
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
* [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
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.
# External Resources
In addition to HomeSpan resources, developers who are new to HomeKit programming should download Apple's [HomeKit Accessory Protocol Specification, Release R2 (HAP-R2)](https://developer.apple.com/homekit/specification/). The download is free, but Apple requires you to register your Apple ID for access to the document.
You ***do not*** need to read the entire document. The whole point of HomeSpan is that it implements all the required HAP operations under the hood so you can focus on just programming whatever logic is needed to control your real-world appliances (lights, fans, RF remote controls, etc.) with the device. However, you will find Chapters 8 and 9 of the HAP guide to be an invaluable reference as it lists and describes all of the Services and Characteristics implemented in HomeSpan, many of which you will routinely utilize in your own HomeSpan sketches.
---
### Feedback or Questions?
Please consider adding to the [HomeSpan Discussion Board](https://github.com/HomeSpan/HomeSpan/discussions), or email me directly at [homespan@icloud.com](mailto:homespan@icloud.com).

View File

@ -1,10 +1,12 @@
# Remote Control Radio Frequency / Infrared Signal Generation
The ESP32 has an on-chip Remote Control (RMT) signal-generator designed to drive an RF or IR transmitter. HomeSpan includes a dedicated, easy-to-use class, **RFControl()**, that interfaces with the ESP32 RMT peripheral so that with a few additional electronic components you can create a HomeSpan device that controls an RF or IR appliance directly from the Home App on your iPhone, or via Siri.
The ESP32 has an on-chip signal-generator peripheral designed to drive an RF or IR transmitter. HomeSpan includes an easy-to-use library that interfaces with this peripheral so that with a few additional electronic components you can create a HomeSpan device that controls an RF or IR appliance directly from the Home App on your iPhone, or via Siri. The library is accessed by placing the following near the top of your sketch:
`#include "extras/RFControl.h"`
## *RFControl(int pin, boolean refClock=true)*
Creating an instance of this **class** initializes the RF/IR signal generator and specifies the ESP32 *pin* to output the signal. You may create more than one instance of this class if driving more than one RF/IR transmitter (each connected to different *pin*), subject to the following limitations: ESP32 - 8 instances; ESP32-S2 and ESP32-S3 - 4 instances; ESP32-C3 - 2 instances. The optional parameter *refClock* is more fully described further below under the `start()` method.
Creating an instance of this **class** initializes the RF/IR signal generator and specifies the ESP32 *pin* to output the signal. You may create more than one instance of this class if driving more than one RF/IR transmitter (each connected to different *pin*), subject to the following limitations: ESP32 - 8 instances; ESP32-S2 - 4 instances; ESP32-C3 - 2 instances. The optional parameter *refClock* is more fully described further below under the `start()` method.
Signals are defined as a sequence of HIGH and LOW phases that together form a pulse train where you specify the duration, in *ticks*, of each HIGH and LOW phase, shown respectively as H1-H4 and L1-L4 in the following diagram:
@ -13,9 +15,9 @@ Signals are defined as a sequence of HIGH and LOW phases that together form a pu
Since most RF/IR signals repeat the same train of pulses more than once, the duration of the last LOW phase should be extended to account for the delay between repeats of the pulse train. Pulse trains are encoded as sequential arrays of 32-bit words, where each 32-bit word represents an individual pulse using the following protocol:
* bits 0-14: the duration, in *ticks* from 0-32767, of the first part of the pulse to be transmitted
* bit 15: indicates whether the first part of the pulse to be transmitted is HIGH (1) or LOW (0)
* bit 15: indicates whether the first part of the pulse to be trasnmitted is HIGH (1) or LOW (0)
* bits 16-30: the duration, in *ticks* from 0-32767, of the second part of the pulse to be transmitted
* bit 31: indicates whether the second part of the pulse to be transmitted is HIGH (1) or LOW (0)
* bit 31: indicates whether the second part of the pulse to be trasnmitted is HIGH (1) or LOW (0)
HomeSpan provides two easy methods to create, store, and transmit a pulse train. The first method relies on the fact that each instance of RFControl maintains its own internal memory structure to store a pulse train of arbitrary length. The functions `clear()`, `add()`, and `pulse()`, described below, allow you to create a pulse train using this internal memory structure. The `start()` function is then used to begin transmission of the full pulse train. This method is generally used when pulse trains are to be created on-the-fly as needed, since each RFControl instance can only store a single pulse train at one time.
@ -79,18 +81,16 @@ rf.start(4,1000); // start transmission of the pulse train; repeat for 4 cycles
uint32_t pulseTrain[] = {RF_PULSE(100,50), RF_PULSE(100,50), RF_PULSE(25,500)}; // create the same pulse train in an external array
rf.start(pulseTrain,3,4,1000); // start transmission using the same parameters
```
#### Diagnostic Messages
The **RFControl** class outputs *Warning \[W\]* messages to the Serial Monitor based on the *Core Debug Level* selected when compiling the sketch using the Arduino IDE. A non-fatal warning message is produced when insufficient Channel resources prevent the creation of a new RFControl object. Calls to the `start()` method for objects that failed to be properly created are silently ignored.
## Example RFControl Sketch
Below is a complete sketch that produces two different pulse trains with the signal output linked to the ESP32 device's built-in LED (rather than an RF or IR transmitter). For illustrative purposes the tick duration has been set to a very long 100𝛍s, and pulse times range from of 1000-10,000 ticks, so that the individual pulses are easily discernable on the LED. Note this example sketch is also available in the Arduino IDE under [*File → Examples → HomeSpan → Other Examples → RemoteControl*](../examples/Other%20Examples/RemoteControl).
Below is a complete sketch that produces two different pulse trains with the signal output linked to the ESP32 device's built-in LED (rather than an RF or IR transmitter). For illustrative purposes the tick duration has been set to a very long 100𝛍s, and pulse times range from of 1000-10,000 ticks, so that the individual pulses are easily discernable on the LED. Note this example sketch is also available in the Arduino IDE under [*File → Examples → HomeSpan → Other Examples → RemoteControl*](../Other%20Examples/RemoteControl).
```C++
/* HomeSpan Remote Control Example */
#include "HomeSpan.h" // include the HomeSpan library
#include "extras/RFControl.h" // include RF Control Library
void setup() {
@ -139,4 +139,4 @@ void loop(){
---
[↩️](../README.md) Back to the Welcome page
[↩️](README.md) Back to the Welcome page

View File

@ -8,7 +8,7 @@ The HomeSpan Library is invoked by including *HomeSpan.h* in your Arduino sketch
## *homeSpan*
At runtime HomeSpan will create a global **object** named `homeSpan` (of type *class Span*) that supports the following methods:
At runtime HomeSpan will create a global **object** named `homeSpan` that supports the following methods:
* `void begin(Category catID, const char *displayName, const char *hostNameBase, const char *modelName)`
* initializes HomeSpan
@ -22,97 +22,65 @@ At runtime HomeSpan will create a global **object** named `homeSpan` (of type *c
* `void poll()`
* checks for HAP requests, local commands, and device activity
* **must** be called repeatedly in each sketch and is typically placed at the top of the Arduino `loop()` method (*unless* `autoPoll()`, described further below, is used instead)
* **must** be called repeatedly in each sketch and is typically placed at the top of the Arduino `loop()` method
---
The following **optional** `homeSpan` methods override various HomeSpan initialization parameters used in `begin()`, and therefore **should** be called before `begin()` to take effect.
Methods with a return type of `Span&` return a reference to `homeSpan` itself and can thus be chained together (e.g. `homeSpan.setControlPin(21).setStatusPin(13);`). If a method is *not* called, HomeSpan uses the default parameter indicated below:
The following **optional** `homeSpan` methods override various HomeSpan initialization parameters used in `begin()`, and therefore **should** be called before `begin()` to take effect. If a method is *not* called, HomeSpan uses the default parameter indicated below:
* `Span& setControlPin(uint8_t pin, triggerType=PushButton::TRIGGER_ON_LOW)`
* sets the ESP32 *pin* to use for the HomeSpan Control Button
* if this method is not called, HomeSpan will assume there is no Control Button
* the optional second argument, *triggerType*, configures the Control Button as follows:
* `PushButton::TRIGGER_ON_LOW` - triggers when *pin* is driven LOW
* suitable for buttons that connect *pin* to GROUND (this is the default when *triggerType* is not specified)
* `PushButton::TRIGGER_ON_HIGH` - triggers when *pin* is driven HIGH
* suitable for buttons that connect *pin* to VCC (typically 3.3V)
* `PushButton::TRIGGER_ON_TOUCH` - uses the device's touch-sensor peripheral to trigger when *pin* has been touched
* not available on ESP32-C3
* as an alternative, you can set *triggerType* to any user-defined function of the form `boolean(int arg)` to utilize any device as a Control Button. See **SpanButton** below for details
* `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 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
* `Span& setStatusPin(uint8_t pin)`
* 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 any equivalent method is called, HomeSpan will assume there is no Status LED
* `Span& setStatusPixel(uint8_t pin, float h=0, float s=100, float v=100)`
* sets the ESP32 *pin* to use for the HomeSpan Status LED
* this method is an *alternative* to using `setStatusPin()` above
* 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 any equivalent method is called, HomeSpan will assume there is no Status LED
* `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
* `Span& setStatusDevice(Blinkable *sDev)`
* sets the Status LED to a user-specified Blinkable device, *sDev*
* this method is an *alternative* to using either `setStatusPin()` or `setStatusPixel()` above
* see [Blinkable](Blinkable.md) for details on how to create generic Blinkable devices
* useful when using an LED connected to a pin expander, or other specialized driver, as the Status LED
* if neither this method nor any equivalent method is called, HomeSpan will assume there is no Status LED
* `Span& setStatusAutoOff(uint16_t duration)`
* `void setStatusAutoOff(uint16_t duration)`
* sets Status LED to automatically turn off after *duration* seconds
* Status LED will automatically turn on, and duration timer will be reset, whenever HomeSpan activates a new blinking pattern
* if *duration* is set to zero, auto-off is disabled (Status LED will remain on indefinitely)
* `int getStatusPin()`
* returns the pin number of the Status LED as set by `setStatusPin(pin)`, or -1 if no pin has been set
* returns the pin number of the Status LED as set by `setStatusPin(pin)`, or -1 if no pin has been set
* `Span& setApSSID(const char *ssid)`
* `void setApSSID(const char *ssid)`
* sets the SSID (network name) of the HomeSpan Setup Access Point (default="HomeSpan-Setup")
* `Span& setApPassword(const char *pwd)`
* `void setApPassword(const char *pwd)`
* sets the password of the HomeSpan Setup Access Point (default="homespan")
* `Span& setApTimeout(uint16_t nSec)`
* `void setApTimeout(uint16_t nSec)`
* sets the duration (in seconds) that the HomeSpan Setup Access Point, once activated, stays alive before timing out (default=300 seconds)
* `Span& setCommandTimeout(uint16_t nSec)`
* `void setCommandTimeout(uint16_t nSec)`
* sets the duration (in seconds) that the HomeSpan End-User Command Mode, once activated, stays alive before timing out (default=120 seconds)
* `Span& setLogLevel(int level)`
* `void setLogLevel(uint8_t level)`
* sets the logging level for diagnostic messages, where:
* 0 = top-level HomeSpan status messages, and any `LOG0()` messages specified in the sketch by the user (default)
* 0 = top-level HomeSpan status messages, and any messages output by the user using `Serial.print()` or `Serial.printf()` (default)
* 1 = all HomeSpan status messages, and any `LOG1()` messages specified in the sketch by the user
* 2 = all HomeSpan status messages plus all HAP communication packets to and from the HomeSpan device, as well as all `LOG1()` and `LOG2()` messages specified in the sketch by the user
* -1 = supresses ALL HomeSpan status messages, including all `LOG0()`, `LOG1()`, and `LOG2()` messages specified in the sketch by the user, freeing up the Serial port for other uses
* the log level setting has no impact on any `Serial.print()` or `Serial.printf()` statements that may be used in a sketch. Use one of the `LOG()` macros instead of `Serial.print()` or `Serial.printf()` if you want to control the output by setting the HomeSpan log level
* the log level setting has no impact on any ESP32 diagnostic messages output by the ESP32 operating system itself. To suppress these mesaages make sure to compile your sketch with the *Core Debug Level* set to "None" in the Tools menu of the Arduino IDE
* note the log level can also be changed at runtime with the 'L' command via the [HomeSpan CLI](CLI.md)
* see [Message Logging](Logging.md) for complete details
* `int getLogLevel()`
* returns the current Log Level as set by `setLogLevel(level)`
* `Span& setPortNum(uint16_t port)`
* `void reserveSocketConnections(uint8_t nSockets)`
* reserves *nSockets* network sockets for uses **other than** by the HomeSpan HAP Server for HomeKit Controller Connections
* for sketches compiled under Arduino-ESP32 v2.0.1 or later, HomeSpan reserves 14 sockets for HAP Controller Connections
* each call to `reserveSocketConnections(nSockets)` reduces this number by *nSockets*
* use this method if you add code to a sketch that requires its own socket connections (e.g. a separate web service, an MQTT server, etc.)
* multiple calls to this method are allowed - the number of sockets reserved will be the sum of *nSockets* across all calls
* note you do not need to separately reserve sockets for built-in HomeSpan functionality
* for example, `enableOTA()` already contains an embedded call to `reserveSocketConnections(1)` since HomeSpan knows one socket must be reserved to support OTA
* `void setPortNum(uint16_t port)`
* sets the TCP port number used for communication between HomeKit and HomeSpan (default=80)
* `Span& setHostNameSuffix(const char *suffix)`
* `void setHostNameSuffix(const char *suffix)`
* sets the suffix HomeSpan appends to *hostNameBase* to create the full hostName
* if not specified, the default is for HomeSpan to append a dash "-" followed the 6-byte Accessory ID of the HomeSpan device
* setting *suffix* to a null string "" is permitted
* example: `homeSpan.begin(Category::Fans, "Living Room Ceiling Fan", "LivingRoomFan");` will yield a default *hostName* of the form *LivingRoomFan-A1B2C3D4E5F6.local*. Calling `homeSpan.setHostNameSuffix("v2")` prior to `homeSpan.begin()` will instead yield a *hostName* of *LivingRoomFanv2.local*
* `Span& setQRID(const char *id)`
* `void setQRID(const char *id)`
* changes the Setup ID, which is used for pairing a device with a [QR Code](QRCodes.md), from the HomeSpan default to *id*
* the HomeSpan default is "HSPN" unless permanently changed for the device via the [HomeSpan CLI](CLI.md) using the 'Q' command
* *id* must be exactly 4 alphanumeric characters (0-9, A-Z, and a-z). If not, the request to change the Setup ID is silently ignored and the default is used instead
@ -121,26 +89,19 @@ Methods with a return type of `Span&` return a reference to `homeSpan` itself an
The following **optional** `homeSpan` methods enable additional features and provide for further customization of the HomeSpan environment. Unless otherwise noted, calls **should** be made before `begin()` to take effect:
* `int enableOTA(boolean auth=true, boolean safeLoad=true)`
* `void enableOTA(boolean auth=true, boolean safeLoad=true)`
* enables [Over-the-Air (OTA) Updating](OTA.md) of a HomeSpan device, which is otherwise disabled
* HomeSpan OTA requires an authorizing password unless *auth* is specified and set to *false*
* the default OTA password for new HomeSpan devices is "homespan-ota"
* this can be changed via the [HomeSpan CLI](CLI.md) using the 'O' command
* note enabling OTA reduces the number of HAP Controller Connections by 1
* OTA Safe Load will be enabled by default unless the second argument is specified and set to *false*. HomeSpan OTA Safe Load checks to ensure that sketches uploaded to an existing HomeSpan device are themselves HomeSpan sketches, and that they also have OTA enabled. See [HomeSpan OTA Safe Load](OTA.md#ota-safe-load) for details
* returns 0 if enabling OTA was successful, or -1 and reports an error to the Serial Monitor if not
* `int enableOTA(const char *pwd, boolean safeLoad=true)`
* an alternative form of `enableOTA()` that allows you to programmatically change the OTA password to the specified *pwd*
* *pwd* must contain between 1 and 32 characters
* this command causes HomeSpan to ignore, but does not otherwise alter, any password stored using the 'O' command
* returns 0 if enabling OTA was successful, or -1 and reports an error to the Serial Monitor if not
* `Span& enableAutoStartAP()`
* `void enableAutoStartAP()`
* enables automatic start-up of WiFi Access Point if WiFi Credentials are **not** found at boot time
* methods to alter the behavior of HomeSpan's Access Point, such as `setApTimeout()`, must be called prior to `enableAutoStartAP()` to have an effect
* `Span& setApFunction(void (*func)())`
* `void setApFunction(void (*func)())`
* replaces HomeSpan's built-in WiFi Access Point with user-defined function *func*
* *func* must be of type *void* and have no arguments
* *func* will be called instead of HomeSpan's built-in WiFi Access Point whenever the Access Point is launched:
@ -150,53 +111,35 @@ The following **optional** `homeSpan` methods enable additional features and pro
* after identifying the SSID and password of the desired network, *func* must call `setWifiCredentials()` to save and use these values
* it is recommended that *func* terminates by restarting the device using `ESP.restart()`. Upon restart HomeSpan will use the SSID and password just saved
* `Span& setWifiCredentials(const char *ssid, const char *pwd)`
* `void setWifiCredentials(const char *ssid, const char *pwd)`
* sets the SSID (*ssid*) and password (*pwd*) of the WiFi network to which HomeSpan will connect
* *ssid* and *pwd* are automatically saved in HomeSpan's non-volatile storage (NVS) for retrieval when the device restarts
* note that the saved values are truncated if they exceed the maximum allowable characters (ssid=32; pwd=64)
* :warning: SECURITY WARNING: The purpose of this function is to allow advanced users to *dynamically* set the device's WiFi Credentials using a customized Access Point function specified by `setApFunction(func)`. It it NOT recommended to use this function to hardcode your WiFi SSID and password directly into your sketch. Instead, use one of the more secure methods provided by HomeSpan, such as typing 'W' from the CLI, or launching HomeSpan's Access Point, to set your WiFi credentials without hardcoding them into your sketch
* `Span& setVerboseWifiReconnect(bool verbose)`
* when trying connecting to WiFi, HomeSpan normally logs "Trying to connect to..." messages to the Serial Monitor and the Web Log
* calling this method with *verbose* set to *false* supresses these messages
* calling this method a second time with *verbose* set to *true* re-activates these messages (default behavior)
> :warning: SECURITY WARNING: The purpose of this function is to allow advanced users to *dynamically* set the device's WiFi Credentials using a customized Access Point function specified by `setApFunction(func)`. It it NOT recommended to use this function to hardcode your WiFi SSID and password directly into your sketch. Instead, use one of the more secure methods provided by HomeSpan, such as typing 'W' from the CLI, or launching HomeSpan's Access Point, to set your WiFi credentials without hardcoding them into your sketch
* `Span& setWifiCallback(void (*func)())`
* sets an optional user-defined callback function, *func*, to be called by HomeSpan upon start-up just after WiFi connectivity has been initially established. This one-time call to *func* is provided for users that are implementing other network-related services as part of their sketch, but that cannot be started until WiFi connectivity is established. The function *func* must be of type *void* and have no arguments
* `void setWifiCallback(void (*func)())`
* sets an optional user-defined callback function, *func*, to be called by HomeSpan upon start-up just after WiFi connectivity has been established. This one-time call to *func* is provided for users that are implementing other network-related services as part of their sketch, but that cannot be started until WiFi connectivity is established. The function *func* must be of type *void* and have no arguments
* `Span& setWifiCallbackAll(void (*func)(int count))`
* similar to `setWiFiCallback()` above, but the user-defined callback function, *func*, is called by HomeSpan *every* time WiFi connectivity has been established or re-established after a disconnect. The function *func* must be of type *void* and accept a single *int* argument, *count*, into which HomeSpan passes the number of times WiFi has been established or re-established (i.e. *count*=1 on initial WiFi connection; *count*=2 if re-established after the first disconnect, etc.)
* `Span& setPairCallback(void (*func)(boolean status))`
* `void setPairCallback(void (*func)(boolean status))`
* 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 accept one *boolean* argument
* `Span& setControllerCallback(void (*func)())`
* sets an optional user-defined callback function, *func*, to be called by HomeSpan every time a new controller is added, removed, or updated, even if the pairing status does not change
* note this method differs from `setPairCallback()`, which is only called if the device's pairing status changes, such as when the first controller is added during initial pairing, or the last controller is removed when unpairing
* the function *func* must be of type *void* and have no arguments
* see the `controllerListBegin()` and `controllerListEnd()` methods for details on how to read the pairing data for each paired controller (*only needed to support certain advanced use cases*)
* `Span& 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)
* the function *func* must be of type *void* and have one *boolean* argument
* `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
* `Span& setPairingCode(const char *s)`
* `void setPairingCode(const char *s)`
* sets the Setup Pairing Code to *s*, which **must** be exactly eight numerical digits (no dashes)
* example: `homeSpan.setPairingCode("46637726");`
* a hashed version of the Pairing Code will be saved to the device's non-volatile storage, overwriting any currently-stored Pairing Code
* this method operated silently unless *s* contains an invalid code, in which case a fatal error will be reported to the Serial Monitor, the code will *not* be saved, and the sketch will be HALTED
* :warning: SECURTY WARNING: Hardcoding a device's Pairing Code into your sketch is considered a security risk and is **not** recommended. Instead, use one of the more secure methods provided by HomeSpan, such as typing 'S \<code\>' from the CLI, or launching HomeSpan's Access Point, to set your Pairing Code without hardcoding it into your sketch
* if *s* contains an invalid code, an error will be reported and the code will *not* be saved. Instead, the currently-stored Pairing Code (or the HomeSpan default Pairing Code if no code has been stored) will be used
* :exclamation: SECURTY WARNING: Hardcoding a device's Pairing Code into your sketch is considered a security risk and is **not** recommended. Instead, use one of the more secure methods provided by HomeSpan, such as typing 'S \<code\>' from the CLI, or launching HomeSpan's Access Point, to set your Pairing Code without hardcoding it into your sketch
* `Span& setSketchVersion(const char *sVer)`
* `void deleteStoredValues()`
* deletes the value settings of all stored Characteristics from the NVS
* performs the same function as typing 'V' into the CLI
* can by called from anywhere in a sketch
* `void setSketchVersion(const char *sVer)`
* sets the version of a HomeSpan sketch to *sVer*, which can be any arbitrary character string
* if unspecified, HomeSpan uses "n/a" as the default version text
* HomeSpan displays the version of the sketch in the Arduino IDE Serial Monitor upon start-up
@ -206,150 +149,25 @@ The following **optional** `homeSpan` methods enable additional features and pro
* returns the version of a HomeSpan sketch, as set using `void setSketchVersion(const char *sVer)`, or "n/a" if not set
* can by called from anywhere in a sketch
* `Span& enableWebLog(uint16_t maxEntries, const char *timeServerURL, const char *timeZone, const char *logURL)`
* enables a rolling Web Log that displays the most recent *maxEntries* entries created by the user with the `WEBLOG()` macro. Parameters, and their default values if unspecified, are as follows:
* *maxEntries* - maximum number of (most recent) entries to save. If unspecified, defaults to 0, in which case the Web Log will only display status without any log entries
* *timeServerURL* - the URL of a time server that HomeSpan will use to set its clock upon startup after a WiFi connection has been established. HomeSpan will reserve one extra socket connection when a time server is specified. If unspecified, defaults to NULL, in which case HomeSpan skips setting the device clock
* *timeZone* - specifies the time zone to use for setting the clock. Uses POSIX.1 format only and does not support the *Time Zone Database*, or *tzdata*. As per [GNU libc documentation for TZ](https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html), *the offset specifies the time value you must **add to the local time** to get a Coordinated Universal Time value*. "UTC+5:00" means that local time + 5 hours give UTC time. See the GNU libc documentation for some examples, including how to specify North American Eastern Standard Time (EST) and Eastern Daylight Time (EDT) with start and end dates of EDT. If *serverURL=NULL* this field is ignored; if *serverURL!=NULL* this field is required
* *logURL* - the URL of the Web Log page for this device. If unspecified, defaults to "status". If *logURL* is set to NULL HomeSpan will use the *timeServerURL* and *timeZone* parameters to set the clock, but it will *not* serve any Web Log pages in response to any HTTP requests. However, Web Log data is still accumulated internally and the resulting HTML can be accessed anytime by calling the `homeSpan.getWebLog()` method (see below)
* `void enableWebLog(uint16_t maxEntries, const char *timeServerURL, const char *timeZone, const char *logURL)`
* enables a rolling web log that displays the most recent *maxEntries* entries created by the user with the `WEBLOG()` macro. Parameters, and their default values if unspecified, are as follows:
* *maxEntries* - maximum number of (most recent) entries to save. If unspecified, defaults to 0, in which case the web log will only display status without any log entries
* *timeServerURL* - the URL of a time server that HomeSpan will use to set its clock upon startup after a WiFi connection has been established. If unspecified, default to NULL, in which case HomeSpan skips setting the device clock
* *timeZone* - specifies the time zone to use for setting the clock. Uses standard Unix timezone formatting as interpreted by Espressif IDF. Note the IDF uses a somewhat non-intuitive convention such that a timezone of "UTC+5:00" *subtracts* 5 hours from UTC time, and "UTC-5:00" *adds* 5 hours to UTC time. If *serverURL=NULL* this field is ignored; if *serverURL!=NULL* this field is required
* *logURL* - the URL of the log page for this device. If unspecified, defaults to "status"
* example: `homeSpan.enableWebLog(50,"pool.ntp.org","UTC-1:00","myLog");` creates a web log at the URL *http<nolink>://HomeSpan-\[DEVICE-ID\].local:\[TCP-PORT\]/myLog* that will display the 50 most-recent log messages produced with the WEBLOG() macro. Upon start-up (after a WiFi connection has been established) HomeSpan will attempt to set the device clock by calling the server "pool.ntp.org" and adjusting the time to be 1 hour ahead of UTC.
* when attemping to connect to *timeServerURL*, HomeSpan waits 120 seconds for a response. This is done in the background and does not block HomeSpan from running as usual while it tries to set the time. If no response is received after the 120-second timeout period, HomeSpan assumes the server is unreachable and skips the clock-setting procedure. Use `setTimeServerTimeout()` to re-configure the 120-second timeout period to another value
* when attemping to connect to *timeServerURL*, HomeSpan waits 10 seconds for a response. If no response is received after the 10-second timeout period, HomeSpan assumes the server is unreachable and skips the clock-setting procedure. Use `setTimeServerTimeout()` to re-configure the 10-second timeout period to another value
* see [Message Logging](Logging.md) for complete details
* `Span& setTimeServerTimeout(uint32_t tSec)`
* changes the default 120-second timeout period HomeSpan uses when `enableWebLog()` tries set the device clock from an internet time server to *tSec* seconds
* `Span& setWebLogCSS(const char *css)`
* sets the format of the HomeSpan Web Log to the custom style sheet specified by *css*
* see [Message Logging](Logging.md) for details on how to construct *css*
* `Span& setWebLogCallback(void (*func)(String &htmlText))`
* sets an optional user-defined callback function, *func*, to be called by HomeSpan whenever the Web Log is produced
* allows user to add additional custom data to the initial table of the Web Log by **extending** the String *htmlText*, which is passed as a reference to *func*
* the function *func* must be of type *void* and accept one argument of type *String*
* see [Message Logging](Logging.md) for details on how to construct *htmlText*
* `void getWebLog(void (*f)(const char *htmlBuf, void *args), void *userData)`
* when called, HomeSpan *streams* the current Web Log HTML text, and any optionally-specified *userData*, directly to the user-defined function, *f()*, which should return a *void* and accept the following two arguments:
* *htmlBuf* - pointer to part of the HTML text for the Web Log page
* *args* - a pass-through of the *userData* argument
* if user-defined data is not needed, set *userData* to NULL
* to avoid creating a single large text buffer, HomeSpan splits the HTML for the Web Log into chunks of 1024 bytes and repeatedly calls *f()* until all the HTML has been streamed; HomeSpan then makes a final call to *f()* with *htmlBuf* set to NULL indicating to the user that the end of the HTML text has been reached
* this command is primarily used to redirect Web Log pages to a user-defined process for alternative handling, display, or transmission
* see [Message Logging](Logging.md) for more details
* `void processSerialCommand(const char *CLIcommand)`
* processes the *CLIcommand* just as if were typed into the Serial Monitor
* allows for programmatic access to all CLI commands, included any custom commands defined by the user
* will work whether or not device is connected to a computer
* example: `homeSpan.processSerialCommand("A");` starts the HomeSpan Setup Access Point
* example: `homeSpan.processSerialCommand("Q HUB3");` changes the HomeKit Setup ID for QR Codes to "HUB3"
* `Span& setRebootCallback(void (*func)(uint8_t count), uint32_t upTime)`
* sets an optional user-defined callback function, *func*, that is called (just once) when *upTime* milliseconds after rebooting have elapsed (default *upTime*=5000 ms if not specified)
* the function *func* must be of type *void* and accept one argument of type *uint8_t*
* the parameter *count*, which HomeSpan passes to *func*, indicates the number of "short" reboots that have occured prior to the current reboot, where a "short" reboot is any that occurs **before** *upTime* milliseconds have elapsed
* this allows the user to provide a generic form of input to a sketch by rapidly turning on/off power to the device a specified number of times, typically to provide a method of resetting some aspect of a remote device
* example using a lamba function:
* `homeSpan.setRebootCallback( [](uint8_t c) {if(c==3) homeSpan.processSerialCommand("X");} );`
* causes HomeSpan to run the 'X' Serial Command, which erases WiFi data, if the device is "short" rebooted exactly 3 times, where each reboot is for less than 5 seconds
* note that creating 3 short reboots means you actually cycle the power (or press the reset button) a total of 4 times, since the last time you allow the sketch to run without rebooting
* `Span& setSerialInputDisable(boolean val)`
* if *val* is true, disables HomeSpan from reading input from the Serial port
* if *val* is false, re-enables HomeSpan reading input from the Serial port
* useful when the main USB Serial port is needed for reading data from an external Serial peripheral, rather than being used to read input from the Arduino Serial Monitor
* `boolean getSerialInputDisable()`
* returns *true* if HomeSpan reading from the Serial port is currently disabled
* returns *false* if HomeSpan is operating normally and will read any CLI commands input into the Arduino Serial Monitor
---
The following **optional** `homeSpan` methods provide additional run-time functionality for more advanced use cases:
* `void deleteStoredValues()`
* deletes the value settings of all stored Characteristics from the NVS
* performs the same function as typing 'V' into the CLI
* `boolean deleteAccessory(uint32_t aid)`
* deletes Accessory with Accessory ID of *aid*, if found
* returns true if successful (match found), or false if the specified *aid* does not match any current Accessories
* allows for dynamically changing the Accessory database during run-time (i.e. changing the configuration *after* the Arduino `setup()` has finished)
* deleting an Accessory automatically deletes all Services, Characteristics, and any other resources it contains
* outputs Level-1 Log Messages listing all deleted components
* note: though deletions take effect immediately, HomeKit Controllers, such as the Home App, will not be aware of these changes until the database configuration number is updated and rebroadcast - see `updateDatabase()` below
* `boolean updateDatabase()`
* recomputes the database configuration number and, if changed, rebroadcasts the new number via MDNS so all connected HomeKit Controllers, such as the Home App, can request a full refresh to accurately reflect the new configuration
* returns true if configuration number has changed, false otherwise
* *only* needed if you want to make run-time (i.e. after the Arduino `setup()` function has completed) changes to the device's Accessory database
* use anytime after dynamically adding one or more Accessories (with `new SpanAccessory(aid)`) or deleting one or more Accessories (with `homeSpan.deleteAccessory(aid)`)
* **important**: once you delete an Accessory, you cannot re-use the same *aid* when adding a new Accessory (on the same device) unless the new Accessory is configured with the exact same Services and Characteristics as the deleted Accessory
* note: this method is **not** needed if you have a static Accessory database that is fully defined in the Arduino `setup()` function of a sketch
* `Span& resetIID(uint32_t newIID)`
* resets the IID count for the current Accessory to *newIID*, which must be greater than 0
* throws an error and halts program if called before at least one Accessory is created
* example: `homeSpan.resetIID(100)` causes HomeSpan to set the IID to 100 for the very next Service or Characteristic defined within the current Accessory, and then increment the IID count going forward so that any Services or Characteristics subsequently defined (within the same Accessory) have IID=101, 102, etc.
* note: calling this function only affects the IID generation for the current Accessory (the count will be reset to IID=1 upon instantiation of a new Accessory)
* `const_iterator controllerListBegin()` and `const_iterator controllerListEnd()`
* returns a *constant iterator* pointing to either the *beginning*, or the *end*, of an opaque linked list that stores all controller data
* iterators should be defined using the `auto` keyword as follows: `auto myIt=homeSpan.controllerListBegin();`
* controller data can be read from a de-referenced iterator using the following methods:
* `const uint8_t *getID()` returns pointer to the 36-byte ID of the controller
* `const uint8_t *getLTPK()` returns pointer to the 32-byte Long Term Public Key of the controller
* `boolean isAdmin()` returns true if controller has admin permissions, else returns false
* <details><summary>click here for example code</summary><br>
```C++
// Extract and print the same data about each controller that HomeSpan prints to the Serial Monitor when using the 's' CLI command
Serial.printf("\nController Data\n");
for(auto it=homeSpan.controllerListBegin(); it!=homeSpan.controllerListEnd(); ++it){ // loop over each controller
Serial.printf("Admin=%d",it->isAdmin()); // indicate if controller has admin permissions
Serial.printf(" ID="); // print the 36-byte Device ID of the controller
for(int i=0;i<36;i++)
Serial.printf("%02X",it->getID()[i]);
Serial.printf(" LTPK="); // print the 32-byte Long-Term Public Key of the controller)
for(int i=0;i<32;i++)
Serial.printf("%02X",it->getLTPK()[i]);
Serial.printf("\n");
}
```
</details>
---
The following `homeSpan` methods are considered experimental, since not all use cases have been explored or debugged. Use with caution:
* `void autoPoll(uint32_t stackSize, uint32_t priority, uint32_t cpu)`
* an *optional* method to create a separate task that repeatedly calls `poll()` in the background. This frees up the Ardino `loop()` method for any user-defined code to run in parallel that would otherwise block, or be blocked by, calling `poll()` in the `loop()` method. Parameters, and their default values if unspecified, are as follows:
* *stackSize* - size of stack, in bytes, used by the polling task. Default=8192 if unspecified
* *priority* - priority at which task runs. Minimum is 1. Maximum is typically 24, but it depends on how the ESP32 operating system is configured. If you set it to an arbitrarily high value (e.g. 999), it will be set to the maximum priority allowed. Default=1 if unspecified
* *cpu* - specifies the CPU on which the polling task will run. Valid values are 0 and 1. This parameter is ignored on single-cpu boards. Default=0 if unspecified
* if used, **must** be placed in a sketch as the last line in the Arduino `setup()` method
* HomeSpan will throw and error and halt if both `poll()`and `autoPoll()` are used in the same sketch - either place `poll()` in the Arduino `loop()` method **or** place `autoPoll()` at the the end of the Arduino `setup()` method
* if this method is used, and you have no need to add your own code to the main Arduino `loop()`, you can safely skip defining a blank `void loop(){}` function in your sketch
* warning: if any code you add to the Arduino `loop()` method tries to alter any HomeSpan settings or functions running in the background `poll()` task, race conditions may yield undefined results
* `TaskHandle_t getAutoPollTask()`
* returns the task handle for the Auto Poll Task, or NULL if Auto Polling has not been used
* `void setTimeServerTimeout(uint32_t tSec)`
* changes the default 10-second timeout period HomeSpan uses when `enableWebLog()` tries set the device clock from an internet time server to *tSec* seconds
## *SpanAccessory(uint32_t aid)*
Creating an instance of this **class** adds a new HAP Accessory to the HomeSpan HAP Database.
* every HomeSpan sketch requires at least one Accessory
* a sketch can contain a maximum of 150 Accessories per sketch (if exceeded, a runtime error will the thrown and the sketch will halt)
* a sketch can contain a maximum of 41 Accessories per sketch (if exceeded, a runtime error will the thrown and the sketch will halt)
* there are no associated methods
* the argument *aid* is optional.
@ -382,19 +200,15 @@ The following methods are supported:
* note though this functionality is defined by Apple in HAP-R2, it seems to have been deprecated and no longer serves any purpose or has any affect on the Home App
* `SpanService *addLink(SpanService *svc)`
* adds *svc* as a Linked Service. Returns a pointer to the calling Service itself so that the method can be chained during instantiation
* note that Linked Services are only applicable for select HAP Services. See Apple's HAP-R2 documentation for full details
* adds *svc* as a Linked Service. Returns a pointer to the calling Service itself so that the method can be chained during instantiation.
* note that Linked Services are only applicable for select HAP Services. See Apple's [HAP-R2](https://developer.apple.com/support/homekit-accessory-protocol/) documentation for full details.
* example: `(new Service::Faucet)->addLink(new Service::Valve)->addLink(new Service::Valve);` (links two Valves to a Faucet)
* `vector<T> getLinks<T=SpanService *>(const char *serviceName=NULL)`
* template function that returns a vector of pointers to Services that were added using `addLink()`
* if template parameter, *T*, is left blank, the elements of the returned vector will be of type *SpanService \**
* if template parameter, *T*, is specified, the elements of the returned vector will be cast into type *T*
* if *serviceName* is specified, only those services matching *serviceName* will be included in the return vector
* *serviceName* must be one of HomeSpan's built-in Services (e.g. "Valve")
* if *serviceName* is left blank or set to NULL, all services will be included in the return vector
* this function is useful for creating loops that iterate over all linked Services
* example: from within a Faucet Service containing linked Valves defined in *MyValveService*, use `for(auto valve : getLinks<MyValveService *>()) { if(valve->active->getVal()) ... }` to check which Valves are active
* `vector<SpanService *> getLinks()`
* returns a vector of pointers to Services that were added using `addLink()`
* useful for creating loops that iterate over all linked Services
* note that the returned vector points to generic SpanServices, which should be re-cast as needed
* example: `for(auto myValve : faucet::getLinks()) { if((MyValve *)myValve)->active->getVal()) ... }` checks all Valves linked to to a Faucet
* `virtual boolean update()`
* HomeSpan calls this method upon receiving a request from a HomeKit Controller to update one or more Characteristics associated with the Service. Users should override this method with code that implements that requested updates using one or more of the SpanCharacteristic methods below. Method **must** return *true* if update succeeds, or *false* if not.
@ -409,9 +223,6 @@ The following methods are supported:
* 0=single press (SpanButton::SINGLE)
* 1=double press (SpanButton::DOUBLE)
* 2=long press (SpanButton::LONG)
* `uint32_t getIID()`
* returns the IID of the Service
## *SpanCharacteristic(value [,boolean nvsStore])*
@ -419,18 +230,13 @@ This is a **base class** from which all HomeSpan Characteristics are derived, an
* instantiated Characteristics are added to the HomeSpan HAP Database and associated with the last Service instantiated
* instantiating a Characteristic without first instantiating a Service throws an error during initialization
* the first argument optionally allows you to set the initial *value* of the Characteristic at startup using the following formats:
* for NUMERIC Characteristics, *value* can be any integer or decimal numeric type, such as `boolean`, `int`, `uint64_t`, `double`, etc. HomeSpan will automatically cast *value* into a variable with the correct numerical precision for the Characteristic specified
* for STRING Characteristics, *value* must be either of the type `char *`, or a literal quote-enclosed UTF-8 string
* for TLV8 Characteristics, *value* must be of the type `TLV8`
* for DATA Characteristics, *value* must be a brace-enclosed *pair* of the form `{uint8_t *data, size_t len}`, where *len* specifies the length of the size of the byte-array *data*
* if *value* is not specified, HomeSpan will supply a reasonable default for the Characteristic
* for numerical Characteristics, throws a runtime warning if *value* is outside of the min/max range for the Characteristic, where min/max is either the HAP default, or any new values set via a call to `setRange()`
* the first argument optionally allows you to set the initial *value* of the Characteristic at startup. If *value* is not specified, HomeSpan will supply a reasonable default for the Characteristic
* throws a runtime warning if *value* is outside of the min/max range for the Characteristic, where min/max is either the HAP default, or any new values set via a call to `setRange()`
* the second optional argument, if set to `true`, instructs HomeSpan to save updates to this Characteristic's value in the device's non-volative storage (NVS) for restoration at startup if the device should lose power. If not specified, *nvsStore* will default to `false` (no storage)
* examples:
* `new Characteristic::Brightness();` Brightness initialized to default value
* `new Characteristic::Brightness(50);` Brightness initialized to 50
* `new Characteristic::Brightness(50,true);` Brightness initialized to 50; updates to the value are saved to, and restored from, NVS
* `new Characteristic::Brightness(50,true);` Brightness initialized to 50; updates saved in NVS
#### The following methods are supported for numerical-based Characteristics (e.g. *int*, *float*...):
@ -440,17 +246,13 @@ This is a **base class** from which all HomeSpan Characteristics are derived, an
* example with template excluded : `int tilt = Characteristic::CurrentTiltAngle->getVal();`
* `type T getNewVal<T>()`
* a template method that returns the desired **new** value to which a HomeKit Controller has requested the Characteristic be updated. Same casting rules as for `getVal<>()`
* only applicable when called from within the `update()` loop of a **SpanService** (if called outside of the `update()` loop, the return value is that same as calling `getVal<>()`)
* a template method that returns the desired **new** value to which a HomeKit Controller has requested the Characteristic be updated. Same casting rules as for `getVal<>()`. Only applicable for numerical-based Characteristics
* `void setVal(value [,boolean notify])`
* sets the value of a numerical-based Characteristic to *value*, and, if *notify* is set to true, notifies all HomeKit Controllers of the change. The *notify* flag is optional and will be set to true if not specified. Setting the *notify* flag to false allows you to update a Characateristic without notifying any HomeKit Controllers, which is useful for Characteristics that HomeKit automatically adjusts (such as a countdown timer) but will be requested from the Accessory if the Home App closes and is then re-opened
* works with any integer, boolean, or floating-based numerical *value*, though HomeSpan will convert *value* into the appropriate type for each Characteristic (e.g. calling `setValue(5.5)` on an integer-based Characteristic results in *value*=5)
* throws a runtime warning if *value* is outside of the min/max range for the Characteristic, where min/max is either the HAP default, or any new min/max range set via a prior call to `setRange()`
* note that *value* is **not** restricted to being an increment of the step size; for example it is perfectly valid to call `setVal(43.5)` after calling `setRange(0,100,5)` on a floating-based Characteristic even though 43.5 does does not align with the step size specified. The Home App will properly retain the value as 43.5, though it will round to the nearest step size increment (in this case 45) when used in a slider graphic (such as setting the temperature of a thermostat)
* throws a runtime warning if called from within the `update()` routine of a **SpanService** *and* `isUpdated()` is *true* for the Characteristic (i.e. it is being updated at the same time via the Home App), *unless* you are changing the value of a Characteristic in response to a *write-response* request from HomeKit (typically used only for certain TLV-based Characteristics)
* note this method can be used to update the value of a Characteristic even if the Characteristic is not permissioned for event notifications (EV), in which case the value stored by HomeSpan will be updated but the Home App will *not* be notified of the change
* *value* is **not** restricted to being an increment of the step size; for example it is perfectly valid to call `setVal(43.5)` after calling `setRange(0,100,5)` on a floating-based Characteristic even though 43.5 does does not align with the step size specified. The Home App will properly retain the value as 43.5, though it will round to the nearest step size increment (in this case 45) when used in a slider graphic (such as setting the temperature of a thermostat)
* `SpanCharacteristic *setRange(min, max, step)`
* overrides the default HAP range for a Characteristic with the *min*, *max*, and *step* parameters specified
@ -464,8 +266,9 @@ This is a **base class** from which all HomeSpan Characteristics are derived, an
* `SpanCharacteristic *setValidValues(int n, [int v1, int v2 ...])`
* overrides the default HAP Valid Values for Characteristics that have specific enumerated Valid Values with a variable-length list of *n* values *v1*, *v2*, etc.
* works on Characteristics with UINT8, UINT16, UINT32, and INT formats only
* a warning message is thrown, and the request is ignored, if this method is called on a Characteristic with any other format
* an error is thrown if:
* called on a Characteristic that does not have specific enumerated Valid Values, or
* called more than once on the same Characteristic
* returns a pointer to the Characteristic itself so that the method can be chained during instantiation
* example: `(new Characteristic::SecuritySystemTargetState())->setValidValues(3,0,1,3);` creates a new Valid Value list of length=3 containing the values 0, 1, and 3. This has the effect of informing HomeKit that a SecuritySystemTargetState value of 2 (Night Arm) is not valid and should not be shown as a choice in the Home App
@ -477,49 +280,8 @@ This is a **base class** from which all HomeSpan Characteristics are derived, an
* `char *getNewString()`
* equivalent to `getNewVal()`, but used exclusively for string-characteristics (i.e. a null-terminated array of characters)
* `void setString(const char *value [,boolean notify])`
* `void setString(const char *value)`
* equivalent to `setVal(value)`, but used exclusively for string-characteristics (i.e. a null-terminated array of characters)
#### The following methods are supported for DATA (i.e. byte-array) Characteristics:
* `size_t getData(uint8_t *data, size_t len)`
* similar to `getVal()`, but exclusively used for byte-array Characteristics
* fills byte array *data*, of specified size *len*, with all bytes "encoded" as the current value of the Characteristic
* returns the total number of bytes encoded in the Characteristic
* if *len* is less than the total number of bytes encoded, no data is extracted (i.e. *data* is unmodified) and a warning message is thrown indicating that the size of the *data* array is insufficient to extract all the bytes encoded in the Characteristic
* setting *data* to NULL returns the total number of bytes encoded without extracting any data. This can be used to help create a *data* array of sufficient size in advance of extracting the data
* note: byte-array Characteristics are encoded and transmitted as base-64 strings. HomeSpan automatically peforms all encoding and decoding between this format and the specified byte arrays. But when output to the Serial Monitor using the 'i' CLI command, the value of byte-array Characteristics are displayed in their base-64 string format (only the first 32 characters are shown), since base-64 is the representation that is actually transmitted to and from HomeKit
* a warning message is thrown if the value stored in the Characteristic is not in base-64 format
* `size_t getNewData(uint8_t *data, size_t len)`
* similar to `getData()`, but fills byte array *data*, of specified size *len*, with bytes based on the desired **new** value to which a HomeKit Controller has requested the Characteristic be updated
* `void setData(uint8_t *data, size_t len [,boolean notify])`
* similar to `setVal()`, but exclusively used for byte-array Characteristics
* updates the Characteristic by "filling" it with *len* bytes from bytes array *data*
#### The following methods are supported for TLV8 (structured-data) Characteristics:
* `size_t getTLV(TLV8 &tlv)`
* similar to `getVal()`, but exclusively used for TLV8 Characteristics
* fills TLV8 structure *tlv* with TLV8 records from the current value of the Characteristic
* returns the total number of bytes encoded in the Characteristic
* if *tlv8* is not empty, TLV8 records from the Characteristic will be appended to any existing records
* similar to DATA Characteristics, TLV8 Characteristics are stored and transmitted as base-64 strings
* a warning message is thrown if the value stored in the Characteristic is not in base-64 format, or does not appear to contain TLV8 records
* `size_t getNewTLV(TLV8 &tlv)`
* similar to `getTLV()`, but fills TLV8 structure *tlv* with TLV8 records based on the desired **new** value to which a HomeKit Controller has requested the Characteristic be updated
* `void setTLV(TLV8 &tlv [,boolean notify])`
* similar to `setVal()`, but exclusively used for TLV8 Characteristics
* updates the Characteristic by packing the TLV8 structure *tlv* into a byte array and then encoding it in base-64 for storage as a string
* `NULL_TLV`
* this is not a method, but rather a static HomeSpan constant defined as an empty TLV8 object. It may used a placeholder wherever an empty TLV8 object is needed, such as when you want to instantiate a TLV8 Characteristic with *nvsStore=true*, but you don't yet want set the TLV8 value
* example: `new Characteristic::DisplayOrder(NULL_TLV,true);`
* see the [TLV8 Characteristics](TLV8.md) page for complete details on how to read/process/create TLV8 Objects using HomeSpan's TLV8 Library.
#### The following methods are supported for all Characteristics:
@ -555,10 +317,7 @@ This is a **base class** from which all HomeSpan Characteristics are derived, an
* returns a pointer to the Characteristic itself so that the method can be chained during instantiation
* example: `(new Characteristic::RotationSpeed())->setUnit("percentage");`
* `uint32_t getIID()`
* returns the IID of the Characteristic
### *SpanButton(int pin, uint16_t longTime, uint16_t singleTime, uint16_t doubleTime, boolean (\*triggerType)(int))*
## *SpanButton(int pin, uint16_t longTime, uint16_t singleTime, uint16_t doubleTime)*
Creating an instance of this **class** attaches a pushbutton handler to the ESP32 *pin* specified.
@ -566,69 +325,24 @@ Creating an instance of this **class** attaches a pushbutton handler to the ESP3
* instantiating a Button without first instantiating a Service throws an error during initialization
The first argument is required; the rest are optional:
* *pin* - the ESP32 pin to which the button is connected
* *pin* - the ESP32 pin to which a one pole of a normally-open pushbutton will be connected; the other pole is connected to ground
* *longTime* - the minimum time (in millis) required for the button to be pressed and held to trigger a Long Press (default=2000 ms)
* *singleTime* - the minimum time (in millis) required for the button to be pressed to trigger a Single Press (default=5 ms)
* *doubleTime* - the maximum time (in millis) allowed between two Single Presses to qualify as a Double Press (default=200 ms)
* *triggerType* - pointer to a boolean function that accepts a single *int* argument and returns `true` or `false` depending on whether or not a "press" has been triggered on the *pin* number passed to the *int* argument. For ease of use, you may simply choose from the following built-in functions:
* `SpanButton::TRIGGER_ON_LOW` - triggers when *pin* is driven LOW. Suitable for buttons that connect *pin* to GROUND (this is the default when *triggerType* is not specified)
* `SpanButton::TRIGGER_ON_HIGH` - triggers when *pin* is driven HIGH. Suitable for buttons that connect *pin* to VCC (typically 3.3V)
* `SpanButton::TRIGGER_ON_TOUCH`- uses the device's touch-sensor peripheral to trigger when a sensor attached to *pin* has been touched (not available on ESP32-C3 devices)
When any of these built-in functions are selected (or *triggerType* is left unspecified and the default is used), SpanButton will automatically configure the *pin* as needed upon instantiation.
Alternatively, you can set *triggerType* to any user-defined function of the form `boolean(int arg)` and provide your own logic for determining whether a trigger has occured on the specified *pin*, which is passed through to your function as *arg*. In this case *arg* can either represent an actual device pin, or simply be an arbitrary *int* your function utilizes, such as the virtual pin number on a multiplexer. Note: if you specify your own function for *triggerType* you also must include in your sketch any code needed to initialize the logic or configure whatever resource *triggerType* is utilizing (such as a pin multiplexer).
For convenience, a second form of the *SpanButton()* constructor is also provided:
* `SpanButton(int pin, boolean (*triggerType)(int), uint16_t longTime=2000, uint16_t singleTime=5, uint16_t doubleTime=200)`
* this allows you to set just the *pin* and *triggerType* while leaving the remaining parameters at their default values
#### Trigger Rules ###
Trigger Rules:
* If button is pressed and continuously held, a Long Press will be triggered every longTime ms until the button is released
* If button is pressed for more than singleTime ms but less than longTime ms and then released, a Single Press will be triggered, UNLESS the button is pressed a second time within doubleTime ms AND held again for at least singleTime ms, in which case a DoublePress will be triggered; no further events will occur until the button is released
* If singleTime>longTime, only Long Press triggers can occur
* If doubleTime=0, Double Presses cannot occur
#### Usage ####
HomeSpan automatically calls the `button(int pin, int pressType)` method of a Service upon a trigger event in any SpanButton associated with that Service, where *pin* is the ESP32 pin to which the pushbutton is connected, and *pressType* is an integer that can also be represented by the enum constants indicated:
* 0=single press (`SpanButton::SINGLE`)
* 1=double press (`SpanButton::DOUBLE`)
* 2=long press (`SpanButton::LONG`)
HomeSpan automatically calls the `button(int pin, int pressType)` method of a Service upon a trigger event in any Button associated with that Service, where *pin* is the ESP32 pin to which the pushbutton is connected, and *pressType* is an integer that can also be represented by the enum constants indicated:
* 0=single press (SpanButton::SINGLE)
* 1=double press (SpanButton::DOUBLE)
* 2=long press (SpanButton::LONG)
HomeSpan will report a warning, but not an error, during initialization if the user had not overridden the virtual button() method for a Service contaning one or more Buttons; triggers of those Buttons will simply ignored.
When using one or more Touch Sensors, HomeSpan automatically calibrates the threshold at which they are triggered by polling the baseline sensor reading upon instantiation of first SpanButton of type `SpanButton::TRIGGER_ON_TOUCH`. For ESP32 devices, the threshold is set to 50% of the baseline value since triggers occur when a sensor value falls *below* the threhold level. For ESP32-S2 and ESP32-S3 devices, the threshold is set to 200% of the baseline value since triggers occur when a sensor value rises *above* the threhold level. Normally HomeSpan's auto calibration will result in accurate detection of SINGLE, DOUBLE, and LONG presses of touch sensors. However, if needed you can override the calibration and set your own threshold value using the following class-level method:
* `void SpanButton::setTouchThreshold(uintXX_t thresh)`
* sets the threshold value above (for ESP32 devices) or below (for ESP32-S2 and ESP32-S3 devices) which touch sensors are triggered to *thresh*
* *XX* is 16 (for ESP32 devices) or 32 (for ESP32-S2 and ESP32-S3 devices)
* the threshold specified is considered global and used for *all* SpanButton instances of type `SpanButton::TRIGGER_ON_TOUCH`
* this method can be called either before or after SpanButtons are created
In addition, you can also override the ESP32's touch sensor timing parameters using the following class-level method:
* `void SpanButton::setTouchCycles(uint16_t measureTime, uint16_t sleepTime)`
* changes the measurement time and sleep time clock cycles to *measureTime* and *sleepTime*, respectively. This is simply a pass-though call to the Arduino-ESP32 library `touchSetCycles()` function
* unless a specific threshold value has been set with `setTouchThreshold()`, `setTouchCycles()` must be called *before* instantiating the first SpanButton() of type `SpanButton::TRIGGER_ON_TOUCH` so that HomeSpan will calibrate the touch threshold based on the new timing parameters specified
### *SpanToggle(int pin, boolean (\*triggerType)(int)=PushButton::TRIGGER_ON_LOW, uint16_32 toggleTime=5)*
Creating an instance of this **class** attaches a toggle-switch handler to the ESP32 *pin* specified. This is a child class of *SpanButton* and thus derives all of the same functionality. For example, you can set *triggerType* to PushButton::TRIGGER_ON_HIGH, create your own trigger function, etc. However, instead of HomeSpan calling `button(int pin, int pressType)` when a pushbutton is "pressed," HomeSpan calls the same `button()` method when the switch is "toggled" from one position to another. In this case the parameter *pressType* that is passed into `button()` has a different set of enumerations:
* 3=switch is closed (`SpanToggle::CLOSED`)
* 4=switch is open (`SpanToggle::OPEN`)
Note there are no *singleTime*, *longTime*, or *doubleTime* parameters in the constructor since you can't single-press, double-press, or long-press a toggle switch. Instead, the constructor supports the single parameter *toggleTime* (default=5ms if left unspecified) that sets the minimum time at which the switch needs to be moved to the closed position in order to trigger a call to the `button()` method. This effectively "debounces" the toggle switch.
SpanToggle also supports the following additional method:
* `int position()`
* returns the current position of the toggle switch (i.e. SpanToggle::CLOSED or SpanToggle::OPEN)
* is equivalent to the *pressType* parameter passed to the `button()` method, but can be called from anywhere in a sketch
* useful for reading the initial state of a contact switch upon start-up so that the initial value of Characteristic::ContactSensorState can be set accordingly
* example `sensorState=new Characteristic::ContactSensorState(toggleSwitch->position()==SpanToggle::OPEN);`
### *SpanUserCommand(char c, const char \*desc, void (\*f)(const char \*buf [,void \*obj]) [,void \*userObject])*
Creating an instance of this **class** adds a user-defined command to the HomeSpan Command-Line Interface (CLI), where:
@ -667,21 +381,17 @@ To create more than one user-defined command, simply create multiple instances o
### *CUSTOM_CHAR(name,uuid,perms,format,defaultValue,minValue,maxValue,staticRange)*
### *CUSTOM_CHAR_STRING(name,uuid,perms,defaultValue)*
### *CUSTOM_CHAR_DATA(name,uuid,perms)*
### *CUSTOM_CHAR_TLV8(name,uuid,perms)*
Creates a custom Characteristic that can be added to any Service. Custom Characteristics are generally ignored by the Home App but may be used by other third-party applications (such as *Eve for HomeKit*). The first form should be used create numerical Characterstics (e.g., UINT8, BOOL...); the second form is used to STRING-based Characteristics; the third form is used for DATA-based (i.e. byte-array) Characteristics; and the fourth form is used for TLV8-based (i.e. *structured* byte-array) Characteristics Parameters are as follows (note that quotes should NOT be used in any of the macro parameters, except for *defaultValue* when applied to a STRING-based Characteristic):
Creates a custom Characteristic that can be added to any Service. Custom Characteristics are generally ignored by the Home App but may be used by other third-party applications (such as *Eve for HomeKit*). The first form should be used create numerical Characterstics (e.g., UINT8, BOOL...). The second form is used to String-based Characteristics. Parameters are as follows (note that quotes should NOT be used in any of the macro parameters, except for *defaultValue* when applied to a STRING-based Characteristic):
* *name* - the name of the custom Characteristic. This will be added to the Characteristic namespace so that it is accessed the same as any HomeSpan Characteristic. Use UTF-8 coded string for non-ASCII characters.
* *uuid* - the UUID of the Characteristic as defined by the manufacturer. Must be either:
* *exactly* 36 characters of the form XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX, where *X* represent a valid hexidecimal digit, or
* a single hexidecimal number of the form XXXXXXXX with *8 digits or less*, and no leading zeros
* *perms* - additive list of permissions. Valid values are PR, PW, EV, AA, TW, HD, and WR (e.g. PR+PW+EV)
* *format* - for numerical Characteristics, specifies the number format. Valid value are BOOL, UINT8, UINT16, UNIT32, UINT64, INT, and FLOAT. Not applicable for the STRING, DATA, or TLV8 Characteristic macros
* *defaultValue* - specifies the default value of the Characteristic when not defined during instantiation. Not applicable for the DATA or TLV7 Characteristic macros.
* *minValue* - specifies the default minimum range for a valid value, which may be able to be overriden by a call to `setRange()`. Not applicable for the STRING, DATA or TLV8 Characteristic macros
* *minValue* - specifies the default minimum range for a valid value, which may be able to be overriden by a call to `setRange()`. Not applicable for the STRING, DATA or TLV8 Characteristic macros
* *staticRange* - set to *true* if *minValue* and *maxValue* are static and cannot be overridden with a call to `setRange()`. Set to *false* if calls to `setRange()` are allowed. Not applicable for the STRING, DATA or TLV8 Characteristic macros
* *name* - the name of the custom Characteristic. This will be added to the Characteristic namespace so that it is accessed the same as any HomeSpan Characteristic
* *uuid* - the UUID of the Characteristic as defined by the manufacturer. Must be *exactly* 36 characters in the form XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX, where *X* represent a valid hexidecimal digit. Leading zeros are required if needed as described more fully in HAP-R2 Section 6.6.1
* *perms* - additive list of permissions as described in HAP-R2 Table 6-4. Valid values are PR, PW, EV, AA, TW, HD, and WR
* *format* - specifies the format of the Characteristic value, as described in HAP-R2 Table 6-5. Valid value are BOOL, UINT8, UINT16, UNIT32, UINT64, INT, and FLOAT (note that the HomeSpan does not presently support the TLV8 or DATA formats). Not applicable for Strings-based Characteristics
* *defaultValue* - specifies the default value of the Characteristic if not defined during instantiation
* *minValue* - specifies the default minimum range for a valid value, which may be able to be overriden by a call to `setRange()`. Not applicable for Strings-based Characteristics
* *minValue* - specifies the default minimum range for a valid value, which may be able to be overriden by a call to `setRange()`. Not applicable for Strings-based Characteristics
* *staticRange* - set to *true* if *minValue* and *maxValue* are static and cannot be overridden with a call to `setRange()`. Set to *false* if calls to `setRange()` are allowed. Not applicable for Strings-based Characteristics
As an example, the first line below creates a custom Characteristic named "Voltage" with a UUID code that is recognized by the *Eve for HomeKit* app. The parameters show that the Characteristic is read-only (PR) and notifications are enabled (EV). The default range of allowed values is 0-240, with a default of 120. The range *can* be overridden by subsequent calls to `setRange()`. The second line below creates a custom read-only String-based Characteristic:
@ -697,13 +407,9 @@ new Service::LightBulb();
new Characteristic::UserTag(); // adds UserTag Characteristic and retains default initial value of "Tag 123"
```
Note that Custom Characteristics must be created at the global level (i.e. not inside `setup()`) and prior to calling `homeSpan.begin()`
Note that Custom Characteristics must be created prior to calling `homeSpan.begin()`
> Advanced Tip 1: When presented with an unrecognized Custom Characteristic, *Eve for HomeKit* helpfully displays a *generic control* allowing you to interact with any Custom Characteristic you create in HomeSpan. However, since Eve does not recognize the Characteristic, it will only render the generic control if the Characteristic includes a **description** field, which you can add to any Characteristic using the `setDescription()` method described above. You may also want to use `setUnit()` and `setRange()` so that the Eve App displays a control with appropriate ranges for your Custom Characteristic.
> Advanced Tip 2: The DATA format is not currently used by any native Home App Characteristic, though it is part of the HAP-R2 specifications. This format is included in HomeSpan because other applications, such as *Eve for HomeKit* do use these types of Characteristics to create functionality beyond that of the Home App, and are thus provided for advanced users to experiment.
> Advanced Tip 3: When using multi-file sketches, the compiler will throw a "redefinition error" if you define the same Custom Characteristic in more than one file. To avoid this error and allow the same Custom Characteristic to be used across more than one file, add the line `#define CUSTOM_CHAR_HEADER` *before* `#include "HomeSpan.h"` in each file containing a *duplicate* definition of a previously-defined Custom Characteristic.
> Advanced Tip: When presented with an unrecognized Custom Characteristic, *Eve for HomeKit* helpfully displays a *generic control* allowing you to interact with any Custom Characteristic you create in HomeSpan. However, since Eve does not recognize the Characteristic, it will only render the generic control if the Characteristic includes a **description** field, which you can add to any Characteristic using the `setDescription()` method described above. You may also want to use `setUnit()` and `setRange()` so that the Eve App displays a control with appropriate ranges for your Custom Characteristic.
### *CUSTOM_SERV(name,uuid)*
@ -714,7 +420,7 @@ Creates a custom Service for use with third-party applications (such as *Eve for
Custom Services may contain a mix of both Custom Characteristics and standard HAP Characteristics, though since the Service itself is custom, the Home App will ignore the entire Service even if it contains some standard HAP Characterstics. Note that Custom Services must be created prior to calling `homeSpan.begin()`
A fully worked example showing how to use both the ***CUSTOM_SERV()*** and ***CUSTOM_CHAR()*** macros to create a Pressure Sensor Accessory that is recognized by *Eve for HomeKit* can be found in the Arduino IDE under [*File → Examples → HomeSpan → Other Examples → CustomService*](../examples/Other%20Examples/CustomService).
A fully worked example showing how to use both the ***CUSTOM_SERV()*** and ***CUSTOM_CHAR()*** macros to create a Pressure Sensor Accessory that is recognized by *Eve for HomeKit* can be found in the Arduino IDE under [*File → Examples → HomeSpan → Other Examples → CustomService*](../Other%20Examples/CustomService).
## Other Macros
@ -740,6 +446,24 @@ If REQUIRED is defined in the main sketch *prior* to including the HomeSpan libr
#include "HomeSpan.h"
```
---
#### Deprecated functions (available for backwards compatibility with older sketches):
* `SpanRange(int min, int max, int step)`
* this legacy class was limited to integer-based parameters and has been re-coded to simply call the more generic `setRange(min, max, step)` method
* last supported version: [v1.2.0](https://github.com/HomeSpan/HomeSpan/blob/release-1.2.0/docs/Reference.md#spanrangeint-min-int-max-int-step)
* **please use** `setRange(min, max, step)` **for all new sketches**
* `void homeSpan.setMaxConnections(uint8_t nCon)`
* this legacy method was used to set the total number of HAP Controller Connections HomeSpan implements upon start-up to ensure there are still free sockets available for user-defined code requiring separate network resources
* last supported version: [v1.4.2](https://github.com/HomeSpan/HomeSpan/blob/release-1.4.2/docs/Reference.md)
* this method has been replaced by the more flexible method, `reserveSocketConnections(uint8_t nSockets)`
* allows you to simply reserve network sockets for other custom code as needed
* upon calling `homespan.begin()`, HomeSpan automatically determines how many sockets are left that it can use for HAP Controller Connections
* **please use** `homeSpan.reserveSocketConnections(uint8_t nSockets)` **for all new sketches**
---
[↩️](../README.md) Back to the Welcome page
[↩️](README.md) Back to the Welcome page

View File

@ -1,455 +1,197 @@
# HomeSpan Services and Characteristics
Below is a list of all Services supported by HomeSpan, along with a brief description of each Service. Click on the arrow next to the description of any Service to see a list of all Characteristics supported by the Service, as well as a description of each Characteristic.[^1]
HomeSpan implements all [HAP-R2](https://developer.apple.com/homekit/specification/) Services and Characteristics except for those that involve video or audio streaming, Apple TV, or advanced lock management (i.e. all HAP Services except those that require Characteristics with a TLV8 data type).
A blue diamond (🔹) next to a Characteristic means it is **required** for that Service, otherwise it is optional. Information included for each Characteristic is as follows:
HomeSpan Services and Characteristics are implemented as C++ Classes with names that exactly match the spelling and capitalization specified by Apple in Sections 8 and 9 of [HAP-R2](https://developer.apple.com/homekit/specification/), but without any spaces. HomeSpan Services are defined in HomeSpan's `Service` namespace. HomeSpan Characteristics are defined in HomeSpan's `Characteristic` namespace. For example, HomeSpan defines the *Carbon Dioxide Sensor* Service (HAP Service 8.7) as `Service::CarbonDioxideSensor`, and the *Carbon Dioxide Detected* Characteristic (HAP Characteristic 9.16) as `Characteristic::CarbonDioxideDetected`.
* **Format** - the native format of the Characteristic's value. Note that *string* means a standard C-string (i.e. char \*)
* **Perms**, where
* PR = Paired Read. This means HomeKit can read the value from HomeSpan[^2]
* PW = Paired Write. This means HomeKit can write the value to HomeSpan, which triggers a call to `update()`
* EV = Event Notification. This means HomeSpan can push notifications of value changes to HomeKit using `setVal()`
* **Min** / **Max** - the default minimum and maximum values allowed in HomeKit. For applicable numerical Characteristics, you can change the allowed minimum and maximum values using `setRange()`
* **Constants/Defaults**
* for enumerated Characteristics, a list of all allowed values in the form of pre-defined constant expressions and their equivalent numeric values. A check mark next to a constant indicates that it is the default value. If your Accessory does not support certain states of a Characteristic, you can change the allowed values using `setValidValues()`
* for all other Characteristics, the actual default value that is used if you do not specify one when instantiating the Characteristic
HomeSpan Services and Characteristics are instantiated with a C++ `new` command. Services do not take any arguments, whereas Characteristics take a single, optional argument that is used to initialize the value of the Characteristic at startup. If this argument is not specified, HomeSpan will apply a reasonable [default value](#characteristic-types-and-defaults) based on the Characteristic's type and allowed range.
[^1]: The hexidecimal numbers in parentheses next to each Service and Characteristic represent the short-form of the Apple's UUID for that Service or Characteristic. These are provided for informational purposes only (you do not need to use them, or even know about them, to create HomeSpan sketches)
A list of all HomeSpan Services is provided in the table below. For each Service the table also indicates which Characteristics are required and which are optional. For example, a dimmable light bulb could be configured in HomeSpan as such:
[^2]: Though rarely needed, you can change the permissions of a Characteristic using `setPerms()`, `addPerms()`, and `removePerms()`
HomeSpan Services and Characteristics are implemented as C++ Classes with names that exactly match the spelling and capitalization specified by Apple in Sections 8 and 9 of HAP-R2, but without any spaces. HomeSpan Services are defined in HomeSpan's `Service` namespace. HomeSpan Characteristics are defined in HomeSpan's `Characteristic` namespace. For example, HomeSpan defines the *Carbon Dioxide Sensor* Service (HAP Service 8.7) as `Service::CarbonDioxideSensor`, and the *Carbon Dioxide Detected* Characteristic (HAP Characteristic 9.16) as `Characteristic::CarbonDioxideDetected`.
```C++
new Service::LightBulb(); // instantiate a Light Bulb Service
new Characteristics:On(); // instantiate the required On Characteristic without setting initial value
new Characteristic::Brightness(50); // instantiate an optional Brightness Characteristic and set initial value to 50%
new Characteristic::Name("Living Room Lamp"); // instantiate an optional Name Characteristic for this Service, and set to "Living Room Lamp"
```
The pre-defined constant expressions for enumerated Characteristics are in namespaces that match the name of the Characteristic. For example, to set the *Air Quality* Characteristic of an *Air Quality Sensor* Service, you could use `setVal(Characteristic::AirQuality::GOOD)` or, equivalently, `setVal(2)`.[^3]
Please see Sections 8 and 9 of [HAP-R2](https://developer.apple.com/homekit/specification/) for a complete description of all HAP Services and Characteristics. Note that HomeSpan's Service and Characteristic Classes already contain all the required HAP fields, such as the UUID, Format, and Permissions, so you don't need to specify any of these parameters.
[^3]: Note that a Characteristic's pre-defined constants are inherited by objects that you create from that Characteristic. This means that if you create a pointer to the *AirQuality* Characteristic using `Characteristic::AirQuality *air = new Characteristic::AirQuality;` then later in your code you can either say<br> `air->setVal(Characteristic::AirQuality::GOOD)` or `air->GOOD`. Both will work, but the latter is much easier to type.
Additionally, when first starting up, HomeSpan begins by validating the device's configuration to ensure each Service you instantiate includes all required Characteristics, but does not include any Characteristics that are neither required nor optional. If any errors are found, HomeSpan reports them to the Arduino Serial Monitor and halts the program.
<!-- AUTOGENERATED_TEXT. DO NOT EDIT THIS LINE OR ANYTHING BELOW -->
### Service List
| Service | Required Characteristics | Optional Characteristics |
| ------- | -------------------- | ------------------- |
| AccessoryInformation| FirmwareRevision<br>Identity<br>Manufacturer<br>Model<br>Name<br>SerialNumber | HardwareRevision |
| AirPurifier | Active<br>CurrentAirPurifierState<br>TargetAirPurifierState | Name<br>RotationSpeed<br>SwingMode<br>LockPhysicalControls |
| AirQualitySensor | AirQuality | Name<br>OzoneDensity<br>NitrogenDioxideDensity<br>SulphurDioxideDensity<br>PM25Density<br>PM10Density<br>VOCDensity<br>StatusActive<br>StatusFault<br>StatusTampered<br>StatusLowBattery |
| BatteryService | BatteryLevel<br>ChargingState<br>StatusLowBattery | Name |
| CarbonDioxideSensor | CarbonDioxideDetected | Name<br>StatusActive<br>StatusFault<br>StatusTampered<br>StatusLowBattery<br>CarbonDioxideLevel<br>CarbonDioxidePeakLevel |
| CarbonMonoxideSensor | CarbonMonoxideDetected | Name<br>StatusActive<br>StatusFault<br>StatusTampered<br>StatusLowBattery<br>CarbonMonoxideLevel<br>CarbonMonoxidePeakLevel |
| ContactSensor | ContactSensorState | Name<br>StatusActive<br>StatusFault<br>StatusTampered<br>StatusLowBattery |
| Door | CurrentPosition<br>TargetPosition<br>PositionState | Name<br>HoldPosition<br>ObstructionDetected |
| Doorbell | ProgrammableSwitchEvent | Name<br>Volume<br>Brightness |
| Fan | Active | Name<br>CurrentFanState<br>TargetFanState<br>RotationDirection<br>RotationSpeed<br>SwingMode<br>LockPhysicalControls |
| Faucet | Active | StatusFault<br>Name |
| FilterMaintenance | FilterChangeIndication | Name<br>FilterLifeLevel<br>ResetFilterIndication |
| GarageDoorOpener | CurrentDoorState<br>TargetDoorState<br>ObstructionDetected | LockCurrentState<br>LockTargetState<br>Name |
| HAPProtocolInformation | Version | *none* |
| HeaterCooler | Active<br>CurrentTemperature<br>CurrentHeaterCoolerState<br>TargetHeaterCoolerState | Name<br>RotationSpeed<br>TemperatureDisplayUnits<br>SwingMode<br>CoolingThresholdTemperature<br>HeatingThresholdTemperature<br>LockPhysicalControls |
| HumidifierDehumidifier | Active<br>CurrentRelativeHumidity<br>CurrentHumidifierDehumidifierState<br>TargetHumidifierDehumidifierState | Name<br>RelativeHumidityDehumidifierThreshold<br>RelativeHumidityHumidifierThreshold<br>RotationSpeed<br>SwingMode<br>WaterLevel<br>LockPhysicalControls |
| HumiditySensor | CurrentRelativeHumidity | Name<br>StatusActive<br>StatusFault<br>StatusTampered<br>StatusLowBattery |
| InputSource | Identifier | ConfiguredName<br>IsConfigured<br>CurrentVisibilityState<br>TargetVisibilityState |
| IrrigationSystem | Active<br>ProgramMode<br>InUse | RemainingDuration<br>Name<br>StatusFault |
| LeakSensor | LeakDetected | Name<br>StatusActive<br>StatusFault<br>StatusTampered<br>StatusLowBattery |
| LightBulb | On | Brightness<br>Hue<br>Name<br>Saturation<br>ColorTemperature |
| LightSensor | CurrentAmbientLightLevel | Name<br>StatusActive<br>StatusFault<br>StatusTampered<br>StatusLowBattery |
| LockMechanism | LockCurrentState<br>LockTargetState | Name |
| Microphone | Mute | Name<br>Volume |
| MotionSensor | MotionDetected | Name<br>StatusActive<br>StatusFault<br>StatusTampered<br>StatusLowBattery |
| OccupancySensor | OccupancyDetected | Name<br>StatusActive<br>StatusFault<br>StatusTampered<br>StatusLowBattery |
| Outlet | On<br>OutletInUse | Name |
| SecuritySystem | SecuritySystemCurrentState<br>SecuritySystemTargetState | Name<br>SecuritySystemAlarmType<br>StatusFault<br>StatusTampered |
| ServiceLabel | ServiceLabelNamespace | *none* |
| Slat | CurrentSlatState<br>SlatType | Name<br>SwingMode<br>CurrentTiltAngle<br>TargetTiltAngle |
| SmokeSensor | SmokeDetected | Name<br>StatusActive<br>StatusFault<br>StatusTampered<br>StatusLowBattery |
| Speaker | Mute | Name<br>Volume |
| StatelessProgrammableSwitch | ProgrammableSwitchEvent | Name<br>ServiceLabelIndex |
| Switch | On | Name |
| Television | Active | ConfiguredName<br>ActiveIdentifier<br>RemoteKey<br>PowerModeSelection |
| TemperatureSensor | CurrentTemperature | Name<br>StatusActive<br>StatusFault<br>StatusTampered<br>StatusLowBattery |
| Thermostat | CurrentHeatingCoolingState<br>TargetHeatingCoolingState<br>CurrentTemperature<br>TargetTemperature<br>TemperatureDisplayUnits | CoolingThresholdTemperature<br>CurrentRelativeHumidity<br>HeatingThresholdTemperature<br>Name<br>TargetRelativeHumidity |
| Valve | Active<br>InUse<br>ValveType | SetDuration<br>RemainingDuration<br>IsConfigured<br>ServiceLabelIndex<br>StatusFault<br>Name |
| Window | CurrentPosition<br>TargetPosition<br>PositionState | Name<br>HoldPosition<br>ObstructionDetected |
| WindowCovering | CurrentPosition<br>TargetPosition | Name<br>PositionState<br>HoldPosition<br>CurrentHorizontalTiltAngle<br>TargetHorizontalTiltAngle<br>CurrentVerticalTiltAngle<br>TargetVerticalTiltAngle<br>ObstructionDetected |
## MANDATORY SERVICES
### AccessoryInformation (3E)
<i> Required Identification Information. For each Accessory in a HomeSpan device this must be included as the first Service.</i><br><table>
<tr><th>Characteristic</th><th>Format</th><th>Perms</th><th>Min</th><th>Max</th><th>Constants/Defaults</th></tr>
<tr><td><b>Identify (14) :small_blue_diamond:</b><ul><li> triggers an update when HomeKit wants HomeSpan to run its identification routine for an Accessory</li></ul></td><td align="center">bool</td><td align="center">PW</td><td align="center">1</td><td align="center">1</td><td><ul><li><span>RUN_ID&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>Name (23) </b><ul><li> default display name of the Accessory</li></ul></td><td align="center">string</td><td align="center">PR</td><td align="center">-</td><td align="center">-</td><td align="center">"unnamed"</td></tr>
<tr><td><b>FirmwareRevision (52) </b><ul><li> must be in form x[.y[.z]] - informational only</li></ul></td><td align="center">string</td><td align="center">PR+EV</td><td align="center">-</td><td align="center">-</td><td align="center">"1.0.0"</td></tr>
<tr><td><b>Manufacturer (20) </b><ul><li> any string - informational only</li></ul></td><td align="center">string</td><td align="center">PR</td><td align="center">-</td><td align="center">-</td><td align="center">"HomeSpan"</td></tr>
<tr><td><b>Model (21) </b><ul><li> any string - informational only</li></ul></td><td align="center">string</td><td align="center">PR</td><td align="center">-</td><td align="center">-</td><td align="center">"HomeSpan-ESP32"</td></tr>
<tr><td><b>SerialNumber (30) </b><ul><li> any string - informational only</li></ul></td><td align="center">string</td><td align="center">PR</td><td align="center">-</td><td align="center">-</td><td align="center">"HS-12345"</td></tr>
<tr><td><b>HardwareRevision (53) </b><ul><li> must be in form x[.y[.z]] - informational only</li></ul></td><td align="center">string</td><td align="center">PR</td><td align="center">-</td><td align="center">-</td><td align="center">"1.0.0"</td></tr>
</table><br>
### Characteristic Types and Defaults
## LIGHTS, POWER, AND SWITCHES
### BatteryService (96)
<i> Defines a standalone Battery Service.</i><br><table>
<tr><th>Characteristic</th><th>Format</th><th>Perms</th><th>Min</th><th>Max</th><th>Constants/Defaults</th></tr>
<tr><td><b>BatteryLevel (68) :small_blue_diamond:</b><ul><li> measured as a percentage</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">100</td><td align="center">100</td></tr>
<tr><td><b>ChargingState (8F) :small_blue_diamond:</b><ul><li> indicates state of battery charging</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">2</td><td><ul><li><span>NOT_CHARGING&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>CHARGING&nbsp(1)&nbsp;</span></li><li><span>NOT_CHARGEABLE&nbsp(2)&nbsp;</span></li></ul></td></tr>
<tr><td><b>StatusLowBattery (79) :small_blue_diamond:</b><ul><li> indicates state of battery</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_LOW_BATTERY&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>LOW_BATTERY&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>ConfiguredName (E3) </b><ul><li> default display name of this Service</li></ul></td><td align="center">string</td><td align="center">PW+PR+EV</td><td align="center">-</td><td align="center">-</td><td align="center">"unnamed"</td></tr>
</table><br>
|Characteristic|Type|Default
|---|---|---|
|Active|uint8_t|0|
|ActiveIdentifier|uint32_t|0|
|AirQuality|uint8_t|0|
|BatteryLevel|uint8_t|0|
|Brightness|int|0|
|CarbonMonoxideLevel|double|0|
|CarbonMonoxidePeakLevel|double|0|
|CarbonMonoxideDetected|uint8_t|0|
|CarbonDioxideLevel|double|0|
|CarbonDioxidePeakLevel|double|0|
|CarbonDioxideDetected|uint8_t|0|
|ChargingState|uint8_t|0|
|CoolingThresholdTemperature|double|10|
|ColorTemperature|uint32_t|50|
|ConfiguredName|char \*|"unnamed"|
|ContactSensorState|uint8_t|1|
|CurrentAmbientLightLevel|double|1|
|CurrentHorizontalTiltAngle|int|0|
|CurrentAirPurifierState|uint8_t|1|
|CurrentSlatState|uint8_t|0|
|CurrentPosition|uint8_t|0|
|CurrentVerticalTiltAngle|int|0|
|CurrentHumidifierDehumidifierState|uint8_t|1|
|CurrentDoorState|uint8_t|1|
|CurrentFanState|uint8_t|1|
|CurrentHeatingCoolingState|uint8_t|0|
|CurrentHeaterCoolerState|uint8_t|1|
|CurrentRelativeHumidity|double|0|
|CurrentTemperature|double|0|
|CurrentTiltAngle|int|0|
|CurrentVisibilityState|uint8_t|0|
|FilterLifeLevel|double|0|
|FilterChangeIndication|uint8_t|0|
|FirmwareRevision|char \*|"1.0.0"|
|HardwareRevision|char \*|"1.0.0"|
|HeatingThresholdTemperature|double|16|
|HoldPosition|boolean|false|
|Hue|double|0|
|Identifier|uint32_t|0|
|Identify|boolean|false|
|InUse|uint8_t|0|
|IsConfigured|uint8_t|0|
|LeakDetected|uint8_t|0|
|LockCurrentState|uint8_t|0|
|LockPhysicalControls|uint8_t|0|
|LockTargetState|uint8_t|0|
|Manufacturer|char \*|"HomeSpan"|
|Model|char \*|"HomeSpan-ESP32"|
|MotionDetected|boolean|false|
|Mute|boolean|false|
|Name|char \*|"unnamed"|
|NitrogenDioxideDensity|double|0|
|ObstructionDetected|boolean|false|
|PM25Density|double|0|
|OccupancyDetected|uint8_t|0|
|OutletInUse|boolean|false|
|On|boolean|false|
|OzoneDensity|double|0|
|PM10Density|double|0|
|PositionState|uint8_t|2|
|PowerModeSelection|uint8_t|0|
|ProgramMode|uint8_t|0|
|ProgrammableSwitchEvent|uint8_t|0|
|RelativeHumidityDehumidifierThreshold|double|50|
|RelativeHumidityHumidifierThreshold|double|50|
|RemainingDuration|uint32_t|60|
|RemoteKey|uint8_t|0|
|ResetFilterIndication|uint8_t|0|
|RotationDirection|int|0|
|RotationSpeed|double|0|
|Saturation|double|0|
|SecuritySystemAlarmType|uint8_t|0|
|SecuritySystemCurrentState|uint8_t|3|
|SecuritySystemTargetState|uint8_t|3|
|SerialNumber|char \*|"HS-12345"|
|ServiceLabelIndex|uint8_t|1|
|ServiceLabelNamespace|uint8_t|1|
|SlatType|uint8_t|0|
|SmokeDetected|uint8_t|0|
|StatusActive|boolean|true|
|StatusFault|uint8_t|0|
|StatusJammed|uint8_t|0|
|StatusLowBattery|uint8_t|0|
|StatusTampered|uint8_t|0|
|SulphurDioxideDensity|double|0|
|SwingMode|uint8_t|0|
|TargetAirPurifierState|uint8_t|1|
|TargetFanState|uint8_t|1|
|TargetTiltAngle|int|0|
|SetDuration|uint32_t|60|
|TargetHorizontalTiltAngle|int|0|
|TargetHumidifierDehumidifierState|uint8_t|0|
|TargetPosition|uint8_t|0|
|TargetDoorState|uint8_t|1|
|TargetHeaterCoolerState|uint8_t|0|
|TargetHeatingCoolingState|uint8_t|0|
|TargetRelativeHumidity|double|0|
|TargetTemperature|double|16|
|TemperatureDisplayUnits|uint8_t|0|
|TargetVerticalTiltAngle|int|0|
|TargetVisibilityState|uint8_t|0|
|ValveType|uint8_t|0|
|Version|char \*|"1.0.0"|
|VOCDensity|double|0|
|Volume|uint8_t|0|
|WaterLevel|double|0|
### LightBulb (43)
<i> Defines any type of Light.</i><br><table>
<tr><th>Characteristic</th><th>Format</th><th>Perms</th><th>Min</th><th>Max</th><th>Constants/Defaults</th></tr>
<tr><td><b>On (25) :small_blue_diamond:</b><ul><li> indicates if the Service is active/on</li></ul></td><td align="center">bool</td><td align="center">PR+PW+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>OFF&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>ON&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>Brightness (8) </b><ul><li> measured as a percentage</li></ul></td><td align="center">int</td><td align="center">PR+PW+EV</td><td align="center">0</td><td align="center">100</td><td align="center">0</td></tr>
<tr><td><b>Hue (13) </b><ul><li> color (in degrees) from red (0) to green (120) to blue (240) and back to red (360)</li></ul></td><td align="center">float</td><td align="center">PR+PW+EV</td><td align="center">0</td><td align="center">360</td><td align="center">0</td></tr>
<tr><td><b>Saturation (2F) </b><ul><li> color saturation, measured as a percentage</li></ul></td><td align="center">float</td><td align="center">PR+PW+EV</td><td align="center">0</td><td align="center">100</td><td align="center">0</td></tr>
<tr><td><b>ColorTemperature (CE) </b><ul><li> measured in inverse megaKelvin (= 1,000,000 / Kelvin)</li></ul></td><td align="center">uint32</td><td align="center">PR+PW+EV</td><td align="center">140</td><td align="center">500</td><td align="center">200</td></tr>
<tr><td><b>ConfiguredName (E3) </b><ul><li> default display name of this Service</li></ul></td><td align="center">string</td><td align="center">PW+PR+EV</td><td align="center">-</td><td align="center">-</td><td align="center">"unnamed"</td></tr>
</table><br>
### Outlet (47)
<i> Defines a controllable Outlet used to power any light or appliance.</i><br><table>
<tr><th>Characteristic</th><th>Format</th><th>Perms</th><th>Min</th><th>Max</th><th>Constants/Defaults</th></tr>
<tr><td><b>On (25) :small_blue_diamond:</b><ul><li> indicates if the Service is active/on</li></ul></td><td align="center">bool</td><td align="center">PR+PW+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>OFF&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>ON&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>OutletInUse (26) :small_blue_diamond:</b><ul><li> indicates if an appliance or light is plugged into the outlet, regardless of whether on or off </li></ul></td><td align="center">bool</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_IN_USE&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>IN_USE&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>ConfiguredName (E3) </b><ul><li> default display name of this Service</li></ul></td><td align="center">string</td><td align="center">PW+PR+EV</td><td align="center">-</td><td align="center">-</td><td align="center">"unnamed"</td></tr>
</table><br>
### HAP Format Codes (HAP-R2 Table 6-5)
### StatelessProgrammableSwitch (89)
<i> Defines a "Stateless" Programmable Switch that can be used to trigger actions in the Home App.</i><br><table>
<tr><th>Characteristic</th><th>Format</th><th>Perms</th><th>Min</th><th>Max</th><th>Constants/Defaults</th></tr>
<tr><td><b>ProgrammableSwitchEvent (73) :small_blue_diamond:</b><ul><li> specifies type of button press</li></ul></td><td align="center">uint8</td><td align="center">PR+EV+NV</td><td align="center">0</td><td align="center">2</td><td><ul><li><span>SINGLE_PRESS&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>DOUBLE_PRESS&nbsp(1)&nbsp;</span></li><li><span>LONG_PRESS&nbsp(2)&nbsp;</span></li></ul></td></tr>
<tr><td><b>ServiceLabelIndex (CB) </b><ul><li> numerical index used to distinguish multiple copies of the same Service within an Accessory</li></ul></td><td align="center">uint8</td><td align="center">PR</td><td align="center">1</td><td align="center">255</td><td align="center">1</td></tr>
</table><br>
### Switch (49)
<i> Defines a generic Switch.</i><br><table>
<tr><th>Characteristic</th><th>Format</th><th>Perms</th><th>Min</th><th>Max</th><th>Constants/Defaults</th></tr>
<tr><td><b>On (25) :small_blue_diamond:</b><ul><li> indicates if the Service is active/on</li></ul></td><td align="center">bool</td><td align="center">PR+PW+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>OFF&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>ON&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>ConfiguredName (E3) </b><ul><li> default display name of this Service</li></ul></td><td align="center">string</td><td align="center">PW+PR+EV</td><td align="center">-</td><td align="center">-</td><td align="center">"unnamed"</td></tr>
</table><br>
## HEATING, VENTILATION, AND AIR CONDITIONING (HVAC)
### AirPurifier (BB)
<i> Defines a basic Air Purifier with an optional fan and swing mode. Optional Linked Services: <b>FilterMaintenance</b>. Combine with an <b>AirSensor</b> Service for automated operations.</i><br><table>
<tr><th>Characteristic</th><th>Format</th><th>Perms</th><th>Min</th><th>Max</th><th>Constants/Defaults</th></tr>
<tr><td><b>Active (B0) :small_blue_diamond:</b><ul><li> indicates if the Service is active/on</li></ul></td><td align="center">uint8</td><td align="center">PW+PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>INACTIVE&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>ACTIVE&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>CurrentAirPurifierState (A9) :small_blue_diamond:</b><ul><li> indicates current state of air purification</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">2</td><td><ul><li><span>INACTIVE&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>IDLE&nbsp(1)&nbsp;</span></li><li><span>PURIFYING&nbsp(2)&nbsp;</span></li></ul></td></tr>
<tr><td><b>TargetAirPurifierState (A8) :small_blue_diamond:</b><ul><li> indicates desired state of air purifier</li></ul></td><td align="center">uint8</td><td align="center">PW+PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>MANUAL&nbsp(0)&nbsp;</span></li><li><span>AUTO&nbsp(1)&nbsp;</span>:heavy_check_mark:</li></ul></td></tr>
<tr><td><b>RotationSpeed (29) </b><ul><li> measured as a percentage</li></ul></td><td align="center">float</td><td align="center">PR+PW+EV</td><td align="center">0</td><td align="center">100</td><td align="center">0</td></tr>
<tr><td><b>SwingMode (B6) </b><ul><li> indicates whether swing-mode is enabled</li></ul></td><td align="center">uint8</td><td align="center">PR+EV+PW</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>SWING_DISABLED&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>SWING_ENABLED&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>LockPhysicalControls (A7) </b><ul><li> indicates if local control lock is enabled</li></ul></td><td align="center">uint8</td><td align="center">PW+PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>CONTROL_LOCK_DISABLED&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>CONTROL_LOCK_ENABLED&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>ConfiguredName (E3) </b><ul><li> default display name of this Service</li></ul></td><td align="center">string</td><td align="center">PW+PR+EV</td><td align="center">-</td><td align="center">-</td><td align="center">"unnamed"</td></tr>
</table><br>
### Fan (B7)
<i> Defines a Fan. Combine with a <b>LightBulb</b> Service to create a Lighted Ceiling Fan.</i><br><table>
<tr><th>Characteristic</th><th>Format</th><th>Perms</th><th>Min</th><th>Max</th><th>Constants/Defaults</th></tr>
<tr><td><b>Active (B0) :small_blue_diamond:</b><ul><li> indicates if the Service is active/on</li></ul></td><td align="center">uint8</td><td align="center">PW+PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>INACTIVE&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>ACTIVE&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>CurrentFanState (AF) </b><ul><li> indicates current state of a fan</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">2</td><td><ul><li><span>INACTIVE&nbsp(0)&nbsp;</span></li><li><span>IDLE&nbsp(1)&nbsp;</span>:heavy_check_mark:</li><li><span>BLOWING&nbsp(2)&nbsp;</span></li></ul></td></tr>
<tr><td><b>TargetFanState (BF) </b><ul><li> indicates desired state of fan</li></ul></td><td align="center">uint8</td><td align="center">PW+PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>MANUAL&nbsp(0)&nbsp;</span></li><li><span>AUTO&nbsp(1)&nbsp;</span>:heavy_check_mark:</li></ul></td></tr>
<tr><td><b>RotationDirection (28) </b><ul><li> indicates the rotation direction of a fan</li></ul></td><td align="center">int</td><td align="center">PR+PW+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>CLOCKWISE&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>COUNTERCLOCKWISE&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>RotationSpeed (29) </b><ul><li> measured as a percentage</li></ul></td><td align="center">float</td><td align="center">PR+PW+EV</td><td align="center">0</td><td align="center">100</td><td align="center">0</td></tr>
<tr><td><b>SwingMode (B6) </b><ul><li> indicates whether swing-mode is enabled</li></ul></td><td align="center">uint8</td><td align="center">PR+EV+PW</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>SWING_DISABLED&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>SWING_ENABLED&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>LockPhysicalControls (A7) </b><ul><li> indicates if local control lock is enabled</li></ul></td><td align="center">uint8</td><td align="center">PW+PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>CONTROL_LOCK_DISABLED&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>CONTROL_LOCK_ENABLED&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>ConfiguredName (E3) </b><ul><li> default display name of this Service</li></ul></td><td align="center">string</td><td align="center">PW+PR+EV</td><td align="center">-</td><td align="center">-</td><td align="center">"unnamed"</td></tr>
</table><br>
### FilterMaintenance (BA)
<i> Defines a Filter Maintainence check. Use only as a Linked Service for the <b>AirPurifier</b> Service.</i><br><table>
<tr><th>Characteristic</th><th>Format</th><th>Perms</th><th>Min</th><th>Max</th><th>Constants/Defaults</th></tr>
<tr><td><b>FilterChangeIndication (AC) :small_blue_diamond:</b><ul><li> indicates state of filter</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NO_CHANGE_NEEDED&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>CHANGE_NEEDED&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>FilterLifeLevel (AB) </b><ul><li> measured as a percentage of remaining life</li></ul></td><td align="center">float</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">100</td><td align="center">100</td></tr>
<tr><td><b>ResetFilterIndication (AD) </b><ul><li> triggers an update when the user chooses to reset the <b>FilterChangeIndication</b> (only appears in Eve App, not Home App)</li></ul></td><td align="center">uint8</td><td align="center">PW</td><td align="center">1</td><td align="center">1</td><td><ul><li><span>RESET_FILTER&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>ConfiguredName (E3) </b><ul><li> default display name of this Service</li></ul></td><td align="center">string</td><td align="center">PW+PR+EV</td><td align="center">-</td><td align="center">-</td><td align="center">"unnamed"</td></tr>
</table><br>
### HeaterCooler (BC)
<i> Defines a standalone Heater, Cooler, or combined Heater/Cooler.</i><br><table>
<tr><th>Characteristic</th><th>Format</th><th>Perms</th><th>Min</th><th>Max</th><th>Constants/Defaults</th></tr>
<tr><td><b>Active (B0) :small_blue_diamond:</b><ul><li> indicates if the Service is active/on</li></ul></td><td align="center">uint8</td><td align="center">PW+PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>INACTIVE&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>ACTIVE&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>CurrentTemperature (11) :small_blue_diamond:</b><ul><li> current temperature measured in Celsius</li></ul></td><td align="center">float</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">100</td><td align="center">0</td></tr>
<tr><td><b>CurrentHeaterCoolerState (B1) :small_blue_diamond:</b><ul><li> indicates whether appliance is currently heating, cooling, idle, or off</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">3</td><td><ul><li><span>INACTIVE&nbsp(0)&nbsp;</span></li><li><span>IDLE&nbsp(1)&nbsp;</span>:heavy_check_mark:</li><li><span>HEATING&nbsp(2)&nbsp;</span></li><li><span>COOLING&nbsp(3)&nbsp;</span></li></ul></td></tr>
<tr><td><b>TargetHeaterCoolerState (B2) :small_blue_diamond:</b><ul><li> indicates desired state of heater/cooler</li></ul></td><td align="center">uint8</td><td align="center">PW+PR+EV</td><td align="center">0</td><td align="center">2</td><td><ul><li><span>AUTO&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>HEAT&nbsp(1)&nbsp;</span></li><li><span>COOL&nbsp(2)&nbsp;</span></li></ul></td></tr>
<tr><td><b>RotationSpeed (29) </b><ul><li> measured as a percentage</li></ul></td><td align="center">float</td><td align="center">PR+PW+EV</td><td align="center">0</td><td align="center">100</td><td align="center">0</td></tr>
<tr><td><b>TemperatureDisplayUnits (36) </b><ul><li> indicates the desired units to display the temperature on the device itself (has no effect on Home App)</li></ul></td><td align="center">uint8</td><td align="center">PW+PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>CELSIUS&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>FAHRENHEIT&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>SwingMode (B6) </b><ul><li> indicates whether swing-mode is enabled</li></ul></td><td align="center">uint8</td><td align="center">PR+EV+PW</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>SWING_DISABLED&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>SWING_ENABLED&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>CoolingThresholdTemperature (D) </b><ul><li> cooling turns on when temperature (in Celsius) rises above this threshold</li></ul></td><td align="center">float</td><td align="center">PR+PW+EV</td><td align="center">10</td><td align="center">35</td><td align="center">10</td></tr>
<tr><td><b>HeatingThresholdTemperature (12) </b><ul><li> heating turns on when temperature (in Celsius) falls below this threshold</li></ul></td><td align="center">float</td><td align="center">PR+PW+EV</td><td align="center">0</td><td align="center">25</td><td align="center">16</td></tr>
<tr><td><b>LockPhysicalControls (A7) </b><ul><li> indicates if local control lock is enabled</li></ul></td><td align="center">uint8</td><td align="center">PW+PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>CONTROL_LOCK_DISABLED&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>CONTROL_LOCK_ENABLED&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>ConfiguredName (E3) </b><ul><li> default display name of this Service</li></ul></td><td align="center">string</td><td align="center">PW+PR+EV</td><td align="center">-</td><td align="center">-</td><td align="center">"unnamed"</td></tr>
</table><br>
### HumidifierDehumidifier (BD)
<i> Defines a Humidifer, Dehumidifier, or combined Humidifer/Dehumidifier.</i><br><table>
<tr><th>Characteristic</th><th>Format</th><th>Perms</th><th>Min</th><th>Max</th><th>Constants/Defaults</th></tr>
<tr><td><b>Active (B0) :small_blue_diamond:</b><ul><li> indicates if the Service is active/on</li></ul></td><td align="center">uint8</td><td align="center">PW+PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>INACTIVE&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>ACTIVE&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>CurrentRelativeHumidity (10) :small_blue_diamond:</b><ul><li> current humidity measured as a percentage</li></ul></td><td align="center">float</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">100</td><td align="center">0</td></tr>
<tr><td><b>CurrentHumidifierDehumidifierState (B3) :small_blue_diamond:</b><ul><li> indicates current state of humidifier/dehumidifer</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">3</td><td><ul><li><span>INACTIVE&nbsp(0)&nbsp;</span></li><li><span>IDLE&nbsp(1)&nbsp;</span>:heavy_check_mark:</li><li><span>HUMIDIFYING&nbsp(2)&nbsp;</span></li><li><span>DEHUMIDIFYING&nbsp(3)&nbsp;</span></li></ul></td></tr>
<tr><td><b>TargetHumidifierDehumidifierState (B4) :small_blue_diamond:</b><ul><li> indicates desired state of humidifier/dehumidifier</li></ul></td><td align="center">uint8</td><td align="center">PW+PR+EV</td><td align="center">0</td><td align="center">2</td><td><ul><li><span>AUTO&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>HUMIDIFY&nbsp(1)&nbsp;</span></li><li><span>DEHUMIDIFY&nbsp(2)&nbsp;</span></li></ul></td></tr>
<tr><td><b>RelativeHumidityDehumidifierThreshold (C9) </b><ul><li> dehumidfier turns on when humidity rises above this threshold</li></ul></td><td align="center">float</td><td align="center">PR+PW+EV</td><td align="center">0</td><td align="center">100</td><td align="center">50</td></tr>
<tr><td><b>RelativeHumidityHumidifierThreshold (CA) </b><ul><li> humidfier turns on when humidity falls below this threshold</li></ul></td><td align="center">float</td><td align="center">PR+PW+EV</td><td align="center">0</td><td align="center">100</td><td align="center">50</td></tr>
<tr><td><b>RotationSpeed (29) </b><ul><li> measured as a percentage</li></ul></td><td align="center">float</td><td align="center">PR+PW+EV</td><td align="center">0</td><td align="center">100</td><td align="center">0</td></tr>
<tr><td><b>SwingMode (B6) </b><ul><li> indicates whether swing-mode is enabled</li></ul></td><td align="center">uint8</td><td align="center">PR+EV+PW</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>SWING_DISABLED&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>SWING_ENABLED&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>WaterLevel (B5) </b><ul><li> measured as a percentage</li></ul></td><td align="center">float</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">100</td><td align="center">0</td></tr>
<tr><td><b>LockPhysicalControls (A7) </b><ul><li> indicates if local control lock is enabled</li></ul></td><td align="center">uint8</td><td align="center">PW+PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>CONTROL_LOCK_DISABLED&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>CONTROL_LOCK_ENABLED&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>ConfiguredName (E3) </b><ul><li> default display name of this Service</li></ul></td><td align="center">string</td><td align="center">PW+PR+EV</td><td align="center">-</td><td align="center">-</td><td align="center">"unnamed"</td></tr>
</table><br>
### Slat (B9)
<i> Defines a motorized ventilation Slat(s).</i><br><table>
<tr><th>Characteristic</th><th>Format</th><th>Perms</th><th>Min</th><th>Max</th><th>Constants/Defaults</th></tr>
<tr><td><b>CurrentSlatState (AA) :small_blue_diamond:</b><ul><li> indicates current state of slats</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">2</td><td><ul><li><span>FIXED&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>JAMMED&nbsp(1)&nbsp;</span></li><li><span>SWINGING&nbsp(2)&nbsp;</span></li></ul></td></tr>
<tr><td><b>SlatType (C0) :small_blue_diamond:</b><ul><li> indicates the direction of a slat or group of slats</li></ul></td><td align="center">uint8</td><td align="center">PR</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>HORIZONTAL&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>VERTICAL&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>SwingMode (B6) </b><ul><li> indicates whether swing-mode is enabled</li></ul></td><td align="center">uint8</td><td align="center">PR+EV+PW</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>SWING_DISABLED&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>SWING_ENABLED&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>CurrentTiltAngle (C1) </b><ul><li> current angle (in degrees) of slats from fully up or left (-90) to fully open (0) to fully down or right (90)</li></ul></td><td align="center">int</td><td align="center">PR+EV</td><td align="center">-90</td><td align="center">90</td><td align="center">0</td></tr>
<tr><td><b>TargetTiltAngle (C2) </b><ul><li> indicated desired angle (in degrees) of slats from fully up or left (-90) to fully open (0) to fully down or right (90) </li></ul></td><td align="center">int</td><td align="center">PW+PR+EV</td><td align="center">-90</td><td align="center">90</td><td align="center">0</td></tr>
<tr><td><b>ConfiguredName (E3) </b><ul><li> default display name of this Service</li></ul></td><td align="center">string</td><td align="center">PW+PR+EV</td><td align="center">-</td><td align="center">-</td><td align="center">"unnamed"</td></tr>
</table><br>
### Thermostat (4A)
<i> Defines a Thermostat used to control a furnace, air conditioner, or both.</i><br><table>
<tr><th>Characteristic</th><th>Format</th><th>Perms</th><th>Min</th><th>Max</th><th>Constants/Defaults</th></tr>
<tr><td><b>CurrentHeatingCoolingState (F) :small_blue_diamond:</b><ul><li> indicates whether appliance is currently heating, cooling, or just idle</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">2</td><td><ul><li><span>IDLE&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>HEATING&nbsp(1)&nbsp;</span></li><li><span>COOLING&nbsp(2)&nbsp;</span></li></ul></td></tr>
<tr><td><b>TargetHeatingCoolingState (33) :small_blue_diamond:</b><ul><li> indicates desired state of appliance</li></ul></td><td align="center">uint8</td><td align="center">PW+PR+EV</td><td align="center">0</td><td align="center">3</td><td><ul><li><span>OFF&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>HEAT&nbsp(1)&nbsp;</span></li><li><span>COOL&nbsp(2)&nbsp;</span></li><li><span>AUTO&nbsp(3)&nbsp;</span></li></ul></td></tr>
<tr><td><b>CurrentTemperature (11) :small_blue_diamond:</b><ul><li> current temperature measured in Celsius</li></ul></td><td align="center">float</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">100</td><td align="center">0</td></tr>
<tr><td><b>TargetTemperature (35) :small_blue_diamond:</b><ul><li> indicates desired temperature measures in Celsius</li></ul></td><td align="center">float</td><td align="center">PW+PR+EV</td><td align="center">10</td><td align="center">38</td><td align="center">16</td></tr>
<tr><td><b>TemperatureDisplayUnits (36) :small_blue_diamond:</b><ul><li> indicates the desired units to display the temperature on the device itself (has no effect on Home App)</li></ul></td><td align="center">uint8</td><td align="center">PW+PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>CELSIUS&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>FAHRENHEIT&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>CoolingThresholdTemperature (D) </b><ul><li> cooling turns on when temperature (in Celsius) rises above this threshold</li></ul></td><td align="center">float</td><td align="center">PR+PW+EV</td><td align="center">10</td><td align="center">35</td><td align="center">10</td></tr>
<tr><td><b>CurrentRelativeHumidity (10) </b><ul><li> current humidity measured as a percentage</li></ul></td><td align="center">float</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">100</td><td align="center">0</td></tr>
<tr><td><b>HeatingThresholdTemperature (12) </b><ul><li> heating turns on when temperature (in Celsius) falls below this threshold</li></ul></td><td align="center">float</td><td align="center">PR+PW+EV</td><td align="center">0</td><td align="center">25</td><td align="center">16</td></tr>
<tr><td><b>TargetRelativeHumidity (34) </b><ul><li> indicates desired humidity measured as a percentage</li></ul></td><td align="center">float</td><td align="center">PW+PR+EV</td><td align="center">0</td><td align="center">100</td><td align="center">0</td></tr>
<tr><td><b>ConfiguredName (E3) </b><ul><li> default display name of this Service</li></ul></td><td align="center">string</td><td align="center">PW+PR+EV</td><td align="center">-</td><td align="center">-</td><td align="center">"unnamed"</td></tr>
</table><br>
## STANDALONE SENSORS
### AirQualitySensor (8D)
<i> Defines an Air Quality Sensor. </i><br><table>
<tr><th>Characteristic</th><th>Format</th><th>Perms</th><th>Min</th><th>Max</th><th>Constants/Defaults</th></tr>
<tr><td><b>AirQuality (95) :small_blue_diamond:</b><ul><li> a subjective description</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">5</td><td><ul><li><span>UNKNOWN&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>EXCELLENT&nbsp(1)&nbsp;</span></li><li><span>GOOD&nbsp(2)&nbsp;</span></li><li><span>FAIR&nbsp(3)&nbsp;</span></li><li><span>INFERIOR&nbsp(4)&nbsp;</span></li><li><span>POOR&nbsp(5)&nbsp;</span></li></ul></td></tr>
<tr><td><b>OzoneDensity (C3) </b><ul><li> measured in &micro;g/m<sup>3</sup></li></ul></td><td align="center">float</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1000</td><td align="center">0</td></tr>
<tr><td><b>NitrogenDioxideDensity (C4) </b><ul><li> measured in &micro;g/m<sup>3</sup></li></ul></td><td align="center">float</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1000</td><td align="center">0</td></tr>
<tr><td><b>SulphurDioxideDensity (C5) </b><ul><li> measured in &micro;g/m<sup>3</sup></li></ul></td><td align="center">float</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1000</td><td align="center">0</td></tr>
<tr><td><b>PM25Density (C6) </b><ul><li> 2.5-micron particulate density, measured in &micro;g/m<sup>3</sup></li></ul></td><td align="center">float</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1000</td><td align="center">0</td></tr>
<tr><td><b>PM10Density (C7) </b><ul><li> 10-micron particulate density, measured in &micro;g/m<sup>3</sup></li></ul></td><td align="center">float</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1000</td><td align="center">0</td></tr>
<tr><td><b>VOCDensity (C8) </b><ul><li> measured in &micro;g/m<sup>3</sup></li></ul></td><td align="center">float</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1000</td><td align="center">0</td></tr>
<tr><td><b>StatusActive (75) </b><ul><li> indicates whether the Service is properly functioning </li></ul></td><td align="center">bool</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_FUNCTIONING&nbsp(0)&nbsp;</span></li><li><span>FUNCTIONING&nbsp(1)&nbsp;</span>:heavy_check_mark:</li></ul></td></tr>
<tr><td><b>StatusFault (77) </b><ul><li> indicates whether the Service has a fault (only appears in Eve App, not Home App)</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NO_FAULT&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>FAULT&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>StatusTampered (7A) </b><ul><li> indicates whether the Service has been tampered with</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_TAMPERED&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>TAMPERED&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>StatusLowBattery (79) </b><ul><li> indicates state of battery</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_LOW_BATTERY&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>LOW_BATTERY&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>ConfiguredName (E3) </b><ul><li> default display name of this Service</li></ul></td><td align="center">string</td><td align="center">PW+PR+EV</td><td align="center">-</td><td align="center">-</td><td align="center">"unnamed"</td></tr>
</table><br>
### CarbonDioxideSensor (97)
<i> Defines a Carbon Dioxide Sensor.</i><br><table>
<tr><th>Characteristic</th><th>Format</th><th>Perms</th><th>Min</th><th>Max</th><th>Constants/Defaults</th></tr>
<tr><td><b>CarbonDioxideDetected (92) :small_blue_diamond:</b><ul><li> indicates if abnormal level is detected</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NORMAL&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>ABNORMAL&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>CarbonDioxideLevel (93) </b><ul><li> measured on parts per million (ppm)</li></ul></td><td align="center">float</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">100000</td><td align="center">0</td></tr>
<tr><td><b>CarbonDioxidePeakLevel (94) </b><ul><li> measured in parts per million (ppm)</li></ul></td><td align="center">float</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">100000</td><td align="center">0</td></tr>
<tr><td><b>StatusActive (75) </b><ul><li> indicates whether the Service is properly functioning </li></ul></td><td align="center">bool</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_FUNCTIONING&nbsp(0)&nbsp;</span></li><li><span>FUNCTIONING&nbsp(1)&nbsp;</span>:heavy_check_mark:</li></ul></td></tr>
<tr><td><b>StatusFault (77) </b><ul><li> indicates whether the Service has a fault (only appears in Eve App, not Home App)</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NO_FAULT&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>FAULT&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>StatusTampered (7A) </b><ul><li> indicates whether the Service has been tampered with</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_TAMPERED&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>TAMPERED&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>StatusLowBattery (79) </b><ul><li> indicates state of battery</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_LOW_BATTERY&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>LOW_BATTERY&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>ConfiguredName (E3) </b><ul><li> default display name of this Service</li></ul></td><td align="center">string</td><td align="center">PW+PR+EV</td><td align="center">-</td><td align="center">-</td><td align="center">"unnamed"</td></tr>
</table><br>
### CarbonMonoxideSensor (7F)
<i> Defines a Carbon Monoxide Sensor.</i><br><table>
<tr><th>Characteristic</th><th>Format</th><th>Perms</th><th>Min</th><th>Max</th><th>Constants/Defaults</th></tr>
<tr><td><b>CarbonMonoxideDetected (69) :small_blue_diamond:</b><ul><li> indicates if abnormal level is detected</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NORMAL&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>ABNORMAL&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>CarbonMonoxideLevel (90) </b><ul><li> measured in parts per million (ppm)</li></ul></td><td align="center">float</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">100</td><td align="center">0</td></tr>
<tr><td><b>CarbonMonoxidePeakLevel (91) </b><ul><li> measured in parts per million (ppm)</li></ul></td><td align="center">float</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">100</td><td align="center">0</td></tr>
<tr><td><b>StatusActive (75) </b><ul><li> indicates whether the Service is properly functioning </li></ul></td><td align="center">bool</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_FUNCTIONING&nbsp(0)&nbsp;</span></li><li><span>FUNCTIONING&nbsp(1)&nbsp;</span>:heavy_check_mark:</li></ul></td></tr>
<tr><td><b>StatusFault (77) </b><ul><li> indicates whether the Service has a fault (only appears in Eve App, not Home App)</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NO_FAULT&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>FAULT&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>StatusTampered (7A) </b><ul><li> indicates whether the Service has been tampered with</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_TAMPERED&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>TAMPERED&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>StatusLowBattery (79) </b><ul><li> indicates state of battery</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_LOW_BATTERY&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>LOW_BATTERY&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>ConfiguredName (E3) </b><ul><li> default display name of this Service</li></ul></td><td align="center">string</td><td align="center">PW+PR+EV</td><td align="center">-</td><td align="center">-</td><td align="center">"unnamed"</td></tr>
</table><br>
### ContactSensor (80)
<i> Defines a Contact Sensor.</i><br><table>
<tr><th>Characteristic</th><th>Format</th><th>Perms</th><th>Min</th><th>Max</th><th>Constants/Defaults</th></tr>
<tr><td><b>ContactSensorState (6A) :small_blue_diamond:</b><ul><li> indictates if contact is detected (i.e. closed)</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>DETECTED&nbsp(0)&nbsp;</span></li><li><span>NOT_DETECTED&nbsp(1)&nbsp;</span>:heavy_check_mark:</li></ul></td></tr>
<tr><td><b>StatusActive (75) </b><ul><li> indicates whether the Service is properly functioning </li></ul></td><td align="center">bool</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_FUNCTIONING&nbsp(0)&nbsp;</span></li><li><span>FUNCTIONING&nbsp(1)&nbsp;</span>:heavy_check_mark:</li></ul></td></tr>
<tr><td><b>StatusFault (77) </b><ul><li> indicates whether the Service has a fault (only appears in Eve App, not Home App)</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NO_FAULT&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>FAULT&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>StatusTampered (7A) </b><ul><li> indicates whether the Service has been tampered with</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_TAMPERED&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>TAMPERED&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>StatusLowBattery (79) </b><ul><li> indicates state of battery</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_LOW_BATTERY&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>LOW_BATTERY&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>ConfiguredName (E3) </b><ul><li> default display name of this Service</li></ul></td><td align="center">string</td><td align="center">PW+PR+EV</td><td align="center">-</td><td align="center">-</td><td align="center">"unnamed"</td></tr>
</table><br>
### HumiditySensor (82)
<i> Defines a Humidity Sensor.</i><br><table>
<tr><th>Characteristic</th><th>Format</th><th>Perms</th><th>Min</th><th>Max</th><th>Constants/Defaults</th></tr>
<tr><td><b>CurrentRelativeHumidity (10) :small_blue_diamond:</b><ul><li> current humidity measured as a percentage</li></ul></td><td align="center">float</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">100</td><td align="center">0</td></tr>
<tr><td><b>StatusActive (75) </b><ul><li> indicates whether the Service is properly functioning </li></ul></td><td align="center">bool</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_FUNCTIONING&nbsp(0)&nbsp;</span></li><li><span>FUNCTIONING&nbsp(1)&nbsp;</span>:heavy_check_mark:</li></ul></td></tr>
<tr><td><b>StatusFault (77) </b><ul><li> indicates whether the Service has a fault (only appears in Eve App, not Home App)</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NO_FAULT&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>FAULT&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>StatusTampered (7A) </b><ul><li> indicates whether the Service has been tampered with</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_TAMPERED&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>TAMPERED&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>StatusLowBattery (79) </b><ul><li> indicates state of battery</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_LOW_BATTERY&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>LOW_BATTERY&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>ConfiguredName (E3) </b><ul><li> default display name of this Service</li></ul></td><td align="center">string</td><td align="center">PW+PR+EV</td><td align="center">-</td><td align="center">-</td><td align="center">"unnamed"</td></tr>
</table><br>
### LeakSensor (83)
<i> Defines a Leak Sensor.</i><br><table>
<tr><th>Characteristic</th><th>Format</th><th>Perms</th><th>Min</th><th>Max</th><th>Constants/Defaults</th></tr>
<tr><td><b>LeakDetected (70) :small_blue_diamond:</b><ul><li> indictates if a leak is detected</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_DETECTED&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>DETECTED&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>StatusActive (75) </b><ul><li> indicates whether the Service is properly functioning </li></ul></td><td align="center">bool</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_FUNCTIONING&nbsp(0)&nbsp;</span></li><li><span>FUNCTIONING&nbsp(1)&nbsp;</span>:heavy_check_mark:</li></ul></td></tr>
<tr><td><b>StatusFault (77) </b><ul><li> indicates whether the Service has a fault (only appears in Eve App, not Home App)</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NO_FAULT&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>FAULT&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>StatusTampered (7A) </b><ul><li> indicates whether the Service has been tampered with</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_TAMPERED&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>TAMPERED&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>StatusLowBattery (79) </b><ul><li> indicates state of battery</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_LOW_BATTERY&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>LOW_BATTERY&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>ConfiguredName (E3) </b><ul><li> default display name of this Service</li></ul></td><td align="center">string</td><td align="center">PW+PR+EV</td><td align="center">-</td><td align="center">-</td><td align="center">"unnamed"</td></tr>
</table><br>
### LightSensor (84)
<i> Defines a Light Sensor.</i><br><table>
<tr><th>Characteristic</th><th>Format</th><th>Perms</th><th>Min</th><th>Max</th><th>Constants/Defaults</th></tr>
<tr><td><b>CurrentAmbientLightLevel (6B) :small_blue_diamond:</b><ul><li> measured in Lux (lumens/m<sup>2</sup></li></ul></td><td align="center">float</td><td align="center">PR+EV</td><td align="center">0.0001</td><td align="center">100000</td><td align="center">1</td></tr>
<tr><td><b>StatusActive (75) </b><ul><li> indicates whether the Service is properly functioning </li></ul></td><td align="center">bool</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_FUNCTIONING&nbsp(0)&nbsp;</span></li><li><span>FUNCTIONING&nbsp(1)&nbsp;</span>:heavy_check_mark:</li></ul></td></tr>
<tr><td><b>StatusFault (77) </b><ul><li> indicates whether the Service has a fault (only appears in Eve App, not Home App)</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NO_FAULT&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>FAULT&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>StatusTampered (7A) </b><ul><li> indicates whether the Service has been tampered with</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_TAMPERED&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>TAMPERED&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>StatusLowBattery (79) </b><ul><li> indicates state of battery</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_LOW_BATTERY&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>LOW_BATTERY&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>ConfiguredName (E3) </b><ul><li> default display name of this Service</li></ul></td><td align="center">string</td><td align="center">PW+PR+EV</td><td align="center">-</td><td align="center">-</td><td align="center">"unnamed"</td></tr>
</table><br>
### MotionSensor (85)
<i> Defines a Motion Sensor.</i><br><table>
<tr><th>Characteristic</th><th>Format</th><th>Perms</th><th>Min</th><th>Max</th><th>Constants/Defaults</th></tr>
<tr><td><b>MotionDetected (22) :small_blue_diamond:</b><ul><li> indicates if motion is detected</li></ul></td><td align="center">bool</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_DETECTED&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>DETECTED&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>StatusActive (75) </b><ul><li> indicates whether the Service is properly functioning </li></ul></td><td align="center">bool</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_FUNCTIONING&nbsp(0)&nbsp;</span></li><li><span>FUNCTIONING&nbsp(1)&nbsp;</span>:heavy_check_mark:</li></ul></td></tr>
<tr><td><b>StatusFault (77) </b><ul><li> indicates whether the Service has a fault (only appears in Eve App, not Home App)</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NO_FAULT&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>FAULT&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>StatusTampered (7A) </b><ul><li> indicates whether the Service has been tampered with</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_TAMPERED&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>TAMPERED&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>StatusLowBattery (79) </b><ul><li> indicates state of battery</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_LOW_BATTERY&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>LOW_BATTERY&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>ConfiguredName (E3) </b><ul><li> default display name of this Service</li></ul></td><td align="center">string</td><td align="center">PW+PR+EV</td><td align="center">-</td><td align="center">-</td><td align="center">"unnamed"</td></tr>
</table><br>
### OccupancySensor (86)
<i> Defines and Occupancy Sensor.</i><br><table>
<tr><th>Characteristic</th><th>Format</th><th>Perms</th><th>Min</th><th>Max</th><th>Constants/Defaults</th></tr>
<tr><td><b>OccupancyDetected (71) :small_blue_diamond:</b><ul><li> indicates if occupanccy is detected</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_DETECTED&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>DETECTED&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>StatusActive (75) </b><ul><li> indicates whether the Service is properly functioning </li></ul></td><td align="center">bool</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_FUNCTIONING&nbsp(0)&nbsp;</span></li><li><span>FUNCTIONING&nbsp(1)&nbsp;</span>:heavy_check_mark:</li></ul></td></tr>
<tr><td><b>StatusFault (77) </b><ul><li> indicates whether the Service has a fault (only appears in Eve App, not Home App)</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NO_FAULT&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>FAULT&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>StatusTampered (7A) </b><ul><li> indicates whether the Service has been tampered with</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_TAMPERED&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>TAMPERED&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>StatusLowBattery (79) </b><ul><li> indicates state of battery</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_LOW_BATTERY&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>LOW_BATTERY&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>ConfiguredName (E3) </b><ul><li> default display name of this Service</li></ul></td><td align="center">string</td><td align="center">PW+PR+EV</td><td align="center">-</td><td align="center">-</td><td align="center">"unnamed"</td></tr>
</table><br>
### SmokeSensor (87)
<i> Defines a Smoke Sensor.</i><br><table>
<tr><th>Characteristic</th><th>Format</th><th>Perms</th><th>Min</th><th>Max</th><th>Constants/Defaults</th></tr>
<tr><td><b>SmokeDetected (76) :small_blue_diamond:</b><ul><li> indicates if smoke is detected</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_DETECTED&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>DETECTED&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>StatusActive (75) </b><ul><li> indicates whether the Service is properly functioning </li></ul></td><td align="center">bool</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_FUNCTIONING&nbsp(0)&nbsp;</span></li><li><span>FUNCTIONING&nbsp(1)&nbsp;</span>:heavy_check_mark:</li></ul></td></tr>
<tr><td><b>StatusFault (77) </b><ul><li> indicates whether the Service has a fault (only appears in Eve App, not Home App)</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NO_FAULT&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>FAULT&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>StatusTampered (7A) </b><ul><li> indicates whether the Service has been tampered with</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_TAMPERED&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>TAMPERED&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>StatusLowBattery (79) </b><ul><li> indicates state of battery</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_LOW_BATTERY&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>LOW_BATTERY&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>ConfiguredName (E3) </b><ul><li> default display name of this Service</li></ul></td><td align="center">string</td><td align="center">PW+PR+EV</td><td align="center">-</td><td align="center">-</td><td align="center">"unnamed"</td></tr>
</table><br>
### TemperatureSensor (8A)
<i> Defines a Temperature Sensor.</i><br><table>
<tr><th>Characteristic</th><th>Format</th><th>Perms</th><th>Min</th><th>Max</th><th>Constants/Defaults</th></tr>
<tr><td><b>CurrentTemperature (11) :small_blue_diamond:</b><ul><li> current temperature measured in Celsius</li></ul></td><td align="center">float</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">100</td><td align="center">0</td></tr>
<tr><td><b>StatusActive (75) </b><ul><li> indicates whether the Service is properly functioning </li></ul></td><td align="center">bool</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_FUNCTIONING&nbsp(0)&nbsp;</span></li><li><span>FUNCTIONING&nbsp(1)&nbsp;</span>:heavy_check_mark:</li></ul></td></tr>
<tr><td><b>StatusFault (77) </b><ul><li> indicates whether the Service has a fault (only appears in Eve App, not Home App)</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NO_FAULT&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>FAULT&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>StatusTampered (7A) </b><ul><li> indicates whether the Service has been tampered with</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_TAMPERED&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>TAMPERED&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>StatusLowBattery (79) </b><ul><li> indicates state of battery</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_LOW_BATTERY&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>LOW_BATTERY&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>ConfiguredName (E3) </b><ul><li> default display name of this Service</li></ul></td><td align="center">string</td><td align="center">PW+PR+EV</td><td align="center">-</td><td align="center">-</td><td align="center">"unnamed"</td></tr>
</table><br>
## DOORS, LOCKS, AND WINDOWS
### Door (81)
<i> Defines a motorized Door.</i><br><table>
<tr><th>Characteristic</th><th>Format</th><th>Perms</th><th>Min</th><th>Max</th><th>Constants/Defaults</th></tr>
<tr><td><b>CurrentPosition (6D) :small_blue_diamond:</b><ul><li> current position (as a percentage) from fully closed (0) to full open (100)</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">100</td><td align="center">0</td></tr>
<tr><td><b>TargetPosition (7C) :small_blue_diamond:</b><ul><li> indicates target position (as a percentage) from fully closed (0) to full open (100)</li></ul></td><td align="center">uint8</td><td align="center">PW+PR+EV</td><td align="center">0</td><td align="center">100</td><td align="center">0</td></tr>
<tr><td><b>ObstructionDetected (24) </b><ul><li> indicates if obstruction is detected</li></ul></td><td align="center">bool</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_DETECTED&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>DETECTED&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>ConfiguredName (E3) </b><ul><li> default display name of this Service</li></ul></td><td align="center">string</td><td align="center">PW+PR+EV</td><td align="center">-</td><td align="center">-</td><td align="center">"unnamed"</td></tr>
</table><br>
### Doorbell (121)
<i> Defines a Doorbell. Can be used on a standalone basis or in conjunction with a <b>LockMechanism</b> Service.</i><br><table>
<tr><th>Characteristic</th><th>Format</th><th>Perms</th><th>Min</th><th>Max</th><th>Constants/Defaults</th></tr>
<tr><td><b>ProgrammableSwitchEvent (73) :small_blue_diamond:</b><ul><li> specifies type of button press</li></ul></td><td align="center">uint8</td><td align="center">PR+EV+NV</td><td align="center">0</td><td align="center">2</td><td><ul><li><span>SINGLE_PRESS&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>DOUBLE_PRESS&nbsp(1)&nbsp;</span></li><li><span>LONG_PRESS&nbsp(2)&nbsp;</span></li></ul></td></tr>
<tr><td><b>ConfiguredName (E3) </b><ul><li> default display name of this Service</li></ul></td><td align="center">string</td><td align="center">PW+PR+EV</td><td align="center">-</td><td align="center">-</td><td align="center">"unnamed"</td></tr>
</table><br>
### GarageDoorOpener (41)
<i> Defines a motorized Garage Door Opener.</i><br><table>
<tr><th>Characteristic</th><th>Format</th><th>Perms</th><th>Min</th><th>Max</th><th>Constants/Defaults</th></tr>
<tr><td><b>CurrentDoorState (E) :small_blue_diamond:</b><ul><li> indicates current state of a door</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">4</td><td><ul><li><span>OPEN&nbsp(0)&nbsp;</span></li><li><span>CLOSED&nbsp(1)&nbsp;</span>:heavy_check_mark:</li><li><span>OPENING&nbsp(2)&nbsp;</span></li><li><span>CLOSING&nbsp(3)&nbsp;</span></li><li><span>STOPPED&nbsp(4)&nbsp;</span></li></ul></td></tr>
<tr><td><b>TargetDoorState (32) :small_blue_diamond:</b><ul><li> indicates desired state of door</li></ul></td><td align="center">uint8</td><td align="center">PW+PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>OPEN&nbsp(0)&nbsp;</span></li><li><span>CLOSED&nbsp(1)&nbsp;</span>:heavy_check_mark:</li></ul></td></tr>
<tr><td><b>ObstructionDetected (24) :small_blue_diamond:</b><ul><li> indicates if obstruction is detected</li></ul></td><td align="center">bool</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_DETECTED&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>DETECTED&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>LockCurrentState (1D) </b><ul><li> indicates state of a lock</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">3</td><td><ul><li><span>UNLOCKED&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>LOCKED&nbsp(1)&nbsp;</span></li><li><span>JAMMED&nbsp(2)&nbsp;</span></li><li><span>UNKNOWN&nbsp(3)&nbsp;</span></li></ul></td></tr>
<tr><td><b>LockTargetState (1E) </b><ul><li> indicates desired state of lock</li></ul></td><td align="center">uint8</td><td align="center">PW+PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>UNLOCK&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>LOCK&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>ConfiguredName (E3) </b><ul><li> default display name of this Service</li></ul></td><td align="center">string</td><td align="center">PW+PR+EV</td><td align="center">-</td><td align="center">-</td><td align="center">"unnamed"</td></tr>
</table><br>
### LockMechanism (45)
<i> Defines an electronic Lock.</i><br><table>
<tr><th>Characteristic</th><th>Format</th><th>Perms</th><th>Min</th><th>Max</th><th>Constants/Defaults</th></tr>
<tr><td><b>LockCurrentState (1D) :small_blue_diamond:</b><ul><li> indicates state of a lock</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">3</td><td><ul><li><span>UNLOCKED&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>LOCKED&nbsp(1)&nbsp;</span></li><li><span>JAMMED&nbsp(2)&nbsp;</span></li><li><span>UNKNOWN&nbsp(3)&nbsp;</span></li></ul></td></tr>
<tr><td><b>LockTargetState (1E) :small_blue_diamond:</b><ul><li> indicates desired state of lock</li></ul></td><td align="center">uint8</td><td align="center">PW+PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>UNLOCK&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>LOCK&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>ConfiguredName (E3) </b><ul><li> default display name of this Service</li></ul></td><td align="center">string</td><td align="center">PW+PR+EV</td><td align="center">-</td><td align="center">-</td><td align="center">"unnamed"</td></tr>
</table><br>
### Window (8B)
<i> Defines a motorized Window.</i><br><table>
<tr><th>Characteristic</th><th>Format</th><th>Perms</th><th>Min</th><th>Max</th><th>Constants/Defaults</th></tr>
<tr><td><b>CurrentPosition (6D) :small_blue_diamond:</b><ul><li> current position (as a percentage) from fully closed (0) to full open (100)</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">100</td><td align="center">0</td></tr>
<tr><td><b>TargetPosition (7C) :small_blue_diamond:</b><ul><li> indicates target position (as a percentage) from fully closed (0) to full open (100)</li></ul></td><td align="center">uint8</td><td align="center">PW+PR+EV</td><td align="center">0</td><td align="center">100</td><td align="center">0</td></tr>
<tr><td><b>ObstructionDetected (24) </b><ul><li> indicates if obstruction is detected</li></ul></td><td align="center">bool</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_DETECTED&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>DETECTED&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>ConfiguredName (E3) </b><ul><li> default display name of this Service</li></ul></td><td align="center">string</td><td align="center">PW+PR+EV</td><td align="center">-</td><td align="center">-</td><td align="center">"unnamed"</td></tr>
</table><br>
### WindowCovering (8C)
<i> Defines a motorized Window Shade, Screen, Awning, etc.</i><br><table>
<tr><th>Characteristic</th><th>Format</th><th>Perms</th><th>Min</th><th>Max</th><th>Constants/Defaults</th></tr>
<tr><td><b>TargetPosition (7C) :small_blue_diamond:</b><ul><li> indicates target position (as a percentage) from fully closed (0) to full open (100)</li></ul></td><td align="center">uint8</td><td align="center">PW+PR+EV</td><td align="center">0</td><td align="center">100</td><td align="center">0</td></tr>
<tr><td><b>CurrentPosition (6D) :small_blue_diamond:</b><ul><li> current position (as a percentage) from fully closed (0) to full open (100)</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">100</td><td align="center">0</td></tr>
<tr><td><b>CurrentHorizontalTiltAngle (6C) </b><ul><li> current angle (in degrees) of slats from fully up (-90) to fully open (0) to fully down (90) </li></ul></td><td align="center">int</td><td align="center">PR+EV</td><td align="center">-90</td><td align="center">90</td><td align="center">0</td></tr>
<tr><td><b>TargetHorizontalTiltAngle (7B) </b><ul><li> indicates desired angle (in degrees) of slats from fully up (-90) to fully open (0) to fully down (90)</li></ul></td><td align="center">int</td><td align="center">PW+PR+EV</td><td align="center">-90</td><td align="center">90</td><td align="center">0</td></tr>
<tr><td><b>CurrentVerticalTiltAngle (6E) </b><ul><li> current angle (in degrees) of slats from fully left (-90) to fully open (0) to fully right (90)</li></ul></td><td align="center">int</td><td align="center">PR+EV</td><td align="center">-90</td><td align="center">90</td><td align="center">0</td></tr>
<tr><td><b>TargetVerticalTiltAngle (7D) </b><ul><li> indicates desired angle (in degrees) of slats from fully left (-90) to fully open (0) to fully right (90)</li></ul></td><td align="center">int</td><td align="center">PW+PR+EV</td><td align="center">-90</td><td align="center">90</td><td align="center">0</td></tr>
<tr><td><b>ObstructionDetected (24) </b><ul><li> indicates if obstruction is detected</li></ul></td><td align="center">bool</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_DETECTED&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>DETECTED&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>ConfiguredName (E3) </b><ul><li> default display name of this Service</li></ul></td><td align="center">string</td><td align="center">PW+PR+EV</td><td align="center">-</td><td align="center">-</td><td align="center">"unnamed"</td></tr>
</table><br>
## WATER SYSTEMS
### Faucet (D7)
<i> Defines the master control for a multi-Valve appliance. Linked Services: <b>Valve</b> (at least one required), and <b>HeaterCooler</b> (optional).</i><br><table>
<tr><th>Characteristic</th><th>Format</th><th>Perms</th><th>Min</th><th>Max</th><th>Constants/Defaults</th></tr>
<tr><td><b>Active (B0) :small_blue_diamond:</b><ul><li> indicates if the Service is active/on</li></ul></td><td align="center">uint8</td><td align="center">PW+PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>INACTIVE&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>ACTIVE&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>StatusFault (77) </b><ul><li> indicates whether the Service has a fault (only appears in Eve App, not Home App)</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NO_FAULT&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>FAULT&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>ConfiguredName (E3) </b><ul><li> default display name of this Service</li></ul></td><td align="center">string</td><td align="center">PW+PR+EV</td><td align="center">-</td><td align="center">-</td><td align="center">"unnamed"</td></tr>
</table><br>
### IrrigationSystem (CF)
<i> Defines an Irrigation System. Linked Services: <b>Valve</b> Service (at least one required).</i><br><table>
<tr><th>Characteristic</th><th>Format</th><th>Perms</th><th>Min</th><th>Max</th><th>Constants/Defaults</th></tr>
<tr><td><b>Active (B0) :small_blue_diamond:</b><ul><li> indicates if the Service is active/on</li></ul></td><td align="center">uint8</td><td align="center">PW+PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>INACTIVE&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>ACTIVE&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>ProgramMode (D1) :small_blue_diamond:</b><ul><li> indicates if pre-scheduled program is running</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">2</td><td><ul><li><span>NONE&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>SCHEDULED&nbsp(1)&nbsp;</span></li><li><span>SCHEDULE_OVERRIDEN&nbsp(2)&nbsp;</span></li></ul></td></tr>
<tr><td><b>InUse (D2) :small_blue_diamond:</b><ul><li> if Service is set to active, this indictes whether it is currently in use</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_IN_USE&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>IN_USE&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>RemainingDuration (D4) </b><ul><li> duration (in seconds) remaining for Service to be active/on</li></ul></td><td align="center">uint32</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">3600</td><td align="center">60</td></tr>
<tr><td><b>StatusFault (77) </b><ul><li> indicates whether the Service has a fault (only appears in Eve App, not Home App)</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NO_FAULT&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>FAULT&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>ConfiguredName (E3) </b><ul><li> default display name of this Service</li></ul></td><td align="center">string</td><td align="center">PW+PR+EV</td><td align="center">-</td><td align="center">-</td><td align="center">"unnamed"</td></tr>
</table><br>
### Valve (D0)
<i> Defines an electronic Valve. Can be used standalone or as a Linked Service for either a <b>Faucet</b> or <b>IrrigationSystem</b> Service.</i><br><table>
<tr><th>Characteristic</th><th>Format</th><th>Perms</th><th>Min</th><th>Max</th><th>Constants/Defaults</th></tr>
<tr><td><b>Active (B0) :small_blue_diamond:</b><ul><li> indicates if the Service is active/on</li></ul></td><td align="center">uint8</td><td align="center">PW+PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>INACTIVE&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>ACTIVE&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>InUse (D2) :small_blue_diamond:</b><ul><li> if Service is set to active, this indictes whether it is currently in use</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_IN_USE&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>IN_USE&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>ValveType (D5) :small_blue_diamond:</b><ul><li> indicates the type of valve</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">3</td><td><ul><li><span>GENERIC&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>IRRIGATION&nbsp(1)&nbsp;</span></li><li><span>SHOWER_HEAD&nbsp(2)&nbsp;</span></li><li><span>FAUCET&nbsp(3)&nbsp;</span></li></ul></td></tr>
<tr><td><b>SetDuration (D3) </b><ul><li> specifies the duration (in seconds) for a Service to remain on once activated</li></ul></td><td align="center">uint32</td><td align="center">PW+PR+EV</td><td align="center">0</td><td align="center">3600</td><td align="center">60</td></tr>
<tr><td><b>RemainingDuration (D4) </b><ul><li> duration (in seconds) remaining for Service to be active/on</li></ul></td><td align="center">uint32</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">3600</td><td align="center">60</td></tr>
<tr><td><b>IsConfigured (D6) </b><ul><li> indicates if a predefined Service has been configured</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_CONFIGURED&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>CONFIGURED&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>ServiceLabelIndex (CB) </b><ul><li> numerical index used to distinguish multiple copies of the same Service within an Accessory</li></ul></td><td align="center">uint8</td><td align="center">PR</td><td align="center">1</td><td align="center">255</td><td align="center">1</td></tr>
<tr><td><b>StatusFault (77) </b><ul><li> indicates whether the Service has a fault (only appears in Eve App, not Home App)</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NO_FAULT&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>FAULT&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>ConfiguredName (E3) </b><ul><li> default display name of this Service</li></ul></td><td align="center">string</td><td align="center">PW+PR+EV</td><td align="center">-</td><td align="center">-</td><td align="center">"unnamed"</td></tr>
</table><br>
## SECURITY SYSTEMS
### SecuritySystem (7E)
<i> Defines a Security System. Often used in combination with <b>MotionSensor</b> and <b>ContactSensor</b> Services.</i><br><table>
<tr><th>Characteristic</th><th>Format</th><th>Perms</th><th>Min</th><th>Max</th><th>Constants/Defaults</th></tr>
<tr><td><b>SecuritySystemCurrentState (66) :small_blue_diamond:</b><ul><li> indicates current state of the security system </li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">4</td><td><ul><li><span>ARMED_STAY&nbsp(0)&nbsp;</span></li><li><span>ARMED_AWAY&nbsp(1)&nbsp;</span></li><li><span>ARMED_NIGHT&nbsp(2)&nbsp;</span></li><li><span>DISARMED&nbsp(3)&nbsp;</span>:heavy_check_mark:</li><li><span>ALARM_TRIGGERED&nbsp(4)&nbsp;</span></li></ul></td></tr>
<tr><td><b>SecuritySystemTargetState (67) :small_blue_diamond:</b><ul><li> indicates desired state of the security system</li></ul></td><td align="center">uint8</td><td align="center">PW+PR+EV</td><td align="center">0</td><td align="center">3</td><td><ul><li><span>ARM_STAY&nbsp(0)&nbsp;</span></li><li><span>ARM_AWAY&nbsp(1)&nbsp;</span></li><li><span>ARM_NIGHT&nbsp(2)&nbsp;</span></li><li><span>DISARM&nbsp(3)&nbsp;</span>:heavy_check_mark:</li></ul></td></tr>
<tr><td><b>SecuritySystemAlarmType (8E) </b><ul><li> indicates whether alarm was triggered for known reason</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>KNOWN&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>UNKNOWN&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>StatusFault (77) </b><ul><li> indicates whether the Service has a fault (only appears in Eve App, not Home App)</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NO_FAULT&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>FAULT&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>StatusTampered (7A) </b><ul><li> indicates whether the Service has been tampered with</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_TAMPERED&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>TAMPERED&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>ConfiguredName (E3) </b><ul><li> default display name of this Service</li></ul></td><td align="center">string</td><td align="center">PW+PR+EV</td><td align="center">-</td><td align="center">-</td><td align="center">"unnamed"</td></tr>
</table><br>
## TELEVISIONS
### InputSource (D9)
<i> Defines an Input Source for a TV. Use only as a Linked Service for the <b>Television</b> Service.</i><br><table>
<tr><th>Characteristic</th><th>Format</th><th>Perms</th><th>Min</th><th>Max</th><th>Constants/Defaults</th></tr>
<tr><td><b>Identifier (E6) :small_blue_diamond:</b><ul><li> numerical Identifer of the <b>InputSource</b>.</li></ul></td><td align="center">uint32</td><td align="center">PR</td><td align="center">0</td><td align="center">255</td><td align="center">0</td></tr>
<tr><td><b>ConfiguredName (E3) </b><ul><li> default display name of this Service</li></ul></td><td align="center">string</td><td align="center">PW+PR+EV</td><td align="center">-</td><td align="center">-</td><td align="center">"unnamed"</td></tr>
<tr><td><b>IsConfigured (D6) </b><ul><li> indicates if a predefined Service has been configured</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>NOT_CONFIGURED&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>CONFIGURED&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>CurrentVisibilityState (135) </b><ul><li> current visibility of the Service, as selectable on the Settings Page of the Home App</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>VISIBLE&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>NOT_VISIBLE&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>TargetVisibilityState (134) </b><ul><li> indicates desired visibility of the Service, as selectable on the Settings Page of the Home App</li></ul></td><td align="center">uint8</td><td align="center">PW+PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>VISIBLE&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>NOT_VISIBLE&nbsp(1)&nbsp;</span></li></ul></td></tr>
</table><br>
### Television (D8)
<i> Defines a TV. Optional Linked Services: <b>InputSource</b> and <b>TelevisionSpeaker</b>.</i><br><table>
<tr><th>Characteristic</th><th>Format</th><th>Perms</th><th>Min</th><th>Max</th><th>Constants/Defaults</th></tr>
<tr><td><b>Active (B0) :small_blue_diamond:</b><ul><li> indicates if the Service is active/on</li></ul></td><td align="center">uint8</td><td align="center">PW+PR+EV</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>INACTIVE&nbsp(0)&nbsp;</span>:heavy_check_mark:</li><li><span>ACTIVE&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>ActiveIdentifier (E7) </b><ul><li> numerical Identifier of the <b>InputSource</b> selected in the Home App.</li></ul></td><td align="center">uint32</td><td align="center">PW+PR+EV</td><td align="center">0</td><td align="center">255</td><td align="center">0</td></tr>
<tr><td><b>DisplayOrder (136) </b><ul><li> specifies the order in which the TV inputs are displayed for selection in the Home App</li></ul></td><td align="center">tlv8</td><td align="center">PR+EV</td><td align="center">-</td><td align="center">-</td><td align="center">NULL_TLV</td></tr>
<tr><td><b>RemoteKey (E1) </b><ul><li> triggers an update when the corresponding key is pressed in the Remote Control widget on an iPhone </li></ul></td><td align="center">uint8</td><td align="center">PW</td><td align="center">4</td><td align="center">15</td><td><ul><li><span>UP&nbsp(4)&nbsp;</span></li><li><span>DOWN&nbsp(5)&nbsp;</span></li><li><span>LEFT&nbsp(6)&nbsp;</span></li><li><span>RIGHT&nbsp(7)&nbsp;</span></li><li><span>CENTER&nbsp(8)&nbsp;</span></li><li><span>BACK&nbsp(9)&nbsp;</span></li><li><span>PLAY_PAUSE&nbsp(11)&nbsp;</span></li><li><span>INFO&nbsp(15)&nbsp;</span></li></ul></td></tr>
<tr><td><b>PowerModeSelection (DF) </b><ul><li> when defined, creates a "View TV Settings" button in the Home App that triggers an update to this Characteristic when pressed </li></ul></td><td align="center">uint8</td><td align="center">PW</td><td align="center">0</td><td align="center">0</td><td><ul><li><span>VIEW_SETTINGS&nbsp(0)&nbsp;</span></li></ul></td></tr>
<tr><td><b>ConfiguredName (E3) </b><ul><li> default display name of this Service</li></ul></td><td align="center">string</td><td align="center">PW+PR+EV</td><td align="center">-</td><td align="center">-</td><td align="center">"unnamed"</td></tr>
</table><br>
### TelevisionSpeaker (113)
<i> Defines a Television Speaker that can be controlled via the Remote Control widget on an iPhone. Use only as a Linked Service for the <b>Television</b> Service.</i><br><table>
<tr><th>Characteristic</th><th>Format</th><th>Perms</th><th>Min</th><th>Max</th><th>Constants/Defaults</th></tr>
<tr><td><b>VolumeControlType (E9) :small_blue_diamond:</b><ul><li> indicates the type of volume control</li></ul></td><td align="center">uint8</td><td align="center">PR+EV</td><td align="center">0</td><td align="center">3</td><td><ul><li><span>NONE&nbsp(0)&nbsp;</span></li><li><span>RELATIVE&nbsp(1)&nbsp;</span></li><li><span>RELATIVE_CURRENT&nbsp(2)&nbsp;</span></li><li><span>ABSOLUTE&nbsp(3)&nbsp;</span>:heavy_check_mark:</li></ul></td></tr>
<tr><td><b>VolumeSelector (EA) :small_blue_diamond:</b><ul><li> triggered by presses to the iPhone's volume up/down buttons when TV is selected in the Remote Control widget</li></ul></td><td align="center">uint8</td><td align="center">PW</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>VOLUME_UP&nbsp(0)&nbsp;</span></li><li><span>VOLUME_DOWN&nbsp(1)&nbsp;</span></li></ul></td></tr>
<tr><td><b>ConfiguredName (E3) </b><ul><li> default display name of this Service</li></ul></td><td align="center">string</td><td align="center">PW+PR+EV</td><td align="center">-</td><td align="center">-</td><td align="center">"unnamed"</td></tr>
</table><br>
## MISCELLANEOUS
### ServiceLabel (CC)
<i> Defines a naming scheme for un-nameable Services, such as a <b>StatelessProgrammableSwitch</b>, by Linking them to this Service. When used, those other Services must each include a <b>ServiceLabelIndex</b> Characteristic with a unique value.</i><br><table>
<tr><th>Characteristic</th><th>Format</th><th>Perms</th><th>Min</th><th>Max</th><th>Constants/Defaults</th></tr>
<tr><td><b>ServiceLabelNamespace (CD) :small_blue_diamond:</b><ul><li> indicates how un-named Services linked together with a <b>ServiceLabel</b> Service should be displayed in the Home App </li></ul></td><td align="center">uint8</td><td align="center">PR</td><td align="center">0</td><td align="center">1</td><td><ul><li><span>DOTS&nbsp(0)&nbsp;</span></li><li><span>NUMERALS&nbsp(1)&nbsp;</span>:heavy_check_mark:</li></ul></td></tr>
</table><br>
|HAP-R2 Format Code|HomeSpan C++ Type|
|------------------|-----------------|
|BOOL|boolean|
|UINT8|uint8_t|
|UINT16|uint16_t|
|UINT32|uint32_t|
|UINT64|uint64_t|
|INT|int|
|FLOAT|double|
|STRING|char \*|
|TLV8|(not implemented)|
|DATA|(not implemented)|
---
[↩️](../README.md) Back to the Welcome page
[↩️](README.md) Back to the Welcome page

View File

@ -1,58 +0,0 @@
# Common Problems and Potential Solutions
### *My HomeSpan device does not appear in the Home App when I try to pair it*
* There are a few reasons this may occur:
* **You have not yet entered your WiFi credentials**. HomeSpan can't connect to your WiFi network until you provide it with your WiFi&nbsp;SSID and password. If HomeSpan can't find these Credentials it outputs a warning message to the Serial Monitor during start-up. You can enter your WiFi Credentials into HomeSpan either directly from the Serial Monitor by using the 'W' CLI command, or via the HomeSpan Access Point Setup Page, or by hardcoding it into your sketch with the `homeSpan.setWifiCredentials()` function, though this last method is not recommended for security reasons.
* **You are out of range of your WiFi network, or entered the wrong WiFi Credentials**. When HomeSpan first boots, if you've previously entered your WiFi Credentials (see above) it will use them to connect to your WiFi network. Check the Serial Monitor for status on whether a connection has been successfully made. If not, make sure your device is in the range of your WiFi network, and re-enter your WiFi Credentials in case you entered them incorrectly the first time. To double-check that your HomeSpan device is indeed connected to your network after HomeSpan reports a successful connection, open up a terminal window on your computer and use the `ping` command to confirm you can reach the device.
* **Your iPhone and ESP32 device are not connected to the same WiFi network**. Make sure your HomeSpan device is connected to the same SSID as your iPhone and any HomeKit Hubs (e.g. HomePods or Apple TV). Some routers provide a separate SSID to use for IoT ("Internet of Things") devices. If you decide to use a separate SSID, make sure to configure your router so that message traffic flows unimpeded between your main SSID and your dedicated IoT SSID with absolutely no filtering of any messages. Also note that like most commercial HomeKit devices, ESP32 devices operate only on the 2.4 GHz WiFi band. Most iPhones can operate on either the 2.4 GHz or the 5.0 GHz WiFi bands, so if your router provides multi-band access, you need to make sure it is configured to allow unimpeded cross-traffic between the bands.
* **Your device thinks it is already paired (this is the most common reason)**. Check the Serial Monitor when HomeSpan first boots - it will let you know if the device is currently *paired* or *unpaired*. If its already *paired* you must unpair it before it can be paired again with the Home App. Normally you would unpair the device from the Home App itself, but if for whatever reason you can't (perhaps the device is no longer showing up in the Home App) you can force HomeSpan to forget all its pairing data and reset its state to *unpaired* by typing either the 'U' or 'H' CLI command into the Serial Monitor. The 'U' command instructs HomeSpan to simply erase all its *Controller* pairing data and reset its state to *unpaired*. The 'H' command instructs HomeSpan to erase all its *Controller* pairing data **and** its *HomeKit Device ID*, after which it reboots into the *unpaired* state and generates a new *HomeKit Device ID*. Typing 'H' is recommended to get the cleanest refresh. Note that your WiFi Credentials and Pairing Setup Code are not changed by either of these commands.
### *HomeSpan works correctly when my ESP32 is plugged into a computer or separately powered through the USB port, but it fails to work when powered directly through the ESP32 5V pin without any USB connection*
* On some ESP32 boards, the USB-UART chip only receives power if power is applied through the USB port, and thus remains unpowered if the ESP32 is powered solely through the ESP32 5V pin. As a result, the Serial RX pin associated with UART0 on the ESP32, which is normally driven by the USB-UART chip, is free to float at any voltage. If this pin floats low, the ESP32 thinks there is data to be read from the Serial Monitor, and HomeSpan falls into an infinite loop trying to read this non-existent data.
* **Resolution:** Add a pull-up resistor (10 kΩ should be fine) connecting the UART0 RX pin on your board to +3.3V. For most boards the default UART0 RX pin is as follows:
* Pin 3 for the ESP32
* Pin 44 for the ESP32-S2 and ESP32-S3
* Pin 20 for the ESP32-C3
* Note that adding this pull-up resistor should **not** interefere with normal serial operation of the board, such as using the Serial Monitor or uploading sketches.
* *As an alternative*, instead of adding a pull-up resistor, you can simply exclude (or comment out) the `Serial.begin()` line in your sketch, or call `homeSpan.setSerialInputDisable(false)` to disable HomeSpan reading from the Serial port. This should prevent the problem of HomeSpan hanging when you power it through the 5V pin, but it unfortunately means the Serial Monitor will not function when you connect the board to your computer, and you will need to add back `Serial.begin()`, or remove `homeSpan.setSerialInputDisable(false)` whenever you want to use the Serial Monitor.
### *HomeSpan crashes when I enable PSRAM, but works fine if I disable PSRAM*
* If your ESP32 comes with Quad or Octal PSRAM, the chip will likely need to use one or more additional I/O pins so that it can access the PSRAM using an extended SPI bus, as required for these types of PSRAM. If you happen to use one of those pins for something else (e.g an input button, an LED, etc.) the ESP32 will likely crash whenever PSRAM is enabled.
* **Resolution:** Check the documentation for your board to see what pins are reserved for use when PSRAM is enabled, and *don't use those pins for anything else.*
### *The Serial Monitor is reporting* "gpio: gpio_set_level(226): GPIO output gpio_num error"
* This is an ESP32-generated error message and it occurs if you try to set the output of a pin that either does not exist on your chip, or does exist but it is input-only (i.e. it cannot be used as a output). This typically occurs when you try to compile code for one chip (such as an ESP32-S2) on another chip (such as an ESP32-C3). The code will compile fine, but may produce the above error during run-time.
* **Resolution:** Check the documentation for your board and *use only pins that exist for your chip, and are not reserved for internal functions, and are not input-only.*
### *My sketch is too large to be uploaded (error when trying to upload)*
* Though all ESP32 chips have a minimum of 4MB of flash memory, the amount that can be used to store a program depends on the *partition scheme* selected. By default, the ESP32 uses a parition scheme that reserves 1408 KB for a SPIFFS partition (SPI Flash File Storage) and splits the majority of the remaining flash into two equal OTA partitions of 1280 KB each for program storage. Most HomeSpan sketches come close to filling up an entire OTA partition, and if you add a lot of other libraries you will likely exceed 1280&nbsp;KB.
* **Resolution:** Select a different partition table that does not reserve so much flash memory for a SPIFFS partition since SPIFFS is not used at all by HomeSpan and *this partition is just wasting space.*
* From within the Arduino IDE, the easiest way to reduce the SPIFFS partition is to select the *Minimal SPIFFS* partition scheme from under the Tools menu, and then simply recompile and upload your sketch. This scheme reserves only 128 KB for the SPIFFS partition, which leaves a full 1920 KB of program storage for each OTA partition. This represents a 50% increase in program size, which should suffice for most applications.
* If for some reason you still need more space, and you only have 4MB of flash, you can try selecting the *Huge App* partition scheme, which reserves 896 KB for SPIFFS and leaves a *single* partition of 3072 KB for program storage. OTA will unfortunately *not* be available in this scheme.
* Note: if you are not using the Arduino IDE to compile and upload HomeSpan sketches, please consult the documentation for your IDE to learn how to change the partition scheme.
* Note: if none of the built-in partition schemes contain the right balance of partition sizes for your sketch, you can always create a custom partition scheme as demonstrated in HomeSpan's [CustomNVSParititon Example](../examples/Other%20Examples/CustomNVSPartition). This technique should work even if not using the Arduino IDE.
---
[↩️](../README.md) Back to the Welcome page

View File

@ -1,283 +0,0 @@
# Stepper Motor Control
HomeSpan includes dedicated classes that provide for easy control of a stepper motor connected to an ESP32 via a stepper motor driver board. These classes allow one or more stepper motors to operate smoothly and asynchronously in the background while HomeSpan continues to run in the foreground. On devices with dual processors, stepper-motor control can be run either on the same or a different processor from HomeSpan.
The HomeSpan class that contains all the methods to control a stepper motor is called **StepperControl**. However, this is an abstract class and cannot be instantiated directly. Instead you instantiate stepper motor objects using driver-specific child-classes (derived from **StepperControl**) that contain all the logic to configure and operate a particular driver board. Each child class supports one or more constructors allowing you to specify which output pins on your ESP32 device will be connected to the required pins on your driver board.
The following drivers are currently included in HomeSpan:
* **[Stepper_TB6612](StepperDrivers/Stepper_TB6612.md)**
* This class is used to operate stepper motors driven by a [Toshiba TB6612](https://cdn-shop.adafruit.com/datasheets/TB6612FNG_datasheet_en_20121101.pdf) (or equivalent) chip
* Can be used either with or without ESP32 PWM pins
* See, for example, the [Adafruit TB6612 1.2A DC/Stepper Motor Driver Breakout Board](https://www.adafruit.com/product/2448)
* **[Stepper_A3967](StepperDrivers/Stepper_A3967.md)**
* This class is used to operate stepper motors driven by an [Allegro A3967](https://cdn.sparkfun.com/datasheets/Robotics/A3967-Datasheet.pdf) (or equivalent) chip
* See, for example, the [Sparkfun EasyDriver Stepper Motor Board](https://www.sparkfun.com/products/12779)
* **[Stepper_ULN2003A](StepperDrivers/Stepper_ULN2003A.md)**
* This class is used to operate stepper motors driven by a [Texas Instruments ULN2003A](https://www.ti.com/lit/ds/symlink/uln2003a.pdf) (or equivalent) chip
* See, for example, the [Opencircuit ULN2003 Stepper Motor Driver Board](https://opencircuit.shop/product/uln2003-stepper-motor-driver-module)
* **[Stepper_UNIPOLAR](StepperDrivers/Stepper_UNIPOLAR.md)**
* This class provides a generic driver for use with any center-tapped unipolar stepper motor
* Use requires a driver board that can convert the low-voltage/low-current digital signals from 4 pins on the ESP32 to higher-voltage/higher-current signals suitable for operating the stepper motor
Click on any of the driver-specific classes above for complete details on how to wire and configure a particular driver board.
## StepperControl Methods
The **StepperControl** class provides the following methods to operate and control a stepper motor object instantiated with one of the driver-specific classes described above:
* `StepperControl *enable()`
* enables current flow to the stepper motor coils, actively holding the motor in its position
* returns pointer to itself so methods can be daisy-chained
* example: `myMotor=(new Stepper_TB6612(23,32,22,14,33,27))->enable();`
* `StepperControl *disable()`
* disables current flow to the stepper motor coils and leaves them in a state of high impedence, allowing the motor to turn freely
* returns pointer to itself so methods can be daisy-chained
* example: `myMotor=(new Stepper_TB6612(23,32,22,14,33,27))->disable();`
* `StepperControl *brake()`
* disables current flow to the stepper motor coils but leaves them in a state of low impedence, preventing the motor from freely turning
* applicable only for driver chips that support a "short brake" mode, otherwise has no effect
* returns pointer to itself so methods can be daisy-chained
* example: `myMotor=(new Stepper_TB6612(23,32,22,14,33,27))->brake();`
* `void move(int nSteps, uint32_t msDelay, endAction_t endAction=NONE)`
* enables the stepper motor and turns it *nSteps* steps. Note this is a **non-blocking** function and returns immediately after being called while the motor turns for *nSteps* steps in the background
* *nSteps* - the number of steps to turn. A positive number turns the motor in one direction; a negative number turns the motor in the opposite direction; a value of zero causes the motor to *stop* if it is already turning
* *msDelay* - the delay, in milliseconds, to pause between steps. Must be greater than zero. The lower the number, the faster the motor turns, subject to limitations of the motor itself
* *endAction* - an optional action to be performed *after* the motor has finished moving *nSteps* steps. Choices include:
* **StepperControl::NONE** - no action is taken; the stepper motor is left in the enabled state (this is the default)
* **StepperControl::DISABLE** - current to the stepper motor is disabled
* **StepperControl::BRAKE** - the stepper motor is placed in a brake state
* if this method is called while the stepper motor is already turning, the number of steps to turn will be reset to the new *nSteps* value. It is okay to change the sign of *nSteps* to reverse the direction of motor while it is turning, though this may not be desireable depending on what your motor is connected to in the real world
* calling this method with a value of *nSteps=0* causes the motor to stop, if it is already turning. If the motor is not turning, calling this method with *nSteps=0* simply enables the motor and the immediately performs the *endAction* (if specified).
* example: `myMotor.move(200,5,StepperControl::BRAKE);` starts the motor turning for 200 steps with a delay of 5ms between steps. When the motor has completed all 200 steps, it is placed in the brake state.
* `int stepsRemaining()`
* returns the number of steps remaining to turn
* may be positive or negative depending on the direction the motor is turning
* returns zero if the motor is not turning
* example: `myMotor.move(200,5); while(myMotor.stepsRemaining()!=0); myMotor.move(-300,5);` starts the motor turning, waits until it completes all 200 steps, and then turns the motor in the opposite direction for 300 steps
* `int position()`
* returns the absolute position of the stepper motor, which is defined as the cumulative sum of the all positive and negative steps turned since initial start-up
* can be called when the stepper motor is turning or when it is stopped
* example: `myMotor.move(-800,5); while(myMotor.stepsRemaining()); myMotor.move(200,5); while(myMotor.stepsRemaining()); Serial.print(myMotor.position())` would print a value of -600 after the motor finishes turning (first one direction for 800 steps, and then the other for 200 steps)
* `void setPosition(int pos)`
* resets the current position counter to *pos*
* this method does *not* turn the motor; it only resets the internal position counter as returned by `position()`
* this method is only effective when the motor is **not** turning (if called when the motor is turning the internal position counter remainms unchanged)
* example: `myMotor.move(300,5); while(myMotor.stepsRemaining()); myMotor.setPosition(-200); myMotor.move(600,5); while(myMotor.stepsRemaining()); Serial.print(myMotor.position())` would print a value of +400 after the motor finishes turning
* `void moveTo(int nPosition, uint32_t msDelay, endAction_t endAction=NONE)`
* enables the stepper motor and turns it to the position *nPosition*. Note this is a **non-blocking** function and returns immediately after being called while the stepper motor turns until it reaches *nPosition*
* *nPosition* - the position to which the stepper move should turn, where position is defined as the cumulative number of positive and negative steps the motor has turned since initial start-up, as returned by `position()`
* *msDelay* - the delay, in milliseconds, to pause between steps. Must be greater than zero. The lower the number, the faster the motor turns, subject to limitations of the motor itself
* *endAction* - an optional action to be performed *after* the motor has reached *nPosition*. Choices include:
* **StepperControl::NONE** - no action is taken; the stepper motor is left in the enabled state (this is the default)
* **StepperControl::DISABLE** - current to the stepper motor is disabled
* **StepperControl::BRAKE** - the stepper motor is placed in a brake state
* it is okay to call this method while the stepper motor is already turning; the motor will either continue turning in the same direction, or reverse direction, until it reaches the *nPosition* specified
* calls to `stepsRemaining()` after calling `moveTo()` work as expected - the value returned will be the number of steps remaining until the motor reaches the *nPosition* specified
* note that `moveTo(nPosition)` is mathematically the same as `move(nPosition-position())`, but the `moveTo()` method is more accurate since it computes the position of the motor directly inside the task that is actually controlling the motor
* `StepperControl *setAccel(float accelSize, float accelSteps)`
* adds an additional set of delays between steps so that the motor gradually accelerates when it starts and decelerates when it stops
* *accelSize* - the maximum size of the additional delay, expressed as a factor to be multiplied by the *msDelay* parameter used in `move()` and `moveTo()`. Must be a value greater or equal to 0. The larger the value, the greater the magnitude of the acceleration and deceleration. A value of zero yields no acceleration/deceleration
* *accelSteps* - the number of steps over which the *accelSize* factor exponentially decays, at which point he motor begins turning at the full speed specified by the *msDelay* parameter. Must be a value greater or equal to 1. The larger the value, the longer the acceleration and deceleration period
* the total delay between steps (when *stepsRemaining* is not zero) is given by the following formula:
$$totalDelay = msDelay \times (1 + accelSize \times (e^{\frac{-\mid nSteps-stepsRemaining \mid}{accelSteps}} + e^{\frac{-(\mid stepsRemaining \mid - 1)}{accelSteps}}))$$
* example: `myMotor.setAccel(10,20); myMotor.move(200,5);`
* yields a 55ms delay after the first step, a 52ms delay after the second step, a 50ms delay after the third step, and so forth, until at step 82 the additional delay has fully decayed such that the delay between steps remains fixed at the 5ms *msDelay* parameter specified. Then, starting at step 118 (with 82 steps remaining) the delay increases to 6ms; at step 134 it further increases to 7ms, and so forth, until the delay reaches its maxmimum of 55ms once again at step 199 just before the motor stops turning at step 200
* returns pointer to itself so methods can be daisy-chained
* example: `myMotor=(new Stepper_TB6612(23,32,22,14,33,27))->setAccel(10,20);`
* `StepperControl *setStepType(int mode)`
* sets the step type of the motor to one of the following *mode* enumerations:
* **StepperControl::FULL_STEP_ONE_PHASE** (0)
* **StepperControl::FULL_STEP_TWO_PHASE** (1)
* **StepperControl::HALF_STEP** (2)
* **StepperControl::QUARTER_STEP** (4)
* **StepperControl::EIGHTH_STEP** (8)
* *mode* can be specified using either the name of the enumeration or its integer equivalent
* returns pointer to itself so methods can be daisy-chained
* example: `myMotor=(new Stepper_TB6612(23,32,22,14,33,27))->setStepType(StepperControl::HALF_STEP);`
* smaller step types provide for smoother operation of the motor, but require more steps to turn a complete revolution
* not all *modes* are supported by all driver chips
* the quarter- and eighth-step modes require microstepping PWM functionality
* it is possible, though not recommended, to change the step type *mode* while the motor is turning
* see [Stepper Motor Modes](StepperModes.md) for a brief primer on how stepper motors are typically driven
### CPU and Task Priority
All **StepperControl** constructors support an *optional* final parameter consisting of a *brace-enclosed* pair of numbers (not shown above). The first number in the braces specifies the *priority* of the background task used to control the stepper motor. The second number in the braces specifies the CPU (0 or 1) that **StepperControl** will use to run the background control task (this number is ignored for single-processor chips). The default (and recommended) value of this optional final parameter is {1,0}. Example:
* `new Stepper_TB6612(23,32,22,14,{0,1})` instantiates control of a stepper motor driven by a TB6612 chip, where ESP32 pins 23, 32, 22, and 14 are connected to the AIN1, AIN2, BIN1, and BIN2 pins on the TB6612, respectively; the priority of the background task is set to 0; and the task will be run on cpu 1 (applicable only for a dual-processor chip)
## Example Sketches
Below is a simple sketch demonstrating the above methods:
```C++
// StepperControl Example using TB6612-based Driver Board with HALF STEP PWM MODE
#include "HomeSpan.h" // HomeSpan includes all the StepperControl classes
StepperControl *motor; // create a global pointer to StepperControl so it can be accessed in both setup() and loop()
///////////////////
void setup() {
Serial.begin(115200);
delay(1000);
Serial.printf("\nHomeSpan Stepper Control\n\n");
motor=new Stepper_TB6612(23,32,22,14,33,27); // instantiate the motor object with optional PWM pin specified (33 and 27)
motor->setStepType(StepperControl::HALF_STEP); // set the mode to HALF STEP, which means 400 steps are needed for a complete revolution of a 200-step motor
motor->setAccel(10,20); // add acceleration parameters: extra delay is 10x, decaying over 20 steps
Serial.printf("Moving motor 400 steps and waiting until motor stops...\n");
motor->move(-400,5); // move the motor -400 steps (1 revolution), with 5ms between steps.
while(motor->stepsRemaining()); // wait until there no remaining steps
Serial.printf("Moving motor to absolute position of +1200 (i.e reverse direction for 1600 steps, or 4 revolutions) without waiting...\n");
motor->moveTo(1200,2,StepperControl::BRAKE); // move the motor to an absolute position of 1200 steps with 2ms between steps; enter brake state when done
// Motor will continue moving in background even once setup() exits and loop() below starts
}
///////////////////
void loop(){
Serial.printf("Motor has %d remaining steps\n",motor->stepsRemaining());
delay(1000); // motor is unaffected by delay()
if(motor->position()==1200){
Serial.printf("Motor has reached final position and is now stopped.\n");
while(1);
}
}
```
### Motorized Window Shade Example
A fully worked example showing how to use the *StepperControl* class within a complete HomeSpan sketch to control a Motorize Window Shade using both the TB6612 and A3967 driver boards can be found in the Arduino IDE under [*File → Examples → HomeSpan → Other Examples → MotorizedWindowShade*](../examples/Other%20Examples/MotorizedWindowShade).
## Creating your own **StepperControl** Driver
If none of the above motor driver classes works for your specific chip or driver board, it is relatively straightfoward to create a new driver to use in your sketch. This is because all the logic to operate a stepper motor in the background is already embedded in the abstract **StepperControl** class. To create your own driver, start by creating a child class derived from **StepperControl**. Next, add a constructor that defines the pins and performs any initializations if needed. Finally, define the following methods that **StepperControl** calls to operate the motor:
* `void onStep(boolean direction)` - contains the logic to advance the motor by a single step based on the *direction* parameter
* `void onEnable()` - contains the logic that enables the motor driver
* `void onDisable()` - contains the logic that disables the motor driver
* `void onBrake()` - contains the logic that puts the motor into a short brake state
* `StepperControl *setStepType(int mode)` - contains the logic to set the step type mode based on the *mode* parameter
Only the first method, `onStep()`, is required to be defined. You can leave any of the other methods undefined if they are not applicable for your specific driver board. You can of course create additional methods to reflect any other features your driver board may support.
As an example, below is the complete code for the **Stepper_A3967** class:
```C++
#include "HomeSpan.h"
//////////////////////////
struct Stepper_A3967 : StepperControl {
int m1Pin;
int m2Pin;
int stepPin;
int dirPin;
int enablePin;
//////////////////////////
Stepper_A3967(int m1Pin, int m2Pin, int stepPin, int dirPin, int enablePin, std::pair<uint32_t, uint32_t> taskParams = {1,0}) : StepperControl(taskParams.first,taskParams.second){
this->m1Pin=m1Pin;
this->m2Pin=m2Pin;
this->stepPin=stepPin;
this->dirPin=dirPin;
this->enablePin=enablePin;
pinMode(m1Pin,OUTPUT);
pinMode(m2Pin,OUTPUT);
pinMode(stepPin,OUTPUT);
pinMode(dirPin,OUTPUT);
pinMode(enablePin,OUTPUT);
setStepType(FULL_STEP_TWO_PHASE);
}
//////////////////////////
void onStep(boolean direction) override {
digitalWrite(dirPin,direction);
digitalWrite(stepPin,HIGH);
digitalWrite(stepPin,LOW);
}
//////////////////////////
void onEnable() override {
digitalWrite(enablePin,0);
}
//////////////////////////
void onDisable() override {
digitalWrite(enablePin,1);
}
//////////////////////////
StepperControl *setStepType(int mode) override {
switch(mode){
case FULL_STEP_TWO_PHASE:
digitalWrite(m1Pin,LOW);
digitalWrite(m2Pin,LOW);
break;
case HALF_STEP:
digitalWrite(m1Pin,HIGH);
digitalWrite(m2Pin,LOW);
break;
case QUARTER_STEP:
digitalWrite(m1Pin,LOW);
digitalWrite(m2Pin,HIGH);
break;
case EIGHTH_STEP:
digitalWrite(m1Pin,HIGH);
digitalWrite(m2Pin,HIGH);
break;
default:
ESP_LOGE(STEPPER_TAG,"Unknown StepType=%d",mode);
}
return(this);
}
};
```
---
[↩️](../README.md) Back to the Welcome page

View File

@ -1,33 +0,0 @@
# Stepper_A3967
This is a derived class of **StepperControl** designed to operate stepper motors driven by an [Allegro A3967](https://cdn.sparkfun.com/datasheets/Robotics/A3967-Datasheet.pdf) (or equivalent) chip.
The Allegro A3967 is a specialized driver designed for stepper motors. It contains a built-in PWM generator and pre-programmed stepping modes. Wiring for the [Sparkfun EasyDriver Stepper Motor Board](https://learn.sparkfun.com/tutorials/easy-driver-hook-up-guide?_ga=2.152816825.1841726212.1688220137-156607829.1686369274) that uses this chip is as follows:
#### **Power Connections**
* *GND* - connect to GND on the ESP32, and to ground of external DC power supply
* *M+* - connect to external DC power supply that will drive stepper motor. An on-board regulator also uses this supply to provide VCC to the rest of the board. For use with an ESP32, you must short the 3.3V/5V jumper with a blob of solder to select 3.3V
#### **Motor Connections**
* *Motor A* - connect to the "A" coil of the stepper motor
* *Motor B* - connect to the "B" coil of the stepper motor
#### **Control Connections**
* *MS1, MS2* - connect to two digital pins on the ESP32 - used to set the step type mode
* *STEP, DIR* - connect to two digital pins on the ESP32 - used to step the motor and set the direction
* *ENABLE* - connect to a digital pin on the ESP32 - used to enable/disable to motor driver
* *SLEEP, RESET* - already pulled high on the EasyDriver board, so no connection neeed. If using a different driver board, ensure these pins are pulled high, else connect to VCC
* *PFD* - not used
The **Stepper_A3967** class includes the following constructor:
* `Stepper_A3967(int MS1, int MS2, int STEP, int DIR, int ENABLE)`
* controls the driver board using 5 digital pins from the ESP32, where the parameters specify the pin numbers. Supports the following step type modes:
* FULL_STEP_TWO_PHASE
* HALF_STEP
* QUARTER_STEP
* EIGHTH_STEP
❗Note: The A3967 chip does not support a short brake state. Calls to the `brake()` method, as well as setting the *endAction* parameter in the `move()` and `moveTo()` methods to **StepperControl::BRAKE** have no effect on the motor driver.
---
[↩️](../Stepper.md) Back to the Stepper Motor Control page

View File

@ -1,39 +0,0 @@
# Stepper_TB6612
This is a derived class of **StepperControl** designed to operate stepper motors driven by a [Toshiba TB6612](https://cdn-shop.adafruit.com/datasheets/TB6612FNG_datasheet_en_20121101.pdf) (or equivalent) chip, either with or without the use of ESP32 PWM pins.
The Toshiba TB6612 is a generic motor driver providing direct control of two full H-bridges. Wiring for the [Adafruit TB6612 1.2A DC/Stepper Motor Driver Breakout Board](https://learn.adafruit.com/adafruit-tb6612-h-bridge-dc-stepper-motor-driver-breakout) that uses this chip is as follows:
#### **Power Connections**
* *VCC* - connect to +3.3V on ESP32
* *VM+* - connect to external DC power supply that will drive stepper motor (5-13V)
* *GND* - connect to GND on the ESP32, and to ground of external DC power supply
#### **Motor Connections**
* *MOTORA* - connect to the "A" coil of the stepper motor
* *MOTORB* - connect to the "B" coil of the stepper motor
#### **Control Connections**
* *AIN1, AIN2* - connect to two digital pins on the ESP32 - used to control direction and state of coil *A*
* *BIN1, BIN2* - connect to two digital pins on the ESP32 - used to control direction and state of coil *B*
* *PWMA, PWMB* - if using PWM, connect to two digital pins on the ESP32; if not, connect to +3.3V on ESP32 to pull high
* *STBY* - connect to +3.3V on ESP32 to pull high
The **Stepper_TB6612** class includes two constructors:
* `Stepper_TB6612(int AIN1, int AIN2, int BIN1, int BIN2)`
* controls the driver board using only 4 digital pins from the ESP32, where the parameters specify the pin numbers. Supports the following step type modes:
* FULL_STEP_ONE_PHASE
* FULL_STEP_TWO_PHASE
* HALF_STEP
* `Stepper_TB6612(int AIN1, int AIN2, int BIN1, int BIN2, int PWMA, int PWMB)`
* controls the driver board using 4 digital pins and 2 PWM pins from the ESP32, where the parameters specify the pin numbers. Supports the following step type modes:
* FULL_STEP_ONE_PHASE
* FULL_STEP_TWO_PHASE
* HALF_STEP
* QUARTER_STEP
* EIGHTH_STEP
---
[↩️](../Stepper.md) Back to the Stepper Motor Control page

View File

@ -1,37 +0,0 @@
# Stepper_ULN2003A
This is a derived class of **StepperControl** designed to operate stepper motors driven by a [Texas Instruments ULN2003A](https://www.ti.com/lit/ds/symlink/uln2003a.pdf) (or equivalent) chip.
The Texas Instruments ULN2003A chip containins an array of seven Darlington transistor pairs each capable of converting low-voltage/low-current digital signals into higher-voltage/higher-current outputs suitable for driving a stepper motor.[^1]
[^1]: Only four of the seven Darlington transistor pairs in the ULN2003A are needed to drive a typical unipolar stepper motor.
Various manufacturers have incorporated this chip into a dedicated stepper motor board designed to drive unipolar motors such the [28BYJ&#8209;48 5&#8209;Volt Stepper](https://opencircuit.shop/product/28byj-48-5v-stepper-motor-4-phase-5-wire) and [28BYJ&#8209;48 12&#8209;Volt Stepper](https://opencircuit.shop/product/28byj-48-12v-stepper-motor-4-phase-5-wire). Wiring for the [Opencircuit ULN2003 Stepper Motor Driver Board](https://opencircuit.shop/product/uln2003-stepper-motor-driver-module) that uses this chip is as follows:
#### **Power Connections**[^2]
* - connect to an external DC power supply that will drive stepper motor (5-12V)
* - connect to GND on the ESP32, and to ground of external DC power supply
#### **Motor Connections**
* plug the motor directly into the board's 5-pin connector
#### **Control Connections**
* *IN1, IN2, IN3, IN4* - connect to four digital pins on the ESP32 - used to step the motor in either direction
The **Stepper_ULN2003A** class includes the following constructors:
* `Stepper_ULN2003A(int IN1, int IN2, int IN3, int IN4)`
* controls the driver board using only 4 digital pins from the ESP32, where the parameters specify the pin numbers. Supports the following step type modes:
* FULL_STEP_ONE_PHASE
* FULL_STEP_TWO_PHASE
* HALF_STEP
❗Note: The ULN2003A chip does not support a short brake state. Calls to the `brake()` method, as well as setting the *endAction* parameter in the `move()` and `moveTo()` methods to **StepperControl::BRAKE** have no effect on the motor driver.<br><br>
> [!TIP]
> If you set the motor to move very slowly (e.g. 500ms per step) you will be able to track how the current is turned on and off for each phase of the motor coils depending on the *step mode* selected by observing the four LEDs (labeled *A, B, C, D*) built into the driver board. See also this [Last Minute Engineers Tutorial](https://lastminuteengineers.com/28byj48-stepper-motor-arduino-tutorial) for a detailed presentation of the ULN2003A and its use to drive a 28BYJ&#8209;48 5&#8209;Volt Stepper Motor.
[^2]: the ULN2003A is a passive chip - there is no VCC power connection between the driver board and the ESP32
---
[↩️](../Stepper.md) Back to the Stepper Motor Control page

View File

@ -1,73 +0,0 @@
# Stepper_UNIPOLAR
This class provides a generic driver for use with any center-tapped unipolar stepper motor. Requires the use of a driver board that can convert the low-voltage/low-current digital signals from 4 pins on the ESP32 to higher-voltage/higher-current outputs suitable for direct connection to the two phases (*A* and *B*) of each coil (*1* and *2*) in the stepper motor.
The **Stepper_UNIPOLAR** class includes the following constructor:
* `Stepper_UNIPOLAR(int coil1A, int coil1B, int coil2A, int coil2B)`
* controls the driver board using 4 digital pins from the ESP32, where the parameters specify the pin numbers
* the driver circuit should be connected and configured such that when any of ESP32 pins specified above are set to are HIGH, current flows through the corresponding coil/phase. Similarly, when a pin is set LOW, the driver circuit should stop the flow of current through the corresponding coil/phase.
* supported modes are as follows:
* FULL_STEP_ONE_PHASE
* FULL_STEP_TWO_PHASE
* HALF_STEP
❗Note: This class does not support a short brake state. Calls to the `brake()` method, as well as setting the *endAction* parameter in the `move()` and `moveTo()` methods to **StepperControl::BRAKE** have no effect on the motor driver.<br><br>
> [!WARNING]
> **Note the order of the constructor parameters!** The first two parameters specify the ESP32 pins that control the current flowing through phases *A* and *B* of ***Coil 1***; the second two parameters are for phases *A* and *B* of ***Coil 2***.
It does *not* matter which coil is defined as *1* or *2*, nor which side is called *A* or *B*, as long as the first two parameters are for one of the coils and the second two are for the other coil. You'll know if you mis-specified the order of the pins because the motor will vibrate back and forth instead of turning clockwise or counterclockwise.
## Technical Details
The patterns by which this class sets the specified pins HIGH and LOW depend on the *step mode* chosen as follows:
#### FULL_STEP_ONE_PHASE
* 4-step cycles where in each step current flows only through **one** phase of **one** of the coils
<table>
<tr><th></th><th colspan="2">Phase A</th><th colspan="2">Phase B</th></tr>
<tr><th></th><th>Coil 1</th><th>Coil 2</th><th>Coil 1</th><th>Coil 2</th></th></tr>
<tr><th>Step 1</th><td align="center">HIGH</td><td align="center">-</td><td align="center">-</td><td align="center">-</td></td></tr>
<tr><th>Step 2</th><td align="center">-</td><td align="center">HIGH</td><td align="center">-</td><td align="center">-</td></td></tr>
<tr><th>Step 3</th><td align="center">-</td><td align="center">-</td><td align="center">HIGH</td><td align="center">-</td></td></tr>
<tr><th>Step 4</th><td align="center">-</td><td align="center">-</td><td align="center">-</td><td align="center">HIGH</td></td></tr>
</table>
<br>
#### FULL_STEP_TWO_PHASE
* 4-step cycles where in each step current flows through **one** phase of **each** of the coils
<table>
<tr><th></th><th colspan="2">Phase A</th><th colspan="2">Phase B</th></tr>
<tr><th></th><th>Coil 1</th><th>Coil 2</th><th>Coil 1</th><th>Coil 2</th></th></tr>
<tr><th>Step 1</th><td align="center">HIGH</td><td align="center">HIGH</td><td align="center">-</td><td align="center">-</td></td></tr>
<tr><th>Step 2</th><td align="center">-</td><td align="center">HIGH</td><td align="center">HIGH</td><td align="center">-</td></td></tr>
<tr><th>Step 3</th><td align="center">-</td><td align="center">-</td><td align="center">HIGH</td><td align="center">HIGH</td></td></tr>
<tr><th>Step 4</th><td align="center">HIGH</td><td align="center">-</td><td align="center">-</td><td align="center">HIGH</td></td></tr>
</table>
<br>
#### HALF_STEP
* 8-step cycles formed by interleaving the 4 steps of the FULL_STEP_ONE_PHASE mode with the 4 steps of the FULL_STEP_TWO_PHASE mode
<table>
<tr><th></th><th colspan="2">Phase A</th><th colspan="2">Phase B</th></tr>
<tr><th></th><th>Coil 1</th><th>Coil 2</th><th>Coil 1</th><th>Coil 2</th></th></tr>
<tr><th>Step 1</th><td align="center">HIGH</td><td align="center">-</td><td align="center">-</td><td align="center">-</td></td></tr>
<tr><th>Step 2</th><td align="center">HIGH</td><td align="center">HIGH</td><td align="center">-</td><td align="center">-</td></td></tr>
<tr><th>Step 3</th><td align="center">-</td><td align="center">HIGH</td><td align="center">-</td><td align="center">-</td></td></tr>
<tr><th>Step 4</th><td align="center">-</td><td align="center">HIGH</td><td align="center">HIGH</td><td align="center">-</td></td></tr>
<tr><th>Step 5</th><td align="center">-</td><td align="center">-</td><td align="center">HIGH</td><td align="center">-</td></td></tr>
<tr><th>Step 6</th><td align="center">-</td><td align="center">-</td><td align="center">HIGH</td><td align="center">HIGH</td></td></tr>
<tr><th>Step 7</th><td align="center">-</td><td align="center">-</td><td align="center">-</td><td align="center">HIGH</td></td></tr>
<tr><th>Step 8</th><td align="center">HIGH</td><td align="center">-</td><td align="center">-</td><td align="center">HIGH</td></td></tr>
</table>
---
[↩️](../Stepper.md) Back to the Stepper Motor Control page

View File

@ -1,55 +0,0 @@
# Stepper Motor Modes
A typical stepper motor has two sets of coils, *A* and *B*. Applying a current to one or both coils creates a magnetic field that fixes the motor's position. Any changes to the direction and/or magnitude of the current flowing through the coils causes the motor to "step" to a new fixed position. By repeatedly changing the current flow according to a specific pattern the motor can be made to continuously step clockwise or counterclockwise. The specific pattern chosen, known as the *step mode* in the **StepperControl** class, determines the size of the step and overall performance of the motor. The speed at which changes to the current flow are made determines the speed at which the motor rotates. The modes supported by **StepperControl** are described below.
## FULL STEP TWO PHASE MODE (without PWM)
In this mode, a constant current is applied to both coils *A* and *B*. The motor is stepped by alternatively flipping the direction of the current flow in each coil as follows:
<img width="250" alt="image" src="https://github.com/HomeSpan/HomeSpan/assets/68477936/8bea7031-7325-4ded-8ebd-5554d8f1e13d"><br>
Note that though the pattern repeats after four steps, it is not symmetrical - running the pattern from left to right causes the motor to rotate in one direction, whereas running the pattern from right to left will cause it to rotate in the opposite direction. Many stepper motors are constructed to have 200 full steps, which means you need to repeat the above pattern 25 times to cause the motor to complete a single revolution.
Since in this mode each coil has only two possible states (i.e. the direction of the current flow), only one digital signal per coil is required to implement the stepping pattern. However, fully flipping the direction of the current flow in a coil changes the magnetic fields very rapidly, which creates a rather clunky motion when stepping the motor.
## FULL STEP ONE PHASE MODE (without PWM)
In this mode, a constant current is alternatively applied to either *A* **or** *B*, while also flipping its direction as follows:
<img width="250" alt="image" src="https://github.com/HomeSpan/HomeSpan/assets/68477936/cbf2fea5-072e-4fef-9231-504bb483b0c0"><br>
This mode uses only half the power as the FULL STEP TWO PHASE mode since current only flows through one coil at a time. Also, though the step size is the same, the transtition from one step to another is not as harsh since the current to each coil transtions from one direction to zero before flipping to the other direction. However, since in this mode each coil has three possible states for current flow (positive, negative, and off), two digital signals per coil are required to implement the stepping pattern.
## HALF STEP MODE (without PWM)
Though the the step sizes for the two modes above are the same, the set of positions they represent as the motor rotates are out of sync by 1/2 step. This means that by interleaving the four steps of the two modes together, we can create the following 8-step pattern where the step size is *half* that of a full step:
<img width="400" alt="image" src="https://github.com/HomeSpan/HomeSpan/assets/68477936/ec317c77-fbd9-4641-9d50-d822b477c9ec"><br>
The advantage of this mode is that the motor moves much more smoothly, and can be stopped in half-step increments for more precise control. However, it takes twice as many steps for the motor to complete a full rotation (and thus moves twice as slow given the same delay between steps).
One other disadvantage of this mode is that in four of the steps both coils are powered, whereas in the other four only one coil is powered. This results in uneven motor torque as the motor rotates, as well as different holding torques depending on which step the motor lies when stopped. Depending on your specific application (raising a shade, closing a door, etc.) this may or may not be of concern.
## QUARTER STEP and EIGHTH STEP MODES (including PWM versions of the above modes)
In the above modes, current to each coil is either "fully on" in one direction, fully on in the other direction, or completely off. As a result, there is a trade-off between step granularity and smoothness of rotation versus power and torque consistency. The solution to this problem is to drive the current in each coil with a PWM signal (or an equivalent limiting mechanism) that allows the *magnitude* of the current flow to be controlled in addition to its direction.
Typically this is done by setting the magnitude of the current in each coil based on sinusoidal patterns offset by 90° as follows:
<img width="505" alt="image" src="https://github.com/HomeSpan/HomeSpan/assets/68477936/75a6176b-b5b4-4b85-a394-a4d6e1f9bf3d"><br>
Here, the blue dots perfectly replicate the steps of the FULL STEP ONE PHASE mode (e.g. positive/off/negative/off for coil *A*), the red dots replicate the steps of the FULL STEP TWO PHASE MODE, and the combined set of both the red blue and red dots taken together replicate the steps of the HALF STEP mode. Each non-PWM mode above can therefore be replicated using a single set of PWM-based sinusoidal curves, but with one important difference: the PWM method yields the same power for every step in every mode, avoiding the problems of inconsistent torque described above for the non-PWM modes. This is because power is typically proportional to the *square* of the magnitude of the current running through a coil; coil *A* follows a cosine curve; coil *B* follows a sine curve; and and $cos^2+sin^2=1$, which is a constant.
It is generally preferable to use the PWM method when running a motor in any of the three modes above, rather than running with just a constant current applied to each coil, provided that PWM signals are available. Fortunately, many stepper motor driver boards contain a built-in PWM module. And if they don't, you can have **StepperControl** generate the required PWM signals using the ESP32's built-in PWM peripheral.
In addition to providing better motor performance, the PWM method also provides for even more granular stepping modes by adding "microsteps" to the step pattern. For example, adding 8 additional steps between the half-steps, as indicated on the curves by the 8 black diamonds placed between the blue and red dots, yields a QUARTER STEP mode. This mode is twice as "smooth" as the HALF STEP mode, but requires twice as many steps for the motor to complete a full rotation. Halving the steps even further (not shown on the diagram) yields the EIGHTH STEP mode where each cycle contains 32 individual stepping points. **StepperControl** supports both the QUARTER STEP and EIGHTH STEP modes, though you can easily extend this further by adding modes with 64 or 128 steps per cycle. For motors designed to operate a window shade, such granularity is generally not needed, and too fine a granularity can cause misteps depending on the specific characteristics of the motor.
---
[↩️](Stepper.md) Back to the Stepper Motor Control page

View File

@ -1,313 +0,0 @@
# TLV8 Characteristics
Most HomeKit Characteristics store a single numerical value or simple string. However, HomeKit supports two additional storage formats - a simple list of bytes (the **DATA** format) and a structured list of tags and values (the **TLV8** format). The DATA format is not used by any Apple-defined Characterstics but it is included in HomeSpan for use when creating Custom Characteristics for non-Apple applications.
In contrast, the TLV8 format is used extensively by HomeKit during the initial pairing process as well as whenever a new secure (verified) connection is established between HomeKit and a HomeSpan device. There are also a variety of Apple-defined Characteristics that use the TLV8 format to store and transmit multiple sets of values, each represented as byte-arrays of arbitrary length.
## Overview of TLV8 Format
The TLV8 format itself is quite simple. A TLV8 object comprises one or more TLV8 *records*, where the first byte in a record represents an identifying TAG (from 0-255), the second byte represents the LENGTH of the value, and the remaining LENGTH-bytes represent the VALUE itself, which is always in the form of a *byte-array* (i.e. an array of 0 or more *uint8_t* elements). Notable points about the TLV8 format are as follows:
* since the LENGTH is stored only as a single byte, VALUES requiring more than 255 bytes must be represented as sequential TLV8 records *with the same TAG*
* it is fine (and in fact common) for a TLV8 object to include multiple records with the same TAG, except that they must be *separated by a record with a different TAG*, otherwise the parser reading the data will concatenate the VALUES from sequential records having the same TAG into a single record (as per above)
* records representing a zero-LENGTH VALUE are allowed, and consist of only two bytes: a TAG and a zero (indicating a zero-length VALUE). TAGS with a zero-LENGTH VALUE are often used to separate multiple records having the same TAG
* if the VALUE's byte-array is supposed to represent an single, unsigned integer, it should be arranged in little endian format (i.e. least-significant byte first) and padded with trailing zeros as needed to bring the total LENGTH of the VALUE to either 1, 2, 4, or 8 bytes (representing uint8_t, uint16_t, uint32_t, and uint64_t values)
* if the VALUE's byte-array is supposed to represent a string, it should not include the terminating null since the LENGTH tells you have many characters are in the string
* the bytes that make up a VALUE can themselves represent a separate, complete TLV8 object. There is no limit on the number of "sub-TLVs" that can be recursively nested in a "parent" TLV8 object
* a parser reading TLV8 records should silently ignore any TAG it is not expecting. It may be an error to omit a TAG that the parser requires, but it will be not an error to include a TAG it does not recognize
* it is **not** possible to unambigously determine whether the VALUE byte-array in a TLV8 record is supposed to represent an unsigned integer, a string, an arbitrary series of bytes, a sub-TLV object, or something else entirely. The only identifying information for any given TLV8 record is its TAG number, which ranges from 0-255. There is no general schema or TLV8 protocol that maps TAG types to VALUE types. Rather, the TAG numbers are arbitrary and users must consult the documentation for each Characteristic to learn what TAG numbers are expected, and what their VALUEs are supposed to represent for that specific Characteristic
* since HomeKit data transmissions are often represented in JSON, and JSON is a text-only format, HomeKit requires that TLV8 records are first encoded in base-64 when transmitting JSON to and from Controllers to Accessories.
Fortunately, HomeSpan includes a dedicated TLV8 library (see below) that automatically takes care of many of the above points, which enables you to read, create, and process TLV8 data without worrying about parsing TLV8 records with more than 255 bytes, converting numerical values to little-endian, or encoding/decoding records into base-64.
## *TLV8()*
Creating an instance of HomeSpan's TLV8 **class** using the above constructor builds an empty TLV8 object into which you can add and process TLV8 records. TLV8 objects are instantiated as standard C++ linked-list containers derived from `std::list<tlv8_t>`, where *tlv8_t* is an opaque structure used to store individual TLV8 records.[^opaque]
Also, as shown below, many of the TLV8 methods utilize linked-list *constant* iterators. These are represented by the typedef *TLV8_itc*.[^iterators]
[^opaque]:The *tlv8_t* structure is opaque because in general you will not have to create or interact directly with the structure or its data. Note that in addition to the above TLV8-specific methods, you can use any `std::list` method with a TLV8 object if desired.
[^iterators]:You do not need expert knowledge of C++ containers and iterators in order to use the TLV8 library, but a basic understanding of containers and iterators will make the library much easier to learn and enjoyable to use.
The method for adding a generic TLV8 record to a TLV8 object is as follows:
* `TLV8_itc add(uint8_t tag, size_t len, const uint8_t *val)`
* where *tag* is the TAG identifier for the record to add and *val* is a pointer to a byte-array containing *len* elements
* example: `TLV8 myTLV; uint8_t v[]={0x01, 0x05, 0xE3, 0x4C}; tlv.add(1, sizeof(v), v);
* setting *val* to NULL reserves *len* bytes of space for the TLV8 record within the TLV8 object, but does not copy any data
* this method returns a TLV8 constant iterator to the resulting TLV8 record so you can reference the record at a later time if needed
In addition to the above generic method suitable for any type of data, the following methods make it easier to add TLV8 records with specific, frequently-used types of data:
* `TLV8_itc add(uint8_t tag, uintXX_t val)`
* adds a TLV8 record containing a single, unsigned numeric value, *val* (i.e. uint8_t, uint16_t, uint32_t, or uint64_t)
* `TLV8_itc add(uint8_t tag, const char *val)`
* adds a TLV8 record containing all the non-null bytes of a null-terminated character string, *val*
* `TLV8_itc add(uint8_t tag, TLV8 &subTLV)`
* adds a TLV8 record containing all the bytes of an entire TLV8 object, *subTLV*
* `TLV8_itc add(uint8_t tag)`
* adds a zero-length TLV8 record containing nothing but a TAG identifer
Note that if you *add* consecutive records with the same TAG identifier, the TLV8 library will concatenate their data and combine into a single record. For example, `myTLV.add(1,13); myTLV.add(1,300)` will be combined to produce a single 3-byte recording containing the data 0x0D2C01, where the first byte represents from the number 13 and the second two bytes represent the number 300. This may have been your desired outcome, but likely not what you wanted to happen.
Instead, to create two distinct records with the same tag value, simply interpose a zero-length record with a different TAG identifier between the two as a "separator" like this: `myTLV.add(1,13); myTLV.add(255); myTLV.add(1,300);` Here we used a TAG identifer of 255 to represent the separator, but that choice is arbitrary, unless that TAG happens to be used by the Characteristic for something else (TAG identifiers of 0 or 255 are commonly used as separators).
The method for finding a TLV8 record within a TLV8 object that contains a specific TAG identifer is as follows:
* `TLV8_itc find(uint8_t tag)`
* where *tag* is the TAG identifier you are seeking
* returns a TLV8 constant iterator to *first* record that matches; returns *end()* if no records match
To restrict the search range to a limited set of records, add optional starting and ending iterators *it1* and *it2*:
* `TLV8_itc find(uint8_t tag [, TLV8_itc it1 [, TLV8_itc it2]])`
* returns a TLV8 constant iterator to the *first* record within the range of constant iterators from *it1* to *it2* that matches the specified *tag*
* search range is inclusive of *it1* but exclusive of *it2*
* returns *it2* if no records match
* if *it2* is unspecified, default is *end()*; if *it1* is unspecified, default is *begin()*
* note `myTLV.find(tag)` is equivalent to `myTLV.find(tag, myTLV.begin(), myTLV.end())`
Use of the C++ `auto` keyword is generally the best way to save the TVL8_itc iterator that is returned from the `find()` and `add()` methods. For example, `auto myIT = myTLV.find(6)` sets *myIT* to a constant iterator pointing to the first TLV8 record in *myTLV* that has a TAG identifer of 6.
The method for finding the LENGTH of the data VALUE stored in a particular TLV8 record is as follows:
* `int len(TLV8_itc it)`
* where *it* is an constant iterator pointing to a specific TLV8 record
* returns the length of the data VALUE stored in the associated record, which may be zero for a zero-LENGTH record
* returns -1 if *it* points to the *end()* of the TLV8 object
A typical use of the `len()` method is to simultaneously check whether a TLV8 object contains a particular TAG identifier, and that the LENGTH of the TAG matches an expected value. For example, if a certain Characteristic requires a TLV8 record with a TAG identifer of 6 to contain a 32-byte registration number, you can perform the following check:
```C++
auto myIT = myTLV.find(6);
if(myTLV.len(myIT)!=32)
Serial.printf("Error: TAG 6 is either missing or of improper length\n");
else
Serial.printf("TAG 6 containing 32 bytes of data has been found\n");
```
The method for printing all of the records in a TLV8 object to the Serial Monitor is as follows:
* `void print()`
* prints all TLV8 records, one per line, to the Serial Monitor
* format of the output is: TAG(LENGTH) VALUE [NUMERIC], where
* TAG = the TAG identifer (0-255)
* LENGTH = length of the VALUE byte-array (may be zero)
* VALUE = a sequential list, in hexadecimal, of all the bytes in the VALUE byte-array (only displayed if LENGTH>0)
* NUMERIC = an unsigned-integer interpretation of the bytes in VALUE, assuming little-endian ordering
* this decimal value is only displayed if LENGTH<=8
* if LENGTH=0, the word "null" is displayed instead
To restrict the the printing range to a limited set of records, add optional starting and ending constant iterators *it1* and *it2*:
* `void print(TLV8_itc it1 [, TLV8_itc it2])`
* prints all TLV8 records between constant iterators *it1* and *it2*
* print range is inclusive of *it1* but exclusive of *it2*
* if *it2* is unspecified, prints only the record pointed to by *it1*
* note `myTLV.print()` is equivalent to `myTLV.print(myTLV.begin(), myTLV.end())`
The output generated by `print()` can contain some very long lines, especially if the VALUE of some of the TLV8 records represents other complete TLV8 objects (known as sub-TLVs or "nested" TLVs). To recursively print all sub-TLV objects, use the following method:
* `void printAll()`
* recursively prints all TLV8 records, one per line, to the Serial Monitor
* inspects each TLV8 record and tries to parse as if the record represented a sub-TLV object
* if parsing is successful, prints the record and then calls `printAll()` on the sub-TLV
* if not, prints the record and ends this branch of the recursion
* the format of each line is the same as that of `print()` except that TAG displays the full path of all TAGs through the branch
* note that the output can be very voluminous if your TLV8 object contains many levels of nested sub-TLVs
* warning: some care is required when interpretating the output[^subTLVs]
[^subTLVs]:The `printAll()` method assumes that any VALUE that is consistent with the format of a sub-TLV must be a sub-TLV, even if its just a simple numeric value. For example, `add(10,65536)` yields a record with a TAG identifer of 10 and a 4-byte VALUE of 0x00000100. The `printAll()` method will display this record along with NUMERIC=65536, but it will also then interpret (and thus display) this VALUE as a sub-TLV containing one zero-length record with TAG identifier=0 and another zero-length record with TAG identifer=1, since the VALUE can be successfully parsed as such.
TLV8 objects manage all of their internal memory requirements, and free up all resources and memory when they go out of scope or are otherwise deleted. However, if you need to "erase" all the contents of a TLV8 object but stil retain the object so you can re-fill with new data, use the following method:
* `void wipe()`
* erases all TLV8 records and frees all associated memory
* leaves an empty TLV8 object ready for re-use
## *TLV8_itc()*
Objects of type *TLV8_itc* are constant iterators that point to specific *tlv8_t* records in a TLV8 object (or to *end()*). TLV8 iterators are used to access, read from, and write to, the data elements in any given TLV8 record, and are thus a critical part of the TLV8 library. However, if you are using the TLV8 library correctly you should rarely, if ever, need to directly instantiate a *TLV8_itc* using its constructor. Instead, simply use the C++ `auto` keyword as noted above.
TLV8_itc iterators can be dereferenced to work with data in an individual TLV8 record using the follow methods:
* `uint8_t getTag()`
* returns the TAG identifier (0-255) of the TLV8 record
* example: `uint8_t tag = myIT->getTag()` or, equivalently, `uint8_t tag = (*myIT).getTag()`
* `size_t getLen()`
* returns the LENGTH of the VALUE byte-array of the TLV8 record
* example: `size_t len = myIT->getLen()` or, equivalently, `size_t len = (*myIT).getLen()`
* `uint8_t *get()`
* returns `uint8_t *` pointing to the first element of the VALUE byte-array of the TLV8 record
* for zero-LENGTH TLV8 records, the return value is NULL
* example: `uint8_t *v = myIT->get();` or, equivalently, `uint8_t *v = (*myIT).get();`
* the `(uint8_t *)` casting operator has been overloaded so you can also obtain this same `uint8_t *` pointer by simply dereferencing the iterator
* example: `auto myIT = myTLV.find(6); uint8_t *v = *myIT;`
* note this only works if the compiler can determine the need to auto-cast into a `uint8_t *` pointer based on the context of the code
* `uint8_t get()[i]`
* returns the *i<sup>th</sup>* element of the VALUE byte-array
* example: `uint8_t n = myIT->get()[i]` or, equivalently, `uint8_t n = (*myIT).get()[i]`
* the subscript operator has also been overloaded so you can obtain the *i<sup>th</sup>* element by simply dereferencing the iterator
* example: `uint8_t n = (*myIT)[i]`
* note there is no range-checking so make sure *i* does not try to reference an element beyond the end of the VALUE byte-array
* `T getVal<class T>()`
* this template function returns a single numeric value of type *T* on the assumption that the VALUE byte-array is storing an unsigned integer in little endian format
* *T* can be *uint8_t*, *uint16_t*, *uint32_t*, or *uint64_t* (if unspecified *T* defaults to *uint32_t*)
* example: `auto myIT = myTLV.add(50,60000); uint16_t n = myIT->getVal<uint16_t>();`
* this method returns the correct numeric value as long as sizeof(*T*) >= LENGTH of the byte-array. For example:
* setting *T=uint64_t* with a VALUE byte-array containing 2 bytes returns the *correct* numeric value
* setting *T=uint16_t* with a VALUE byte-array containing 4 bytes return an *incorrect* numeric value
* this function returns zero for all zero-LENGTH TLV8 records
### A detailed example using the above methods
The following code:
```C++
TLV8 myTLV; // instantiates an empty TLV8 object
myTLV.add(1,8700); // add a TLV8 record with TAG=1 and VALUE=8700
auto it_A = myTLV.add(2,180); // add a TLV8 record with TAG=2 and VALUE=180, and save the iterator that is returned
uint8_t v[32]; // create a 32-byte array, v, and fill it with some data
for(int i=0;i<32;i++)
v[i]=i;
myTLV.add(200,32,v); // add a TLV8 record with TAG=200 and copy all 32 bytes of array v into its VALUE
myTLV.add(50,60000); // add a TLV8 record with TAG=50 and VALUE=60000
myTLV.add(255); // add a zero-length TLV8 record with TAG=255 to act as separator
myTLV.add(50,120000); // add a TLV8 record with TAG=50 and VALUE=120000
myTLV.add(255); // add a zero-length TLV8 record with TAG=255 to act as separator
myTLV.add(50,180000); // add a TLV8 record with TAG=50 and VALUE=180000
myTLV.add(255); // add a zero-length TLV8 record with TAG=255 to act as separator
auto it_B = myTLV.add(50,240000); // add a TLV8 record with TAG=50 and VALUE=240000, and save the iterator that is returned
auto it_C = myTLV.find(50); // find an iterator to the first TLV8 record with TAG=50;
auto it_D = myTLV.find(50,std::next(it_C)); // find an iterator to the first TLV8 record with TAG=50 that occurs AFTER it_C;
auto it_E = myTLV.find(200); // find an iterator to first TLV8 record with TAG=200;
Serial.printf("results of myTLV.print():\n\n");
myTLV.print(); // print the contents of myTLV to the Serial Monitor
Serial.printf("\n");
// print content of it_A:
Serial.printf("it_A: TAG=%d, LENGTH=%d, Value=%d\n", it_A->getTag(), it_A->getLen(), it_A->getVal());
// print content of it_B using alternative syntax:
Serial.printf("it_B: TAG=%d, LENGTH=%d, Value=%d\n", (*it_B).getTag(), (*it_B).getLen(), (*it_B).getVal());
// print contents of it_C and it_D, based on previous find() above:
Serial.printf("it_C TAG=%d, LENGTH=%d, Value=%d\n", (*it_C).getTag(), (*it_C).getLen(), (*it_C).getVal());
Serial.printf("it_D TAG=%d, LENGTH=%d, Value=%d\n", (*it_D).getTag(), (*it_D).getLen(), (*it_D).getVal());
// you can also use the results of find() directly without saving as a separate iterator, though this is computationally inefficient:
if(myTLV.find(1)!=myTLV.end()) // check for match
Serial.printf("Found: TAG=%d, LENGTH=%d, Value=%d\n", myTLV.find(1)->getTag(), myTLV.find(1)->getLen(), myTLV.find(1)->getVal());
// sum up all the bytes in it_E:
int sum=0;
for(int i=0; i < it_E->getLen(); i++)
sum+= (*it_E)[i];
Serial.printf("it_E TAG=%d, LENGTH=%d, Sum of all bytes = %d\n", (*it_E).getTag(), (*it_E).getLen(), sum);
// create a "blank" TLV8 record with TAG=90 and space for 16 bytes:
auto it_F = myTLV.add(90,16,NULL);
// copy the first 16 bytes of it_E into it_F and print the record:
memcpy(*it_F,*it_E,16);
myTLV.print(it_F);
```
produces the following output:
```C++
results of myTLV.print():
1(2) FC21 [8700]
2(1) B4 [180]
200(32) 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F
50(2) 60EA [60000]
255(0) [null]
50(4) C0D40100 [120000]
255(0) [null]
50(4) 20BF0200 [180000]
255(0) [null]
50(4) 80A90300 [240000]
it_A: TAG=2, LENGTH=1, Value=180
it_B: TAG=50, LENGTH=4, Value=240000
it_C TAG=50, LENGTH=2, Value=60000
it_D TAG=50, LENGTH=4, Value=120000
Found: TAG=1, LENGTH=2, Value=8700
it_E TAG=200, LENGTH=32, Sum of all bytes = 496
90(16) 000102030405060708090A0B0C0D0E0F
```
## Reading and Writing TLV8 Characteristics
As fully documented in the [API Reference](Reference.md), the following *SpanCharacteristic* methods are used to read and write TLV8 objects to TLV8 Characteristics:
* `getTLV(TLV8 &tlv)`
* `getNewTLV(TLV8 &tlv)`
* `setTLV(TLV8 &tlv)`
These are analagous to the `getVal()`, `getNewVal()` and `setVal()` methods used for numerical-based Characteristics.
Note that using the above methods *do not* require you to create a separate byte-array that splits records into chunks of 255 bytes, nor does it require you to encode or decode anything into base-64. Rather, you directly read and write to and from the Characteristic into a TLV8 object.[^getString]
For a detailed example of how TLV8 Characteristics are used in practice, see [Tutorial Example 22 - TLV8_Characteristics](../examples/22-TLV8_Characteristics) demonstrating how the **DisplayOrder** TLV8 Charactersitic can be used to set the order in which Input Sources for a TV Service are displayed in the Home App.
[^getString]:Since TLV8 Characteristics are stored as base-64 encoded strings, you can always use `getString()` to read the base-64 text, or `getData()` to decode the string into the full byte-array that represents the entire TLV8 object, if you desire. Also, if you really don't want to use HomeSpan's TLV8 library to produce TLV8 objects, but instead prefer to use your own methods to create a TLV8-compliant byte-array, you can do so and then use `setData()` to save the byte-array you produced to the TLV8 Characteristic, which will perform the base-64 encoding for you. Or, if you want to additionally perform your own base-64 encoding (why?), you can do so and then simply use `setString()` to save the resulting encoded text to the TLV8 Characteristic.
### Write-Response Requests
For most Characteristics, when the Home App sends HomeSpan a request to update a value, it is instructing HomeSpan to perform some sort of action, such as "change the brightness of a lightbulb to 30%" or "change the target state of the door to open." The only feedback the Home App expects to receive in response to such requests is basically an "OK" or "NOT OKAY" message, which is the purpose of the boolean return value in the `update()` method for every Service.
However, sometimes the Home App sends HomeSpan a request for information, rather than a direct instruction to perform a task. In such instances, rather than sending back just an OK/NOT-OKAY message, the Home App expects the Accessory device to update the value of the Characteristic *not* with the new value that the Home App sent, but rather with the information it requested. It then expects this information to be transmitted back to the Home App at the conclusion of the update.
This procedure is known as a "Write-Response Request", and it is the primary purpose for having TLV8 Characteristics, since TLV8 objects are ideal for storing structured information.
Though the procedure is complex, HomeSpan handles all of the protocol details. You only need to focus on reading the TLV8 Characteristic and updating it with the required TLV8 response as follows:
* first, from within the `update()` loop of the applicable Service, check to see if the Home App has requested an update to the TLV8&nbsp;Characteristic;
* if so, create a new TLV8 object and use `getNewTLV()` to load the contents of the updated Characteristic into that TLV8 object;
* then, use the TLV8 library methods described above to read through the TAGS and VALUES in the TLV8 object to determine what data the Home App is conveying and what information it wants returned (based on the specs for the Characteristic);
* next, create a *second* TLV8 object and use the TLV8 library methods above to create the appropriate TAGS and VALUES needed to respond to the information request (again, based on the on the specs for the Characteristic);
* finally, use `setVal()` to update the TLV8 Characteristic with the second TLV8 object
HomeSpan will automatically send the new TLV8 data you placed in the TLV8 Characterstic back to the Home App in its response at the conclusion of the `update()` loop.
---
[↩️](../README.md) Back to the Welcome page

View File

@ -21,7 +21,7 @@ new Service::Television();
new Characteristic::Active(0); // set power to OFF at start-up
new Characteristic::ConfiguredName("Sony TV"); // optional Characteristic to set name of TV
```
More advanced control of a TV can enabled with these *optional* Characteristics:
More advanced control of a TV can enabled with two other optional Characteristics:
* `Characteristic::RemoteKey()` - this write-only numerical Characteristic enables HomeSpan to read button presses from the Remote Control widget on an iPhone that can be found under the Control Center. This widget is normally used to control Apple TVs, but it seems any Television Accessory created per above can also be operated from the Remote Control widget. The layout of the widget (which cannot be modified) includes 4 arrows, a central select button, a play/pause button, a large "back" button, and an "info" button. When a "key" is pressed, the Home App sends an update to `Characteristic::RemoteKey()` that can be read by HomeSpan using the usual `update()` method. Values are as follows:
@ -38,23 +38,6 @@ More advanced control of a TV can enabled with these *optional* Characteristics:
* `Characteristic::ActiveIdentifier()` - this numerical Characteristic is used to control the input source for the TV (e.g. HDMI-1, HDMI-2, Netflix, etc.). It is only used when input sources are defined and linked using `Service::InputSource()` (see below), in which case it is a *required* Characteristic
* `Characteristic::DisplayOrder()` - this TLV8 Characteristic is used to control the order in which linked Input Sources are displayed in the Home App
* absent specifying the order with this Characteristic, the Home App will display the Input Sources in a random order within the selection section (under the power button), and in numerical order on the settings page of the Accessory based on the numeric Identifier for each Input Source
* the format of the TLV8 object used by this Characteristic is a series of TLV8 "Identifier" records with TAG=1 and a VALUE set to the Identifer of a particular Input Source; the "Identifier" records should each be separated by an empty TLV8 record with TAG=0
* example, the following code snippet sets the display order for three input sources with Identifiers 10, 20, and 30 to be 20, 30, and then 10:
```C++
TLV8 orderTLV; // create an empty TLV8 object named "orderTLV"
orderTLV.add(1,20); // TAG=1, VALUE=20 (the Identifier of the first Input Source to be displayed)
orderTLV.add(0); // TAG=0 (empty record used as a separator)
orderTLV.add(1,30); // TAG=1, VALUE=30 (the Identifier of the second Input Source to be displayed)
orderTLV.add(0); // TAG=0 (empty record used as a separator)
orderTLV.add(1,10); // TAG=1, VALUE=10 (the Identifier of the third Input Source to be displayed)
new Characteristic::DisplayOrder(orderTLV); // instantiate the DisplayOrder Characteristic and set its value to the orderTLV object
```
### `Service::InputSource()`
Use `Service::InputSource()` to create a new input source selection for the TV, such as HDMI-1, HDMI-2, Netflix, etc. The use of `Service::InputSource()` is optional - it is perfectly okay to create a Television Service without the ability to select different Input Sources. However, if used, each Input Source Service added should be defined in the *same* Accessory as the Television Service to which it applies, and ***must*** be linked to that Television Service using `addLink()`. The Home App behaves unexpectedly if it finds any Input Source Services that are not linked to a Television Service.
@ -67,26 +50,16 @@ All of this is accomplished by using a combination of some, or all, of the follo
* `Characteristic::ConfiguredName()` - similar to how its used when applied to `Service::Television()`, this Characteristic allows you set the default name for an Input Source. Note that if you change the name of an Input Source in the Home App, an update will be sent to HomeSpan with the new name for you to use in your sketch if needed. This is very different from the usual `Characteristic::Name()` used for many other Services, and for which name changes performed in the Home App are never communicated back to the Accessory
* `Characteristic::Identifier()` - this numerical Characteristic sets an ID for each Input Source. Any unsigned 32-bit number can be used as an ID, provided it is *unique* and not used by any other Input Source in the same TV Service. When you use the Input Source Selector in the Home App to choose a particular Input Source, the `Characteristic::ActiveIdentifier()` from the Television Service (see above) will be updated with a value that matches the ID corresponding to the chosen Input Source. Within HomeSpan you simply use the `update()` method to determine when `Characteristic::ActiveIdentifer()` is updated, and, based on its value, which Input Source was chosen. HomeKit does not seem to require `Characteristic::Identifier()` be defined for an Input Source. However, if it not set, the Home App will not allow it to be displayed as a choice in the Input Source Selector, which defeats the purpose of creating an Input Source!
* `Characteristic::Identifier()` - this numerical Characteristic sets an ID for each Input Source. Any unsigned 32-bit number can be used as an ID, provided it is *unique* and not used by any other Input Source in the same TV Service. When you use the Input Source Selector in the Home App to choose a particular Input Soure, the `Characteristic::ActiveIdentifier()` from the Television Service (see above) will be updated with a value that matches the ID corresponding to the chosen Input Source. Within HomeSpan you simply use the `update()` method to determine when `Characteristic::ActiveIdentifer()` is updated, and, based on its value, which Input Source was chosen. HomeKit does not seem to require `Characteristic::Identifier()` be defined for an Input Source. However, if it not set, the Home App will not allow it to be displayed as a choice in the Input Source Selector, which defeats the purpose of creating an Input Source!
* `Characteristic::IsConfigured()` - this Characteristic determines whether an Input Source is allowed to appear as a choice in the Input Source Selector of the Home App. If IsConfigured() is defined and set to 0, the Input Source will appear in the Settings page, but it will be excluded as a choice from the Input Source Selector. If IsConfigured() is defined and set to 1, the Input Source will appear in the Settings page, and will also be included as a choice in the Input Source Selector. If `Characteristic::IsConfigured()` is not defined for an Input Source, that source will still appear as a choice in the Input Source Selector, but it will *not* appear in the list of Input Sources found on the Settings page. This means you will not be able to rename the Input Source from the Home App, nor toggle it as an allowable choice in the Input Selector (see below)
* `Characteristic::CurrentVisibilityState()` and `Characteristic::TargetVisibilityState()` - these two Characteristics work in tandem much like any current-state/target-state pair. When these are defined for an Input Source, a checkbox toggle appears next to the name of the Input Source on the Settings page, provided `Characteristic::IsConfigured()` has also been defined. Clicking the checkbox causes the Home App to toggle the TargetVisibilityState between 0 to 1, where 0 ironically means the checkbox is *checked*, and 1 means it is *unchecked* (the reverse of what you might expect!). If you read this update in HomeSpan you can then use `setVal()` to change the CurrentVisibiltyState() to match the TargetVisibilityState(). Setting CurrentVisibilityState() to 0 means the Input Source appears as a choice in the Input Source Selector. Setting CurrentVisibilityState() to 1 means it does not appear as a selection. Note these features only operate if an ID has been set for the Input Source with `Characteristic::Identifier()`, and IsConfigured() has been defined and set to 1
### `Service::TelevisionSpeaker()`
This Service allows you to change the volume of a television using the iPhone's physical volume control buttons when operating the TV via the iPhone's Remote Control widget (found in the iPhone Control Center). Similar the Input Source Service above, the Television Speaker Service ***must*** be linked to a Television Service using `addLink()`. The Television Speaker Service requires the following two Characteristics:
* `Characteristic::VolumeControlType()` - this read-only Characteristic seems to be required but there is uncertainty as to its purpose. In the example HomeSpan sketches I initialized this Characteristic with a value of 3 based on what others have done in scripts based on [HomeBridge](https://developers.homebridge.io/#/service/TelevisionSpeaker)
* `Characteristic::VolumeSelector()` - this write-only Characterstic is updated whenever the user is operating the TV via the iPhone's Remote Control widget and the physical volume control buttons are pressed. The Home App sends a value of 0 when the up-volume button is pressed, and value of 1 when the down-volume button is pressed. These values can be read the usual way by creating an `update()` method for a class that is derived from the Television Speaker Service
### Examples
* Please see [*File → Examples → HomeSpan → Other Examples → Television*](../examples/Other%20Examples/Television) for a complete worked example demonstrating the effects of using different combinations of the above Characteristics
* For details on how to use TLV8 records with the DisplayOrder Characteristic, see [Tutorial Example 22 - TLV8 Characteristics](../examples/22-TLV8_Characteristics)
* For more advanced use case, see the Television Example on the [HomeSpan Reference Sketches](https://github.com/HomeSpan/HomeSpanReferenceSketches) page
* Also, don't forget to check out the [HomeSpan Projects](https://github.com/topics/homespan) page for some real-world examples of TV sketches and controllers.
Please see [*File → Examples → HomeSpan → Other Examples → Television*](../Other%20Examples/Television) for a complete worked example demonstrating the effects of using different combinations of the above Characteristics. Also, don't forget to check out the [HomeSpan Projects](https://github.com/topics/homespan) page for some real-world examples of TV sketches and controllers.
### Credits
@ -98,4 +71,4 @@ Much thanks to @unreality for the PR to include Television codes and associated
---
[↩️](../README.md) Back to the Welcome page
[↩️](README.md) Back to the Welcome page

View File

@ -1,9 +1,8 @@
# HomeSpan Tutorials
The HomeSpan library includes many tutorial sketches of increasing complexity that take you through all the functions and features of HomeSpan. The sketches are extensively annotated, and you'll even learn a lot about HomeKit itself by working through all the examples. If you've already loaded HomeSpan into your Arduino IDE, the tutorials will be found under *File → Examples → HomeSpan*. Each sketch is ready to be compiled and uploaded to your ESP32 device so you can see them in action. Alternatively, you can explore just the code within GitHub by clicking on any of titles below. Note: you may want to first read through the [HomeSpan API Overview](Overview.md) before exploring the tutorials. They will probably make a lot more sense if you do!
The HomeSpan library includes 16 tutorial sketches of increasing complexity that take you through all the functions and features of HomeSpan. The sketches are extensively annotated, and you'll even learn a lot about HomeKit itself by working through all the examples. If you've already loaded HomeSpan into your Arduino IDE, the tutorials will be found under *File → Examples → HomeSpan*. Each sketch is ready to be compiled and uploaded to your ESP32 device so you can see them in action. Alternatively, you can explore just the code within GitHub by clicking on any of titles below. Note: you may want to first read through the [HomeSpan API Overview](Overview.md) before exploring the tutorials. They will probably make a lot more sense if you do!
>[!TIP]
>Each example is designed to be operated after pairing your ESP32 to HomeKit so you can control HomeSpan from the Home App on your iPhone, iPad, or Mac. In principle, once you configure and pair your device to HomeKit, your Home App should automatically reflect all changes in your configuration whenever you upload a different tutorial. However, in practice this is not always the case as it seems HomeKit sometimes caches information about devices, which means what you see in your Home App may not be fully in sync with your sketch. If this occurs, unpairing and then re-pairing the ESP32 device usually fixes the issue. If not, you may have to reset the HomeKit Device ID on the ESP32 device so that HomeKit thinks it is a new device and will not use any cached data. This is very easy to do - see the [HomeSpan Command-Line Interface (CLI)](CLI.md) page for details.
> :heavy_check_mark: Each example is designed to be operated after pairing your ESP32 to HomeKit so you can control HomeSpan from the Home App on your iPhone, iPad, or Mac. In principle, once you configure and pair your device to HomeKit, your Home App should automatically reflect all changes in your configuration whenever you upload a different tutorial. However, in practice this is not always the case as it seems HomeKit sometimes caches information about devices, which means what you see in your Home App may not be fully in sync with your sketch. If this occurs, unpairing and then re-pairing the ESP32 device usually fixes the issue. If not, you may have to reset the ID on the ESP32 device so that HomeKit thinks it is a new device and will not use any cached data. This is very easy to do - see the [HomeSpan Command-Line Interface (CLI)](CLI.md) page for details.
### [Example 1 - SimpleLightBulb](../examples/01-SimpleLightBulb)
This first example introduces the HomeSpan library and demonstrates how to implement a simple on/off light control using a combination of HomeSpan Accessory, Service, and Characteristic objects. Once this sketch has been uploaded to your HomeSpan device and the device is paired to your home, a new "lightbulb" tile will appear in the Home App of your iPhone, iPad, or Mac. Though the tile will be fully operational (i.e. you can change the status of the lightbulb from "on" or "off"), we won't yet connect an actual light or LED to the HomeSpan device, so nothing real will light up. Instead, in this and the next few examples, we'll focus on learning about the different ways HomeKit controls can be configured. Starting in Example 5, we'll connect an LED to the device and introduce the methods that actually turn the LED on and off from your Home App. HomeSpan API topics covered in this example include:
@ -69,9 +68,7 @@ Example 12 introduces HomeKit *Event Notifications* to implement two new accesso
* setting the value of a Characteristic and triggering an Event Notification with the `setVal()` method
### [Example 13 - TargetStates](../examples/13-TargetStates)
Example 13 demonstrates the simultaneous use of both the `update()` and `loop()` methods by implementing two new Services: a Garage Door Opener and a motorized Window Shade. Both examples showcase HomeKit's Target-State/Current-State framework. New HomeSpan API topics covered in this example include:
* using Enumerated Constants to set the values of Characteristics that represent discrete states (e.g. "raising", "closing")
Example 13 we demonstrate the simultaneous use of both the `update()` and `loop()` methods by implementing two new Services: a Garage Door Opener and a motorized Window Shade. Both examples showcase HomeKit's Target-State/Current-State framework.
### [Example 14 - EmulatedPushButtons](../examples/14-EmulatedPushButtons)
Example 14 demonstrates how you can use the `setVal()` and `timeVal()` methods inside a Service's `loop()` method to create a tile in the Home App that emulates a pushbutton switch. In this example pressing the tile in the Home App will cause it to turn on, blink an LED 3 times, and then turn off (just like a real pushbutton might do).
@ -102,67 +99,28 @@ Example 19 illustrates, through the implementation of two On/Off LEDs, how to ad
* enabling the HomeSpan Web Log and specifying an optional NTP time server with the `homeSpan.enableWebLog()` method
* using the `WEBLOG()` macro to create Web Log messages
### [Example 20 - AdvancedTechniques](../examples/20-AdvancedTechniques)
Example 20 illustrates a number of advanced techniques through the implementation of a "dynamic" bridge that allows Light Accessories to be *interactively* added and deleted at any time without the need to reboot the device. New HomeSpan API topics covered in this example include:
* creating custom CLI commands using `SpanUserCommand()`
* dynamically deleting Accessories with `homeSpan.deleteAccessory()`
* refreshing the Accessory database (which automatically updates the Home App) using `homeSpan.updateDatabase()`
* using `homeSpan.autoPoll()` to implement HomeSpan Polling in the background (and on the second core, if available)
### [Example 21 - AccessoryIdentifier](../examples/21-AccessoryIdentifier)
Example 21 shows how the Identifier Characteristic that is always present in each Accessory's required AccessoryInformation Service can be used to create a custom "identification routine" that can be triggered from within the Home App when pairing a device. This example does not use any new HomeSpan methods.
### [Example 22 - TLV8 Characteristics](../examples/22-TLV8_Characteristics)
Example 22 demonstrates how to create and utilize TLV8-based Characteristics through the implementation of the DisplayOrder Characteristic used to set the order in which input sources for a Television Service are presented in the Home App. New HomeSpan API topics covered in this example include:
* creating TLV8 objects using HomeSpan's TLV8 class
* updating TLV8 Characteristics using `setTLV()`
## Other Examples
The following examples showcase a variety of HomeSpan and HomeKit functionality as referenced in different sections of the HomeSpan documentation. The sketches can be found in the Arduino IDE under *File → Examples → HomeSpan → Other Examples*
### [TableLamp](../examples/Other%20Examples/TableLamp)
### [TableLamp](../Other%20Examples/TableLamp)
A basic implementation of a Table Lamp Accessory. Used as the tutorial in [HomeSpan API Overview](Overview.md)
### [RemoteControl](../examples/Other%20Examples/RemoteControl)
### [RemoteControl](../Other%20Examples/RemoteControl)
A standalone example that shows how to use HomeSpan's *RFControl* class to produce a custom pulse train. For illustrative purposes the pulse widths are very long and suitable for output to an LED so you can "see" the pulse train. See the [RF/IR Generation](RMT.md) page for full details
### [ServoControl](../examples/Other%20Examples/ServoControl)
### [ServoControl](../Other%20Examples/ServoControl)
An implementation of a Window Shade that uses HomeSpan's *ServoPin* class to control the horizontal tilt of the slats. See [ServoPin](PWM.md#servopinuint8_t-pin-double-initdegrees-uint16_t-minmicros-uint16_t-maxmicros-double-mindegrees-double-maxdegrees) for full details
### [Television](../examples/Other%20Examples/Television)
### [Television](../Other%20Examples/Television)
An example of HomeKit's *undocumented* Television Service showing how different Characteristics can be used to control a TV's power, input sources, and a few other functions. See the [Television Services and Characteristics](TVServices.md) page for full details
### [Pixel](../examples/Other%20Examples/Pixel)
### [Pixel](../Other%20Examples/Pixel)
Demonstrates how to use HomeSpan's *Pixel* and *Dot* classes to control one- and two-wire Addressable RGB and RGBW LEDs. See the [Addressable RGB LEDs](Pixels.md) page for full details
### [PixelTester](../examples/Other%20Examples/PixelTester)
A sketch to aid in determining the *pixelType* for any RGB(W) LED Strip. See the [Addressable RGB LEDs](Pixels.md) page for full details
### [CustomService](../examples/Other%20Examples/CustomService)
### [CustomService](../Other%20Examples/CustomService)
Demonstrates how to create Custom Services and Custom Characteristics in HomeSpan to implement an Atmospheric Pressure Sensor recognized by the *Eve for HomeKit* app. See [Custom Characteristics and Custom Services Macros](Reference.md#custom-characteristics-and-custom-services-macros) for full details
### [ProgrammableHub](../examples/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](../examples/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
### [FadingLED](../examples/Other%20Examples/FadingLED)
Demonstrates how the *LedPin* class can use the ESP32's built-in fading control to automatically fade an LED from from one level of brightness to another over a specified period of time. See the [LedPin](PWM.md#pulse-width-modulation-pwm) page for full details
### [MotorizedWindowShade](../examples/Other%20Examples/MotorizedWindowShade)
Demonstrates how to use the *StepperControl* class to operate a stepper motor. Implements a motorized window shade based on [Example&nbsp;13](../examples/13-TargetStates) above. See the [Stepper Motor Control](Stepper.md) page for full details
### [CustomNVSPartition](../examples/Other%20Examples/CustomNVSPartition)
Demonstrates how to create a Custom Partition Scheme for your sketch by adding a *partitions.csv* file to your sketch folder. Can be used to expand the size of the non-volatile-storage (NVS) partition, which may be needed when creating a HomeSpan device with many Accessories whose Characteristics you want to save in NVS
### [ExternalReference](../examples/Other%20Examples/ExternalReference)
Demonstrates how to access Characteristics of Services from outside those Services, such as from within the main Arduino `loop()`. In this sketch we re-create the two LEDs in Example 5 with an added function in the main Arduino `loop()` that checks if both LEDs are on at the same time, and if so, they are automatically turned off
---
[↩️](../README.md) Back to the Welcome page
[↩️](README.md) Back to the Welcome page

View File

@ -1,16 +1,5 @@
# HomeSpan User Guide
1. [Introduction](#introduction)
2. [Getting Started](#getting-started)
3. [HomeSpan Device States](#homespan-device-states)
4. [Device Configuration Mode](#device-configuration-mode)
5. [Setting HomeSpans WiFi Credentials and Setup Code](#setting-homespans-wifi-credentials-and-setup-code)
6. [Pairing to HomeKit](#pairing-to-homekit)
7. [Factory Reset](#factory-reset)
8. [Creating a Scannable Tag](#creating-a-scannable-tag)
9. [Status LED Flashing Reference](#status-led-flashing-reference)
## Introduction
This guide explains how to set up and configure a HomeSpan device that has already been programmed to operate one or more appliances, including how to:
* Determine the state of the device by observing the HomeSpan Status LED
@ -84,7 +73,7 @@ Within Device Configuration Mode you can choose one of five possible Actions:
1. *Launch HomeSpans temporary WiFi network*. HomeSpans temporary WiFi network provides a simple web interface for you to input your home networks WiFi credentials and (optionally) create your own HomeKit Setup Code. See [Setting HomeSpans WiFi Credentials and Setup Code](#setting-homespans-wifi-credentials-and-setup-code) for step-by-step instructions.
1. *Unpair the device from Apple HomeKit*. Under normal operation, you control the pairing and unpairing of all HomeKit devices from the Home App on your iPhone or Mac. However, there are some circumstances in which a device may need to be manually unpaired. This is typically done by performing a full [Factory Reset](#factory-reset) of the device, but that also deletes your WiFi Credentials. This action allows you to unpair the device while preserving all other device settings.
1. Unpair the device from Apple HomeKit. Under normal operation, you control the pairing and unpairing of all HomeKit devices from the Home App on your iPhone or Mac. However, there are some circumstances in which a device may need to be manually unpaired. This is typically done by performing a full [Factory Reset](#factory-reset) of the device, but that also deletes your WiFi Credentials. This action allows you to unpair the device while preserving all other device settings.
1. *Erase stored WiFi Credentials*. This allows you delete your WiFi Credentials from the device without losing any HomeKit pairing data, after which the device can be set up with new WiFi Credentials.
@ -168,22 +157,8 @@ The box can be bigger or smaller as long as you keep the same proportions. For
Note that if you cant find *Scancardium* listed as a font choice in either the *Pages* or *Keynote* font dropdown boxes, select Format → Font → Show Fonts from the menu bar of the *Pages* or *Keynote* application to bring up a list of all installed fonts where you can search for, and select, *Scancardium*.
## Status LED Flashing Reference
If a status LED has been connected and configured, HomeSpan will use flashing code to indicate different status.
| Flash pattern | Description |
| --------------------------- | -------------------------------------------------------------------------- |
| LED off, no flashing | By default, no power, unless `setStatusAutoOff(timeout)` has been defined. |
| LED on, no flashing | All good. Device is connected to WiFi and paired. See [PAIRED](#paired). |
| 1-second flash every second | Attempting to connect to WiFi. See [CONNECTING](#connecting). |
| 1 flash every 3 seconds | No WiFi. See [NO-WIFI](#nowifi). |
| 2 flashes every 3 seconds | See [READY-TO-PAIR](#readytopair). |
| 10 flashes per second | The Control button is pressed. Once released, HomeSpan will either<br>- Enter [Device Configuration Mode](#device-configuration-mode).<br>- Exit _Device Configuration Mode_ executing the action selected based on the number of flashes.|
---
[↩️](../README.md) Back to the Welcome page
[↩️](README.md) Back to the Welcome page

Binary file not shown.

View File

@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2022 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
@ -51,6 +51,9 @@
// NOTE: All HomeSpan examples are best understood when reviewed in conjunction with the documentation provided on the HomeSpan GitHub page.
// See https://github.com/HomeSpan/HomeSpan for details and references. In particular, you may want to review the HomeSpan API Overview
// page before proceeding.
// These examples also make frequent reference to Apple's HomeKit Accessory Protocol Specification, known as HAP. You can download this
// directly from Apple at https://developer.apple.com/support/homekit-accessory-protocol.
// LET'S GET STARTED...

View File

@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2022 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*

View File

@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2022 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*

View File

@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2022 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*

View File

@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2022 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
@ -30,11 +30,12 @@
// HomeSpan: A HomeKit implementation for the ESP32 //
// ------------------------------------------------ //
// //
// Example 5: Two working on/off LEDs based on the //
// Example 5: Two working on/off LEDs based on the //
// LightBulb Service //
// //
////////////////////////////////////////////////////////////
#include "HomeSpan.h"
#include "DEV_LED.h" // NEW! Include this new file, DEV_LED.h, which will be fully explained below

View File

@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2022 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
@ -50,11 +50,12 @@ void setup() {
// As usual, all previous comments have been deleted and only new changes from the previous example are shown.
// NOTE: The Arduino/ESP32 code base does not include the function analogWrite() which is typically used to create a PWM
// output to drive the brightness of an LED. Instead, the ESP32 code base itself includes a set of functions to create PWM output
// and the ESP32 chip has built-in PWM functionality specifically for this purpose.
// HomeSpan wraps all of this PWM functionality into a single integrated class called LedPin, making it very easy to define
// dimmable LED pins and set the PWM level (i.e. duty cycle) from 0-100%. Use of this LedPin class is shown in DEV_DimmableLED.
// output to drive the brightness of an LED. The ESP32 code base itself includes a set of functions to create PWM output
// and the ESP32 chip has built-in PWM functionality specifically for this purpose. There are numerous libraries
// you can download that mimics or reproduces analogWrite() in some form or another. HomeSpan conveniently comes with
// it own version of a wrapper around the ESP32 PWM classes that make it very easy to define PWM "channel," attach a pin,
// and set the PWM level (or duty cycle) from 0-100%. These functions are encapsualted in the LedPin class, as defined in
// extras/PwmPin.h. We will include this file in our updated DEV_LED.h for use with DEV_DimmableLED.
Serial.begin(115200);

View File

@ -3,6 +3,8 @@
// DEVICE-SPECIFIC LED SERVICES //
////////////////////////////////////
#include "extras/PwmPin.h" // NEW! Include this HomeSpan "extra" to create LED-compatible PWM signals on one or more pins
struct DEV_LED : Service::LightBulb { // ON/OFF LED
int ledPin; // pin number defined for this LED

View File

@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2022 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
@ -54,8 +54,8 @@ void setup() {
// Rather, the default name of the first Accessory Tile will always be shown by the Home App as the name specified in
// homeSpan.begin() regardless of whether or not the Name Characteristic has been added to the Accessory Information Service.
// Below is a replay of Example 6 showing how the Name Characteristic can be used to change the default names of the second
// and third, but not the first, Accessory Tile.
// Below is a replay of Example 6 showing how the Name Characteristic can be used to change the default names of the second,
// but not the first, Accessory Tile.
Serial.begin(115200);
@ -77,14 +77,6 @@ void setup() {
new DEV_DimmableLED(17);
new SpanAccessory();
new Service::AccessoryInformation();
new Characteristic::Identify();
new Characteristic::Name(u8"Special chars ÄÖÜß"); // Use UTF-8 coded string for non-ASCII characters
new DEV_DimmableLED(18);
} // end of setup()
//////////////////////////////////////

View File

@ -3,6 +3,8 @@
// DEVICE-SPECIFIC LED SERVICES //
////////////////////////////////////
#include "extras/PwmPin.h" // NEW! Include this HomeSpan "extra" to create LED-compatible PWM signals on one or more pins
struct DEV_LED : Service::LightBulb { // ON/OFF LED
int ledPin; // pin number defined for this LED

View File

@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2022 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
@ -39,7 +39,7 @@
void setup() {
// If the only Service defined in the FIRST Accessory of a multi-Accessory device is the required Accessory Information Service,
// If the only Service defined in the FIRST Accessory of a mult-Accessory device is the required Accessory Information Service,
// the device is said to be configured as a "Bridge". Historically there may have been a number of functional differences between bridge
// devices and non-bridge devices, but since iOS 15, it's not obvious there are any differences in functionality, with two exceptions:

View File

@ -3,6 +3,8 @@
// DEVICE-SPECIFIC LED SERVICES //
////////////////////////////////////
#include "extras/PwmPin.h" // allows PWM control of LED brightness
struct DEV_LED : Service::LightBulb { // ON/OFF LED
int ledPin; // pin number defined for this LED

View File

@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2022 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*

View File

@ -3,6 +3,8 @@
// DEVICE-SPECIFIC LED SERVICES //
////////////////////////////////////
#include "extras/PwmPin.h" // allows PWM control of LED brightness
struct DEV_LED : Service::LightBulb { // ON/OFF LED
int ledPin; // pin number defined for this LED

View File

@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2022 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*

View File

@ -3,6 +3,10 @@
// DEVICE-SPECIFIC LED SERVICES //
////////////////////////////////////
#include "extras/PwmPin.h" // library of various PWM functions
////////////////////////////////////
struct DEV_LED : Service::LightBulb { // ON/OFF LED
int ledPin; // pin number defined for this LED
@ -178,7 +182,7 @@ struct DEV_RgbLED : Service::LightBulb { // RGB LED (Command Cathode)
sprintf(cBuf,"RGB=(%d,%d,%d)\n",R,G,B);
LOG1(cBuf);
redPin->set(R); // update each ledPin with new values
redPin->set(R); // update the ledPin channels with new values
greenPin->set(G);
bluePin->set(B);

View File

@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2022 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
@ -25,47 +25,33 @@
*
********************************************************************************/
////////////////////////////////////////////////////////////////
// //
// HomeSpan: A HomeKit implementation for the ESP32 //
// ------------------------------------------------ //
// //
// Example 11: Service Names: //
// * setting the names of individual Services //
// * "changing" the icons in a bridge Accessory //
// //
////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
// //
// HomeSpan: A HomeKit implementation for the ESP32 //
// ------------------------------------------------ //
// //
// Example 11: Service Names: //
// * setting the names of individual Services //
// * changing the icons in a bridge Accessory //
// //
////////////////////////////////////////////////////////////
#include "HomeSpan.h"
// INITIAL NOTE: Apple is constantly updating how the Home App Icons are chosen and how/if/where/when the Names for
// Accessories and Services are displayed. This example has been tested and verified as of iOS 17.2.1.
void setup() {
// As described in previous examples, when pairing a device the Home App will choose default names for each
// Accessory Tile, unless you override those default names with your own names by adding a Name Characteristic
// to the Accessory Information Service for each Accessory (except the first, which is typically the Bridge Accessory).
// The same process holds true for the names of the Services in an Accessory with multiple Services: if a Service is not named,
// the Home App will generate one. You can of course change the names of individual Services when prompted
// during the pairing process, or at any time after pairing from within the appropriate settings pages in the Home App.
// But more importantly, you can name Services in your sketch so that those name show up when pairing, saving you the need to
// rename them from the settings pages in the Home App.
// The same process holds true for the names of the Services in an Accessory with multiple Services, such as a Ceiling Fan with a Light.
// When pairing, the Home App will choose default names for each Service (such as Fan, Fan 2, Light, Light 2) depending on the types
// of Services included. Similar to the names of Accessory Tiles, you can change the names of individual Services when prompted
// during the pairing process, or at any time after pairing from within the appropriate settings pages in the Home App. More importantly,
// you can override the default Service names generated by the Home App by simply adding the Name Characteristic to any Service.
// Whereas we previously used the *Name* Characteristic to provide names for Accessory Tiles, we use the *ConfiguredName* Characteristic
// to provide names for individual Services within each Accessory.
// One important distinction between Name and ConfigureName is that Name is only used by the Home App during pairing. After that,
// any changes you make to the name of an Accessory Tile from within the Home App are never communicated back to HomeSpan, and any changes
// you might make to those names in your sketch will not be reflected in the Home App unless you unpair and re-pair the device. In contrast,
// ConfiguredName works like any other Characteristic: changes made to ConfiguredName from within a sketch are proporgated to the Home App,
// and any edits you make to a Service's name in the Home App trigger a corresponding call to update() in HomeSpan so HomeSpan and the Home App
// are always in sync with regard to the names of any Services that includes the ConfiguredName Characteristic.
// NOTE: Service names (whether those generated by the Home App or specified via the ConfiguredName Characteristic) are only displayed on the
// control screen of an Accessory Tile if there are two more more Services of the same type. But even if a Service name does not appear in the Home App,
// However, note that Service names (whether or not overridden) only appear in the Home App if there is a chance of ambiguity,
// such as a Accessory with two Services of the same type. But even if a Service name does not appear in the Home App,
// it will still be used by Siri to control a specific Service within an Accessory by voice.
// In the example below we create 5 different functional Accessories, each illustrating how names, as well as icons, are chosen by the Home App
@ -76,92 +62,103 @@ void setup() {
homeSpan.begin(Category::Bridges,"HomeSpan Bridge");
// Our initial Accessory is therefore the "Bridge" Accessory
// Our first Accessory is the "Bridge" Accessory
new SpanAccessory();
new Service::AccessoryInformation();
new Characteristic::Identify();
// Our first "functional" Accessory is a combination of a LightBulb, Outlet, and Switch. Note that when pairing, the Home App generates
// default names of "Light", "Outlet", and "Switch" for these three Services, though these names are NOT displayed on the control screen
// of the Accessory since there is only one type of each Service. Also note that the Home App selects a LightBulb icon for the Accessory Tile
// Our second Accessory is a Ceiling Fan with a single Light. There are three things to note:
//
// * when pairing, the Home App will generate default names of "Light" and "Fan" for the two Services.
// However, these names are not displayed on the control screen of the Accessory since there is no
// ambiguity between the Light and Fan controls - the Home App displays them differently
//
// * the icon used by the Home App for the Accessory Tile is a Lightbulb. Why does it choose this instead of a Fan icon?
// Recall from Example 3 that for Accessories with multiple Services, if there is any ambiguity of which icon to use,
// the Home App chooses based on the Category of the device. But since this device is configured as a Bridge, the
// Category provides no helpful information to the Home App. In such cases the Home App picks an icon for the
// Accessory Tile that matches the first functional Service in the Accessory, which in this instance in a LightBulb
//
// * when opening the control screen by clicking the Accessory Tile, the LightBulb control will appear on the left, and
// the Fan control will appear on the right
new SpanAccessory();
new Service::AccessoryInformation();
new Characteristic::Identify();
new Characteristic::Name("Light First"); // this sets the name of the Accessory Tile
new Service::LightBulb(); // the icon of the Accessory Tile will be a Lightbulb, since this is the first functional Service
new Characteristic::On();
new Service::Outlet();
new Characteristic::On();
new Characteristic::OutletInUse();
new Service::Switch();
new Characteristic::Name("Light with Fan"); // this sets the name of the Accessory Tile
new Service::LightBulb(); // the icon of the Accessory Tile will be a Lightbulb, since this is the first functional Service
new Characteristic::On();
new Service::Fan();
new Characteristic::Active();
// Our second Accessory is similar to the first, but here we define the Switch Service first. Note that the Home App now selects
// a Switch icon for the Accessory Tile
new SpanAccessory();
// Our third Accessory is identical to the second, except we swapped the order of the Lightbulb and Fan Services.
// The result is that the Home App now displays the Accessory Tile with a Fan icon intead of a Lightbulb icon.
// Also, when opening the control screen by clicking on the Accessory Tile, the Fan control will now appear on the
// left, and the LightBulb control on the right.
new SpanAccessory();
new Service::AccessoryInformation();
new Characteristic::Identify();
new Characteristic::Name("Switch First"); // this sets the name of the Accessory Tile
new Service::Switch(); // the icon of the Accessory Tile will be a Switch, since this is the first functional Service
new Characteristic::On();
new Service::Outlet();
new Characteristic::On();
new Characteristic::OutletInUse();
new Service::LightBulb();
new Characteristic::Name("Fan with Light"); // this sets the name of the Accessory Tile
new Service::Fan(); // the icon of the Accessory Tile will be a Fan, since this is the first functional Service
new Characteristic::Active();
new Service::LightBulb();
new Characteristic::On();
// Our third Accessory is similar to the second, but here we define 2 Switches, 2 LightBulbs, but still only 1 Outlet. This time, during pairing
// the Home App generates default names of Switch, Switch 2, Light, Light 2, and Outlet. Importantly, note that on the control screen for
// this Accessory, the Home App now displays the names of the Switches ("Switch" and "Switch 2") as well as the LightBulbs ("Light" and "Light 2")
// under each corresponding control, but it does NOT display the name "Outlet" under the Outlet control since there is only one Outlet Service
new SpanAccessory();
new Service::AccessoryInformation();
new Characteristic::Identify();
new Characteristic::Name("Two Switches"); // this sets the name of the Accessory Tile
new Service::Switch(); // the icon of the Accessory Tile will be a Switch, since this is the first functional Service
new Characteristic::On();
new Service::Switch();
new Characteristic::On();
new Service::Outlet();
new Characteristic::On();
new Characteristic::OutletInUse();
new Service::LightBulb();
new Characteristic::On();
new Service::LightBulb();
new Characteristic::On();
// Our fourth and final Accessory is the same as the third, but this time we use the ConfiguredName Characteristic to define a name for each Service.
// When pairing, you should see the Home App now uses the names below instead of generating default names as it did in the other examples. You
// should also see these names displayed under each control on the control screen for the Accessory, with the exception of the Outlet Service.
// Though we did provide a name for the Outlet, since there is only one Outlet Service in this Accessory, the Home App does not display its name.
// Howevever, if from the settings screen for this Accessory you further navigate to the "Accessories" page, you will indeed see the names for each
// Service exactly as specified below, including the Outlet name "Aux Power"
// Our fourth Accessory shows what happens if we implement two identical LightBulb Services (without any Fan Service).
// Since both Services are LightBulbs, the Home App sensibly picks a Lightbulb icon for the Accessory Tile. However,
// when you click the Accessory Tile and open the control screen, you'll note that the Home App now does display the names
// of the Service beneath each control. In this case the Home App uses the default names "Light 1" and "Light 2". The Home App
// presumably shows the names of each Service since the two controls are identical and there is otherwise no way of telling which
// control operates which light.
new SpanAccessory();
new SpanAccessory();
new Service::AccessoryInformation();
new Characteristic::Identify();
new Characteristic::Name("Central Control"); // this sets the name of the Accessory Tile
new Service::Switch(); // the icon of the Accessory Tile will be a Switch, since this is the first functional Service
new Characteristic::On();
new Characteristic::ConfiguredName("High Voltage"); // this sets the name of the first Switch Service
new Service::Switch();
new Characteristic::Identify();
new Characteristic::Name("Ceiling Lights"); // this sets the name of the Accessory Tile
new Service::LightBulb();
new Characteristic::On();
new Characteristic::ConfiguredName("Low Voltage"); // this sets the name of the second Switch Service
new Service::Outlet();
new Service::LightBulb();
new Characteristic::On();
new Characteristic::OutletInUse();
new Characteristic::ConfiguredName("Aux Power"); // this sets the name of the Outlet Service
new Service::LightBulb();
new Characteristic::On();
new Characteristic::ConfiguredName("Main Lights"); // this sets the name of the first LightBulb Service
new Service::LightBulb();
new Characteristic::On();
new Characteristic::ConfiguredName("Accent Lights"); // this sets the name of the second LightBulb Service
// Our fifth Accessory combines a single Fan Service with two identical LightBulb Services. Since the first functional Service implemented
// is a Fan, the Home App will pick a Fan icon for the Accessory Tile. Also, since we added Name Characteristics to two LightBulb
// Services, their default names generated by the Home App ("Light 1" and "Light 2") will be changed to the names specified. Finally,
// note that the Home App displays a more compact form of controls on the control screen since there are three Services. The arrangement
// and style of the controls will depend on what combination of Characteristics are implemented for each Service.
new SpanAccessory();
new Service::AccessoryInformation();
new Characteristic::Identify();
new Characteristic::Name("Fan with Lights"); // this sets the name of the Accessory Tile
new Service::Fan();
new Characteristic::Active();
new Service::LightBulb();
new Characteristic::Name("Main Light"); // this changes the default name of this LightBulb Service from "Light 1" to "Main Light"
new Characteristic::On();
new Service::LightBulb();
new Characteristic::Name("Night Light"); // this changes the default name of this LightBulb Service from "Light 2" to "Night Light"
new Characteristic::On();
// Our sixth Accessory is similar to the fifth, except we added some more features to some of the Services. Note how this changes
// the layout of the controls on the control screen.
new SpanAccessory();
new Service::AccessoryInformation();
new Characteristic::Identify();
new Characteristic::Name("Multi-Function Fan");
new Service::Fan();
new Characteristic::Active();
new Characteristic::RotationDirection(); // add a control to change the direcion of rotation
new Characteristic::RotationSpeed(0); // add a control to set the rotation speed
new Service::LightBulb();
new Characteristic::Name("Main Light");
new Characteristic::On();
new Characteristic::Brightness(100); // make this light dimmable (with intitial value set to 100%)
new Service::LightBulb();
new Characteristic::Name("Night Light"); // don't add anything new to this light
new Characteristic::On();
} // end of setup()

View File

@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2022 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*

View File

@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2022 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
@ -60,19 +60,18 @@ void setup() {
// guesses the actions a device is taking, and updates it tile's icon accordingly, by comparing the value of the target state
// Characteristic it sets, and the current state Characteristic it receives in the form of Event Notifications. When they are the same,
// HomeKit assumes the physical device has reached the required position. When they differ, HomeKit assumes something will be opening,
// closing, raising, lowering, etc. Sometimes a little experimenting and a lot of trial and error is required to fully understand how
// each Service responds to different combinations of Characteristic values.
// closing, raising, lowering, etc. The details of this process for each Service is outlined in the HAP documentation, but beware
// the document is not always up to date with the lastest version of the HomeKit application. Sometimes a little experimenting and a lot
// of trial and error is required to fully understand how each Service responds to different combinations of Characteristic values.
// As always, we won't be connecting our ESP32 to an actual garage door or window shade but will instead simulate their responses and
// actions for illustrative purposes. In some ways the code is more complicated because of the need to simulate values - it might be
// easier if we actually were connecting to a garage door or window shade!
// Fully commented code for both of our derived Services can be found in DEV_DoorsWindows.h. These examples do not introduce any new
// HomeSpan functions, but you will see how to use HomeSpan's ENUMERATED CONSTANTS, instead of just plain integers, to set the values
// of Characteristics where the values represent discrete states (e.g. "lowering", "opening").
// Please see HomeSpan's Services and Characteristics page for a complete list of the enumerated constants available for Characteristics
// where they are applicable.
// HomeSpan functions or features. Rather we are combining everything learned so far into two reasonably complex Services. You may
// want to reference the HAP documentation for these two parent Services to fully understand the meaning of the different value settings
// for each of the Services' Characteristics.
Serial.begin(115200);

View File

@ -3,33 +3,17 @@
// DEVICE-SPECIFIC LED SERVICES //
////////////////////////////////////
struct DEV_GarageDoor : Service::GarageDoorOpener { // A Garage Door Opener
struct DEV_GarageDoor : Service::GarageDoorOpener { // A Garage Door Opener
Characteristic::CurrentDoorState *current; // reference to the Current Door State Characteristic (specific to Garage Door Openers)
Characteristic::TargetDoorState *target; // reference to the Target Door State Characteristic (specific to Garage Door Openers)
SpanCharacteristic *obstruction; // reference to the Obstruction Detected Characteristic (specific to Garage Door Openers)
SpanCharacteristic *current; // reference to the Current Door State Characteristic (specific to Garage Door Openers)
SpanCharacteristic *target; // reference to the Target Door State Characteristic (specific to Garage Door Openers)
SpanCharacteristic *obstruction; // reference to the Obstruction Detected Characteristic (specific to Garage Door Openers)
DEV_GarageDoor() : Service::GarageDoorOpener(){ // constructor() method
// Below we use enumerated constants rather than integers to set the values of the Characteristics.
// Using enumerated constants means not having to remember the integer code for each state. You'll find
// a complete list of all available enumerated constants on HomeSpan's Services and Characteristics page.
// Note the use of enumerated constants is optional - you can always use the integer code representing
// each state instead.
DEV_GarageDoor() : Service::GarageDoorOpener(){ // constructor() method
current=new Characteristic::CurrentDoorState(Characteristic::CurrentDoorState::CLOSED); // here we use the fully-qualified name of the constant "CLOSED"
target=new Characteristic::TargetDoorState(target->CLOSED); // here we use the name of the object instead of the fully-qualified name (much less typing)
// Below we must use the fully-qualified name of the enumerated constant and cannot use "obstruction->NOT_DETECTED".
// Why? Because above we declared "obstruction" to be a pointer to a generic SpanCharacteristic instead of a pointer to
// the more specific Characteristic::ObstructionDetected. Either is fine, and it's just a matter of programming preference
// (as you can see we use both conventions in this sketch). But the downside of using SpanCharacteristic to declare a
// Characteristic that contains enumerated constants is that the object itself does not know about these constants. This is
// because all enumerated constants are uniquely defined within their respective specific Characteristic classes, and not in the
// generic SpanCharacteristic class from which all specific Characterstics are derived.
obstruction=new Characteristic::ObstructionDetected(Characteristic::ObstructionDetected::NOT_DETECTED); // this works
// obstruction=new Characteristic::ObstructionDetected(obstruction->NOT_DETECTED); // this would produce a compiler error (try it and see)
current=new Characteristic::CurrentDoorState(1); // initial value of 1 means closed
target=new Characteristic::TargetDoorState(1); // initial value of 1 means closed
obstruction=new Characteristic::ObstructionDetected(false); // initial value of false means NO obstruction is detected
Serial.print("Configuring Garage Door Opener"); // initialization message
Serial.print("\n");
@ -40,13 +24,13 @@ struct DEV_GarageDoor : Service::GarageDoorOpener { // A Garage Door Opener
// see HAP Documentation for details on what each value represents
if(target->getNewVal()==target->OPEN){ // HomeKit is requesting the door to be in OPEN position
if(target->getNewVal()==0){ // if the target-state value is set to 0, HomeKit is requesting the door to be in open position
LOG1("Opening Garage Door\n");
current->setVal(current->OPENING); // set the current-state value to OPENING
obstruction->setVal(false); // clear any prior obstruction detection - note we do not bother using an enumerated constant here
current->setVal(2); // set the current-state value to 2, which means "opening"
obstruction->setVal(false); // clear any prior obstruction detection
} else {
LOG1("Closing Garage Door\n"); // else HomeKit must be requesting the door to be in the CLOSED position
current->setVal(current->CLOSING); // set the current-state value to CLOSING
LOG1("Closing Garage Door\n"); // else the target-state value is set to 1, and HomeKit is requesting the door to be in the closed position
current->setVal(3); // set the current-state value to 3, which means "closing"
obstruction->setVal(false); // clear any prior obstruction detection
}
@ -59,13 +43,13 @@ struct DEV_GarageDoor : Service::GarageDoorOpener { // A Garage Door Opener
if(current->getVal()==target->getVal()) // if current-state matches target-state there is nothing do -- exit loop()
return;
if(current->getVal()==current->CLOSING && random(100000)==0){ // here we simulate a random obstruction, but only if the door is closing (not opening)
current->setVal(current->STOPPED); // if our simulated obstruction is triggered, set the curent-state to STOPPED
obstruction->setVal(true); // and set obstruction-detected to true
if(current->getVal()==3 && random(100000)==0){ // here we simulate a random obstruction, but only if the door is closing (not opening)
current->setVal(4); // if our simulated obstruction is triggered, set the curent-state to 4, which means "stopped"
obstruction->setVal(true); // and set obstruction-detected to true
LOG1("Garage Door Obstruction Detected!\n");
}
if(current->getVal()==current->STOPPED) // if the current-state is stopped, there is nothing more to do - exit loop()
if(current->getVal()==4) // if the current-state is stopped, there is nothing more to do - exit loop()
return;
// This last bit of code only gets called if the door is in a state that represents actively opening or actively closing.
@ -99,6 +83,15 @@ struct DEV_WindowShade : Service::WindowCovering { // A motorized Window Sha
boolean update(){ // update() method
// The logic below is based on how HomeKit appears to operate in practice, which is NOT consistent with
// HAP documentation. In that document HomeKit seems to support fully opening or fully closing a window shade, with
// an optional control to HOLD the window shade at a given in-between position while it is moving.
// In practice, HomeKit does not appear to implement any form of a HOLD control button, even if you instantiate that
// Characteristic. Instead, HomeKit provides a full slider control, similar to the brightness control for a lightbulb,
// that allows you to set the exact position of the window shade from 0-100%. This obviates the need to any sort of HOLD button.
// The resulting logic is also very simple:
if(target->getNewVal()>current->getVal()){ // if the target-position requested is greater than the current-position, simply log a "raise" message
LOG1("Raising Shade\n"); // ** there is nothing more to do - HomeKit keeps track of the current-position so knows raising is required
} else
@ -123,7 +116,11 @@ struct DEV_WindowShade : Service::WindowCovering { // A motorized Window Sha
// the user in the Home App. If it finds current and target positions are the same, it knows the shade is stopped. Otherwise
// it will report the shade is raising or lowering depending on whether the specified target state is greater or less than
// the current state.
// According to HAP, the Characteristic Position State is also required. However, this seems duplicative and is NOT needed
// at all given the way HomeKit uses current position. HomeSpan will warn you if Position State is not defined (since it
// is technically required) but this works fine without it.
} // loop
};

View File

@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2022 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
@ -40,7 +40,7 @@
void setup() {
// Though HomeKit and the HomeKit Accessory Protocol (HAP) Specification provide a very flexible framework
// for creating iOS- and MacOS-controlled devices, they do not contain every possible desired feature.
// for creating iOS- and MacOS-controlled devices, they does not contain every possible desired feature.
//
// One very common Characteristic HomeKit does not seem to contain is a simple pushbutton, like the type you
// would find on a remote control. Unlike switches that can be "on" or "off", a pushbutton has no state.

View File

@ -87,3 +87,10 @@ struct DEV_Blinker : Service::LightBulb { // LED Blinker
};
//////////////////////////////////
// HomeKit Bug Note: There is an apparent bug in HomeKit uncovered during the development of this example.
// If you have an Accessory with three or more Services, and the Accessory receives a notification message
// from the device, AND the HomeKit interface is open to show the detailed control for this Service tile
// in the HomeKit app, then for some reason HomeKit sends an update() request back to the device asking to
// set the Characteristic to the value that it just received from an Event Notification. HomeKit is not supposed
// to send update requests in response to an Event Notification.

View File

@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2022 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
@ -47,9 +47,9 @@ void setup() {
// Additionally, we want HomeKit to reflect any changes in the device as a result of such manual actions - HomeKit should know
// when the light has been turned on or off manually.
// One way to accomplish would be via custom code added to the loop() method of your derived Service that monitors a pushbutton,
// One way to accomplish would be via custom code added to the loop() method of your derived Service that monitors the button,
// checks when it is pressed, debounces button noise, performs some actions when pressed, and informs HomeKit of the actions with
// the setVal() method. Or you can simply use HomeSpan's built-in SpanButton() object.
// the setVal() method. Or you can use HomeSpan's built-in SpanButton() object.
// SpanButton() is a Service-level object, meaning it attaches itself to the last Service you define. Typically you would instantiate
// one of more SpanButton() objects directly inside the constructor for your derived Service.
@ -61,21 +61,18 @@ void setup() {
// It's fine to change this to a longer value, but a shorter value is not recommended as this may allow spurious triggers unless
// you debounce your switch with hardware.
// The SpanButton() constructor takes 5 arguments, in the following order:
// The SpanButton() constructor takes 4 arguments, in the following order:
//
// pin - the pin number to which the PushButton is attached (required)
// longTime - the minimum length of time (in milliseconds) the button needs to be pushed to be considered a LONG press (optional; default=2000 ms)
// singleTime - the minimum length of time (in milliseconds) the button needs to be pushed to be considered a SINGLE press (optional; default=5 ms)
// doubleTime - the maximum length of time (in milliseconds) between button presses to create a DOUBLE press (optional; default=200 ms)
// triggerType - the action that causes a trigger on the pin (optional; default=SpanButton::TRIGGER_ON_LOW). Built-in choices include:
//
// SpanButton::TRIGGER_ON_LOW: used for a button that connects pin to GROUND
// SpanButton::TRIGGER_ON_HIGH: used for a button that connects pin to VCC (typically +3.3V)
// SpanButton::TRIGGER_ON_TOUCH: used when a pin is connected to a touch pad/sensor
// When a SpanButton() is first instantiated, HomeSpan configures the specified pin in accordance with the triggerType chosen.
// When a SpanButton() is instantiated, it sets the specified pin on the ESP32 to be an INPUT with PULL-UP, meaning that the pin will
// normally return a value of HIGH when read. Your actual PushButton should be connected so that this pin is GROUNDED when the button
// is pressed.
// Then, HomeSpan continuously polls all pins with associated SpanButton() objects and checks for triggers, which indicates the button was
// HomeSpan automatically polls all pins with associated SpanButton() objects and checks for LOW values, which indicates the button was
// pressed, but not yet released. It then starts a timer. If the button is released after being pressed for less than singleTime milliseconds,
// nothing happens. If the button is released after being pressed for more than singleTime milliseconds, but for less than longTime milliseconds,
// a SINGLE press is triggered, unless you press once again within doubleTime milliseconds to trigger a DOUBLE press. If the button is held for more
@ -93,7 +90,7 @@ void setup() {
// Also in contrast with the loop method, the button() method takes two 'int' arguments, and should defined as follows:
//
// void button(int pin, int pressType)
// void button(int pin, int pressType)
//
// where "pin" is the pin number of the PushButton that was triggered, and pressType is set to 0 for a SINGLE press, 1 for a DOUBLE press,
// and 2 for a LONG press. You can also use the pre-defined constants SpanButton::SINGLE, SpanButton::DOUBLE, and SpanButton::LONG in place
@ -107,7 +104,7 @@ void setup() {
//
// C++ Note: For an extra check, you can also place the the contextual keyword "override" after your method definition as such:
//
// void button(int buttonPin, int pressType) override {...your code...}
// void button(int buttonPin, int pressType) override {...your code...}
//
// Doing so allows the compiler to check that you are indeed over-riding the base class button() method and not inadvertently creating a new
// button() method with an incorrect signature that will never be called by SpanButton(). In fact, you could add "override" to the definition
@ -153,115 +150,3 @@ void loop(){
homeSpan.poll();
} // end of loop()
//////////////// ADDITIONAL NOTES ////////////////////////
// DEFAULT VALUES AND ALTERNATIVE CONSTRUCTORS
// --------------------------------------------
// As shown in this example, the following creates a SpanButton suitable for connecting pin 23 to GROUND via a pushbutton, and uses
// SpanButton's default values for longTime, singleTime, and doubleTime:
//
// new SpanButton(23);
//
// This is exactly the same as if you explicitly set each parameter to its default value:
//
// new SpanButton(23,2000,5,200,SpanButton::TRIGGER_ON_LOW); // equivalent to above
//
// If instead you want to create a SpanButton that connects pin 23 to VCC via a pushbutton using SpanButton::TRIGGER_ON-HIGH,
// you need to explictly set all the other parameters, even if you are satisfied with their default values, since triggerType
// is the last argument in the constructor:
//
// new SpanButton(23,2000,5,200,SpanButton::TRIGGER_ON_HIGH);
//
// Because this can be cumbersome, SpanButton includes an alternative constructor where triggerType is the second paramater, instead
// of the last. In this case triggerType is required, but longTime, singleTime, and doubleTime are still optional.
//
// For example, the following creates a SpanButton suitable for connecting pin 23 to a touch pad/sensor, and uses
// SpanButton's default values for longTime, singleTime, and doubleTime:
//
// new SpanButton(23,SpanButton::TRIGGER_ON_TOUCH);
//
// which is of course equivalent to:
//
// new SpanButton(23,SpanButton::TRIGGER_ON_TOUCH,2000,5,200);
// TOUCH PAD/SENSOR CALIBRATION
// ----------------------------
// SpanButton makes use of the ESP32's internal touch sensor peripheral to monitor pins for "touches". There are a number
// of paramaters that must be specified for touches to be accurately detected, depending on the exact size and shape of your
// touch pads. Upon instantiation of a SpanButton() with triggerType=SpanButton::TRIGGER_ON_TOUCH, SpanButton will conveniently
// perform an automatic calibration that sets an appropriate threshold level for detecting touches.
//
// However, if you need to, you can override this calibration process using the following two class-level functions:
//
// SpanButton::setTouchThreshold() - explicitly sets the threshold for detecting touches (i.e. overrides the auto-calibration)
// SpanButton::setTouchCycles() - explicitly sets the measurement and sleep times used by the ESP32's internal touch peripheral
//
// See the SpanButton secion of the Reference API for details on how to use these optional functions.
// THE triggerType FUNCTION
// -------------------------
// Though the three triggerType objects supported by SpanButton (SpanButton::TRIGGER_ON_LOW, etc.) may appear to be nothing more than
// constants, they are actually boolean functions that each accept a single integer argument. When SpanButton calls the triggerType function,
// it passes the pin number specified in the constructor as the integer argument, and the triggerType function returns TRUE if the
// "pushbutton" associated with the pin number is "pressed," or FALSE if it is not.
//
// For example, the definitions of SpanButton::TRIGGER_ON_LOW and SpanButton::TRIGGER_ON_HIGH are as follows:
//
// boolean TRIGGER_ON_LOW(int pinArg) { return( !digitalRead(pinArg) ); }
// boolean TRIGGER_ON_HIGH(int pinArg) { return( digitalRead(pinArg) ); }
//
// The definitions for SpanButton::TRIGGER_ON_TOUCH are more complicated since the ESP32 touch sensor library returns either a 2-byte
// or 4-byte numeric value when the state of pin configured as a touch sensor is read, rather than a simple 0 or 1. The triggerType
// function must therefore compare the value read from the touch sensor pin to some pre-computed "threshold" to determine whether or not
// the touch pad has in fact been touched. This is the threshold value that HomeSpan auto-calibrates for you as described above.
//
// Making things even more complex is that the ESP32 touch pins work in the reverse direction as touch pins on the ESP32-S2 and ESP32-S3.
// On the former, the values read from a touch sensor DECREASE when the touch pad is touched. On the latter, the values increase when the
// touch pad is touched. This means that for ESP32 devices, HomeSpan uses the following definition for SpanButton::TRIGGER_ON_TOUCH:
//
// boolean TRIGGER_ON_TOUCH(int pinArg) { return ( touchRead(pinArg) < threshold ); }
//
// whereas on ESP32-S2 and ESP32-S3 devices, HomeSpan uses a definition that flips the direction of the comparison:
//
// boolean TRIGGER_ON_TOUCH(int pinArg) { return ( touchRead(pinArg) > threshold ); }
//
// For ESP32-C3 devices, HomeSpan does not define TRIGGER_ON_TOUCH at all since there are no touch pins on an ESP32-C3 device! The compiler
// will throw an error if you try to create a SpanButton with triggerType=SpanButton::TRIGGER_ON_TOUCH, or if you call either of the
// calibration functions above.
//
// CREATING YOUR OWN triggerType FUNCTION
// --------------------------------------
// You are not limited to choosing among HomeSpan's three built-in triggerType functions. You can instead create your own triggerType function
// and pass it to SpanButton as the triggerType parameter in the SpanButton constructor. Your function must be of the form `boolean func(int)`,
// and should return TRUE if the "pushbutton" associated with the pin number that HomeSpan passes to your function as the integer argument
// has been "pressed", or FALSE if it has not. This allows you to expand the used of SpanButton to work with pin multiplexers, pin extenders,
// or any device that may require custom handling via a third-party library.
//
// For example, if you were using an MCP I/O Port Expander with the Adafruit mcp library, you could create a triggerType function for a pin
// on the MCP device that is connected to ground through a pushbutton as such:
//
// boolean MCP_READ(int mcpPin) { return ( !mcp.digitalRead(mcpPin) ); }
//
// And then simply pass MCP_READ to SpanButton as the triggerType parameter using any of the SpanButton constuctors:
//
// new SpanButton(23,MCP_READ); // uses default longTime, singleTime, and doubleTime
// new SpanButton(23,MCP_READ,2000,5,200); // expliclty sets longTime, singleTime, and doubletime
// new SpanButton(23,2000,5,200,MCP_READ); // alternative constructor with arguments in a different order
//
// Alternatively, you can use a lambda function as the triggerType parameter, thus creating your function on the fly when instantiating a SpanButton:
//
// new SpanButton(23,[](int mcpPin)->boolean{ return ( !mcp.digitalRead(mcpPin) ); });
//
// Note: If you create your own triggerType function, don't forget to perform any initialization of the "pin", or setup/configuration of a
// pin extender, etc., prior to instantiating a SpanButton that uses your custom function. HomeSpan cannot do this for you.
//

View File

@ -3,6 +3,10 @@
// DEVICE-SPECIFIC LED SERVICES //
////////////////////////////////////
#include "extras/PwmPin.h" // library of various PWM functions
////////////////////////////////////
struct DEV_DimmableLED : Service::LightBulb { // Dimmable LED
// This version of the Dimmable LED Service is similar to the one last used in Example 11, but now includes support for 3 physical PushButtons
@ -16,6 +20,7 @@ struct DEV_DimmableLED : Service::LightBulb { // Dimmable LED
int powerPin; // NEW! pin with pushbutton to turn on/off LED
int raisePin; // NEW! pin with pushbutton to increase brightness
int lowerPin; // NEW! pin with pushButton to decrease brightness
int channel; // PWM channel used for this LED (should be unique for each LED)
SpanCharacteristic *power; // reference to the On Characteristic
SpanCharacteristic *level; // reference to the Brightness Characteristic
int favoriteLevel=50; // NEW! keep track of a 'favorite' level
@ -31,8 +36,7 @@ struct DEV_DimmableLED : Service::LightBulb { // Dimmable LED
// NEW! Below we create three SpanButton() objects. In the first we specify the pin number, as required, but allow SpanButton() to use
// its default values for a LONG press (2000 ms), a SINGLE press (5 ms), and a DOUBLE press (200 ms). In the second and third we change the
// default LONG press time to 500 ms, which works well for repeatedly increasing or decreasing the brightness. Since we do not specify
// a triggerType, SpanButton uses the default TRIGGER_ON_TOUCH, which is suitable for a pushbutton that connects pin to GROUND when pressed.
// default LONG press time to 500 ms, which works well for repeatedly increasing or decreasing the brightness.
// All of the logic for increasing/decreasing brightness, turning on/off power, and setting/resetting a favorite brightness level is found
// in the button() method below.
@ -48,6 +52,8 @@ struct DEV_DimmableLED : Service::LightBulb { // Dimmable LED
Serial.print("Configuring Dimmable LED: Pin="); // initialization message
Serial.print(ledPin->getPin());
Serial.print(" Channel=");
Serial.print(channel);
Serial.print("\n");
} // end constructor

View File

@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2022 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*

View File

@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2022 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
@ -35,6 +35,12 @@
// //
////////////////////////////////////////////////////////////
// WARNING: THIS EXAMPLE STOPPED WORKING CORRECTLY SOMEWHERE AROUND THE IOS 15.2 OR IOS 15.3 UPDATE
// AND DOES NOT WORK AS OF IOS 15.4.1
//
// THE PROBLEM APPEARS TO BE IN THE RENDERING OF INDIVIDUAL VALVES IN THE HOME APP INTERFACE. THEY
// APPEAR IN THE EVE HOMEKIT APPLICATION, BUT NOT APPLE'S HOME APP.
#include "HomeSpan.h"
// HAP normally treats multiple Services created within the same Accessory as independent of one another. However, certain HAP Services are designed to represent a central point
@ -106,19 +112,19 @@ struct Shower : Service::Faucet { // this is our Shower structur
SpanCharacteristic *active=new Characteristic::Active(); // our implementation only requires the Active Characteristic
Shower(int nHeads){ // this is the constructor for Shower. It takes a single argument that specifies the number of spray heads (WaterValves)
for(int i=0;i<nHeads;i++) // for each spray head needed ---
addLink(new WaterValve(this)); // --- instantiate a new WaterValve AND link it to the Shower. Also, pass the Shower object's pointer to WaterValve constructor. We'll see why below.
Shower(int nHeads){ // this is the constructor for Shower. It takes a single argument that specifies the number of spray heads (WaterValves)
for(int i=0;i<nHeads;i++) // for each spray head needed ---
addLink(new WaterValve(this,i+1)); // --- instantiate a new WaterValve AND link it to the Shower. Also, pass the Shower object's pointer to WaterValve constructor. We'll see why below.
}
struct WaterValve : Service::Valve { // here we define our WaterValve structure as a child class of the HomeSpan Valve Service
SpanCharacteristic *active=new Characteristic::Active(1);; // the Active Characteristic is used to specify whether the Valve is Active (open) or Inactive (closed)
SpanCharacteristic *inUse=new Characteristic::InUse(); // the InUse Characteristic is used to specify whether water is actually flowing through value
SpanCharacteristic *active=new Characteristic::Active(1);; // the Active Characteristic is used to specify whether the Valve is Active (open) or Inactive (closed)
SpanCharacteristic *inUse=new Characteristic::InUse(); // the InUser Characteristic is used to specify whether water is actually flowing through value
Shower *shower; // storage for the pointer to the "controlling" Shower Service
WaterValve(Shower *s){ // this is constructor for WaterValve. It takes a single argument that points to the "controlling" Shower Service
shower=s; // store the pointer to the Shower Service
new Characteristic::ValveType(Characteristic::ValveType::SHOWER_HEAD); // specify the Valve Type as a Shower Head (note use of constant "Characteristic::ValveType::SHOWER_HEAD")
WaterValve(Shower *s, int i){ // this is constructor for WaterValve. It takes a single argument that points to the "controlling" Shower Service
shower=s; // store the pointer to the Shower Service
new Characteristic::ValveType(2); // specify the Value Type (2=Shower Head; see HAP R2 for other choices)
}
boolean update() override { // HomeSpan calls this whenever the Home App requests a change in a Valve's Active Characteristic
@ -128,10 +134,10 @@ struct Shower : Service::Faucet { // this is our Shower structur
}
void loop() override { // Here we check if the Shower is turned on or off, and determine if that means we need to update the Valve
if(shower->active->getVal() && active->getVal() && !inUse->getVal()) // If the Shower is Active, and the Valve is Active but NOT showing InUse...
inUse->setVal(1); // ...show Valve as InUse
else if(!shower->active->getVal() && inUse->getVal()) // Otherwise, if the Shower is NOT Active but Valve IS showing InUse...
inUse->setVal(0); // ...show Valve as NOT InUse
if(shower->active->getVal() && active->getVal() && !inUse->getVal()) // If the Shower is Active, and the Valve is Active, but InUse is NOT Active...
inUse->setVal(1); // ...set the InUse Characteristic to Active
else if(!shower->active->getVal() && inUse->getVal()) // Otherwise, if the Shower is NOT Active but InUse is Active...
inUse->setVal(0); // ...set the InUse Characteristic to NOT Active
}
}; // WaterValve

View File

@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2021-2024 Gregg E. Berman
* Copyright (c) 2021-2022 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*

View File

@ -3,6 +3,10 @@
// DEVICE-SPECIFIC LED SERVICES //
////////////////////////////////////
#include "extras/PwmPin.h" // library of various PWM functions
////////////////////////////////////
struct DEV_DimmableLED : Service::LightBulb { // Dimmable LED
// This version of the Dimmable LED Service includes a PushButton that can be used to turn on/off the LED. Status of both the

View File

@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2022 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*

View File

@ -1,251 +0,0 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2022-2024 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
********************************************************************************/
//////////////////////////////////////////////////////////////////
// //
// HomeSpan: A HomeKit implementation for the ESP32 //
// ------------------------------------------------ //
// //
// Example 20: Demonstrates various advance HomeSpan functions //
// by implementing a Bridge in which one or more //
// Lightbulb Accessories can be added and deleted //
// *dynamically* without needing to restart the //
// device //
// //
//////////////////////////////////////////////////////////////////
#include "HomeSpan.h"
// In Example 20 we will implement a bridge device supporting up to 10 Lightbulb Accessories. However, rather than pre-specifying the number of Lights, we
// will allow Light Accessories to be added and deleted dynamically by the user via the CLI. Changes are reflected in the Home App without the need to restart
// the device! Note this example uses a variety of advanced HomeSpan functions, as well as some detailed features of both the ESP32-IDF and C++ that have not been used
// in any of the previous examples.
// We will use a C++ array with 10 elements containing integers representing the Light "ID" of each Lightbulb Accessory implemented. An ID of zero means there is no
// Light defined in that element.
#include <array> // include the C++ standard library array container
std::array<int,10> lights; // declare "lights" to be an array of 10 integers
using std::fill; // place the std library function fill, remove, and find, into the global namespace so we can use them below without prefacing with "std::"
using std::remove;
using std::find;
// We will use non-volatile storage (NVS) to store the lights array so that the device can restore the current configuration upon rebooting
nvs_handle savedData; // declare savdData as a handle to be used with the NVS (see the ESP32-IDF for details on how to use NVS storage)
//////////////////////////////////////
void setup() {
Serial.begin(115200);
fill(lights.begin(),lights.end(),0); // initialize lights array with zeros in each of the 10 elements (no Light Accessories defined)
size_t len;
nvs_open("SAVED_DATA",NVS_READWRITE,&savedData); // open a new namespace called SAVED_DATA in the NVS
if(!nvs_get_blob(savedData,"LIGHTS",NULL,&len)) // if LIGHTS data found
nvs_get_blob(savedData,"LIGHTS",&lights,&len); // retrieve data
homeSpan.setLogLevel(1);
homeSpan.begin(Category::Lighting,"HomeSpan Lights");
// We begin by creating the Bridge Accessory
new SpanAccessory(1); // here we specified the AID=1 for clarity (it would default to 1 anyway if left blank)
new Service::AccessoryInformation();
new Characteristic::Identify();
new Characteristic::Model("HomeSpan Dynamic Bridge"); // defining the Model is optional
// Now we create Light Accessories based on what is recorded in the lights array
// We'll use C++ iterators to loop over all elements until we reach the end of the array, or find an element with a value of zero
for(auto it=lights.begin(); it!=lights.end() && *it!=0; it++) // loop over all elements (stopping when we get to the end, or hit an element with a value of zero)
addLight(*it); // call addLight (defined further below) with an argument equal to the integer stored in that element
// Next we create four user-defined CLI commands so we can add and delete Light Accessories from the CLI.
// The functions for each command are defined further below.
new SpanUserCommand('a',"<num> - add a new light accessory with id=<num>",addAccessory);
new SpanUserCommand('d',"<num> - delete a light accessory with id=<num>",deleteAccessory);
new SpanUserCommand('D'," - delete ALL light accessories",deleteAllAccessories);
new SpanUserCommand('u',"- update accessories database",updateAccessories);
// Finally we call autoPoll to start polling the background. Note this is purely optional and only used here to illustrate how to
// use autoPoll - you could instead have called the usual homeSpan.poll() function by including it inside the Arduino loop() function
homeSpan.autoPoll();
} // end of setup()
// Usually the Arduino loop() function would be defined somewhere here. But since we used autoPoll in the setup() function,
// we don't have to define the loop() function at all in this sketch! Why don't we get an error? Because HomeSpan includes
// a default loop() function, which prevents the compiler from complaining about loop() being undefined.
///////////////////////////
// This function creates a new Light Accessory with n as the "ID".
// It is called initially in setup() above to create Light Accessories based
// on what was stored in the lights array. It is also called in response to
// typing 'a' into the CLI (see below), which dynamically adds a new Light Accessory
// while the device is running.
void addLight(int n){
char name[32];
sprintf(name,"Light-%d",n); // create the name of the device using the specified "ID"
char sNum[32];
sprintf(sNum,"%010d",n); // create serial number from the ID - this is helpful in case we rename the Light to something else using the Home App
Serial.printf("Adding Accessory: %s\n",name);
new SpanAccessory(n+1); // IMPORTANT: add 1, since first Accessory with AID=1 is already used by the Bridge Accessory
new Service::AccessoryInformation();
new Characteristic::Identify();
new Characteristic::Name(name);
new Characteristic::SerialNumber(sNum);
new Service::LightBulb();
new Characteristic::On(0,true);
}
///////////////////////////
// This function is called in response to typing '@a <num>' into the CLI.
// It adds a new Light Accessory with ID=num, by calling addLight(num) above.
void addAccessory(const char *buf){
int n=atoi(buf+1); // read the value of <num> specified
if(n<1){ // ensure <num> is greater than 0
Serial.printf("Invalid Accessory number!\n");
return;
}
if(find(lights.begin(),lights.end(),n)!=lights.end()){ // search for this ID in the existing lights array - if found, report an error and return
Serial.printf("Accessory Light-%d already implemented!\n",n);
return;
}
auto it=find(lights.begin(),lights.end(),0); // find the next "free" element in the light array (the first element with a value of zero)
if(it==lights.end()){ // if there were no elements with a zero, the array is full and no new Lights can be added
Serial.printf("Can't add any more lights - max is %d!\n",lights.size());
return;
}
*it=n; // save light number
nvs_set_blob(savedData,"LIGHTS",&lights,sizeof(lights)); // update data in the NVS
nvs_commit(savedData);
addLight(n); // add light accessory by calling the function above!
}
///////////////////////////
// This function deletes an existing Light Accessory and is called
// in response to typing '@d <num>' into the CLI.
void deleteAccessory(const char *buf){
int n=atoi(buf+1); // same as above, we read the specified <num> and check that it is valid (i.e. greater than 0)
if(n<1){
Serial.printf("Invalid Accessory number!\n");
return;
}
// Below we use the homeSpan method deleteAccessory(aid) to completely delete the Accessory with AID=n+1.
// We add 1 because the AID of the first Light Accessory is 2, since the Bridge Accessory has an AID of 1.
// The deleteAccessory() method returns true if an Accessory with matching AID is found, otherwise it returns false.
// When deleting an Accessory, HomeSpan will print a delete message for every Service, Characteristic, loop() method,
// button() method, and SpanButton, associated with that Accessory. These are Level-1 Log messages, so you'll need
// to have the Log Level in the sketch set to 1 or 2 to receive the output.
if(homeSpan.deleteAccessory(n+1)){ // if deleteAccessory() is true, a match has been found
Serial.printf("Deleting Accessory: Light-%d\n",n);
fill(remove(lights.begin(),lights.end(),n),lights.end(),0); // remove entry from lights array and fill any undefined elements with zero
nvs_set_blob(savedData,"LIGHTS",&lights,sizeof(lights)); // update data in the NVS
nvs_commit(savedData);
} else {
Serial.printf("No such Accessory: Light-%d\n",n);
}
}
///////////////////////////
void deleteAllAccessories(const char *buf){
// This function is called in response to typing '@D' into the CLI.
// It deletes all Light Accessories
if(lights[0]==0){ // first check that there is at least one Light Accessory by checking for a non-zero ID in lights[0]
Serial.printf("There are no Light Accessories to delete!\n");
return;
}
for(auto it=lights.begin(); it!=lights.end() && *it!=0; it++) // use an iterator to loop over all non-zero elements in the lights array...
homeSpan.deleteAccessory(*it+1); // ... and delete the matching Light Accessory (don't forgot to add 1 to the Light ID to form the AID)
fill(lights.begin(),lights.end(),0); // zero out all the elements in the lights array, since all Light Accessories have been deleted
nvs_set_blob(savedData,"LIGHTS",&lights,sizeof(lights)); // update data in the NVS
nvs_commit(savedData);
Serial.printf("All Light Accessories deleted!\n");
}
///////////////////////////
// Lastly we have the all-important updateAccessories function.
// This is called in response to typing '@u' into the CLI.
// Though the above functions can be used to add and delete Light Accessories
// dyammically, Controllers such as the Home App that are already connected to
// the device don't yet know additional Light Accessories have been added to (or
// deleted from) the overall Accessories datase. To let them know, HomeSpan needs
// to increment the HAP Configuration Number and re-broadcast it via MDNS so all
// connected Controllers are aware that they need to request a refresh from the device.
// When you type '@u' into the CLI, you should see a lot of activity between the device
// and any connected Controllers as they request a refresh. Be patient - it can take up to a
// minute for changes to be properly reflected in the Home App on your iPhone or Mac.
void updateAccessories(const char *buf){
// note the updateDatabase() method returns true if the database has indeed changed (e.g. one or more new Light Accessories were added), or false if nothing has changed
if(homeSpan.updateDatabase())
Serial.printf("Accessories Database updated. New configuration number broadcasted...\n");
else
Serial.printf("Nothing to update - no changes were made!\n");
}
///////////////////////////

View File

@ -1,173 +0,0 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
********************************************************************************/
////////////////////////////////////////////////////////////
// //
// HomeSpan: A HomeKit implementation for the ESP32 //
// ------------------------------------------------ //
// //
// Example 21: Using the Identify Characteristic //
// //
// //
////////////////////////////////////////////////////////////
// This sketch is similar to Example 5, in which we implemented two simple Lightbulb Accessories,
// except now we will also add functionality for the Identify Characteristic (we will also configure
// the device as a Bridge Accessory, instead of two standalone Accessories).
// Recall that the Identify Characteristic has been instantiated in every example sketch since it
// is a required Characteristic of the AccessoryInformation Service, and that Service is itself
// required to be present for every Accessory. Thus, every Accessory (including the Bridge
// Accessory if used), has its own instant of the Identify Characteristic.
// Though not typically used during normal operation of an Accessory, the Identify Characteristic
// can be useful when first pairing your device to HomeKit. You may have noticed when pairing your
// device using the Home App that there is the word "Identify" at the bottom of each of the screens
// that ask you what you want to name each Accessory, what room the Accessory should be assigned to, etc.
// Clicking "Identify" on any of those screens causes HomeKit to send an update request to the
// Identify Characteristic associated with the corresponding Accessory. As with any Characteristic that
// is updated via the Home App, this will trigger a call to the update() method for the enclosing Service.
// The purpose of this is so that your device can run some sort of "identification routine" when requested,
// allowing you to visually confirm that you are indeed pairing the correct device. For example, if you
// have three separate devices wired to three different lights or appliances, you want to make sure that when
// you start pairing each of them to the Home App you are connected to the device you intend.
// The identification routine can be anything you choose. The only HAP requirement is that it should not take
// longer than 5 seconds to run. In the sketch below we have created an identification routine that logs a
// message to the Serial Monitor and blinks the LED associated with the Accessory 3 times whenever its
// Identify Characteristic is updated.
#include "HomeSpan.h"
//////////////////////////////////////
// Below is the same DEV_LED Lightbulb Service we've used in many of the previous examples
struct DEV_LED : Service::LightBulb {
int ledPin;
SpanCharacteristic *power;
DEV_LED(int ledPin) : Service::LightBulb(){
power=new Characteristic::On();
this->ledPin=ledPin;
pinMode(ledPin,OUTPUT);
}
boolean update(){
digitalWrite(ledPin,power->getNewVal());
LOG0("LED %d: Power %s\n",ledPin,power->getNewVal()?"ON":"OFF");
return(true);
}
};
//////////////////////////////////////
// NEW: Here we derive a new class, DEV_INFO, from the Accessory Information Service
// This structure takes a single argument (ledPin), creates a name from it, and assigns
// it to the Name Characteristic.
// It also instantiates the required Identify Characteristic, and implements an update() method
// that logs a message to the Serial Monitor and blinks the associated LED three times.
// Note that in the update() method we do not bother to check which Characteristic has been updated.
// This is because the only possibility is the Identify Characteristic.
// Also, we do not need to use getNewVal() to check the value. The Home App always sends a value of 1,
// since it is just trying to trigger the identification routine (the value itself is meaningless).
struct DEV_INFO : Service::AccessoryInformation {
int ledPin;
DEV_INFO(int ledPin) : Service::AccessoryInformation(){
new Characteristic::Identify();
char c[64];
sprintf(c,"LED-%d",ledPin);
new Characteristic::Name(c);
this->ledPin=ledPin;
pinMode(ledPin,OUTPUT);
}
boolean update(){
LOG0("Running Identification for LED %d\n",ledPin);
for(int i=0;i<3;i++){
digitalWrite(ledPin,HIGH);
delay(500);
digitalWrite(ledPin,LOW);
delay(500);
}
return(true);
}
};
//////////////////////////////////////
void setup() {
Serial.begin(115200);
homeSpan.setLogLevel(1);
homeSpan.begin(Category::Lighting,"HomeSpan LEDS");
// Here we replace the usual construct:
// new SpanAccessory();
// new Service::AccessoryInformation();
// new Characteristic::Identify();
// with this:
new SpanAccessory();
new DEV_INFO(13); // instantiate a new DEV_INFO structure that will run our custom identification routine to blink an LED on pin 13 three times
new SpanAccessory();
new DEV_INFO(16); // Note we instantiate a new DEV_INFO structure for each Accessory in this device
new DEV_LED(16); // Here we instantiate the usual DEV_LED structure that controls the LED during normal operation
new SpanAccessory(); // Here we add a second LED Accessory
new DEV_INFO(17);
new DEV_LED(17);
}
//////////////////////////////////////
void loop(){
homeSpan.poll();
}
//////////////////////////////////////
// NOTE: Once a device has been paired, it is no longer possible to trigger the Identify Characteristic from the Home App.
// Apple assumes that the identification routine is no longer needed since you can always identify the device by simply operating it.
// However, the Eve for HomeKit app DOES provide an "ID" button in the interface for each Accessory that can be used to trigger
// the identification routine for that Accessory at any time after the device has been paired.

View File

@ -1,159 +0,0 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
********************************************************************************/
////////////////////////////////////////////////////////////////
// //
// HomeSpan: A HomeKit implementation for the ESP32 //
// ------------------------------------------------ //
// //
// Example 22: Demonstrates the use of the TLV8 Library //
// by implementing DisplayOrder, an optional //
// TLV8 Characteristic used with the TV Service //
// to set the order in which TV Inputs are //
// displayed for selection in the Home App //
// //
////////////////////////////////////////////////////////////////
#include "HomeSpan.h"
// NOTE: Please see the "Other Examples -> Television" sketch for complete details on how to implement a Television Service. The focus
// of this sketch is solely to demonstrate how to use the TLV8 Library to create TLV8 data for use with the DisplayOrder Characteristic.
// First we define a simple Television Input Source Service
struct TVInput : Service::InputSource {
SpanCharacteristic *inputID;
SpanCharacteristic *inputName;
TVInput(uint32_t id, const char *name) : Service::InputSource() {
inputID = new Characteristic::Identifier(id);
inputName = new Characteristic::ConfiguredName(name);
new Characteristic::IsConfigured(Characteristic::IsConfigured::CONFIGURED);
new Characteristic::CurrentVisibilityState(Characteristic::CurrentVisibilityState::VISIBLE);
}
};
// Next we define a simple Television Service
struct HomeSpanTV : Service::Television {
SpanCharacteristic *active = new Characteristic::Active(0);
SpanCharacteristic *activeID = new Characteristic::ActiveIdentifier(10);
SpanCharacteristic *displayOrder; // Create a pointer to use for the new TLV8 DisplayOrder Characteristic, which will be instantiated below once we build the TLV8 record
HomeSpanTV() : Service::Television() {
// Before we instantiate displayOrder, we need to build a TLV8 object with the information required
// by the DisplayOrder Characteristic. The (undocumented by Apple!) TLV8 specifications for the
// DisplayOrder Characteristic are as follows:
// TAG NAME FORMAT DESCRIPTION
// ---- ------------- ------ --------------------------------------------
// 0x01 inputSourceID uint32 ID of the Input Source to be displayed first
// 0x00 separator none Empty element to separate the inputSourceIDs
// 0x01 inputSourceID uint32 ID of the Input Source to be displayed second
// 0x00 separator none Empty element to separate the inputSourceIDs
// 0x01 inputSourceID uint32 ID of the Input Source to be displayed third
// 0x00 separator none Empty element to separate the inputSourceIDs
// etc...
// To start, instantiate a new TLV8 object
TLV8 orderTLV; // creates an empty TLV8 object
// Next, fill it with TAGS and VALUES based on the above specification. The easiest, though
// not necessarily most elegant, way to do this is by simply adding each TAG/VALUE as follows:
orderTLV.add(1,10); // TAG=1, VALUE=ID of first Input Source to be displayed
orderTLV.add(0); // TAG=0 (no value)
orderTLV.add(1,20); // TAG=1, VALUE=ID of the second Input Source to be displayed
orderTLV.add(0); // TAG=0 (no value)
orderTLV.add(1,50); // TAG=1, VALUE=ID of the third Input Source to be displayed
orderTLV.add(0); // TAG=0 (no value)
orderTLV.add(1,30); // TAG=1, VALUE=ID of the fourth Input Source to be displayed
orderTLV.add(0); // TAG=0 (no value)
orderTLV.add(1,40); // TAG=1, VALUE=ID of the fifth Input Source to be displayed
// Based on the above structure, we expect the Home App to display our input sources based on their IDs
// in the following order: 10, 20, 50, 30, 40. These IDs must of course match the IDs you choose
// for your input sources when you create them at the end of this sketch in setup()
// Now we can instantiate displayOrder using the TLV8 object created above as its initial value
displayOrder = new Characteristic::DisplayOrder(orderTLV); // set the "value" of DisplayOrder to be the orderTLV object we just created
// That's it - you've created your first TLV8 Characteristic!
}
// Below we define the usual update() loop. There is nothing "TLV-specific" about this part of the code
boolean update() override {
if(active->updated()){
LOG0("Set TV Power to: %s\n",active->getNewVal()?"ON":"OFF");
}
if(activeID->updated()){
LOG0("Set Input Source to ID=%d\n",activeID->getNewVal());
}
return(true);
}
};
///////////////////////////////
void setup() {
Serial.begin(115200);
homeSpan.setLogLevel(2);
homeSpan.begin(Category::Television,"HomeSpan Television");
SPAN_ACCESSORY();
(new HomeSpanTV()) // Define a Television Service and link in the InputSources!
->addLink(new TVInput(10,"Xfinity"))
->addLink(new TVInput(20,"BlueRay Disc"))
->addLink(new TVInput(30,"Amazon Prime"))
->addLink(new TVInput(40,"Netflix"))
->addLink(new TVInput(50,"Hulu"))
;
}
//////////////////////////////////////
void loop(){
homeSpan.poll();
}
//////////////////////////////////////

View File

@ -1,138 +0,0 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2023 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
********************************************************************************/
// This example demonstrates the use of a custom Partition Scheme file: "partitions.csv"
// During compilation, if a file with this EXACT name is placed in the sketch folder,
// the esptool performing the compilation will use the partition scheme found
// in "partitions.csv" regardless of what partition scheme you selected in the Arduino IDE.
// Note if you change the partition scheme it is highly recommended that you fully erase the flash
// upon your next compile/upload by enabling the "Erase All Flash" option from the Arduino IDE menu.
// NOTE: remember to turn OFF this option after you've successully uploaded a sketch with the new
// partition scheme, else you will continue to erase everything saved in the NVS every time you upload
// a new sketch (which is likely NOT what you want to occur).
// The main reason for wanting to create your own partition scheme is to expand the NVS space.
// All of the pre-configured partition scheme you can select from the Arduino IDE provide
// for 504 records of NVS space. This is usuall sufficient for most HomeSpan projects, but if
// you have a LOT of Accessories (as per below) AND you are saving their states in NVS, you can
// use up all the NVS space. If this occurs, HomeSpan will warn you of low NVS space upon boot-up.
// The custom partition scheme included in this sketch folder solves this problem by eliminating
// the SPIFFs partition (which is generally not used by HomeSpan) and using this portion of the flash
// to provide an NVS space with 3906 records --- more than enough for even the largest projects.
// For reference, in addition to HomeSpan's internal use of NVS (about 32 records), saving a
// numerical Characteristic consumes one additional NVS record, and saving a string Characteristic (of
// less than 32 characters) consumes two NVS records. Also, the ESP32 WiFi stack consumes about 130
// additional NVS records once initialized. As such, the sketch below requires:
// 32 records (internal HomeSpan use)
// + 320 records (80 Accessories * 4 saved numerical Characterstics)
// + 160 records (80 Accessories * 2 records per saved string Characterstic)
// + 130 records (with WiFi initialized)
// ----------------------------------------
// = 642 NVS records needed (which exceeds the normal 504 limit, unless a custom partition scheme is used)
// Note that once HomeSpan is paired with HomeKit, additional NVS records will be consumed to store the
// pairing information for each verified HomeKit Controller.
// Note also that when compiling under the Arduino IDE, the IDE reports the size of partition based on the
// Partition Scheme you selected in the IDE menu, even though that scheme is not actually used if you have your
// own "partition.csv" file, as in this example. This may lead the IDE to report an incorrect partition size.
///////////////////////////////////////////////////////////////////////////////////////////
#include "HomeSpan.h"
#define MAX_LIGHTS 80 // configure for 80 Light Accessories
struct RGB_Light : Service::LightBulb {
Characteristic::On power{0,true}; // save these 4 numerical Characteristics (4*80 = 320 NVS records)
Characteristic::Hue H{0,true};
Characteristic::Saturation S{0,true};
Characteristic::Brightness V{0,true};
int lightNumber;
RGB_Light(int n) : Service::LightBulb(){
lightNumber=n;
LOG0("Configured RGB Light-%0d\n",lightNumber);
}
boolean update(){
if(power.updated())
LOG0("Light-%d: Power=%s",lightNumber,power.getNewVal()?"ON":"OFF");
if(H.updated())
LOG0("Light-%d: Hue=%d",lightNumber,H.getNewVal());
if(S.updated())
LOG0("Light-%d: Saturation=%d",lightNumber,S.getNewVal());
if(V.updated())
LOG0("Light-%d: Brightness=%d",lightNumber,V.getNewVal());
return(false);
}
};
void setup() {
Serial.begin(115200);
homeSpan.begin(Category::Lighting,"HomeSpan Max");
new SpanAccessory();
new Service::AccessoryInformation();
new Characteristic::Identify();
for(int i=1;i<=MAX_LIGHTS;i++){
char c[60];
sprintf(c,"Light-%02d",i);
new SpanAccessory();
new Service::AccessoryInformation();
new Characteristic::Identify();
new Characteristic::Name(c,true); // save this string Characteristic (2*80 = 160 NVS records)
new RGB_Light(i);
}
}
//////////////////////////////////////
void loop(){
homeSpan.poll();
}

View File

@ -1,14 +0,0 @@
# --- Custom Partition Table for HomeSpan ---
#
# Similar to min_spiffs, except that the 128K SPIFF block at the end
# is replaced by a 128K NVS block, and the initial 20K NVS block
# is no longer used. Note this table is designed for use with 4MB Flash.
# To use with 8MB Flash, increase app0 and app1 by 2048K to become 3968K each.
# To use with 16MB Flash, increase app0 and app1 by 6144K to become 8064K each
#
unused_nvs,data,nvs,,20K,
otadata,data,ota,,8K,
app0,app,ota_0,,1920K,
app1,app,ota_1,,1920K,
nvs,data,nvs,,128K,
coredump,data,coredump,,64K,
1 # --- Custom Partition Table for HomeSpan ---
2 #
3 # Similar to min_spiffs, except that the 128K SPIFF block at the end
4 # is replaced by a 128K NVS block, and the initial 20K NVS block
5 # is no longer used. Note this table is designed for use with 4MB Flash.
6 # To use with 8MB Flash, increase app0 and app1 by 2048K to become 3968K each.
7 # To use with 16MB Flash, increase app0 and app1 by 6144K to become 8064K each
8 #
9 unused_nvs,data,nvs,,20K,
10 otadata,data,ota,,8K,
11 app0,app,ota_0,,1920K,
12 app1,app,ota_1,,1920K,
13 nvs,data,nvs,,128K,
14 coredump,data,coredump,,64K,

View File

@ -1,119 +0,0 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
********************************************************************************/
// Sometimes you need to access Characteristics from outside of the Service structure
// in which they were created so you can read and/or modify them in other parts of
// a sketch, such as from within the main Arduino loop().
// This sketch is basically the same as Tutorial Example 5, in which we created
// two working LEDs attached to pins 16, and 17. However, in this sketch we will
// create global pointers to the LED Services that we can then use in the main loop() to
// do something unique.
#include "HomeSpan.h"
//////////////////////////////////////
// First we define our DEV_LED Service exactly the same as in Tutorial Example 5.
// This Service contains a single Characteristic named "power" of type Chacracteristic::On
struct DEV_LED : Service::LightBulb {
int ledPin;
SpanCharacteristic *power;
DEV_LED(int ledPin) : Service::LightBulb(){
power=new Characteristic::On();
this->ledPin=ledPin;
pinMode(ledPin,OUTPUT);
}
boolean update(){
digitalWrite(ledPin,power->getNewVal());
return(true);
}
};
//////////////////////////////////////
// Next we create two pointers to the DEV_LED Service. These are created
// outside of any class or function so they are globally-scoped and can be
// accessed from anywhere else in this sketch.
// Note that there are just POINTERS to DEV_LED objects. The objects themselves
// are not yet created.
DEV_LED *led16; // pointer to a DEV_LED structure to be used below to reference a DEV_LED object assigned to pin 16
DEV_LED *led17; // pointer to a DEV_LED structure to be used below to reference a DEV_LED object assigned to pin 17
//////////////////////////////////////
void setup() {
Serial.begin(115200);
homeSpan.begin(Category::Lighting,"HomeSpan LED");
new SpanAccessory();
new Service::AccessoryInformation();
new Characteristic::Identify();
led16=new DEV_LED(16); // this is the key step - we SAVE the pointer returned by 'new DEV_LED(16)' in the global variable led16 created above
new SpanAccessory();
new Service::AccessoryInformation();
new Characteristic::Identify();
led17=new DEV_LED(17); // also save the pointer to the second LED object using the global variable led17 created above
}
//////////////////////////////////////
void loop(){
homeSpan.poll();
// Because the pointers led16 and led17 were created in the global scope, they will still exist even after setup() is completed.
// This means we can use them to access the Characteristics within each of those Services.
// Here we access the power Characteristic of both Services and check to see if they are BOTH on, and if so,
// we turn them both off and print a "power overload" message.
// Note how you can use all the same methods, such as getVal() and setVal(), just as you would do in the Service itself.
// Caution: always use getVal(), not getNewVal(), which is only formally defined from within the Service update() method.
if(led16->power->getVal() && led17->power->getVal()){
Serial.printf("Power overload! Can't have both LED's on at the same time. Turn off both LEDs...\n");
led16->power->setVal(false);
led17->power->setVal(false);
}
}
//////////////////////////////////////

View File

@ -1,112 +0,0 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2023 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
********************************************************************************/
// HomeSpan Fading-LED Example. Demonstrates use of:
//
// LedPin::fade() and LedPin::fadeStatus() methods
//
// In this sketch we control a single dimmable LED using the Home App as well as a SpanButton.
// You can control the brightness of the LED from the Home App, but the SpanButton only turns the
// LED either fully on (if it's off) or fully off (if it's already on).
//
// Rather than set the LED to a specific brightness, this sketch uses the ESP32's hardware-based fading
// functionality to fade the LED from one level to the next. We set the timing for each fade to be 2000 ms,
// proportional to the difference between the current brightness and the desired brightness. This means it
// will take a full 2 seconds to fade the LED from 0-100, but only 1 second to fade from half-brightness to
// off.
#include "HomeSpan.h"
////////////////////////////////////
struct FadingLED : Service::LightBulb {
LedPin *ledPin; // reference to Led Pin
SpanCharacteristic *power; // reference to the On Characteristic
SpanCharacteristic *level; // reference to the Brightness Characteristic
FadingLED(int _ledPin, int _buttonPin) : Service::LightBulb(){
power=new Characteristic::On();
level=new Characteristic::Brightness(0);
ledPin=new LedPin(_ledPin);
new SpanButton(_buttonPin);
}
boolean update(){
ledPin->fade(power->getNewVal()*level->getNewVal(),2000,LedPin::PROPORTIONAL); // use fade() to set new level; timing=2 seconds, proportional scale
while(ledPin->fadeStatus()==LedPin::FADING); // wait until fading is completed
return(true);
}
void button(int pin, int pressType) override {
// Below we turn LED fully on or off depending on whether power is on
// Unlike above, we will NOT wait for the fading to complete, but will return immediately
if(ledPin->fade(100-(power->getVal())*100,2000,LedPin::PROPORTIONAL)!=0) // use fade to either turn fully on or fully off; check return status to see if call was successful
Serial.printf("Button Press Ignored\n");
}
void loop() override {
// Below we set power and level once fading from a button press is completed
if(ledPin->fadeStatus()==LedPin::COMPLETED){
power->setVal(1-power->getVal());
level->setVal(power->getVal()?100:0);
}
}
};
//////////////////////////////////
void setup() {
Serial.begin(115200);
homeSpan.begin(Category::Lighting,"Fading LED");
new SpanAccessory();
new Service::AccessoryInformation();
new Characteristic::Identify();
new FadingLED(26,4); // first argument is LED Pin, second argument is PushButton Pin
}
//////////////////////////////////////
void loop(){
homeSpan.poll();
}

View File

@ -1,141 +0,0 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2023 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
********************************************************************************/
// This example demonstrates how to control real-world Stepper Motors using HomeSpan's
// StepperControl Class through the implemention of a Motorized Window Shade Accessory.
// The sketch below is based on the more-fully commented WindowShade Accessory included in
// Tutorial Example 13 (this sketch only contains comments related to the use of the stepper motors).
// The Accessory we will use two stepper motors:
// * one motor to open/close the window shade, driven by an Adafruit TB6612 driver board (https://www.adafruit.com/product/2448)
// * one motor to tilt the window shade slats, driven by a SparkFun A3967 driver board (https://www.sparkfun.com/products/12779)
// See HomeSpan's StepperControl documentation for details on the classes used to control these driver boards,
// as well as for instructions on how you can easily extend StepperControl to create a custom driver for any board.
#include "HomeSpan.h"
////////////////////////////////////
struct DEV_WindowShade : Service::WindowCovering {
Characteristic::CurrentPosition currentPos{0,true};
Characteristic::TargetPosition targetPos{0,true};
Characteristic::CurrentHorizontalTiltAngle currentTilt{0,true};
Characteristic::TargetHorizontalTiltAngle targetTilt{0,true};
StepperControl *mainMotor; // motor to open/close shade
StepperControl *slatMotor; // motor to tilt shade slats
DEV_WindowShade(StepperControl *mainMotor, StepperControl *slatMotor) : Service::WindowCovering(){
this->mainMotor=mainMotor; // save pointers to the motors
this->slatMotor=slatMotor;
mainMotor->setAccel(10,20); // set acceleration parameters for main motor
mainMotor->setStepType(StepperControl::HALF_STEP); // set step type to HALF STEP for main motor
LOG0("Initial Open/Close Position: %d\n",currentPos.getVal());
LOG0("Initial Slat Position: %d\n",currentTilt.getVal());
mainMotor->setPosition(currentPos.getVal()*20); // define initial position of main motor
slatMotor->setPosition(currentTilt.getVal()*11.47); // define initial position of slat motor
}
///////////
boolean update(){
if(targetPos.updated()){
// Move motor to absolute position, assuming 400 steps per revolution and 5 revolutions for full open/close travel,
// for a total of 2000 steps of full travel. Specify that motor should enter the BRAKE state upon reaching to desired position.
// Must multiply targetPos, which ranges from 0-100, by 20 to scale to number of motor steps needed
mainMotor->moveTo(targetPos.getNewVal()*20,5,StepperControl::BRAKE);
LOG1("Setting Shade Position=%d\n",targetPos.getNewVal());
}
if(targetTilt.updated()){
// Move motor to absolute position, assuming 2064 steps per revolution and 1/2 revolution for full travel of slat tilt in either direction
// Must multiply targetPos, which ranges from -90 to 90, by 11.47 to scale number of motor steps needed
// Note this driver board for this motor does not support a "short brake" state
slatMotor->moveTo(targetTilt.getNewVal()*11.47,5);
LOG1("Setting Shade Position=%d\n",targetTilt.getNewVal());
}
return(true);
}
///////////
void loop(){
// If the current window shade position or tilt does NOT equal the target position, BUT the motor has stopped moving,
// we must have reached the target position, so set the current position equal to the target position
if(currentPos.getVal()!=targetPos.getVal() && !mainMotor->stepsRemaining()){
currentPos.setVal(targetPos.getVal());
LOG1("Main Motor Stopped at Shade Position=%d\n",currentPos.getVal());
}
if(currentTilt.getVal()!=targetTilt.getVal() && !slatMotor->stepsRemaining()){
currentTilt.setVal(targetTilt.getVal());
LOG1("Slat Motor Stopped at Shade Tilt=%d\n",currentTilt.getVal());
}
}
};
////////////////////////////////////
void setup() {
Serial.begin(115200);
homeSpan.begin(Category::WindowCoverings,"Motorized Shade");
// MAKE SURE TO CHANGE THE PINS NUMBERS BELOW TO MATCH YOUR ESP32 DEVICE!!!
// THE PINS NUMBER SPECIFIED IN THIS EXAMPLE WORK WITH THE ORIGINAL ESP32, BUT WILL LIKELY CRASH AN ESP32-S2, -S3, or -C3.
new SpanAccessory();
new Service::AccessoryInformation();
new Characteristic::Identify();
new DEV_WindowShade(new Stepper_TB6612(23,32,22,14,33,27), new Stepper_A3967(18,21,5,4,19)); // instantiate drivers for each board and specify pins used on ESP32
}
//////////////////////////////////////
void loop(){
homeSpan.poll();
}

View File

@ -1,131 +0,0 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2024 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
********************************************************************************/
/////////////////////// PIXEL TESTER //////////////////////////
// This sketch is designed to help identify the proper settings to use for a NeoPixel, NeoPixel Strip,
// or any device containing one or more single-wire addressable RGB or RGBW LEDs (the "Pixel Device").
// Before compiling, set PIXEL_PIN to the ESP32 pin that is connected to your Pixel Device, and set NPIXELS to
// the numnber of Pixels in the Pixel Device. Note that for some strips a single chip controls more than one LED,
// in which case NPIXELS should be set to the number of controlling chips, NOT the number of LEDs.
// To start, the second argument of the Pixel constructor for the testPixel object below should remain
// set to PixelType::RGBW
// When run, the sketch will repeatedly cycle colors by setting ALL pixels in the device first to RED, then GREEN,
// followed by BLUE, and then finally WHITE. After a short pause, the cycle repeats.
// For each color the brightness will increase from 0 through MAX_BRIGHTNESS, and then back to 0. You can change
// MAX_BRIGHTNESS to something lower than 255 if you want to limit how bright the pixels get.
// For Pixel Devices with more than one pixel, diagnostics are as follows:
//
// * If all 4 colors repeatedly flash in the order expected, this means the base setting of PixelType::RGBW is correct!
//
// * If instead of each pixel being set to the same color, the pixels in the strip each light up with a different color
// (or no color at all), this means you have an RGB LED, not an RGBW LED. Change the second parameter of the constructor
// to PixelType::RGB and re-run the sketch.
//
// * If all of the pixels are being set to the same color, but the sequence is NOT in the order RED, GREEN, BLUE, change
// the second parameter of the constructor so that the order of the PixelType colors match the sequence of the colors
// that appear on the Pixel Device. For example, if your RGBW Pixel Device flashes GREEN, RED, BLUE, and than WHITE, use
// PixelType::GRBW.
// For Pixel Devices with only a single pixel, diagnostics are as follows:
// * If all 4 colors repeatedly flash in the order expected, this means the base setting of PixelType::RGBW is correct!
//
// * If the pixel does not light at all when set to WHITE this means you have an RGB LED, not an RGBW LED. Change the
// second parameter of the constructor to PixelType::RGB and re-run the sketch.
//
// * If all of the pixels are being set to the same color, but the sequence is NOT in the order RED, GREEN, BLUE, change
// the second parameter of the constructor so that the order of the PixelType colors match the sequence of the colors
// that appear on the Pixel Device. For example, if your RGB Pixel Device flashes GREEN, RED, and then BLUE, use
// PixelType::GRB.
//////////////////////////////////////
#include "HomeSpan.h"
//////////////////////////////////////
#define MAX_BRIGHTNESS 255 // maximum brightness when flashing RGBW [0-255]
#define PIXEL_PIN 26 // set this to whatever pin you are using - note pin cannot be "input only"
#define NPIXELS 8 // set to number of pixels in strip
Pixel testPixel(PIXEL_PIN, PixelType::RGBW); // change the second argument until device operates with correct colors
//////////////////////////////////////
void setup() {
Serial.begin(115200);
delay(1000);
Serial.printf("\n\nPixel Test on pin %d with %d pixels\n\n",PIXEL_PIN,NPIXELS);
}
//////////////////////////////////////
void flashColor(boolean r, boolean g, boolean b, boolean w){
for(int i=0;i<MAX_BRIGHTNESS;i++){
testPixel.set(Pixel::RGB(i*r,i*g,i*b,i*w),NPIXELS);
delay(4);
}
for(int i=MAX_BRIGHTNESS;i>=0;i--){
testPixel.set(Pixel::RGB(i*r,i*g,i*b,i*w),NPIXELS);
delay(4);
}
}
//////////////////////////////////////
void loop(){
Serial.printf("Red...");
flashColor(1,0,0,0);
Serial.printf("Green...");
flashColor(0,1,0,0);
Serial.printf("Blue...");
flashColor(0,0,1,0);
if(testPixel.isRGBW()){
Serial.printf("White...");
flashColor(0,0,0,1);
}
Serial.printf("Pausing.\n");
delay(1000);
}
//////////////////////////////////////

View File

@ -1,388 +0,0 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 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 implement a Web Server alongside //
// of HomeSpan to create a Programmable Hub serving up to //
// 12 Configurable Lights. Allows for dynamic changes //
// to Accessories without needing to reboot //
// //
//////////////////////////////////////////////////////////////////
#include "HomeSpan.h"
#include <WebServer.h> // include WebServer library
WebServer webServer(80); // create WebServer on port 80
#define MAX_LIGHTS 12
#define MAX_NAME_LENGTH 32
#define HUB_NAME "lighthub"
enum colorType_t : uint8_t {
NO_COLOR,
TEMPERATURE_ONLY,
FULL_RGB
};
uint32_t aidStore=2; // keep track of unique AID numbers - start with AID=2
struct lightData_t {
char name[MAX_NAME_LENGTH+1]="";
uint32_t aid=0;
boolean isDimmable:1;
colorType_t colorType:2;
} lightData[MAX_LIGHTS];
nvs_handle savedData;
//////////////////////////////////////
void setup() {
Serial.begin(115200);
size_t len;
nvs_open("SAVED_DATA",NVS_READWRITE,&savedData); // open a new namespace called SAVED_DATA in the NVS
if(!nvs_get_blob(savedData,"LIGHTDATA",NULL,&len)) // if LIGHTDATA data found
nvs_get_blob(savedData,"LIGHTDATA",&lightData,&len); // retrieve data
nvs_get_u32(savedData,"AID",&aidStore); // get AID, if it exists
homeSpan.setLogLevel(1);
homeSpan.setHostNameSuffix(""); // use null string for suffix (rather than the HomeSpan device ID)
homeSpan.setPortNum(1201); // change port number for HomeSpan so we can use port 80 for the Web Server
homeSpan.setWifiCallback(setupWeb); // need to start Web Server after WiFi is established
homeSpan.begin(Category::Lighting,"HomeSpan Light Hub",HUB_NAME);
new SpanAccessory(1); // here we specified the AID=1 for clarity (it would default to 1 anyway if left blank)
new Service::AccessoryInformation();
new Characteristic::Identify();
new Characteristic::Model("HomeSpan Programmable Hub");
new Characteristic::AccessoryFlags();
for(int i=0;i<MAX_LIGHTS;i++){ // create Light Accessories based on saved data
if(lightData[i].aid)
addLight(i);
}
new SpanUserCommand('a',"<name> - add non-dimmable light accessory using name=<name>",[](const char *c){addLight(c+1,false,NO_COLOR);});
new SpanUserCommand('A',"<name> - add dimmable light accessory using name=<name>",[](const char *c){addLight(c+1,true,NO_COLOR);});
new SpanUserCommand('t',"<name> - add non-dimmable light accessory with color-temperature control using name=<name>",[](const char *c){addLight(c+1,false,TEMPERATURE_ONLY);});
new SpanUserCommand('T',"<name> - add dimmable light accessory with color-temperature control using name=<name>",[](const char *c){addLight(c+1,true,TEMPERATURE_ONLY);});
new SpanUserCommand('r',"<name> - add non-dimmable light accessory with full RGB color control using name=<name>",[](const char *c){addLight(c+1,false,FULL_RGB);});
new SpanUserCommand('R',"<name> - add dimmable light accessory with full RGB color control using name=<name>",[](const char *c){addLight(c+1,true,FULL_RGB);});
new SpanUserCommand('l'," - list all light accessories",listAccessories);
new SpanUserCommand('d',"<index> - delete a light accessory with index=<index>",[](const char *buf){deleteAccessory(atoi(buf+1));});
new SpanUserCommand('D'," - delete ALL light accessories",deleteAllAccessories);
new SpanUserCommand('u',"- update accessories database",updateAccessories);
} // end of setup()
///////////////////////////
void loop(){
homeSpan.poll();
webServer.handleClient(); // handle incoming web server traffic
}
///////////////////////////
void addLight(int index){
Serial.printf("Adding Light Accessory: Name='%s' Dimmable=%s Color=%s\n",
lightData[index].name,lightData[index].isDimmable?"YES":"NO",lightData[index].colorType==NO_COLOR?"NONE":(lightData[index].colorType==TEMPERATURE_ONLY?"TEMPERATURE_ONLY":"FULL_RGB"));
new SpanAccessory(lightData[index].aid);
new Service::AccessoryInformation();
new Characteristic::Identify();
new Characteristic::Name(lightData[index].name);
char sNum[32];
sprintf(sNum,"Light-%02d",index);
new Characteristic::SerialNumber(sNum);
new Service::LightBulb();
new Characteristic::On(0,true);
if(lightData[index].isDimmable)
new Characteristic::Brightness(100,true);
if(lightData[index].colorType==TEMPERATURE_ONLY)
new Characteristic::ColorTemperature(200,true);
if(lightData[index].colorType==FULL_RGB){
new Characteristic::Hue(0,true);
new Characteristic::Saturation(0,true);
}
}
///////////////////////////
int addLight(const char *name, boolean isDimmable, colorType_t colorType){
int index=0;
for(index=0;index<MAX_LIGHTS && lightData[index].aid;index++);
if(index==MAX_LIGHTS){
Serial.printf("Can't add Light Accessory - maximum number of %d are already defined.\n",MAX_LIGHTS);
return(-1);
}
int n=strncpy_trim(lightData[index].name,name,sizeof(lightData[index].name));
if(n==1){
Serial.printf("Can't add Light Accessory without a name specified.\n");
return(-1);
}
if(n>sizeof(lightData[index].name))
Serial.printf("Warning - name trimmed to max length of %d characters.\n",MAX_NAME_LENGTH);
lightData[index].isDimmable=isDimmable;
lightData[index].colorType=colorType;
lightData[index].aid=aidStore++;
nvs_set_blob(savedData,"LIGHTDATA",&lightData,sizeof(lightData)); // update data in the NVS
nvs_set_u32(savedData,"AID",aidStore);
nvs_commit(savedData);
addLight(index);
return(index);
}
///////////////////////////
size_t strncpy_trim(char *dest, const char *src, size_t dSize){
while(*src==' ') // skip over any leading spaces
src++;
size_t sLen=strlen(src); // string length of src after skipping over leading spaces
while(sLen>0 && src[sLen-1]==' ') // shorten length to remove trailing spaces
sLen--;
size_t sSize=sLen+1; // add room for null terminator
if(dest!=NULL)
*stpncpy(dest,src,(dSize<sSize?dSize:sSize)-1)='\0';
return(sSize); // return total size needed for entire trimmed string, including null terminator
}
///////////////////////////
void deleteAccessory(int index){
if(index<0 || index>=MAX_LIGHTS){
Serial.printf("Invalid Light Accessory index - must be between 0 and %d.\n",MAX_LIGHTS-1);
return;
}
if(homeSpan.deleteAccessory(lightData[index].aid)){ // if deleteAccessory() is true, a match has been found
Serial.printf("Deleting Light Accessory: Name='%s'\n",lightData[index].name);
lightData[index].aid=0;
nvs_set_blob(savedData,"LIGHTDATA",&lightData,sizeof(lightData)); // update data in the NVS
nvs_commit(savedData);
} else {
Serial.printf("Nothing to delete - there is no Light Accessory at index=%d.\n",index);
}
}
///////////////////////////
void deleteAllAccessories(const char *buf){
for(int i=0;i<MAX_LIGHTS;i++){
homeSpan.deleteAccessory(lightData[i].aid);
lightData[i].aid=0;
}
nvs_set_blob(savedData,"LIGHTDATA",&lightData,sizeof(lightData)); // update data in the NVS
nvs_commit(savedData);
Serial.printf("All Light Accessories deleted!\n");
}
///////////////////////////
void updateAccessories(const char *buf){
if(homeSpan.updateDatabase())
Serial.printf("Accessories Database updated. New configuration number broadcasted...\n");
else
Serial.printf("Nothing to update - no changes were made!\n");
}
///////////////////////////
void listAccessories(const char *buf){
Serial.printf("\nIndex Dimmable Color Name\n");
Serial.printf("----- -------- ----- ");
for(int i=0;i<MAX_NAME_LENGTH;i++)
Serial.printf("-");
Serial.printf("\n");
for(int i=0;i<MAX_LIGHTS;i++){
if(lightData[i].aid)
Serial.printf("%5d %8s %5s %-s\n",i,lightData[i].isDimmable?"YES":"NO",lightData[i].colorType==NO_COLOR?"NONE":(lightData[i].colorType==TEMPERATURE_ONLY?"TEMP":"RGB"),lightData[i].name);
}
Serial.printf("\n");
}
///////////////////////////
void setupWeb(){
Serial.printf("Starting Light Server Hub at %s.local\n\n",HUB_NAME);
webServer.begin();
webServer.on("/", []() {
String response = "<html><head><title>HomeSpan Programmable Light Hub</title>";
response += "<style>table, th, td {border: 1px solid black; border-collapse: collapse;} th, td { padding: 5px; text-align: center; } </style></head>\n";
response += "<body><h2>HomeSpan Lights</h2>";
response += "<form action='/addLight' method='get'>";
response += "<table><tr><th style='text-align:left;'>Accessory</th><th>Dim?</th><th>Color Control</th><th>Action</th></tr>";
int openSlots=MAX_LIGHTS;
for(int i=0;i<MAX_LIGHTS;i++){
if(lightData[i].aid){
response += "<tr><td style='text-align:left;'>" + String(lightData[i].name) + "</td>";
response += "<td><input type='checkbox' disabled " + String(lightData[i].isDimmable?"checked>":">") + "</td>";
response += "<td><input type='radio' disabled " + String(lightData[i].colorType==NO_COLOR?"checked>":">") + " NONE ";
response += "<input type='radio' disabled " + String(lightData[i].colorType==TEMPERATURE_ONLY?"checked>":">") + " TEMP ONLY ";
response += "<input type='radio' disabled " + String(lightData[i].colorType==FULL_RGB?"checked>":">") + " FULL COLOR </td>";
response += "<td><button type='button' onclick=\"document.location='/deleteLight?index=" + String(i) + "'\">Delete Light</button></td>";
response += "</tr>";
openSlots--;
}
}
response += "<tr><td style='text-align:left;'><input type='text' name='name' required placeholder='Type accessory name here...' size='"
+ String(MAX_NAME_LENGTH) + "' maxlength='" + String(MAX_NAME_LENGTH) + "'></td>";
response += "<td><input type='checkbox' name='isDimmable'></td>";
response += "<td><input type='radio' checked name='colorType' for='no_color' value='" + String(NO_COLOR) + "'><label for='no_color'> NONE </label>";
response += "<input type='radio' name='colorType' for='temp_only' value='" + String(TEMPERATURE_ONLY) + "'><label for='temp_only'> TEMP ONLY </label>";
response += "<input type='radio' name='colorType' for='full_rgb' value='" + String(FULL_RGB) + "'><label for='full_rgb'> FULL COLOR </label></td>";
response += "<td><input type='submit' value='Add Light'" + String(openSlots?"":" disabled") + "></td>";
response += "</tr>";
response += "</table>";
response += "</form>";
if(!openSlots)
response += "<p>Can't add any more Light Accessories. Max="+ String(MAX_LIGHTS) + "</p>";
response += "<p>Press here to delete ALL Light Accessories: <button type='button' onclick=\"document.location='/deleteAll'\">Delete All Lights</button></p>";
response += "<p>Press here to update the Home App when finished making changes: <button type='button' onclick=\"document.location='/update'\">Upddate HomeKit</button></p>";
response += "</body></html>";
webServer.send(200, "text/html", response);
});
webServer.on("/deleteLight", []() {
int index=atoi(webServer.arg(0).c_str());
String response = "<html><head><title>HomeSpan Programmable Light Hub</title><meta http-equiv='refresh' content = '3; url=/'/></head>";
response += "<body>Deleting Light Accessory '" + String(lightData[index].name) + "'...</body></html>";
deleteAccessory(index);
webServer.send(200, "text/html", response);
});
webServer.on("/deleteAll", []() {
String response = "<html><head><title>HomeSpan Programmable Light Hub</title><meta http-equiv='refresh' content = '3; url=/'/></head>";
response += "<body>Deleting All Light Accessories...</body></html>";
webServer.send(200, "text/html", response);
deleteAllAccessories("");
});
webServer.on("/update", []() {
String response = "<html><head><title>HomeSpan Programmable Light Hub</title><meta http-equiv='refresh' content = '3; url=/'/></head><body>";
if(homeSpan.updateDatabase())
response += "Accessories Database updated. New configuration number broadcasted...";
else
response += "Nothing to update - no changes were made...";
response += "...</body></html>";
webServer.send(200, "text/html", response);
});
webServer.on("/addLight", []() {
colorType_t colorType=NO_COLOR;
boolean isDimmable=false;
int iName=-1;
for(int i=0;i<webServer.args();i++){
if(!webServer.argName(i).compareTo(String("colorType")))
colorType=(colorType_t)webServer.arg(i).toInt();
else if(!webServer.argName(i).compareTo(String("isDimmable")))
isDimmable=true;
else if(!webServer.argName(i).compareTo(String("name")))
iName=i;
}
String response = "<html><head><title>HomeSpan Programmable Light Hub</title><meta http-equiv='refresh' content = '3; url=/'/></head><body>";
if(iName!=-1){
int index=addLight(webServer.arg(iName).c_str(),isDimmable,colorType);
response += "Adding Light Accessory '" + String(lightData[index].name) + "'";
} else
response += "Error - bad URL request";
response += "...</body></html>";
webServer.send(200, "text/html", response);
});
}
///////////////////////////

View File

@ -1,121 +0,0 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2022 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
********************************************************************************/
////////////////////////////////////////////////////////////
// //
// HomeSpan: A HomeKit implementation for the ESP32 //
// ------------------------------------------------ //
// //
// Demonstrates how to use SpanPoint() to implement //
// two remote temperature sensors on separate ESP32 //
// devices. //
// //
// This sketch is for the MAIN DEVICE that contains //
// all the usual HomeSpan logic, plus two instances //
// of SpanPoint to read temperatures from two other //
// remote devices. //
// //
////////////////////////////////////////////////////////////
#include "HomeSpan.h"
struct RemoteTempSensor : Service::TemperatureSensor {
SpanCharacteristic *temp;
SpanCharacteristic *fault;
SpanPoint *remoteTemp;
const char *name;
float temperature;
RemoteTempSensor(const char *name, const char*macAddress) : Service::TemperatureSensor(){
this->name=name;
temp=new Characteristic::CurrentTemperature(-10.0); // set initial temperature
temp->setRange(-50,100); // expand temperature range to allow negative values
fault=new Characteristic::StatusFault(1); // set initial state = fault
remoteTemp=new SpanPoint(macAddress,0,sizeof(float)); // create a SpanPoint with send size=0 and receive size=sizeof(float)
} // end constructor
void loop(){
if(remoteTemp->get(&temperature)){ // if there is data from the remote sensor
temp->setVal(temperature); // update temperature
fault->setVal(0); // clear fault
LOG1("Sensor %s update: Temperature=%0.2f\n",name,temperature*9/5+32);
} else if(remoteTemp->time()>60000 && !fault->getVal()){ // else if it has been a while since last update (60 seconds), and there is no current fault
fault->setVal(1); // set fault state
LOG1("Sensor %s update: FAULT\n",name);
}
} // loop
};
//////////////////////////////////////
void setup() {
Serial.begin(115200);
homeSpan.setLogLevel(1);
homeSpan.begin(Category::Bridges,"Sensor Hub");
new SpanAccessory();
new Service::AccessoryInformation();
new Characteristic::Identify();
new SpanAccessory();
new Service::AccessoryInformation();
new Characteristic::Identify();
new Characteristic::Name("Indoor Temp");
new RemoteTempSensor("Device 1","AC:67:B2:77:42:20"); // pass MAC Address of Remote Device
new SpanAccessory();
new Service::AccessoryInformation();
new Characteristic::Identify();
new Characteristic::Name("Outdoor Temp");
new RemoteTempSensor("Device 2","84:CC:A8:11:B4:84"); // pass MAC Address of Remote Device
} // end of setup()
//////////////////////////////////////
void loop(){
homeSpan.poll();
} // end of loop()
//////////////////////////////////////

View File

@ -1,79 +0,0 @@
/*********************************************************************************
* 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("\n\nThis is a REMOTE Device with MAC Address = %s\n",WiFi.macAddress().c_str());
Serial.printf("NOTE: This MAC Address must be entered into the corresponding SpanPoint() call of the MAIN Device.\n\n");
// In the line below, replace the MAC Address with that of your MAIN HOMESPAN DEVICE
mainDevice=new SpanPoint("84:CC:A8:11:B4:84",sizeof(float),0); // create a SpanPoint with send size=sizeof(float) and receive size=0
homeSpan.setLogLevel(1);
}
void loop() {
boolean success = mainDevice->send(&temperature); // this will show as success as long as the MAIN DEVICE is running
Serial.printf("Send %s\n",success?"Succeded":"Failed");
temperature+=0.5;
if(temperature>35.0)
temperature=-30.0;
delay(20000);
}

View File

@ -1,129 +0,0 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2023 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
********************************************************************************/
#ifndef ARDUINO_ARCH_ESP8266
#error ERROR: THIS SKETCH IS DESIGNED FOR ESP8266 MICROCONTROLLERS!
#endif
// *** THIS SKETCH IS FOR AN ESP8266, NOT AN ESP32 *** //
// This sketch is similar to HomeSpan's RemoteDevice.ino example (designed for an ESP32 running HomeSpan) in which we simulate
// a Remote Temperature Sensor using HomeSpan's SpanPoint class. However, since neither HomeSpan nor SpanPoint is designed to
// run on an ESP8266, we will implement the BASIC communication functionality of SpanPoint by directly calling the equivalent
// ESP-NOW commands that are supported by the ESP8266. This sketch does NOT seek to replicate all of SpanPoint's features, and
// does not include automatic channel calibration or queue management.
// Start by including the following ESP8266 libraries
#include <ESP8266WiFi.h>
#include <espnow.h>
#include <Crypto.h> // this library is needed to implement the hash-code process SpanPoint uses to generate ESP-NOW encryption keys
float temp=-10.0; // this global variable represents our "simulated" temperature (in degrees C)
// Below we encode the MAC Address of the Main ESP32 Device running HomeSpan to which this ESP8266 device will connect
// IMPORTANT: ESP32 devices have TWO MAC Addresses. One is used when the ESP32 is operating in Station (STA) mode. It is the address returned
// by the WiFi.macAddress() function. The other is used when the ESP32 is operating in Access Point (AP) mode. This address is returned by the
// WiFi.softAPmacAddress() function. HomeSpan normally operates the ESP32 with both modes (STA+AP), so both MAC Addresses are active.
// On ESP32 devices, ESP-NOW seems to work fine when each device sends data to other devices via their STA MAC Address. The same is true for ESP8266
// devices sending data to an ESP32 device via ESP-NOW with one critical exception: Once the ESP32 connects (via STA mode) to a WiFi network, which it must
// do to run HomeSpan, for some reason ESP8266 devices can no longer send data via ESP-NOW to the ESP32 using its STA MAC Address.
// The solution is to instead have the ESP8266 send data via ESP-NOW to the ESP32's AP MAC Address. This seems to work regardless of whether or not
// the ESP32 is connected to a central WiFi newtork. To support such use on the ESP32, the SpanPoint constructor includes a fifth, optional parameter
// called "useAPaddress". When creating SpanPoint links of the ESP32 using HomeSpan, set useAPaddress to TRUE if the Remote Device SpanPoint is connecting
// to is an ESP8266. Set "useAPaddress" to FALSE (or leave unspecified, since FALSE is the default) if the Remote Device is an ESP32.
// When HomeSpan first starts (and whenever you type 'i' into the CLI), the Serial Monitor will display the details of each SpanPoint object you instantiated
// in your ESP32 sketch. This output includes the MAC Address at which SpanPoint will be listening for incoming data from Remote Devices. The MAC Address
// shown for the instance of SpanPoint corresponding to this Remote Deivce (i.e. this sketch) is the MAC Address you should use below.
uint8_t main_mac[6]={0x84,0xCC,0xA8,0x11,0xB4,0x85}; // this is the **AP MAC Address** of the Main Device running HomeSpan on an ESP32 as reported in the HomeSpan Serial Monitor
// Next we create a simple, standard ESP-NOW callback function to report on the status of each data transmission
void OnDataSent(uint8_t *mac_addr, uint8_t sendStatus) {
Serial.printf("Last Packet Send Status: %s\n",sendStatus==0?"Success":"Fail");
}
//////////////////////
void setup() {
Serial.begin(115200);
delay(1000);
Serial.printf("\nMAC Address: %s\n",WiFi.macAddress().c_str()); // enter this MAC address as the first argument of the matching SpanPoint object on the ESP32 running HomeSpan
WiFi.mode(WIFI_STA); // set the mode to Station
wifi_set_channel(6); // you also need to manually set the channel to match whatever channel is used by the ESP32 after it connects to your WiFi network
// Hint: As an alterntive, you can add code to this sketch to connect to the same WiFi network that HomeSpan uses. Though this sketch won't make any use of that WiFi network,
// by establishing the connection the ESP8266 automatically configures the channel, which will now match the ESP32.
// Next, initialize ESP-NOW
if (esp_now_init() != 0) {
Serial.println("Error initializing ESP-NOW");
return;
}
// SpanPoint uses ESP-NOW encryption for all communication. This encrpytion is based on two 16-byte keys: a local master key (LMK) and a primary master key (PMK). To generate
// these keys, SpanPoint takes a text-based password (the default is the word "HomeSpan"), creates a 32 byte (256 bit) hash of the text (using the SHA256 method), and uses
// the first 16 bytes as the LMK and the last 16 bytes as the PMK. This is easily replicated as follows:
uint8_t hash[32]; // create space to store as 32-byte hash code
char password[]="HomeSpan"; // specify the password
experimental::crypto::SHA256::hash(password,strlen(password),hash); // create the hash code to be used further below
esp_now_register_send_cb(OnDataSent); // register the callback function we defined above
esp_now_set_self_role(ESP_NOW_ROLE_CONTROLLER); // set the role of this device to be a controller (i.e. it sends data to the ESP32)
esp_now_set_kok(hash+16,16); // next we set the PMK. For some reason this is called KOK on the ESP8266. Note you must set the PMK BEFORE adding any peers
esp_now_add_peer(main_mac, ESP_NOW_ROLE_COMBO, 0, hash, 16); // now we add in the peer, set its role, and specify the LMK
// Hint: The third argument above is the WiFi Channel. However, this is only a reference number stored by ESP-NOW. ESP-NOW does NOT actually set the channel for you.
// We already set the WiFi channel above. To make things easier, ESP-NOW allows you to set the channel as zero, which means ESP-NOW should expect the channel to be whatever was
// already set for the WiFi controller. Recommend always setting this to zero to avoid having any mismatches if you instead specified a real channel.
}
//////////////////////
void loop() {
Serial.printf("Sending Temperature: %f\n",temp);
esp_now_send(main_mac, (uint8_t *)&temp, sizeof(temp)); // Send the Data to the Main Device!
temp+=0.5; // increment the "temperature" by 0.5 C
if(temp>35.0)
temp=-10.0;
delay(5000); // wait 5 seconds before sending another update
}

View File

@ -1,109 +0,0 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2022 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
********************************************************************************/
////////////////////////////////////////////////////////////
// //
// HomeSpan: A HomeKit implementation for the ESP32 //
// ------------------------------------------------ //
// //
// This sketch is for a Remote Temperature Sensor to //
// be used in conjunction with the "MainDevice.ino" //
// sketch running on a separate ESP32 //
// //
// The purpose of these sketches is to demonstrate //
// how to use SpanPoint() to communication between //
// a remote ESP32 device that takes measurements from //
// a sensor, and a separate "main" ESP32 device that //
// is running the full HomeSpan code, and thus //
// connects to HomeKit. //
// //
// This sketch implements an Adafruit ADT7410 I2C //
// Temperature Sensor. If you don't have such a //
// device, please use the sketch "RemoteDevice.ino" //
// instead. That sketch SIMULATES a temperature //
// sensor and therefore allows you to learn how //
// SpanPoint() works even though the temperature data //
// itself isn't real. //
// //
////////////////////////////////////////////////////////////
#include "HomeSpan.h"
#include <Wire.h> // include the I2C library
#define DIAGNOSTIC_MODE
#define SAMPLE_TIME 30000 // Time between temperature samples (in milliseconds)
#define I2C_ADD 0x48 // I2C 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
}

View File

@ -1,9 +1,9 @@
name=HomeSpan
version=1.9.1
version=1.5.1
author=Gregg <homespan@icloud.com>
maintainer=Gregg <homespan@icloud.com>
sentence=A robust and extremely easy-to-use HomeKit implementation for the Espressif ESP32 running on the Arduino IDE.
paragraph=This library provides a microcontroller-focused implementation of Apple's HomeKit Accessory Protocol (HAP - Release R2) designed specifically for the ESP32 running on the Arduino IDE. HomeSpan pairs directly to iOS Home via WiFi without the need for any external bridges or components. Supports ESP32, ESP32-S2, ESP32-C3, and ESP32-S3.
paragraph=This library provides a microcontroller-focused implementation of Apple's HomeKit Accessory Protocol (HAP - Release R2) designed specifically for the ESP32 running on the Arduino IDE. HomeSpan pairs directly to iOS Home via WiFi without the need for any external bridges or components. Compatible with ESP32, ESP32-S2, and ESP32-C3.
url=https://github.com/HomeSpan/HomeSpan
architectures=esp32
includes=HomeSpan.h

View File

@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2022 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
@ -48,13 +48,22 @@ enum FORMAT { // HAP Table 6-5
UINT64=4,
INT=5,
FLOAT=6,
STRING=7,
DATA=8,
TLV_ENC=9
STRING=7
};
///////////////////////////////
typedef boolean BOOL_t;
typedef uint8_t UINT8_t;
typedef uint16_t UINT16_t;
typedef uint32_t UINT32_t;
typedef uint64_t UINT64_t;
typedef int32_t INT_t;
typedef double FLOAT_t;
typedef char * STRING_t;
///////////////////////////////
struct HapChar {
const char *type;
const char *hapName;
@ -69,7 +78,6 @@ struct HapChar {
struct HapCharacteristics {
HAPCHAR( AccessoryFlags, A6, PR+EV, UINT32, true );
HAPCHAR( Active, B0, PW+PR+EV, UINT8, true );
HAPCHAR( ActiveIdentifier, E7, PW+PR+EV, UINT32, true );
HAPCHAR( AirQuality, 95, PR+EV, UINT8, true );
@ -103,10 +111,9 @@ struct HapCharacteristics {
HAPCHAR( CurrentTemperature, 11, PR+EV, FLOAT, false );
HAPCHAR( CurrentTiltAngle, C1, PR+EV, INT, false );
HAPCHAR( CurrentVisibilityState, 135, PR+EV, UINT8, true );
HAPCHAR( DisplayOrder, 136, PR+EV, TLV_ENC, true );
HAPCHAR( FilterLifeLevel, AB, PR+EV, FLOAT, false );
HAPCHAR( FilterChangeIndication, AC, PR+EV, UINT8, true );
HAPCHAR( FirmwareRevision, 52, PR+EV, STRING, true );
HAPCHAR( FirmwareRevision, 52, PR, STRING, true );
HAPCHAR( HardwareRevision, 53, PR, STRING, true );
HAPCHAR( HeatingThresholdTemperature, 12, PR+PW+EV, FLOAT, false );
HAPCHAR( HoldPosition, 6F, PW, BOOL, true );
@ -184,7 +191,7 @@ struct HapCharacteristics {
HAPCHAR( VOCDensity, C8, PR+EV, FLOAT, false );
HAPCHAR( Volume, 119, PW+PR+EV, UINT8, false );
HAPCHAR( VolumeControlType, E9, PR+EV, UINT8, true );
HAPCHAR( VolumeSelector, EA, PW, UINT8, true );
HAPCHAR( VolumeSelector, EA, PR+EV, UINT8, true );
HAPCHAR( WaterLevel, B5, PR+EV, FLOAT, false );
};

View File

@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2022 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
@ -25,48 +25,21 @@
*
********************************************************************************/
// For developer use and testing only - provides a common set of pin numbers mapped to the Adafruit Feather Board.
// For developer use and testing only - provides a common set of pin numbers mapped to the Adafruit Feather Board
// Facilitates the testing of identical code on an ESP32, ESP32-S2, and ESP32-C3 using a common jig without rewiring
#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)
F26=26,F25=25,F34=34,F39=39,F36=36,F4=4, // A0-A5
F22=22,F23=23, // I2C SCL/SDA
F5=5,F18=18,F19=19,F33=33 // SPI SCK/SDO/SDI/CS
};
#if defined(CONFIG_IDF_TARGET_ESP32)
enum {F13=13,F12=12,F27=27,F33=33,F15=15,F32=32,F14=14,F22=22,F23=23,F26=26,F25=25,F34=34,F39=39,F36=36,F4=4,F5=5,F18=18,F19=19,F16=16,F17=17,F21=21};
#define DEVICE_SUFFIX ""
#elif defined(ARDUINO_ESP32S2_DEV)
enum {
F13=1,F12=3,F27=7,F15=10,F32=42,F14=11,F16=20,F17=21,F21=16, // Digital Only (9 pins)
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
BUILTIN_PIXEL=18 // Built-in Neo-Pixel
};
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
enum {F13=11,F12=10,F27=7,F33=3,F15=1,F32=38,F14=33,F22=9,F23=8,F26=17,F25=18,F34=14,F39=12,F36=6,F4=5,F5=36,F18=35,F19=37,F16=44,F17=43};
#define DEVICE_SUFFIX "-S2"
#elif defined(ARDUINO_ESP32C3_DEV)
enum {
F27=19,F32=2,F14=10,F16=20,F17=21,F21=18, // Digital Only (6 pins)
F26=0,F25=1,F4=3, // A0/A1/A5
F22=9,F23=8, // I2C SCL/SDA
F5=4,F18=6,F19=5,F33=7, // SPI SCK/SDO/SDI/CS
BUILTIN_PIXEL=8 // Built-in Neo-Pixel
};
#elif defined(CONFIG_IDF_TARGET_ESP32C3)
enum {F27=2,F33=7,F32=3,F14=10,F22=9,F23=8,F26=0,F25=1,F4=18,F5=4,F18=6,F19=5,F16=20,F17=21,F21=19};
#define DEVICE_SUFFIX "-C3"
#elif defined(ARDUINO_ESP32S3_DEV)
enum {
F13=5,F12=6,F27=7,F15=16,F32=17,F14=18,F16=37,F17=36,F21=38, // Digital Only (9 pins)
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
BUILTIN_PIXEL=48 // Built-in Neo-Pixel
};
#define DEVICE_SUFFIX "-S3"
#endif

File diff suppressed because it is too large Load Diff

206
src/HAP.h
View File

@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2022 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
@ -27,31 +27,14 @@
#pragma once
#include <sstream>
#include <WiFi.h>
#include "HomeSpan.h"
#include "TLV.h"
#include "HAPConstants.h"
#include "HKDF.h"
#include "SRP.h"
const TLV8_names HAP_Names[] = {
{kTLVType_Separator,"SEPARATOR"},
{kTLVType_State,"STATE"},
{kTLVType_PublicKey,"PUBKEY"},
{kTLVType_Method,"METHOD"},
{kTLVType_Salt,"SALT"},
{kTLVType_Error,"ERROR"},
{kTLVType_Proof,"PROOF"},
{kTLVType_EncryptedData,"ENC.DATA"},
{kTLVType_Signature,"SIGNATURE"},
{kTLVType_Identifier,"IDENTIFIER"},
{kTLVType_Permissions,"PERMISSION"}
};
#define hap_controller_IDBYTES 36
#define hap_accessory_IDBYTES 17
/////////////////////////////////////////////////
// NONCE Structure (HAP used last 64 of 96 bits)
@ -63,41 +46,23 @@ struct Nonce {
void inc();
};
/////////////////////////////////////////////////
// Paired Controller Structure for Permanently-Stored Data
struct Controller {
boolean allocated=false; // slot is allocated with Controller data
boolean admin; // Controller has admin privileges
uint8_t ID[36]; // Pairing ID
uint8_t LTPK[32]; // Long Term Ed2519 Public Key
};
/////////////////////////////////////////////////
// Accessory Structure for Permanently-Stored Data
struct Accessory {
uint8_t ID[hap_accessory_IDBYTES]; // Pairing ID in form "XX:XX:XX:XX:XX:XX" (no null terminator)
uint8_t LTSK[crypto_sign_SECRETKEYBYTES]; // Long Term Ed2519 Secret Key
uint8_t LTPK[crypto_sign_PUBLICKEYBYTES]; // Long Term Ed2519 Public Key
};
//////////////////////////////////////////////////////////
// Paired Controller Structure for Permanently-Stored Data
class Controller {
friend class HAPClient;
boolean allocated=false; // DEPRECATED (but needed for backwards compatability with original NVS storage of Controller info)
boolean admin; // Controller has admin privileges
uint8_t ID[36]; // Pairing ID
uint8_t LTPK[32]; // Long Term Ed2519 Public Key
public:
Controller(uint8_t *id, uint8_t *ltpk, boolean ad){
allocated=true;
admin=ad;
memcpy(ID,id,36);
memcpy(LTPK,ltpk,32);
}
Controller(){}
const uint8_t *getID() const {return(ID);}
const uint8_t *getLTPK() const {return(LTPK);}
boolean isAdmin() const {return(admin);}
uint8_t ID[17]; // Pairing ID in form "XX:XX:XX:XX:XX:XX"
uint8_t LTSK[64]; // secret key for Ed25519 signatures
uint8_t LTPK[32]; // public key for Ed25519 signatures
};
/////////////////////////////////////////////////
@ -108,29 +73,33 @@ struct HAPClient {
// common structures and data shared across all HAP Clients
static const int MAX_HTTP=8096; // max number of bytes allowed for HTTP message
static const int MAX_HTTP=8095; // max number of bytes in HTTP message buffer
static const int MAX_CONTROLLERS=16; // maximum number of paired controllers (HAP requires at least 16)
static const int MAX_ACCESSORIES=150; // maximum number of allowed Accessories (HAP limit=150)
static const int MAX_ACCESSORIES=41; // maximum number of allowed Acessories (HAP limit=150, but not enough memory in ESP32 to run that many)
static pairState pairStatus; // tracks pair-setup status
static Accessory accessory; // Accessory ID and Ed25519 public and secret keys - permanently stored
static list<Controller, Mallocator<Controller>> controllerList; // linked-list of Paired Controller IDs and ED25519 long-term public keys - permanently stored
static TLV<kTLVType,10> tlv8; // TLV8 structure (HAP Section 14.1) with space for 10 TLV records of type kTLVType (HAP Table 5-6)
static nvs_handle hapNVS; // handle for non-volatile-storage of HAP data
static nvs_handle srpNVS; // handle for non-volatile-storage of SRP data
static uint8_t httpBuf[MAX_HTTP+1]; // buffer to store HTTP messages (+1 to leave room for storing an extra 'overflow' character)
static HKDF hkdf; // generates (and stores) HKDF-SHA-512 32-byte keys derived from an inputKey of arbitrary length, a salt string, and an info string
static pairState pairStatus; // tracks pair-setup status
static SRP6A srp; // stores all SRP-6A keys used for Pair-Setup
static Accessory accessory; // Accessory ID and Ed25519 public and secret keys- permanently stored
static Controller controllers[MAX_CONTROLLERS]; // Paired Controller IDs and ED25519 long-term public keys - permanently stored
static int conNum; // connection number - used to keep track of per-connection EV notifications
// individual structures and data defined for each Hap Client connection
WiFiClient client; // handle to client
int clientNumber; // client number
Controller *cPair=NULL; // pointer to info on current, session-verified Paired Controller (NULL=un-verified, and therefore un-encrypted, connection)
WiFiClient client=0; // handle to client
Controller *cPair; // pointer to info on current, session-verified Paired Controller (NULL=un-verified, and therefore un-encrypted, connection)
// These temporary Curve25519 keys are generated in the first call to pair-verify and used in the second call to pair-verify so must persist for a short period
// These keys are generated in the first call to pair-verify and used in the second call to pair-verify so must persist for a short period
uint8_t publicCurveKey[32]; // public key for Curve25519 encryption
uint8_t sharedCurveKey[32]; // Pair-Verfied Shared Secret key derived from Accessory's epehmeral secretCurveKey and Controller's iosCurveKey
uint8_t sessionKey[32]; // shared Session Key (derived with various HKDF calls)
uint8_t iosCurveKey[32]; // Curve25519 public key for associated paired controller
struct tempKeys_t {
uint8_t publicCurveKey[crypto_box_PUBLICKEYBYTES]; // Accessory's Curve25519 Public Key
uint8_t sharedCurveKey[crypto_box_PUBLICKEYBYTES]; // Shared-Secret Curve25519 Key derived from Accessory's Secret Key and Controller's Public Key
uint8_t sessionKey[crypto_box_PUBLICKEYBYTES]; // Session Key Curve25519 (derived with various HKDF calls)
uint8_t iosCurveKey[crypto_box_PUBLICKEYBYTES]; // Controller's Curve25519 Public Key
} temp;
// CurveKey and CurveKey Nonces are created once each new session is verified in /pair-verify. Keys persist for as long as connection is open
uint8_t a2cKey[32]; // AccessoryToControllerKey derived from HKDF-SHA-512 of sharedCurveKey (HAP Section 6.5.2)
@ -140,17 +109,19 @@ struct HAPClient {
// define member methods
void processRequest(); // process HAP request
int postPairSetupURL(uint8_t *content, size_t len); // POST /pair-setup (HAP Section 5.6)
int postPairVerifyURL(uint8_t *content, size_t len); // POST /pair-verify (HAP Section 5.7)
int postPairingsURL(uint8_t *content, size_t len); // POST /pairings (HAP Sections 5.10-5.12)
int getAccessoriesURL(); // GET /accessories (HAP Section 6.6)
int getCharacteristicsURL(char *urlBuf); // GET /characteristics (HAP Section 6.7.4)
int putCharacteristicsURL(char *json); // PUT /characteristics (HAP Section 6.7.2)
int putPrepareURL(char *json); // PUT /prepare (HAP Section 6.7.2.4)
void processRequest(); // process HAP request
int postPairSetupURL(); // POST /pair-setup (HAP Section 5.6)
int postPairVerifyURL(); // POST /pair-verify (HAP Section 5.7)
int getAccessoriesURL(); // GET /accessories (HAP Section 6.6)
int postPairingsURL(); // POST /pairings (HAP Sections 5.10-5.12)
int getCharacteristicsURL(char *urlBuf); // GET /characteristics (HAP Section 6.7.4)
int putCharacteristicsURL(char *json); // PUT /characteristics (HAP Section 6.7.2)
int putPrepareURL(char *json); // PUT /prepare (HAP Section 6.7.2.4)
int getStatusURL(); // GET / status (an optional, non-HAP feature)
void tlvRespond(TLV8 &tlv8); // respond to client with HTTP OK header and all defined TLV data records
int receiveEncrypted(uint8_t *httpBuf, int messageSize); // decrypt HTTP request (HAP Section 6.5)
void tlvRespond(); // respond to client with HTTP OK header and all defined TLV data records (those with length>0)
void sendEncrypted(char *body, uint8_t *dataBuf, int dataLen); // send client complete ChaCha20-Poly1305 encrypted HTTP mesage comprising a null-terminated 'body' and 'dataBuf' with 'dataLen' bytes
int receiveEncrypted(); // decrypt HTTP request (HAP Section 6.5)
int notFoundError(); // return 404 error
int badRequestError(); // return 400 error
@ -158,82 +129,27 @@ struct HAPClient {
// define static methods
static void init(); // initialize HAP after start-up
static void init(); // initialize HAP after start-up
static void hexPrintColumn(const uint8_t *buf, int n, int minLogLevel=0); // prints 'n' bytes of *buf as HEX, one byte per row, subject to specified minimum log level
static void hexPrintRow(const uint8_t *buf, int n, int minLogLevel=0); // prints 'n' bytes of *buf as HEX, all on one row, subject to specified minimum log level
static void charPrintRow(const uint8_t *buf, int n, int minLogLevel=0); // prints 'n' bytes of *buf as CHAR, all on one row, subject to specified minimum log level
static void hexPrintColumn(uint8_t *buf, int n); // prints 'n' bytes of *buf as HEX, one byte per row. For diagnostics/debugging only
static void hexPrintRow(uint8_t *buf, int n); // prints 'n' bytes of *buf as HEX, all on one row
static void charPrintRow(uint8_t *buf, int n); // prints 'n' bytes of *buf as CHAR, all on one row
static Controller *findController(uint8_t *id); // returns pointer to controller with matching ID (or NULL if no match)
static tagError addController(uint8_t *id, uint8_t *ltpk, boolean admin); // stores data for new Controller with specified data. Returns tagError (if any)
static Controller *findController(uint8_t *id); // returns pointer to controller with mathching ID (or NULL if no match)
static Controller *getFreeController(); // return pointer to next free controller slot (or NULL if no free slots)
static Controller *addController(uint8_t *id, uint8_t *ltpk, boolean admin); // stores data for new Controller with specified data. Returns pointer to Controller slot on success, else NULL
static int nAdminControllers(); // returns number of admin Controllers stored
static void removeControllers(); // removes all Controllers (sets allocated flags to false for all slots)
static void removeController(uint8_t *id); // removes specific Controller. If no remaining admin Controllers, remove all others (if any) as per HAP requirements.
static void printControllers(int minLogLevel=0); // prints IDs of all allocated (paired) Controller, subject to specified minimum log level
static void saveControllers(); // saves Controller list in NVS
static int nAdminControllers(); // returns number of admin Controller
static void tearDown(uint8_t *id); // tears down connections using Controller with ID=id; tears down all connections if id=NULL
static void printControllers(); // prints IDs of all allocated (paired) Controller
static void callServiceLoops(); // call the loop() method for any Service with that over-rode the default method
static void checkPushButtons(); // checks for PushButton presses and calls button() method of attached Services when found
static void checkNotifications(); // checks for Event Notifications and reports to controllers as needed (HAP Section 6.8)
static void checkTimedWrites(); // checks for expired Timed Write PIDs, and clears any found (HAP Section 6.7.2.4)
static void eventNotify(SpanBuf *pObj, int nObj, HAPClient *ignore=NULL); // transmits EVENT Notifications for nObj SpanBuf objects, pObj, with optional flag to ignore a specific client
static void getStatusURL(HAPClient *, void (*)(const char *, void *), void *); // GET / status (an optional, non-HAP feature)
class HAPTLV : public TLV8 { // dedicated class for HAP TLV8 records
public:
HAPTLV() : TLV8(HAP_Names,11){}
};
};
/////////////////////////////////////////////////
// HapOut Structure
class HapOut : public std::ostream {
private:
struct HapStreamBuffer : public std::streambuf {
const size_t bufSize=1024; // max allowed for HAP encrypted records
char *buffer;
uint8_t *encBuf;
HAPClient *hapClient=NULL;
int logLevel=255; // default is NOT to print anything
boolean enablePrettyPrint=false;
size_t byteCount=0;
size_t indent=0;
uint8_t *hash;
mbedtls_sha512_context *ctx;
void (*callBack)(const char *, void *)=NULL;
void *callBackUserData = NULL;
void flushBuffer();
int_type overflow(int_type c) override;
int sync() override;
size_t getSize(){return(byteCount+pptr()-pbase());}
void printFormatted(char *buf, size_t nChars, size_t nsp);
HapStreamBuffer();
~HapStreamBuffer();
};
HapStreamBuffer hapBuffer;
public:
HapOut() : std::ostream(&hapBuffer){}
HapOut& setHapClient(HAPClient *hapClient){hapBuffer.hapClient=hapClient;return(*this);}
HapOut& setLogLevel(int logLevel){hapBuffer.logLevel=logLevel;return(*this);}
HapOut& prettyPrint(){hapBuffer.enablePrettyPrint=true;hapBuffer.logLevel=0;return(*this);}
HapOut& setCallback(void(*f)(const char *, void *)){hapBuffer.callBack=f;return(*this);}
HapOut& setCallbackUserData(void *userData){hapBuffer.callBackUserData=userData;return(*this);}
uint8_t *getHash(){return(hapBuffer.hash);}
size_t getSize(){return(hapBuffer.getSize());}
static void eventNotify(SpanBuf *pObj, int nObj, int ignoreClient=-1); // transmits EVENT Notifications for nObj SpanBuf objects, pObj, with optional flag to ignore a specific client
};
/////////////////////////////////////////////////
// Extern Variables
extern HapOut hapOut;
extern HAPClient **hap;

View File

@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2022 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
@ -52,7 +52,6 @@ typedef enum {
// HAP Error Codes (HAP Table 5-5)
typedef enum {
tagError_None=0x00,
tagError_Unknown=0x01,
tagError_Authentication=0x02,
tagError_Backoff=0x03,

View File

@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2022 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*

View File

@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2022 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
@ -38,6 +38,6 @@
// incorporated under hkdf.cpp, with a wrapper to always
// use SHA-512 with 32 bytes of output as required by HAP.
namespace HKDF{
struct HKDF {
int create(uint8_t *outputKey, uint8_t *inputKey, int inputLen, const char *salt, const char *info); // output of HKDF is always a 32-byte key derived from an input key, a salt string, and an info string
};

View File

@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2022 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2022 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
@ -24,8 +24,6 @@
* SOFTWARE.
*
********************************************************************************/
#include "version.h"
#include <DNSServer.h>
@ -42,7 +40,7 @@ void Network::scan(){
int n=WiFi.scanNetworks();
free(ssidList);
ssidList=(char **)HS_CALLOC(n,sizeof(char *));
ssidList=(char **)calloc(n,sizeof(char *));
numSSID=0;
for(int i=0;i<n;i++){
@ -52,7 +50,7 @@ void Network::scan(){
found=true;
}
if(!found){
ssidList[numSSID]=(char *)HS_CALLOC(WiFi.SSID(i).length()+1,sizeof(char));
ssidList[numSSID]=(char *)calloc(WiFi.SSID(i).length()+1,sizeof(char));
sprintf(ssidList[numSSID],"%s",WiFi.SSID(i).c_str());
numSSID++;
}
@ -67,26 +65,33 @@ void Network::serialConfigure(){
wifiData.ssid[0]='\0';
wifiData.pwd[0]='\0';
LOG0("*** WiFi Setup - Scanning for Networks...\n\n");
Serial.print("*** WiFi Setup - Scanning for Networks...\n\n");
scan(); // scan for networks
for(int i=0;i<numSSID;i++)
LOG0(" %d) %s\n",i+1,ssidList[i]);
for(int i=0;i<numSSID;i++){
Serial.print(" ");
Serial.print(i+1);
Serial.print(") ");
Serial.print(ssidList[i]);
Serial.print("\n");
}
while(!strlen(wifiData.ssid)){
LOG0("\n>>> WiFi SSID: ");
Serial.print("\n>>> WiFi SSID: ");
readSerial(wifiData.ssid,MAX_SSID);
if(atoi(wifiData.ssid)>0 && atoi(wifiData.ssid)<=numSSID){
strcpy(wifiData.ssid,ssidList[atoi(wifiData.ssid)-1]);
}
LOG0("%s\n",wifiData.ssid);
Serial.print(wifiData.ssid);
Serial.print("\n");
}
while(!strlen(wifiData.pwd)){
LOG0(">>> WiFi PASS: ");
Serial.print(">>> WiFi PASS: ");
readSerial(wifiData.pwd,MAX_PWD);
LOG0("%s\n",mask(wifiData.pwd,2).c_str());
Serial.print(mask(wifiData.pwd,2));
Serial.print("\n");
}
return;
@ -105,20 +110,32 @@ boolean Network::allowedCode(char *s){
void Network::apConfigure(){
LOG0("*** Starting Access Point: %s / %s\n",apSSID,apPassword);
Serial.print("*** Starting Access Point: ");
Serial.print(apSSID);
Serial.print(" / ");
Serial.print(apPassword);
Serial.print("\n");
STATUS_UPDATE(start(LED_AP_STARTED),HS_AP_STARTED)
LOG0("\nScanning for Networks...\n\n");
homeSpan.statusLED.start(LED_AP_STARTED);
Serial.print("\nScanning for Networks...\n\n");
scan(); // scan for networks
for(int i=0;i<numSSID;i++)
LOG0(" %d) %s\n",i+1,ssidList[i]);
for(int i=0;i<numSSID;i++){
Serial.print(" ");
Serial.print(i+1);
Serial.print(") ");
Serial.print(ssidList[i]);
Serial.print("\n");
}
WiFiServer apServer(80);
client=0;
TempBuffer <uint8_t> tempBuffer(MAX_HTTP+1);
uint8_t *httpBuf=tempBuffer.buf;
const byte DNS_PORT = 53;
DNSServer dnsServer;
IPAddress apIP(192, 168, 4, 1);
@ -131,31 +148,38 @@ void Network::apConfigure(){
alarmTimeOut=millis()+lifetime; // Access Point will shut down when alarmTimeOut is reached
apStatus=0; // status will be "timed out" unless changed
LOG0("\nReady.\n");
Serial.print("\nReady.\n");
while(1){ // loop until we get timed out (which will be accelerated if save/cancel selected)
if(homeSpan.controlButton && homeSpan.controlButton->triggered(9999,3000)){
LOG0("\n*** Access Point Terminated. Restarting...\n\n");
STATUS_UPDATE(start(LED_ALERT),HS_AP_TERMINATED)
homeSpan.controlButton->wait();
homeSpan.reboot();
if(homeSpan.controlButton.triggered(9999,3000)){
Serial.print("\n*** Access Point Terminated.");
homeSpan.statusLED.start(LED_ALERT);
homeSpan.controlButton.wait();
Serial.print(" Restarting... \n\n");
homeSpan.statusLED.off();
ESP.restart();
}
if(millis()>alarmTimeOut){
WiFi.softAPdisconnect(true); // terminate connections and shut down captive access point
delay(100);
if(apStatus==1){
LOG0("\n*** Access Point: Exiting and Saving Settings\n\n");
Serial.print("\n*** Access Point: Exiting and Saving Settings\n\n");
return;
} else {
if(apStatus==0)
LOG0("\n*** Access Point: Timed Out (%ld seconds).",lifetime/1000);
else
LOG0("\n*** Access Point: Configuration Cancelled.");
LOG0(" Restarting...\n\n");
STATUS_UPDATE(start(LED_ALERT),HS_AP_TERMINATED)
homeSpan.reboot();
if(apStatus==0){
Serial.print("\n*** Access Point: Timed Out (");
Serial.print(lifetime/1000);
Serial.print(" seconds).");
} else {
Serial.print("\n*** Access Point: Configuration Canceled.");
}
Serial.print(" Restarting...\n\n");
homeSpan.statusLED.start(LED_ALERT);
delay(1000);
homeSpan.statusLED.off();
ESP.restart();
}
}
@ -177,32 +201,22 @@ void Network::apConfigure(){
LOG2("<<<<<<<<< ");
LOG2(client.remoteIP());
LOG2(" <<<<<<<<<\n");
int messageSize=client.available();
if(messageSize>MAX_HTTP){ // exceeded maximum number of bytes allowed
badRequestError();
LOG0("\n*** ERROR: HTTP message of %d bytes exceeds maximum allowed (%d)\n\n",messageSize,MAX_HTTP);
continue;
}
TempBuffer<uint8_t> httpBuf(messageSize+1); // leave room for null character added below
int nBytes=client.read(httpBuf,messageSize); // read all available bytes up to maximum allowed+1
if(nBytes!=messageSize || client.available()!=0){
int nBytes=client.read(httpBuf,MAX_HTTP+1); // read all available bytes up to maximum allowed+1
if(nBytes>MAX_HTTP){ // exceeded maximum number of bytes allowed
badRequestError();
LOG0("\n*** ERROR: HTTP message not read correctly. Expected %d bytes, read %d bytes, %d bytes remaining\n\n",messageSize,nBytes,client.available());
Serial.print("\n*** ERROR: Exceeded maximum HTTP message length\n\n");
continue;
}
httpBuf[nBytes]='\0'; // add null character to enable string functions
char *body=(char *)httpBuf.get(); // char pointer to start of HTTP Body
httpBuf[nBytes]='\0'; // add null character to enable string functions
char *body=(char *)httpBuf; // char pointer to start of HTTP Body
char *p; // char pointer used for searches
if(!(p=strstr((char *)httpBuf.get(),"\r\n\r\n"))){
if(!(p=strstr((char *)httpBuf,"\r\n\r\n"))){
badRequestError();
LOG0("\n*** ERROR: Malformed HTTP request (can't find blank line indicating end of BODY)\n\n");
Serial.print("\n*** ERROR: Malformed HTTP request (can't find blank line indicating end of BODY)\n\n");
continue;
}
@ -214,7 +228,7 @@ void Network::apConfigure(){
cLen=atoi(p+16);
if(nBytes!=strlen(body)+4+cLen){
badRequestError();
LOG0("\n*** ERROR: Malformed HTTP request (Content-Length plus Body Length does not equal total number of bytes read)\n\n");
Serial.print("\n*** ERROR: Malformed HTTP request (Content-Length plus Body Length does not equal total number of bytes read)\n\n");
continue;
}
@ -239,7 +253,7 @@ void Network::processRequest(char *body, char *formData){
String responseHead="HTTP/1.1 200 OK\r\nContent-type: text/html\r\n";
String responseBody="<html><meta charset=\"utf-8\"><head><style>"
String responseBody="<html><head><style>"
"p{font-size:300%; margin:1em}"
"label{font-size:300%; margin:1em}"
"input{font-size:250%; margin:1em}"
@ -258,9 +272,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+="<meta http-equiv = \"refresh\" content = \"" + String(waitTime) + "; url = /wifi-status\" />"
"<p>Initiating WiFi connection to:</p><p><b>" + String(wifiData.ssid) + "</p>";
@ -305,9 +319,9 @@ void Network::processRequest(char *body, char *formData){
WiFi.begin(wifiData.ssid,wifiData.pwd);
} else {
STATUS_UPDATE(start(LED_AP_CONNECTED),HS_AP_CONNECTED)
homeSpan.statusLED.start(LED_AP_CONNECTED); // slow double-blink
responseBody+="<p>SUCCESS! Connected to:</p><p><b>" + String(wifiData.ssid) + "</b></p>";
responseBody+="<p>You may enter new 8-digit Setup Code below, or leave blank to retain existing code.</p>";
@ -326,7 +340,7 @@ void Network::processRequest(char *body, char *formData){
LOG1("In Landing Page...\n");
STATUS_UPDATE(start(LED_AP_CONNECTED),HS_AP_CONNECTED)
homeSpan.statusLED.start(LED_AP_CONNECTED);
waitTime=2;
responseBody+="<p>Welcome to HomeSpan! This page allows you to configure the above HomeSpan device to connect to your WiFi network.</p>"

View File

@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2022 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
@ -28,8 +28,11 @@
#pragma once
#include <WiFi.h>
#include <unordered_set>
#include "Settings.h"
using std::unordered_set;
const int MAX_SSID=32; // max number of characters in WiFi SSID
const int MAX_PWD=64; // max number of characters in WiFi Password
@ -40,8 +43,8 @@ struct Network {
const int MAX_HTTP=4095; // max number of bytes in HTTP message
const char *apSSID=DEFAULT_AP_SSID; // Access Point SSID
const char *apPassword=DEFAULT_AP_PASSWORD; // Access Point password (does not need to be secret - only used to ensure encrypted WiFi connection)
unsigned long lifetime=DEFAULT_AP_TIMEOUT*1000; // length of time (in milliseconds) to keep Access Point alive before shutting down and restarting
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
char **ssidList=NULL;
int numSSID;

View File

@ -1,64 +0,0 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
********************************************************************************/
#pragma once
#ifndef HS_MALLOC
#if defined(BOARD_HAS_PSRAM)
#define HS_MALLOC ps_malloc
#define HS_CALLOC ps_calloc
#define HS_REALLOC ps_realloc
#define ps_new(X) new(ps_malloc(sizeof(X)))X
#else
#define HS_MALLOC malloc
#define HS_CALLOC calloc
#define HS_REALLOC realloc
#define ps_new(X) new X
#endif
template <class T>
struct Mallocator {
typedef T value_type;
Mallocator() = default;
template <class U> constexpr Mallocator(const Mallocator<U>&) {}
[[nodiscard]] T* allocate(std::size_t n) {
auto p = static_cast<T*>(HS_MALLOC(n*sizeof(T)));
if(p==NULL){
Serial.printf("\n\n*** FATAL ERROR: Requested allocation of %d bytes failed. Program Halting.\n\n",n*sizeof(T));
while(1);
}
return p;
}
void deallocate(T* p, std::size_t) noexcept { std::free(p); }
};
template <class T, class U>
bool operator==(const Mallocator<T>&, const Mallocator<U>&) { return true; }
template <class T, class U>
bool operator!=(const Mallocator<T>&, const Mallocator<U>&) { return false; }
#endif

View File

@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2022 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
@ -29,12 +29,29 @@
#include <Arduino.h>
#include "SRP.h"
#include "HAP.h"
/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////
SRP6A::SRP6A(){
uint8_t tBuf[768]; // temporary buffer for staging
uint8_t tHash[64]; // temporary buffer for storing SHA-512 results
char N3072[]="FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74"
"020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437"
"4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"
"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05"
"98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB"
"9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B"
"E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718"
"3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33"
"A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7"
"ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864"
"D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E2"
"08E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF";
// initialize MPI structures
mbedtls_mpi_init(&N);
@ -48,156 +65,134 @@ SRP6A::SRP6A(){
mbedtls_mpi_init(&S);
mbedtls_mpi_init(&k);
mbedtls_mpi_init(&u);
mbedtls_mpi_init(&K);
mbedtls_mpi_init(&M1);
mbedtls_mpi_init(&M1V);
mbedtls_mpi_init(&M2);
mbedtls_mpi_init(&_rr);
mbedtls_mpi_init(&t1);
mbedtls_mpi_init(&t2);
mbedtls_mpi_init(&t3);
// load N and g into MPI structures
// load N and g into mpi structures
mbedtls_mpi_read_string(&N,16,N3072);
mbedtls_mpi_lset(&g,g3072);
}
mbedtls_mpi_lset(&g,5);
//////////////////////////////////////
SRP6A::~SRP6A(){
mbedtls_mpi_free(&N);
mbedtls_mpi_free(&g);
mbedtls_mpi_free(&s);
mbedtls_mpi_free(&x);
mbedtls_mpi_free(&v);
mbedtls_mpi_free(&A);
mbedtls_mpi_free(&b);
mbedtls_mpi_free(&B);
mbedtls_mpi_free(&S);
mbedtls_mpi_free(&k);
mbedtls_mpi_free(&u);
mbedtls_mpi_free(&_rr);
mbedtls_mpi_free(&t1);
mbedtls_mpi_free(&t2);
mbedtls_mpi_free(&t3);
}
//////////////////////////////////////
void SRP6A::createVerifyCode(const char *setupCode, Verification *vData){
TempBuffer<uint8_t> tBuf(80); // temporary buffer for staging
TempBuffer<uint8_t> tHash(64); // temporary buffer for storing SHA-512 results
char *icp; // storage for I:P
// generate random salt, s
randombytes_buf(vData->salt,16); // generate 16 random bytes for salt
// create I:P
asprintf(&icp,"%s:%.3s-%.2s-%.3s",I,setupCode,setupCode+3,setupCode+5);
// compute x = SHA512( s | SHA512( I | ":" | P ) )
memcpy(tBuf,vData->salt,16); // write salt into first 16 bytes of staging buffer
mbedtls_sha512_ret((uint8_t *)icp,strlen(icp),tBuf+16,0); // create hash of username:password and write into last 64 bytes of staging buffer
mbedtls_sha512_ret(tBuf,80,tHash,0); // create second hash of salted, hashed username:password
mbedtls_mpi_read_binary(&x,tHash,64); // load hash result into x
// compute v = g^x %N
mbedtls_mpi_exp_mod(&v,&g,&x,&N,&_rr); // create verifier, v (_rr is an internal "helper" structure that mbedtls uses to speed up subsequent exponential calculations)
mbedtls_mpi_write_binary(&v,vData->verifyCode,384); // write v into verifyCode (padding with initial zeros is less than 384 bytes)
free(icp);
}
//////////////////////////////////////
void SRP6A::createPublicKey(const Verification *vData, uint8_t *publicKey){
TempBuffer<uint8_t> tBuf(768); // temporary buffer for staging
TempBuffer<uint8_t> tHash(64); // temporary buffer for storing SHA-512 results
TempBuffer<uint8_t> privateKey(32); // temporary buffer for generating private key random numbers
// load stored salt, s, and verification code, v
mbedtls_mpi_read_binary(&s,vData->salt,16); // load salt into s for use in later steps
mbedtls_mpi_read_binary(&v,vData->verifyCode,384); // load verifyCode into v for use below
// generate random private key, b
randombytes_buf(privateKey,32); // generate 32 random bytes for private key
mbedtls_mpi_read_binary(&b,privateKey,32); // load private key into b
// compute k = SHA512( N | PAD(g) )
mbedtls_mpi_write_binary(&N,tBuf,384); // write N into first half of staging buffer
mbedtls_mpi_write_binary(&g,tBuf+384,384); // write g into second half of staging buffer (fully padded with leading zeros)
mbedtls_sha512_ret(tBuf,768,tHash,0); // create hash of data
mbedtls_mpi_read_binary(&k,tHash,64); // load hash result into k
// compute B = (k*v + g^b) %N
mbedtls_mpi_mul_mpi(&t1,&k,&v); // t1 = k*v
mbedtls_mpi_exp_mod(&t2,&g,&b,&N,&_rr); // t2 = g^b %N
mbedtls_mpi_add_mpi(&t3,&t1,&t2); // t3 = t1 + t2
mbedtls_mpi_mod_mpi(&B,&t3,&N); // B = t3 %N = ACCESSORY PUBLIC KEY
mbedtls_mpi_write_binary(&B,publicKey,384); // write B into publicKey (padding with initial zeros is less than 384 bytes)
mbedtls_mpi_read_binary(&k,tHash,64); // load hash result into mpi structure k
}
//////////////////////////////////////
void SRP6A::createSessionKey(const uint8_t *publicKey, size_t len){
void SRP6A::createVerifyCode(const char *setupCode, uint8_t *verifyCode, uint8_t *salt){
TempBuffer<uint8_t> tBuf(768); // temporary buffer for staging
TempBuffer<uint8_t> tHash(64); // temporary buffer for storing SHA-512 results
uint8_t tBuf[80]; // temporary buffer for staging
uint8_t tHash[64]; // temporary buffer for storing SHA-512 results
char icp[22]; // storage for I:P
mbedtls_mpi_read_binary(&A,publicKey,len); // load client PublicKey into A
randombytes_buf(salt,16); // generate 16 random bytes using libsodium (which uses the ESP32 hardware-based random number generator)
mbedtls_mpi_read_binary(&s,salt,16);
sprintf(icp,"Pair-Setup:%.3s-%.2s-%.3s",setupCode,setupCode+3,setupCode+5);
// compute x = SHA512( s | SHA512( I | ":" | P ) )
mbedtls_mpi_write_binary(&s,tBuf,16); // write s into first 16 bytes of staging buffer
mbedtls_sha512_ret((uint8_t *)icp,strlen(icp),tBuf+16,0); // create hash of username:password and write into last 64 bytes of staging buffer
mbedtls_sha512_ret(tBuf,80,tHash,0); // create second hash of salted, hashed username:password
mbedtls_mpi_read_binary(&x,tHash,64); // load hash result into mpi structure x
// compute v = g^x % N
mbedtls_mpi_exp_mod(&v,&g,&x,&N,&_rr); // create verifier, v (_rr is an internal "helper" structure that mbedtls uses to speed up subsequent exponential calculations)
mbedtls_mpi_write_binary(&v,verifyCode,384); // write v into verifyCode
}
//////////////////////////////////////
void SRP6A::loadVerifyCode(uint8_t *verifyCode, uint8_t *salt){
mbedtls_mpi_read_binary(&s,salt,16);
mbedtls_mpi_read_binary(&v,verifyCode,384);
}
//////////////////////////////////////
void SRP6A::createPublicKey(){
getPrivateKey(); // create and load b (random 32 bytes)
// compute B = kv + g^b %N
mbedtls_mpi_mul_mpi(&t1,&k,&v); // t1 = k*v
mbedtls_mpi_exp_mod(&t2,&g,&b,&N,&_rr); // t2 = g^b %N
mbedtls_mpi_add_mpi(&t3,&t1,&t2); // t3 = t1 + t2
mbedtls_mpi_mod_mpi(&B,&t3,&N); // B = t3 %N = ACCESSORY PUBLIC KEY
}
//////////////////////////////////////
void SRP6A::getPrivateKey(){
uint8_t privateKey[32];
randombytes_buf(privateKey,32); // generate 32 random bytes using libsodium (which uses the ESP32 hardware-based random number generator)
mbedtls_mpi_read_binary(&b,privateKey,32);
}
//////////////////////////////////////
void SRP6A::createSessionKey(){
uint8_t tBuf[768]; // temporary buffer for staging
uint8_t tHash[64]; // temporary buffer for storing SHA-512 results
// compute u = SHA512( PAD(A) | PAD(B) )
mbedtls_mpi_write_binary(&A,tBuf,384); // write A into first half of staging buffer (padding with initial zeros is less than 384 bytes)
mbedtls_mpi_write_binary(&B,tBuf+384,384); // write B into second half of staging buffer (padding with initial zeros is less than 384 bytes)
mbedtls_mpi_write_binary(&A,tBuf,384); // write A into first half of staging buffer
mbedtls_mpi_write_binary(&B,tBuf+384,384); // write B into second half of staging buffer
mbedtls_sha512_ret(tBuf,768,tHash,0); // create hash of data
mbedtls_mpi_read_binary(&u,tHash,64); // load hash result into mpi structure u
// compute S = (A * v^u)^b %N
// compute S = (Av^u)^b %N
mbedtls_mpi_exp_mod(&t1,&v,&u,&N,&_rr); // t1 = v^u %N
mbedtls_mpi_mul_mpi(&t2,&A,&t1); // t2 = A*t1
mbedtls_mpi_mod_mpi(&t1,&t2,&N); // t1 = t2 %N (this is needed to reduce size of t2 before next calculation)
mbedtls_mpi_exp_mod(&S,&t1,&b,&N,&_rr); // S = t1^b %N
// compute K = SHA512( PAD(S) )
// compute K = SHA512( S )
mbedtls_mpi_write_binary(&S,tBuf,384); // write S into staging buffer (only first half of buffer will be used)
mbedtls_sha512_ret(tBuf,384,K,0); // create hash of data - this is the SRP SHARED SESSION KEY, K
mbedtls_sha512_ret(tBuf,384,tHash,0); // create hash of data
mbedtls_mpi_read_binary(&K,tHash,64); // load hash result into mpi structure K. This is the SRP SHARED SECRET KEY
mbedtls_mpi_write_binary(&K,sharedSecret,64); // store SHARED SECRET in easy-to-use binary (uint8_t) format
}
//////////////////////////////////////
int SRP6A::verifyProof(){
int SRP6A::verifyClientProof(const uint8_t *proof){
uint8_t tBuf[976]; // temporary buffer for staging
uint8_t tHash[64]; // temporary buffer for storing SHA-512 results
TempBuffer<uint8_t> tBuf(976); // temporary buffer for staging
TempBuffer<uint8_t> tHash(64); // temporary buffer for storing SHA-512 results
memcpy(M1,proof,64); // load client Proof into M1
size_t count=0; // total number of bytes for final hash
size_t count=0; // total number of bytes for final hash
size_t sLen;
// compute M1V = SHA512( SHA512(N) xor SHA512(g) | SHA512(I) | s | A | B | K )
mbedtls_mpi_write_binary(&N,tBuf,384); // write N into staging buffer
mbedtls_sha512_ret(tBuf,384,tHash,0); // create hash of data
mbedtls_sha512_ret(&g3072,1,tBuf,0); // create hash of g, but place output directly into staging buffer
mbedtls_sha512_ret((uint8_t *)g3072,1,tBuf,0); // create hash of g, but place output directly into staging buffer
for(int i=0;i<64;i++) // H(g) -> H(g) XOR H(N), with results in first 64 bytes of staging buffer
tBuf[i]^=tHash[i];
@ -214,12 +209,13 @@ int SRP6A::verifyClientProof(const uint8_t *proof){
mbedtls_mpi_write_binary(&B,tBuf+count,sLen); // concatenate B to staging buffer. Note B is NOT padded with leading zeros (so may be less than 384 bytes)
count+=sLen; // increment total bytes written to staging buffer
memcpy(tBuf+count,K,64); // concatenate K to staging buffer (should always be 64 bytes since it is a hashed value)
mbedtls_mpi_write_binary(&K,tBuf+count,64); // concatenate K to staging buffer (should always be 64 bytes since it is a hashed value)
count+=64; // final total of bytes written to staging buffer
mbedtls_sha512_ret(tBuf,count,tHash,0); // create hash of data - this is M1V
if(!memcmp(M1,tHash,64)) // check that client Proof M1 matches M1V
mbedtls_sha512_ret(tBuf,count,tHash,0); // create hash of data
mbedtls_mpi_read_binary(&M1V,tHash,64); // load hash result into mpi structure M1V
if(!mbedtls_mpi_cmp_mpi(&M1,&M1V)) // cmp_mpi uses same logic as strcmp: returns 0 if EQUAL, otherwise +/- 1
return(1); // success - proof from HAP Client is verified
return(0);
@ -227,34 +223,59 @@ int SRP6A::verifyClientProof(const uint8_t *proof){
//////////////////////////////////////
void SRP6A::createAccProof(uint8_t *proof){
void SRP6A::createProof(){
TempBuffer<uint8_t> tBuf(512); // temporary buffer for staging
uint8_t tBuf[512]; // temporary buffer for staging
// compute M2 = SHA512( A | M1 | K )
// compute M2 = H( A | M1 | K )
mbedtls_mpi_write_binary(&A,tBuf,384); // write A into staging buffer
memcpy(tBuf+384,M1,64); // concatenate M1 (now verified) to staging buffer
memcpy(tBuf+448,K,64); // concatenate K to staging buffer
mbedtls_sha512_ret(tBuf,512,proof,0); // create hash of data writing directly to proof - this is M2
mbedtls_mpi_write_binary(&M1,tBuf+384,64); // concatenate M1 (now verified) to staging buffer
mbedtls_mpi_write_binary(&K,tBuf+448,64); // concatenate K to staging buffer
mbedtls_sha512_ret(tBuf,512,tBuf,0); // create hash of data
mbedtls_mpi_read_binary(&M2,tBuf,64); // load hash results into mpi structure M2
}
//////////////////////////////////////
int SRP6A::loadTLV(kTLVType tag, mbedtls_mpi *mpi, int nBytes){
uint8_t *buf=HAPClient::tlv8.buf(tag,nBytes);
if(!buf)
return(0);
mbedtls_mpi_write_binary(mpi,buf,nBytes);
return(1);
}
//////////////////////////////////////
int SRP6A::writeTLV(kTLVType tag, mbedtls_mpi *mpi){
int nBytes=HAPClient::tlv8.len(tag);
if(nBytes>0){
mbedtls_mpi_read_binary(mpi,HAPClient::tlv8.buf(tag),nBytes);
return(1);
};
return(0);
}
//////////////////////////////////////
void SRP6A::print(mbedtls_mpi *mpi){
char sBuf[2000];
size_t sLen;
mbedtls_mpi_write_string(mpi,16,NULL,0,&sLen);
TempBuffer<char> sBuf(sLen);
mbedtls_mpi_write_string(mpi,16,sBuf,sLen,&sLen);
mbedtls_mpi_write_string(mpi,16,sBuf,2000,&sLen);
Serial.printf("%d %s\n",(sLen-1)/2,sBuf.get()); // subtract 1 for null-terminator, and then divide by 2 to get number of bytes (e.g. 4F = 2 characters, but represents just one mpi byte)
Serial.print((sLen-1)/2); // subtract 1 for null-terminator, and then divide by 2 to get number of bytes (e.g. 4F = 2 characters, but represents just one mpi byte)
Serial.print(" ");
Serial.println(sBuf);
}
//////////////////////////////////////
constexpr char SRP6A::N3072[];
constexpr char SRP6A::I[];
const uint8_t SRP6A::g3072;

View File

@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2022 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
@ -31,19 +31,11 @@
#include <mbedtls/bignum.h>
#include <mbedtls/base64.h>
#include "Utils.h"
/////////////////////////////////////////////////
// Pair-Setup Code Verification Data and Salt
struct Verification {
uint8_t salt[16];
uint8_t verifyCode[384];
};
#include "HAPConstants.h"
/////////////////////////////////////////////////
// SRP-6A Structure from RFC 5054 (Nov 2007)
// ** HAP uses N=3072-bit Group specified in RFC 5054 with Generator g=5
// ** HAP uses N=3072-bit Group specified in RFC 5054
// ** HAP replaces H=SHA-1 with H=SHA-512 (HAP Section 5.5)
//
// I = SRP-6A username, defined by HAP to be the word "Pair-Setup"
@ -51,22 +43,6 @@ struct Verification {
struct SRP6A {
static constexpr char N3072[]="FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74"
"020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437"
"4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"
"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05"
"98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB"
"9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B"
"E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718"
"3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33"
"A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7"
"ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864"
"D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E2"
"08E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF";
static const uint8_t g3072=5;
static constexpr char I[]="Pair-Setup";
mbedtls_mpi N; // N - 3072-bit Group pre-defined prime used for all SRP-6A calculations (384 bytes)
mbedtls_mpi g; // g - pre-defined generator for the specified 3072-bit Group (g=5)
mbedtls_mpi k; // k = H(N | PAD(g)) - SRP-6A multiplier (which is different from versions SRP-6 or SRP-3)
@ -78,25 +54,39 @@ struct SRP6A {
mbedtls_mpi A; // A - public key RECEIVED from HAP Client (max 384 bytes)
mbedtls_mpi u; // u = H(PAD(A) | PAB(B)) - "u-factor" (64 bytes)
mbedtls_mpi S; // S = (A*v^u)^b %N - SRP shared "premaster" key, based on accessory private key and client public key (max 384 bytes)
uint8_t K[64]; // K = H(S) - SRP SHARED SECRET KEY (64 bytes)
uint8_t M1[64]; // M1 - proof RECEIVED from HAP Client (64 bytes)
mbedtls_mpi t1; // temp1 - temporary mpi structures for intermediate results
mbedtls_mpi t2; // temp2 - temporary mpi structures for intermediate results
mbedtls_mpi t3; // temp3 - temporary mpi structures for intermediate results
mbedtls_mpi K; // K = H( S ) - SRP SHARED SECRET KEY (64 bytes)
mbedtls_mpi M1; // M1 - proof RECEIVED from HAP Client (64 bytes)
mbedtls_mpi M1V; // M1V - accessory's independent computation of M1 to verify proof (see code for details of computation)
mbedtls_mpi M2; // M2 - accessory's counter-proof to send to HAP Client after M1=M1V has been verified (64 bytes)
mbedtls_mpi t1; // temporary mpi structures for intermediate results
mbedtls_mpi t2;
mbedtls_mpi t3;
mbedtls_mpi _rr; // _rr - temporary "helper" for large exponential modulus calculations
char I[11]="Pair-Setup"; // I - userName pre-defined by HAP pairing setup protocol
char g3072[2]="\x05"; // g - 3072-bit Group generator
uint8_t sharedSecret[64]; // permanent storage for binary version of SHARED SECRET KEY for ease of use upstream
SRP6A(); // initializes N, G, and computes k
~SRP6A();
void *operator new(size_t size){return(HS_MALLOC(size));} // override new operator to use PSRAM when available
void createVerifyCode(const char *setupCode, Verification *vData); // generates random s and computes v; writes back resulting Verification Data
void createPublicKey(const Verification *vData, uint8_t *publicKey); // generates random b and computes k and B; writes back resulting Accessory Public Key
void createSessionKey(const uint8_t *publicKey, size_t len); // computes u, S, and K from Client Public Key, A (of variable length)
int verifyClientProof(const uint8_t *proof); // verifies Client Proof, M1, received from HAP client (return 1 on success, 0 on failure)
void createAccProof(uint8_t *proof); // computes M2; write back resulting Accessory Proof
void createVerifyCode(const char *setupCode, uint8_t *verifyCode, uint8_t *salt);
void loadVerifyCode(uint8_t *verifyCode, uint8_t *salt);
void getSalt(); // generates and stores random 16-byte salt, s
void getPrivateKey(); // generates and stores random 32-byte private key, b
void getSetupCode(char *c); // generates and displays random 8-digit Pair-Setup code, P, in format XXX-XX-XXX
void createPublicKey(); // computes x, v, and B from random s, P, and b
void createSessionKey(); // computes u from A and B, and then S from A, v, u, and b
int loadTLV(kTLVType tag, mbedtls_mpi *mpi, int nBytes); // load binary contents of mpi into a TLV record and set its length
int writeTLV(kTLVType tag, mbedtls_mpi *mpi); // write binary contents of a TLV record into an mpi
int verifyProof(); // verify M1 SRP6A Proof received from HAP client (return 1 on success, 0 on failure)
void createProof(); // create M2 server-side SRP6A Proof based on M1 as received from HAP Client
void print(mbedtls_mpi *mpi); // prints size of mpi (in bytes), followed by the mpi itself (as a hex character string)
void print(mbedtls_mpi *mpi); // prints size of mpi (in bytes), followed by the mpi itself (as a hex charcter string) - for diagnostic purposes only
};

View File

@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2022 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
@ -24,9 +24,37 @@
* SOFTWARE.
*
********************************************************************************/
// USER-DEFINED SETTINGS AND REFERENCE ENUMERATION CLASSES
#include <core_version.h>
#pragma once
//////////////////////////////////////////////////////
// HomeSpan Version //
#define HS_MAJOR 1
#define HS_MINOR 5
#define HS_PATCH 1
#define STRINGIFY(x) _STR(x)
#define _STR(x) #x
#define HOMESPAN_VERSION STRINGIFY(HS_MAJOR) "." STRINGIFY(HS_MINOR) "." STRINGIFY(HS_PATCH)
#define VERSION(major,minor,patch) major*10000+minor*100+patch
#ifndef REQUIRED
#define REQUIRED 0
#endif
#if (REQUIRED>VERSION(HS_MAJOR,HS_MINOR,HS_PATCH))
#error THIS SKETCH REQUIRES A LATER VERSION OF THE HOMESPAN LIBRARY
#endif
#define ARDUINO_ESP_VERSION STRINGIFY(ARDUINO_ESP32_GIT_DESC)
//////////////////////////////////////////////////////
// DEFAULT SETTINGS //
@ -39,6 +67,9 @@
#define DEFAULT_QR_ID "HSPN" // change with homeSpan.setQRID(qrID);
#define DEFAULT_CONTROL_PIN -1 // change with homeSpan.setControlPin(pin)
#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
@ -52,10 +83,6 @@
#define DEFAULT_WEBLOG_URL "status" // change with optional fourth argument in homeSpan.enableWebLog()
#define DEFAULT_LOW_MEM_THRESHOLD 80000 // default low watermark memory (for internal RAM) threshold that triggers warning
#define DEFAULT_REBOOT_CALLBACK_TIME 5000 // default time (in milliseconds) to check for reboot callback
/////////////////////////////////////////////////////
// OTA PARTITION INFO //
@ -76,11 +103,11 @@
// Message Log Level Control Macros //
// 0=Minimal, 1=Informative, 2=All //
#define LOG0(format,...) do{ if(homeSpan.getLogLevel()>=0)Serial.print ##__VA_OPT__(f)(format __VA_OPT__(,) __VA_ARGS__); }while(0)
#define LOG1(format,...) do{ if(homeSpan.getLogLevel()>=1)Serial.print ##__VA_OPT__(f)(format __VA_OPT__(,) __VA_ARGS__); }while(0)
#define LOG2(format,...) do{ if(homeSpan.getLogLevel()>=2)Serial.print ##__VA_OPT__(f)(format __VA_OPT__(,) __VA_ARGS__); }while(0)
#define LOG0(format,...) Serial.print ##__VA_OPT__(f)(format __VA_OPT__(,) __VA_ARGS__)
#define LOG1(format,...) if(homeSpan.logLevel>0)Serial.print ##__VA_OPT__(f)(format __VA_OPT__(,) __VA_ARGS__)
#define LOG2(format,...) if(homeSpan.logLevel>1)Serial.print ##__VA_OPT__(f)(format __VA_OPT__(,) __VA_ARGS__)
#define WEBLOG(format,...) homeSpan.addWebLog(false, format __VA_OPT__(,) __VA_ARGS__);
#define WEBLOG(format,...) homeSpan.webLog.addLog(format __VA_OPT__(,) __VA_ARGS__)
//////////////////////////////////////////////////////
// Types of Accessory Categories //

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More