Merge pull request #172 from HomeSpan/release-1.4.0

Release 1.4.0
This commit is contained in:
HomeSpan 2021-10-09 15:14:09 -05:00 committed by GitHub
commit 88c4bb5c9f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 542 additions and 448 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
.development .development
.DS_Store

View File

@ -4,21 +4,27 @@ HomeSpan includes integrated access to a number of ESP32 features you'll likely
## Pulse Width Modulation (PWM) ## Pulse Width Modulation (PWM)
The ESP32 has 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 accessd by placing the following near the top of your sketch: 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:
`#include "extras/PwmPin.h"` `#include "extras/PwmPin.h"`
### *LedPin(uint8_t pin)* ### *LedPin(uint8_t pin [,float level [,uint16_t frequency]])*
Creating an instance of this **class** configures the specified *pin* to output a 5000 Hz PWM signal, which is suitable for dimming LEDs. The following methods are supported: 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:
* `void set(uint8_t level)` * *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
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) * sets the PWM %duty-cycle to *level*, where *level* ranges from 0 (LED completely off) to 100 (LED fully on)
* `int getPin()` * `int getPin()`
* returns the pin number * returns the pin number (or -1 if LedPin was not successfully initialized)
LedPin also includes a static class function that converts Hue/Saturation/Brightness values (typically used by HomeKit) to Red/Green/Blue values (typically used to control multi-color LEDS). LedPin also includes a static class function that converts Hue/Saturation/Brightness values (typically used by HomeKit) to Red/Green/Blue values (typically used to control multi-color LEDS).
@ -46,23 +52,27 @@ Creating an instance of this **class** configures the specified *pin* to output
The *minMicros* parameter must be less than the *maxMicros* parameter, but setting *minDegrees* to a value greater than *maxDegrees* is allowed and can be used to reverse the minimum and maximum positions of the Servo Motor. The following methods are supported: The *minMicros* parameter must be less than the *maxMicros* parameter, but setting *minDegrees* to a value greater than *maxDegrees* is allowed and can be used to reverse the minimum and maximum positions of the Servo Motor. The following methods are supported:
* `void set(uint8_t position)` * `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*. * 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()` * `int getPin()`
* returns the pin number * 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*](../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).
Resource limitations: ### PWM Resource Allocation and Limitations
* A maximum of 16 LedPin objects can be instantiated The following PWM resources are available:
* A maximum of 8 ServoPin objects can be instantiated
* A maximum combined total of 16 LedPin and ServoPin objects can be instantiated (for example 10 LedPin and 6 ServoPin objects)
HomeSpan will report a non-fatal error message to the Arduino Serial Monitor for each LedPin or ServoPin that instantiated beyond these limits. Calls to the `set()` method for objects that exceed these limits are ignored. * 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
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.*
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.
## Remote Control Radio Frequency / Infrared Signal Generation ## Remote Control Radio Frequency / Infrared Signal Generation
@ -72,40 +82,70 @@ The ESP32 has an on-chip signal-generator peripheral designed to drive an RF or
### *RFControl(int pin)* ### *RFControl(int pin)*
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*). 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.
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 diagram below. 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 diagram below.
![Pulse Train](images/pulseTrain.png) ![Pulse Train](images/pulseTrain.png)
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. The following methods are used to construct the pulse train, set the number of repeats, set the duration of a *tick*, and start the transmission: 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:
* `static void phase(uint16_t numTicks, uint8_t phase)` * 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 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 trasnmitted is HIGH (1) or LOW (0)
* appends either a HIGH or LOW phase to the pulse train memory buffer, which has room to store a maximum of 1023 phases. Requests to add more than 1023 phases are ignored, but raise a non-fatal warning message. Note that this is a class-level method as there is only one pulse train memory buffer that is **shared** across all instances of the RFControl object 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.
* *numTicks* - the duration, in *ticks* of the pulse phase. Allowable range is 1-32767 ticks. Requests to add a pulse with *numTicks* outside this range are ignored, but raise non-fatal warning message In the second method, you create one or more pulse trains in external arrays of 32-bit words using the protocol above. To begin transmission of a specific pulse train, call the `start()` function with a pointer reference to the external array containing that pulse train. This method is generally used when you want to pre-compute many different pulse trains and have them ready-to-transmit as needed. Note that this method requires the array to be stored in RAM, not PSRAM.
Details of each function are as follows:
* `void clear()`
* clears the pulse train memory structure of a specific instance of RFControl
* `void phase(uint16_t numTicks, uint8_t phase)`
* appends either a HIGH or LOW phase to the pulse train memory buffer for a specific instance of RFControl
* *numTicks* - the duration, in *ticks* of the pulse phase. Allowable range is 0-32767 ticks. Requests to add a pulse with *numTicks* outside this range are ignored, but raise non-fatal warning message
* *phase* - set to 0 to create a LOW phase; set to 1 (or any non-zero number) to create a HIGH phase * *phase* - set to 0 to create a LOW phase; set to 1 (or any non-zero number) to create a HIGH phase
* repeated phases of the same type (e.g. HIGH followed by another HIGH) is permitted and result in a single HIGH phase with a duration equal to the sum of the *numTicks* specified for each repeated phase (this is helpful when generating Manchester-encoded signals) * repeated phases of the same type (e.g. HIGH followed by another HIGH) are permitted and result in a single HIGH or LOW phase with a duration equal to the sum of the *numTicks* specified for each repeated phase (this is helpful when generating Manchester-encoded signals)
* `static void add(uint16_t onTime, uint16_t offTime)` * `void add(uint16_t onTime, uint16_t offTime)`
* a convenience function that create a single HIGH/LOW pulse. Implemented as `phase(onTime,HIGH); phase(offTime,LOW);` as defined above, and subject to all the same limits and error-checks * appends a single HIGH/LOW pulse with duration *onTime* followed by *offTime* to the pulse train of a specific instance of RFControl. This is functionally equivalent to calling `phase(onTime,HIGH);` followed by `phase(offTime,LOW);` as defined above
* `static void clear()`
* clears the pulse train memory buffer
* `void start(uint8_t _numCycles, uint8_t tickTime)` * `void start(uint8_t _numCycles, uint8_t tickTime)`
* `void start(uint32_t *data, int nData, uint8_t nCycles, uint8_t tickTime)`
* starts the transmission of the pulse train stored in the pulse train memory buffer. The signal will be output on the *pin* specified when RFControl was instantiated. Note this is a blocking call—the method waits until transmission is completed before returning. This should not produce a noticeable delay in program operations since most RF/IR pulse trains are only a few tens-of-milliseconds long * in the first variation, this starts the transmission of the pulse train stored in the internal memory structure of a given instance of RFControl that was created using the `clear()`, `add()`, and `phase()` functions above. In the second variation, this starts the transmission of the pulse train stored in an external array *data* containing *nData* 32-bit words. The signal will be output on the pin specified when RFControl was instantiated. Note this is a blocking call—the method waits until transmission is completed before returning. This should not produce a noticeable delay in program operations since most RF/IR pulse trains are only a few tens-of-milliseconds long
* *numCycles* - the total number of times to transmit the pulse train (i.e. a value of 3 means the pulse train will be transmitted once, followed by 2 additional re-transmissions) * *numCycles* - the total number of times to transmit the pulse train (i.e. a value of 3 means the pulse train will be transmitted once, followed by 2 additional re-transmissions). This is an optional argument with a default of 1 if not specified.
* *tickTime* - the duration, in **microseconds**, of a *tick*. This is an optional argument with a default of 1𝛍s if not specified. Valid range is 1-255𝛍s, or set to 0 for 256𝛍s * *tickTime* - the duration, in **microseconds**, of a *tick*. This is an optional argument with a default of 1𝛍s if not specified. Valid range is 1-255𝛍s, or set to 0 for 256𝛍s
* To aid in the creation of a pulse train stored in an external array of 32-bit words, RFControl includes the macro *RF_PULSE(highTicks,lowTicks)* that returns a properly-formatted 32-bit value representing a single HIGH/LOW pulse of duration *highTicks* followed by *lowTicks*. This is basically an analog to the `add()` function. For example, the following code snippet shows two ways of creating and transmitting the same 3-pulse pulse-train --- the only difference being that one uses the internal memory structure of RFControl, and the second uses an external array:
```C++
RFControl rf(11); // create an instance of RFControl
rf.clear(); // clear the internal memory structure
rf.add(100,50); // create pulse of 100 ticks HIGH followed by 50 ticks LOW
rf.add(100,50); // create a second pulse of 100 ticks HIGH followed by 50 ticks LOW
rf.add(25,500); // create a third pulse of 25 ticks HIGH followed by 500 ticks LOW
rf.start(4,1000); // start transmission of the pulse train; repeat for 4 cycles; one tick = 1000𝛍s
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
```
### 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*](https://github.com/HomeSpan/HomeSpan/tree/dev/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*](https://github.com/HomeSpan/HomeSpan/tree/dev/Other%20Examples/RemoteControl).
```C++ ```C++
@ -161,20 +201,4 @@ void loop(){
--- ---
#### Deprecated functions (available for backwards compatibility with older sketches):
*PwmPin(uint8_t channel, uint8_t pin)*
* this legacy function was used to generically control the ESP32's built-in PWM generators to drive a dimmable LED and required the user to keep track of individual PWM channels. It has been replaced by two specific (and much easier-to-use) methods:
* *LedPin(uint8_t pin)* - drives a dimmable LED
* *ServoPin(uint8_t pin [,double initDegrees [,uint16_t minMicros, uint16_t maxMicros, double minDegrees, double maxDegrees]])* - drives a Servo Motor
* last supported version: [v1.2.1](https://github.com/HomeSpan/HomeSpan/blob/release-1.2.1/docs/Extras.md#pwmpinuint8_t-channel-uint8_t-pin)
* **please use** `LedPin` and `ServoPin` **for all new sketches**
---
[↩️](README.md) Back to the Welcome page [↩️](README.md) Back to the Welcome page

View File

@ -48,11 +48,13 @@ Though the device is now programmed and fully operational, it needs to be config
In addition to being able to configure a HomeSpan device using the [HomeSpan CLI](CLI.md) via the Arduino Serial Monitor, HomeSpan provides an alternative method for end-users to configure a standalone HomeSpan device that is not connected to a computer. This method requires the installation of two external components: In addition to being able to configure a HomeSpan device using the [HomeSpan CLI](CLI.md) via the Arduino Serial Monitor, HomeSpan provides an alternative method for end-users to configure a standalone HomeSpan device that is not connected to a computer. This method requires the installation of two external components:
1. a normally-open single-pole pushbutton to function as the HomeSpan Control Button, and 1. a normally-open single-pole pushbutton to function as the HomeSpan Control Button, and
1. an LED to function as the HomeSpan Status LED. 1. an LED (with a current-limiting resistor) to function as the HomeSpan Status LED.
The Control Button should be installed between the HomeSpan Control Pin on the ESP32 and ground. The HomeSpan Control Pin defaults to pin 21, but can be set to any other pin during HomeSpan initializaton (see the [HomeSpan API Reference](Reference.md) for details). The LED can similarly be connected to any pin you specify, but defaults to pin 13, which for some boards is connected to a built-in LED, thereby saving you the need to install a separate LED. 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.
The use of these two components to configure a standalone HomeSpan device, including starting HomeSpan's temporary WiFi network to configure the device's WiFi Credentials and HomeKit Setup Code, are fully explained in the [HomeSpan User Guide](UserGuide.md). 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).
## What Next? ## What Next?

View File

@ -4,7 +4,7 @@ Welcome to HomeSpan - a robust and extremely easy-to-use Arduino library for cre
HomeSpan provides a microcontroller-focused implementation of [Apple's HomeKit Accessory Protocol Specification Release R2 (HAP-R2)](https://developer.apple.com/homekit/specification/) designed specifically for the Espressif ESP32 microcontroller running within the Arduino IDE. HomeSpan pairs directly to HomeKit via your home WiFi network without the need for any external bridges or components. With HomeSpan you can use the full power of the ESP32's I/O functionality to create custom control software and/or hardware to automatically operate external devices from the Home App on your iPhone, iPad, or Mac, or with Siri. HomeSpan 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.
> :exclamation: HomeSpan currently works only under the version 1 series of the Arduino-ESP32 board manager (the latest being version 1.0.6). The new version 2 series (including version 2.0.0 released by Espressif in Sep 2021) is not backwards compatible with version 1.0.6 and breaks HomeSpan. A new version of HomeSpan will be released shortly that is compatible with version 2.0.0 of the Arduino-ESP32 board manager. At this time please use version 1.0.6 of the board manager to compile HomeSpan sketches. 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 ### HomeSpan Highlights
@ -41,18 +41,35 @@ HomeSpan provides a microcontroller-focused implementation of [Apple's HomeKit A
* Launch the WiFi Access Point * Launch the WiFi Access Point
* A standalone, detailed End-User Guide * A standalone, detailed End-User Guide
## Latest Update - HomeSpan 1.3.0 (6/20/2021) ## ❗Latest Update - HomeSpan 1.4.0 (10/9/2021)
**HomeSpan is now fully compatible with Version 2.0.0 of the Arduino-ESP32 Board Manager and will run on the following Espressif chips:**
* **ESP32**
* **ESP32-S2**
* **ESP32-C3**
This update brings a number of new features and enhancements: HomeSpan also maintains full backwards-compatability with Version 1.0.6 of the Arduino-ESP Board Manager should you need to revert to that version. This has been a complicated update! Please report any bugs or issues found when using Version 2 of the Arduino-ESP32.
* The PWM library has been— Also included in HomeSpan 1.4.0 are the following new features and enhancements:
* upgraded to allow for much easier control of up to 16 dimmable LEDs, ***and***
* extended with a dedicated class to simultaneously operate up to 8 Servo Motors! * **The PWM library has been upgraded**
* Characteristic values can be automatically saved in non-volatile storage for retention in the event of a power loss. When power is restored your Accessories will automatically revert to their most recent state! * users can specify a custom PWM frequency for each instance of LedPin() (subject to chip-specific resource limitations)
* The HomeSpan CLI can now be customized — extend the CLI with your own functions and commands! * users can set the duty cycle of an LedPin() as a decimal floating number (e.g., 34.56), instead of just an integer
* Enable the automatic launch of HomeSpan's WiFi Access Point upon start-up whenever WiFi Credentials are not found. * the library automatically sets the duty resolution to the maximum allowable value for a chosen PWM frequency
* For advanced users: create your own custom WiFi Access Point and set your WiFi Credentials programmatically. * the library optimzizes the distribution of multiple LedPin() and ServoPin() instances to ensure all available PWM channels are used
* **The RFControl library has been upgraded**
* the library allows for the transmisison of arbitrary-length pulse trains
* users can pre-load pulse trains into one or more arbitrary-length arrays of 32-bit words for on-demand transmission as needed
* Users can now **limit the selection choices** of certain Characteristics (such as the Target State for a Security System) in the Home App using a new method, `setValidValues()`
* **The Status LED and Control Button are now optional components that are ignored unless specifically enabled**
* because HomeSpan now runs on chips with many different pin configurations, HomeSpan's use of preset pin numbers for the Status LED and Control Button is likely to conflict with many devices
* the default behavior for HomeSpan has been changed to ignore all logic related to the Status LED and Control Button
* to enable the Status LED you must specify the pin to which your LED is attached using the usual method `homeSpan.setStatusPin(pin)`
* to enable the Control Button you must specify the pin to which your Control Button is attached using the usual method `homeSpan.setControlPin(pin)`
See [Releases](https://github.com/HomeSpan/HomeSpan/releases) for details on all changes included in this update. See [Releases](https://github.com/HomeSpan/HomeSpan/releases) for details on all changes included in this update.
# HomeSpan Resources # HomeSpan Resources

View File

@ -29,10 +29,13 @@ At runtime HomeSpan will create a global **object** named `homeSpan` that suppor
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: 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:
* `void setControlPin(uint8_t pin)` * `void setControlPin(uint8_t pin)`
* sets the ESP32 pin to use for the HomeSpan Control Button (default=21) * sets the ESP32 pin to use for the HomeSpan Control Button. If not specified, HomeSpan will assume there is no Control Button
* `void setStatusPin(uint8_t pin)` * `void setStatusPin(uint8_t pin)`
* sets the ESP32 pin to use for the HomeSpan Status LED (default=13). There is also a corresponding `getStatusPin()` method that returns this pin number * sets the ESP32 pin to use for the HomeSpan Status LED. If not specified, HomeSpan will assume there is no Status LED
* `int getStatusPin()`
* returns the pin number of the Status LED as set by `setStatusPin(pin)`, or -1 if no pin has been set
* `void setApSSID(const char *ssid)` * `void setApSSID(const char *ssid)`
* sets the SSID (network name) of the HomeSpan Setup Access Point (default="HomeSpan-Setup") * sets the SSID (network name) of the HomeSpan Setup Access Point (default="HomeSpan-Setup")
@ -217,6 +220,14 @@ The following methods are supported:
* returns a pointer to the Characteristic itself so that the method can be chained during instantiation * returns a pointer to the Characteristic itself so that the method can be chained during instantiation
* example: `(new Characteristic::Brightness(50))->setRange(10,100,5);` * example: `(new Characteristic::Brightness(50))->setRange(10,100,5);`
* `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.
* 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
## *SpanButton(int pin, uint16_t longTime, uint16_t singleTime, uint16_t doubleTime)* ## *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. Creating an instance of this **class** attaches a pushbutton handler to the ESP32 *pin* specified.

View File

@ -1,9 +1,9 @@
name=HomeSpan name=HomeSpan
version=1.3.0 version=1.4.0
author=Gregg <homespan@icloud.com> author=Gregg <homespan@icloud.com>
maintainer=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. 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. The user can then use the full power of the ESP32's I/O functionality to create custom control software and/or hardware to operate external devices. 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 url=https://github.com/HomeSpan/HomeSpan
architectures=esp32 architectures=esp32
includes=HomeSpan.h includes=HomeSpan.h

View File

@ -719,7 +719,7 @@ int HAPClient::postPairVerifyURL(){
memcpy(iosCurveKey,tlv8.buf(kTLVType_PublicKey),32); // save iosCurveKey (will persist until end of verification process) memcpy(iosCurveKey,tlv8.buf(kTLVType_PublicKey),32); // save iosCurveKey (will persist until end of verification process)
crypto_scalarmult_curve25519(sharedCurveKey,secretCurveKey,iosCurveKey); // generate (and persist) Pair Verify SharedSecret CurveKey from Accessory's Curve25519 secret key and Controller's Curve25519 public key (32 bytes) int _x = crypto_scalarmult_curve25519(sharedCurveKey,secretCurveKey,iosCurveKey); // generate (and persist) Pair Verify SharedSecret CurveKey from Accessory's Curve25519 secret key and Controller's Curve25519 public key (32 bytes)
uint8_t *accessoryPairingID = accessory.ID; // set accessoryPairingID uint8_t *accessoryPairingID = accessory.ID; // set accessoryPairingID
size_t accessoryPairingIDLen = 17; size_t accessoryPairingIDLen = 17;

View File

@ -31,6 +31,9 @@
#include <WiFi.h> #include <WiFi.h>
#include <ArduinoOTA.h> #include <ArduinoOTA.h>
#include <esp_ota_ops.h> #include <esp_ota_ops.h>
#include <driver/ledc.h>
#include <mbedtls/version.h>
#include <esp_task_wdt.h>
#include "HomeSpan.h" #include "HomeSpan.h"
#include "HAP.h" #include "HAP.h"
@ -52,6 +55,8 @@ void Span::begin(Category catID, const char *displayName, const char *hostNameBa
this->modelName=modelName; this->modelName=modelName;
sprintf(this->category,"%d",(int)catID); sprintf(this->category,"%d",(int)catID);
esp_task_wdt_delete(xTaskGetIdleTaskHandleForCPU(0)); // required to avoid watchdog timeout messages from ESP32-C3
controlButton.init(controlPin); controlButton.init(controlPin);
statusLED.init(statusPin); statusLED.init(statusPin);
@ -90,21 +95,38 @@ void Span::begin(Category catID, const char *displayName, const char *hostNameBa
Serial.print("Message Logs: Level "); Serial.print("Message Logs: Level ");
Serial.print(logLevel); Serial.print(logLevel);
Serial.print("\nStatus LED: Pin "); Serial.print("\nStatus LED: Pin ");
Serial.print(statusPin); if(statusPin>=0)
Serial.print(statusPin);
else
Serial.print("- *** WARNING: Status LED Pin is UNDEFINED");
Serial.print("\nDevice Control: Pin "); Serial.print("\nDevice Control: Pin ");
Serial.print(controlPin); if(controlPin>=0)
Serial.print(controlPin);
else
Serial.print("- *** WARNING: Device Control Pin is UNDEFINED");
Serial.print("\nSketch Version: "); Serial.print("\nSketch Version: ");
Serial.print(getSketchVersion()); Serial.print(getSketchVersion());
Serial.print("\nHomeSpan Version: "); Serial.print("\nHomeSpan Version: ");
Serial.print(HOMESPAN_VERSION); Serial.print(HOMESPAN_VERSION);
Serial.print("\nESP-IDF Version: "); Serial.print("\nArduino-ESP Ver.: ");
Serial.print(esp_get_idf_version()); Serial.print(ARDUINO_ESP_VERSION);
Serial.printf("\nESP-IDF Version: %d.%d.%d",ESP_IDF_VERSION_MAJOR,ESP_IDF_VERSION_MINOR,ESP_IDF_VERSION_PATCH);
Serial.printf("\nESP32 Chip: %s Rev %d %s-core %dMB Flash", ESP.getChipModel(),ESP.getChipRevision(),
ESP.getChipCores()==1?"single":"dual",ESP.getFlashChipSize()/1024/1024);
#ifdef ARDUINO_VARIANT #ifdef ARDUINO_VARIANT
Serial.print("\nESP32 Board: "); Serial.print("\nESP32 Board: ");
Serial.print(ARDUINO_VARIANT); Serial.print(ARDUINO_VARIANT);
#endif #endif
Serial.printf("\nPWM Resources: %d channels, %d timers, max %d-bit duty resolution",
LEDC_SPEED_MODE_MAX*LEDC_CHANNEL_MAX,LEDC_SPEED_MODE_MAX*LEDC_TIMER_MAX,LEDC_TIMER_BIT_MAX-1);
Serial.printf("\nSodium Version: %s Lib %d.%d",sodium_version_string(),sodium_library_version_major(),sodium_library_version_minor());
char mbtlsv[64];
mbedtls_version_get_string_full(mbtlsv);
Serial.printf("\nMbedTLS Version: %s",mbtlsv);
Serial.print("\nSketch Compiled: "); Serial.print("\nSketch Compiled: ");
Serial.print(__DATE__); Serial.print(__DATE__);
Serial.print(" "); Serial.print(" ");
@ -1598,6 +1620,10 @@ int SpanCharacteristic::sprintfAttributes(char *cBuf, int flags){
if(uvGet<float>(stepValue)>0) if(uvGet<float>(stepValue)>0)
nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?128:0,",\"minStep\":%s",uvPrint(stepValue).c_str()); nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?128:0,",\"minStep\":%s",uvPrint(stepValue).c_str());
} }
if(validValues){
nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?128:0,",\"valid-values\":%s",validValues);
}
} }
if(desc && (flags&GET_DESC)){ if(desc && (flags&GET_DESC)){
@ -1718,6 +1744,41 @@ unsigned long SpanCharacteristic::timeVal(){
return(homeSpan.snapTime-updateTime); return(homeSpan.snapTime-updateTime);
} }
///////////////////////////////
SpanCharacteristic *SpanCharacteristic::setValidValues(int n, ...){
char c[256];
String *s = new String("[");
va_list vl;
va_start(vl,n);
for(int i=0;i<n;i++){
*s+=va_arg(vl,int);
if(i!=n-1)
*s+=",";
}
va_end(vl);
*s+="]";
homeSpan.configLog+=String(" \u2b0c Set Valid Values for ") + String(hapName) + " with IID=" + String(iid);
if(validValues){
sprintf(c," *** ERROR! Valid Values already set for this Characteristic! ***\n");
homeSpan.nFatalErrors++;
} else
if(format!=UINT8){
sprintf(c," *** ERROR! Can't set Valid Values for this Characteristic! ***\n");
homeSpan.nFatalErrors++;
} else {
validValues=s->c_str();
sprintf(c,": ValidValues=%s\n",validValues);
}
homeSpan.configLog+=c;
return(this);
}
/////////////////////////////// ///////////////////////////////
// SpanRange // // SpanRange //
/////////////////////////////// ///////////////////////////////

View File

@ -31,8 +31,11 @@
#error ERROR: HOMESPAN IS ONLY AVAILABLE FOR ESP32 MICROCONTROLLERS! #error ERROR: HOMESPAN IS ONLY AVAILABLE FOR ESP32 MICROCONTROLLERS!
#endif #endif
#pragma GCC diagnostic ignored "-Wpmf-conversions" // eliminates warning messages from use of pointers to member functions to detect whether update() and loop() are overridden by user
#include <Arduino.h> #include <Arduino.h>
#include <unordered_map> #include <unordered_map>
#include <vector>
#include <nvs.h> #include <nvs.h>
#include "Settings.h" #include "Settings.h"
@ -115,8 +118,8 @@ struct Span{
unsigned long alarmConnect=0; // time after which WiFi connection attempt should be tried again unsigned long alarmConnect=0; // time after which WiFi connection attempt should be tried again
const char *defaultSetupCode=DEFAULT_SETUP_CODE; // Setup Code used for pairing const char *defaultSetupCode=DEFAULT_SETUP_CODE; // Setup Code used for pairing
uint8_t statusPin=DEFAULT_STATUS_PIN; // pin for status LED int statusPin=DEFAULT_STATUS_PIN; // pin for status LED
uint8_t controlPin=DEFAULT_CONTROL_PIN; // pin for Control Pushbutton int controlPin=DEFAULT_CONTROL_PIN; // pin for Control Pushbutton
uint8_t logLevel=DEFAULT_LOG_LEVEL; // level for writing out log messages to serial monitor uint8_t logLevel=DEFAULT_LOG_LEVEL; // level for writing out log messages to serial monitor
uint8_t maxConnections=DEFAULT_MAX_CONNECTIONS; // number of simultaneous HAP connections uint8_t maxConnections=DEFAULT_MAX_CONNECTIONS; // number of simultaneous HAP connections
unsigned long comModeLife=DEFAULT_COMMAND_TIMEOUT*1000; // length of time (in milliseconds) to keep Command Mode alive before resuming normal operations unsigned long comModeLife=DEFAULT_COMMAND_TIMEOUT*1000; // length of time (in milliseconds) to keep Command Mode alive before resuming normal operations
@ -168,7 +171,7 @@ struct Span{
void setControlPin(uint8_t pin){controlPin=pin;} // sets Control Pin void setControlPin(uint8_t pin){controlPin=pin;} // sets Control Pin
void setStatusPin(uint8_t pin){statusPin=pin;} // sets Status Pin void setStatusPin(uint8_t pin){statusPin=pin;} // sets Status Pin
int getStatusPin(){return(statusPin);} // gets Status Pin int getStatusPin(){return(statusPin);} // get Status Pin
void setApSSID(const char *ssid){network.apSSID=ssid;} // sets Access Point SSID void setApSSID(const char *ssid){network.apSSID=ssid;} // sets Access Point SSID
void setApPassword(const char *pwd){network.apPassword=pwd;} // sets Access Point Password void setApPassword(const char *pwd){network.apPassword=pwd;} // sets Access Point Password
void setApTimeout(uint16_t nSec){network.lifetime=nSec*1000;} // sets Access Point Timeout (seconds) void setApTimeout(uint16_t nSec){network.lifetime=nSec*1000;} // sets Access Point Timeout (seconds)
@ -256,8 +259,9 @@ struct SpanCharacteristic{
UVal minValue; // Characteristic minimum (not applicable for STRING) UVal minValue; // Characteristic minimum (not applicable for STRING)
UVal maxValue; // Characteristic maximum (not applicable for STRING) UVal maxValue; // Characteristic maximum (not applicable for STRING)
UVal stepValue; // Characteristic step size (not applicable for STRING) UVal stepValue; // Characteristic step size (not applicable for STRING)
boolean staticRange; // Flag that indiates whether Range is static and cannot be changed with setRange() boolean staticRange; // Flag that indicates whether Range is static and cannot be changed with setRange()
boolean customRange=false; // Flag for custom ranges boolean customRange=false; // Flag for custom ranges
const char *validValues=NULL; // Optional JSON array of valid values. Applicable only to uint8 Characteristics
boolean *ev; // Characteristic Event Notify Enable (per-connection) boolean *ev; // Characteristic Event Notify Enable (per-connection)
char *nvsKey=NULL; // key for NVS storage of Characteristic value char *nvsKey=NULL; // key for NVS storage of Characteristic value
@ -274,6 +278,8 @@ struct SpanCharacteristic{
boolean updated(){return(isUpdated);} // returns isUpdated boolean updated(){return(isUpdated);} // returns isUpdated
unsigned long timeVal(); // returns time elapsed (in millis) since value was last updated unsigned long timeVal(); // returns time elapsed (in millis) since value was last updated
SpanCharacteristic *setValidValues(int n, ...); // sets a list of 'n' valid values allowed for a Characteristic and returns pointer to self. Only applicable if format=uint8
String uvPrint(UVal &u){ String uvPrint(UVal &u){
char c[64]; char c[64];
@ -298,7 +304,8 @@ struct SpanCharacteristic{
sprintf(c,"\"%s\"",u.STRING); sprintf(c,"\"%s\"",u.STRING);
return(String(c)); return(String(c));
} // switch } // switch
} // str() return(String()); // included to prevent compiler warnings
}
void uvSet(UVal &u, const char *val){ void uvSet(UVal &u, const char *val){
u.STRING=val; u.STRING=val;
@ -328,7 +335,7 @@ struct SpanCharacteristic{
u.FLOAT=(double)val; u.FLOAT=(double)val;
break; break;
} // switch } // switch
} // set() }
template <class T> T uvGet(UVal &u){ template <class T> T uvGet(UVal &u){
@ -351,7 +358,8 @@ struct SpanCharacteristic{
Serial.print("\n*** WARNING: Can't use getVal() or getNewVal() with string Characteristics.\n\n"); Serial.print("\n*** WARNING: Can't use getVal() or getNewVal() with string Characteristics.\n\n");
return(0); return(0);
} }
} // get() return(0); // included to prevent compiler warnings
}
template <typename A, typename B, typename S=int> SpanCharacteristic *setRange(A min, B max, S step=0){ template <typename A, typename B, typename S=int> SpanCharacteristic *setRange(A min, B max, S step=0){
@ -493,7 +501,7 @@ struct SpanCharacteristic{
/////////////////////////////// ///////////////////////////////
struct SpanRange{ struct [[deprecated("Please use Characteristic::setRange() method instead.")]] SpanRange{
SpanRange(int min, int max, int step); SpanRange(int min, int max, int step);
}; };

View File

@ -403,7 +403,8 @@ int Network::getFormValue(char *formData, const char *tag, char *value, int maxS
sscanf(v,"%2x",(unsigned int *)value++); sscanf(v,"%2x",(unsigned int *)value++);
v+=2; v+=2;
} else { } else {
*value++=*v++; *value++=(*v=='+'?' ':*v); // HTML Forms use '+' for spaces (and '+' signs are escaped)
v++;
} }
len++; len++;
} }

View File

@ -144,8 +144,8 @@ void SRP6A::createPublicKey(){
void SRP6A::getPrivateKey(){ void SRP6A::getPrivateKey(){
uint8_t privateKey[32]; uint8_t privateKey[32];
randombytes_buf(privateKey,16); // generate 32 random bytes using libsodium (which uses the ESP32 hardware-based random number generator)
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); mbedtls_mpi_read_binary(&b,privateKey,32);
} }
@ -164,10 +164,11 @@ void SRP6A::createSessionKey(){
mbedtls_mpi_read_binary(&u,tHash,64); // load hash result into mpi structure u mbedtls_mpi_read_binary(&u,tHash,64); // load hash result into mpi structure u
// compute S = (Av^u)^b %N // compute S = (Av^u)^b %N
mbedtls_mpi_exp_mod(&t1,&v,&u,&N,&_rr); // t1 = v^u %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_mul_mpi(&t2,&A,&t1); // t2 = A*t1
mbedtls_mpi_exp_mod(&S,&t2,&b,&N,&_rr); // S = t2^b %N 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( S ) // compute K = SHA512( S )
@ -176,7 +177,7 @@ void SRP6A::createSessionKey(){
mbedtls_mpi_read_binary(&K,tHash,64); // load hash result into mpi structure K. This is the SRP SHARED SECRET KEY 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 mbedtls_mpi_write_binary(&K,sharedSecret,64); // store SHARED SECRET in easy-to-use binary (uint8_t) format
} }
////////////////////////////////////// //////////////////////////////////////
@ -267,10 +268,10 @@ int SRP6A::writeTLV(kTLVType tag, mbedtls_mpi *mpi){
void SRP6A::print(mbedtls_mpi *mpi){ void SRP6A::print(mbedtls_mpi *mpi){
char sBuf[1000]; char sBuf[2000];
size_t sLen; size_t sLen;
mbedtls_mpi_write_string(mpi,16,sBuf,1000,&sLen); mbedtls_mpi_write_string(mpi,16,sBuf,2000,&sLen);
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((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.print(" ");

View File

@ -27,13 +27,15 @@
// USER-DEFINED SETTINGS AND REFERENCE ENUMERATION CLASSES // USER-DEFINED SETTINGS AND REFERENCE ENUMERATION CLASSES
#include <core_version.h>
#pragma once #pragma once
////////////////////////////////////////////////////// //////////////////////////////////////////////////////
// HomeSpan Version // // HomeSpan Version //
#define HS_MAJOR 1 #define HS_MAJOR 1
#define HS_MINOR 3 #define HS_MINOR 4
#define HS_PATCH 0 #define HS_PATCH 0
#define STRINGIFY(x) _STR(x) #define STRINGIFY(x) _STR(x)
@ -49,7 +51,9 @@
#if (REQUIRED>VERSION(HS_MAJOR,HS_MINOR,HS_PATCH)) #if (REQUIRED>VERSION(HS_MAJOR,HS_MINOR,HS_PATCH))
#error THIS SKETCH REQUIRES A LATER VERISON OF THE HOMESPAN LIBRARY #error THIS SKETCH REQUIRES A LATER VERISON OF THE HOMESPAN LIBRARY
#endif #endif
#define ARDUINO_ESP_VERSION STRINGIFY(ARDUINO_ESP32_GIT_DESC)
////////////////////////////////////////////////////// //////////////////////////////////////////////////////
// DEFAULT SETTINGS // // DEFAULT SETTINGS //
@ -63,12 +67,12 @@
#define DEFAULT_QR_ID "HSPN" // change with homeSpan.setQRID(qrID); #define DEFAULT_QR_ID "HSPN" // change with homeSpan.setQRID(qrID);
#define DEFAULT_CONTROL_PIN 21 // change with homeSpan.setControlPin(pin) #define DEFAULT_CONTROL_PIN -1 // change with homeSpan.setControlPin(pin)
#define DEFAULT_STATUS_PIN 13 // change with homeSpan.setStatusPin(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_SSID "HomeSpan-Setup" // change with homeSpan.setApSSID(ssid)
#define DEFAULT_AP_PASSWORD "homespan" // change with homeSpan.setApPassword(pwd) #define DEFAULT_AP_PASSWORD "homespan" // change with homeSpan.setApPassword(pwd)
#define DEFAULT_OTA_PASSWORD "homespan-ota" // change with 'O' command #define DEFAULT_OTA_PASSWORD "homespan-ota" // change with 'O' command
#define DEFAULT_AP_TIMEOUT 300 // change with homeSpan.setApTimeout(nSeconds) #define DEFAULT_AP_TIMEOUT 300 // change with homeSpan.setApTimeout(nSeconds)
#define DEFAULT_COMMAND_TIMEOUT 120 // change with homeSpan.setCommandTimeout(nSeconds) #define DEFAULT_COMMAND_TIMEOUT 120 // change with homeSpan.setCommandTimeout(nSeconds)
@ -78,7 +82,6 @@
#define DEFAULT_MAX_CONNECTIONS 8 // change with homeSpan.setMaxConnections(num); #define DEFAULT_MAX_CONNECTIONS 8 // change with homeSpan.setMaxConnections(num);
#define DEFAULT_TCP_PORT 80 // change with homeSpan.setPort(port); #define DEFAULT_TCP_PORT 80 // change with homeSpan.setPort(port);
///////////////////////////////////////////////////// /////////////////////////////////////////////////////
// STATUS LED SETTINGS // // STATUS LED SETTINGS //

View File

@ -381,7 +381,7 @@ namespace Service {
// SPAN CHARACTERISTICS (HAP Chapter 9) // // SPAN CHARACTERISTICS (HAP Chapter 9) //
////////////////////////////////////////// //////////////////////////////////////////
// Macro to define Span Characteristic structures based on name of HAP Characteristic, default value, and mix/max value (not applicable for STRING or BOOL which default to min=0, max=1) // Macro to define Span Characteristic structures based on name of HAP Characteristic, default value, and min/max value (not applicable for STRING or BOOL which default to min=0, max=1)
#define CREATE_CHAR(TYPE,HAPCHAR,DEFVAL,MINVAL,MAXVAL) \ #define CREATE_CHAR(TYPE,HAPCHAR,DEFVAL,MINVAL,MAXVAL) \
struct HAPCHAR : SpanCharacteristic { HAPCHAR(TYPE val=DEFVAL, boolean nvsStore=false) : SpanCharacteristic {&hapChars.HAPCHAR} { init(val,nvsStore,(TYPE)MINVAL,(TYPE)MAXVAL); } }; struct HAPCHAR : SpanCharacteristic { HAPCHAR(TYPE val=DEFVAL, boolean nvsStore=false) : SpanCharacteristic {&hapChars.HAPCHAR} { init(val,nvsStore,(TYPE)MINVAL,(TYPE)MAXVAL); } };

View File

@ -89,13 +89,17 @@ PushButton::PushButton(){}
////////////////////////////////////// //////////////////////////////////////
PushButton::PushButton(uint8_t pin){ PushButton::PushButton(int pin){
init(pin); init(pin);
} }
////////////////////////////////////// //////////////////////////////////////
void PushButton::init(uint8_t pin){ void PushButton::init(int pin){
if(pin<0)
return;
status=0; status=0;
doubleCheck=false; doubleCheck=false;
this->pin=pin; this->pin=pin;
@ -106,6 +110,9 @@ void PushButton::init(uint8_t pin){
boolean PushButton::triggered(uint16_t singleTime, uint16_t longTime, uint16_t doubleTime){ boolean PushButton::triggered(uint16_t singleTime, uint16_t longTime, uint16_t doubleTime){
if(pin<0)
return(false);
unsigned long cTime=millis(); unsigned long cTime=millis();
switch(status){ switch(status){
@ -182,6 +189,9 @@ boolean PushButton::triggered(uint16_t singleTime, uint16_t longTime, uint16_t d
////////////////////////////////////// //////////////////////////////////////
boolean PushButton::primed(){ boolean PushButton::primed(){
if(pin<0)
return(false);
if(millis()>singleAlarm && status==1){ if(millis()>singleAlarm && status==1){
status=2; status=2;
@ -200,6 +210,10 @@ int PushButton::type(){
////////////////////////////////////// //////////////////////////////////////
void PushButton::wait(){ void PushButton::wait(){
if(pin<0)
return;
while(!digitalRead(pin)); while(!digitalRead(pin));
} }
@ -225,12 +239,21 @@ Blinker::Blinker(int pin, int timerNum){
////////////////////////////////////// //////////////////////////////////////
void Blinker::init(int pin, int timerNum){ void Blinker::init(int pin, int timerNum){
this->pin=pin; this->pin=pin;
if(pin<0)
return;
pinMode(pin,OUTPUT); pinMode(pin,OUTPUT);
digitalWrite(pin,0); digitalWrite(pin,0);
#if SOC_TIMER_GROUP_TIMERS_PER_GROUP>1 // ESP32 and ESP32-S2 contains two timers per timer group
group=((timerNum/2)%2==0)?TIMER_GROUP_0:TIMER_GROUP_1; group=((timerNum/2)%2==0)?TIMER_GROUP_0:TIMER_GROUP_1;
idx=(timerNum%2==0)?TIMER_0:TIMER_1; idx=(timerNum%2==0)?TIMER_0:TIMER_1; // ESP32-C3 only contains one timer per timer group
#else
group=(timerNum%2==0)?TIMER_GROUP_0:TIMER_GROUP_1;
idx=TIMER_0;
#endif
timer_config_t conf; timer_config_t conf;
conf.alarm_en=TIMER_ALARM_EN; conf.alarm_en=TIMER_ALARM_EN;
@ -238,7 +261,11 @@ void Blinker::init(int pin, int timerNum){
conf.intr_type=TIMER_INTR_LEVEL; conf.intr_type=TIMER_INTR_LEVEL;
conf.counter_dir=TIMER_COUNT_UP; conf.counter_dir=TIMER_COUNT_UP;
conf.auto_reload=TIMER_AUTORELOAD_EN; conf.auto_reload=TIMER_AUTORELOAD_EN;
conf.divider=8000; // 80 MHz clock / 8,000 = 10 kHz clock (0.1 ms pulses) conf.divider=getApbFrequency()/10000; // set divider to yield 10 kHz clock (0.1 ms pulses)
#ifdef SOC_TIMER_GROUP_SUPPORT_XTAL // set clock to APB (default is XTAL!) if clk_src is defined in conf structure
conf.clk_src=TIMER_SRC_CLK_APB;
#endif
timer_init(group,idx,&conf); timer_init(group,idx,&conf);
timer_isr_register(group,idx,Blinker::isrTimer,(void *)this,0,NULL); timer_isr_register(group,idx,Blinker::isrTimer,(void *)this,0,NULL);
@ -251,7 +278,8 @@ void Blinker::init(int pin, int timerNum){
void Blinker::isrTimer(void *arg){ void Blinker::isrTimer(void *arg){
Blinker *b=(Blinker *)arg; Blinker *b=(Blinker *)arg;
#if CONFIG_IDF_TARGET_ESP32
if(b->group){ if(b->group){
if(b->idx) if(b->idx)
TIMERG1.int_clr_timers.t1=1; TIMERG1.int_clr_timers.t1=1;
@ -263,6 +291,25 @@ void Blinker::isrTimer(void *arg){
else else
TIMERG0.int_clr_timers.t0=1; TIMERG0.int_clr_timers.t0=1;
} }
#elif CONFIG_IDF_TARGET_ESP32S2 // for some reason, the ESP32-S2 and ESP32-C3 use "int_clr" instead of "int_clr_timers" in their timer structure
if(b->group){
if(b->idx)
TIMERG1.int_clr.t1=1;
else
TIMERG1.int_clr.t0=1;
} else {
if(b->idx)
TIMERG0.int_clr.t1=1;
else
TIMERG0.int_clr.t0=1;
}
#elif CONFIG_IDF_TARGET_ESP32C3 // ESP32-C3 only has one timer per timer group
if(b->group){
TIMERG1.int_clr.t0=1;
} else {
TIMERG0.int_clr.t0=1;
}
#endif
if(!digitalRead(b->pin)){ if(!digitalRead(b->pin)){
digitalWrite(b->pin,1); digitalWrite(b->pin,1);
@ -279,6 +326,7 @@ void Blinker::isrTimer(void *arg){
} }
timer_set_alarm(b->group,b->idx,TIMER_ALARM_EN); timer_set_alarm(b->group,b->idx,TIMER_ALARM_EN);
} }
////////////////////////////////////// //////////////////////////////////////
@ -292,6 +340,11 @@ void Blinker::start(int period, float dutyCycle){
void Blinker::start(int period, float dutyCycle, int nBlinks, int delayTime){ void Blinker::start(int period, float dutyCycle, int nBlinks, int delayTime){
if(pin<0)
return;
gpio_set_direction((gpio_num_t)pin, GPIO_MODE_INPUT_OUTPUT); // needed to ensure digitalRead() functions correctly on ESP32-C3
period*=10; period*=10;
onTime=dutyCycle*period; onTime=dutyCycle*period;
offTime=period-onTime; offTime=period-onTime;
@ -306,12 +359,20 @@ void Blinker::start(int period, float dutyCycle, int nBlinks, int delayTime){
////////////////////////////////////// //////////////////////////////////////
void Blinker::stop(){ void Blinker::stop(){
if(pin<0)
return;
timer_pause(group,idx); timer_pause(group,idx);
} }
////////////////////////////////////// //////////////////////////////////////
void Blinker::on(){ void Blinker::on(){
if(pin<0)
return;
stop(); stop();
digitalWrite(pin,1); digitalWrite(pin,1);
} }
@ -319,6 +380,10 @@ void Blinker::on(){
////////////////////////////////////// //////////////////////////////////////
void Blinker::off(){ void Blinker::off(){
if(pin<0)
return;
stop(); stop();
digitalWrite(pin,0); digitalWrite(pin,0);
} }

View File

@ -74,7 +74,7 @@ struct TempBuffer {
class PushButton{ class PushButton{
int status; int status;
uint8_t pin; int pin;
boolean doubleCheck; boolean doubleCheck;
uint32_t singleAlarm; uint32_t singleAlarm;
uint32_t doubleAlarm; uint32_t doubleAlarm;
@ -90,7 +90,7 @@ class PushButton{
}; };
PushButton(); PushButton();
PushButton(uint8_t pin); PushButton(int pin);
// Creates generic pushbutton functionality on specified pin // Creates generic pushbutton functionality on specified pin
// that is wired to connect to ground when the button is pressed. // that is wired to connect to ground when the button is pressed.
@ -104,7 +104,7 @@ class PushButton{
// //
// pin: Pin mumber to which pushbutton connects to ground when pressed // pin: Pin mumber to which pushbutton connects to ground when pressed
void init(uint8_t pin); void init(int pin);
// Initializes PushButton, if not configured during instantiation. // Initializes PushButton, if not configured during instantiation.
// //
@ -184,14 +184,18 @@ class Blinker {
// the specified pin, obviating the need for a separate call to init(). // the specified pin, obviating the need for a separate call to init().
// //
// pin: Pin mumber to control. Blinker will set pinMode to OUTPUT automatically // pin: Pin mumber to control. Blinker will set pinMode to OUTPUT automatically
// timerNum: ESP32 Alarm Timer to use. 0=Group0/Timer0, 1=Group0/Timer1, 2=Group1/Timer0, 3=Group1/Timer1 // timerNum: ESP32 Alarm Timer to use.
// For ESP32 and ESP32-S2: 0=Group0/Timer0, 1=Group0/Timer1, 2=Group1/Timer0, 3=Group1/Timer1
// For ESP32-C3: 0=Group0/Timer0, 1=Group1/Timer0
void init(int pin, int timerNum=0); void init(int pin, int timerNum=0);
// Initializes Blinker, if not configured during instantiation. // Initializes Blinker, if not configured during instantiation.
// //
// pin: Pin mumber to control. Blinker will set pinMode to OUTPUT automatically // pin: Pin mumber to control. Blinker will set pinMode to OUTPUT automatically
// timerNum: ESP32 Alarm Timer to use. 0=Group0/Timer0, 1=Group0/Timer1, 2=Group1/Timer0, 3=Group1/Timer1 // timerNum: ESP32 Alarm Timer to use.
// For ESP32 and ESP32-S2: 0=Group0/Timer0, 1=Group0/Timer1, 2=Group1/Timer0, 3=Group1/Timer1
// For ESP32-C3: 0=Group0/Timer0, 1=Group1/Timer0
void start(int period, float dutyCycle=0.5); void start(int period, float dutyCycle=0.5);

View File

@ -3,56 +3,93 @@
/////////////////// ///////////////////
LedPin::LedPin(uint8_t pin, uint8_t level){ LedC::LedC(uint8_t pin, uint16_t freq){
if(numChannels+ServoPin::numChannels>15){
Serial.printf("\n*** ERROR: Can't create LedPin(%d) - no open PWM channels ***\n\n",pin); if(freq==0)
ledChannel.gpio_num=0; freq=DEFAULT_PWM_FREQ;
return;
for(int nMode=0;nMode<LEDC_SPEED_MODE_MAX;nMode++){
for(int nChannel=0;nChannel<LEDC_CHANNEL_MAX;nChannel++){
for(int nTimer=0;nTimer<LEDC_TIMER_MAX;nTimer++){
if(!channelList[nChannel][nMode]){
if(!timerList[nTimer][nMode]){ // if this timer slot is free, use it
timerList[nTimer][nMode]=new ledc_timer_config_t; // create new timer instance
timerList[nTimer][nMode]->speed_mode=(ledc_mode_t)nMode;
timerList[nTimer][nMode]->timer_num=(ledc_timer_t)nTimer;
timerList[nTimer][nMode]->freq_hz=freq;
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0)
timerList[nTimer][nMode]->clk_cfg=LEDC_USE_APB_CLK;
#endif
int res=LEDC_TIMER_BIT_MAX-1; // find the maximum possible resolution
while(getApbFrequency()/(freq*pow(2,res))<1)
res--;
timerList[nTimer][nMode]->duty_resolution=(ledc_timer_bit_t)res;
if(ledc_timer_config(timerList[nTimer][nMode])!=0){
Serial.printf("\n*** ERROR: Frequency=%d Hz is out of allowed range ---",freq);
delete timerList[nTimer][nMode];
timerList[nTimer][nMode]=NULL;
return;
}
}
if(timerList[nTimer][nMode]->freq_hz==freq){ // if timer matches desired frequency (always true if newly-created above)
channelList[nChannel][nMode]=new ledc_channel_config_t; // create new channel instance
channelList[nChannel][nMode]->speed_mode=(ledc_mode_t)nMode;
channelList[nChannel][nMode]->channel=(ledc_channel_t)nChannel;
channelList[nChannel][nMode]->timer_sel=(ledc_timer_t)nTimer;
channelList[nChannel][nMode]->intr_type=LEDC_INTR_DISABLE;
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)
channelList[nChannel][nMode]->flags.output_invert=0;
#endif
channelList[nChannel][nMode]->hpoint=0;
channelList[nChannel][nMode]->gpio_num=pin;
timer=timerList[nTimer][nMode];
channel=channelList[nChannel][nMode];
return;
}
}
}
}
} }
enabled=true;
if(numChannels==0){ // first instantiation of an LedPin
ledc_timer_config_t ledTimer;
ledTimer.timer_num=LEDC_TIMER_0;
ledTimer.duty_resolution=LEDC_TIMER_10_BIT;
ledTimer.freq_hz=5000;
ledTimer.speed_mode=LEDC_HIGH_SPEED_MODE; // configure both the HIGH-Speed Timer 0 and Low-Speed Timer 0
ledc_timer_config(&ledTimer);
ledTimer.speed_mode=LEDC_LOW_SPEED_MODE;
ledc_timer_config(&ledTimer);
}
ledChannel.gpio_num=pin;
if(numChannels<8){
ledChannel.speed_mode=LEDC_LOW_SPEED_MODE;
ledChannel.channel=(ledc_channel_t)(7-numChannels);
} else {
ledChannel.speed_mode=LEDC_HIGH_SPEED_MODE;
ledChannel.channel=(ledc_channel_t)(15-numChannels);
}
numChannels++;
ledChannel.intr_type=LEDC_INTR_DISABLE;
ledChannel.timer_sel=LEDC_TIMER_0;
ledChannel.hpoint=0;
ledc_channel_config(&ledChannel);
set(level);
} }
/////////////////// ///////////////////
void LedPin::set(uint8_t level){ LedPin::LedPin(uint8_t pin, float level, uint16_t freq) : LedC(pin, freq){
if(!enabled)
if(!channel)
Serial.printf("\n*** ERROR: Can't create LedPin(%d) - no open PWM channels and/or Timers ***\n\n",pin);
else
Serial.printf("LedPin=%d: mode=%d channel=%d, timer=%d, freq=%d Hz, resolution=%d bits\n",
channel->gpio_num,
channel->speed_mode,
channel->channel,
channel->timer_sel,
timer->freq_hz,
timer->duty_resolution
);
set(level);
}
///////////////////
void LedPin::set(float level){
if(!channel)
return; return;
ledChannel.duty=level*1023; if(level>100)
ledChannel.duty/=100; level=100;
ledChannel.duty&=0x03FF;
ledc_channel_config(&ledChannel); float d=level*(pow(2,(int)timer->duty_resolution)-1)/100.0;
channel->duty=d;
ledc_channel_config(channel);
} }
@ -115,151 +152,50 @@ void LedPin::HSVtoRGB(float h, float s, float v, float *r, float *g, float *b ){
//////////////////////////// ////////////////////////////
ServoPin::ServoPin(uint8_t pin, double initDegrees, uint16_t minMicros, uint16_t maxMicros, double minDegrees, double maxDegrees){ ServoPin::ServoPin(uint8_t pin, double initDegrees, uint16_t minMicros, uint16_t maxMicros, double minDegrees, double maxDegrees) : LedC(pin, 50){
if(numChannels>7 || numChannels>(15-LedPin::numChannels)){
Serial.printf("\n*** ERROR: Can't create ServoPin(%d) - no open PWM channels ***\n\n",pin);
servoChannel.gpio_num=0;
return;
}
enabled=true;
if(!channel)
Serial.printf("\n*** ERROR: Can't create ServoPin(%d) - no open PWM channels and/or Timers ***\n\n",pin);
else
Serial.printf("ServoPin=%d: mode=%d channel=%d, timer=%d, freq=%d Hz, resolution=%d bits\n",
channel->gpio_num,
channel->speed_mode,
channel->channel,
channel->timer_sel,
timer->freq_hz,
timer->duty_resolution
);
this->minMicros=minMicros; this->minMicros=minMicros;
this->maxMicros=maxMicros; this->maxMicros=maxMicros;
this->minDegrees=minDegrees; this->minDegrees=minDegrees;
microsPerDegree=(double)(maxMicros-minMicros)/(maxDegrees-minDegrees); microsPerDegree=(double)(maxMicros-minMicros)/(maxDegrees-minDegrees);
if(numChannels==0){ // first instantiation of a ServoPin
ledc_timer_config_t ledTimer;
ledTimer.timer_num=LEDC_TIMER_1;
ledTimer.speed_mode=LEDC_HIGH_SPEED_MODE;
ledTimer.duty_resolution=LEDC_TIMER_16_BIT;
ledTimer.freq_hz=50;
ledc_timer_config(&ledTimer);
}
servoChannel.gpio_num=pin;
servoChannel.speed_mode=LEDC_HIGH_SPEED_MODE;
servoChannel.channel=(ledc_channel_t)numChannels++;
servoChannel.intr_type=LEDC_INTR_DISABLE;
servoChannel.timer_sel=LEDC_TIMER_1;
servoChannel.hpoint=0;
servoChannel.duty*=micros2duty;
set(initDegrees); set(initDegrees);
} }
/////////////////// ///////////////////
void ServoPin::set(double degrees){ void ServoPin::set(double degrees){
if(!enabled)
return;
servoChannel.duty=(degrees-minDegrees)*microsPerDegree+minMicros;
if(servoChannel.duty<minMicros)
servoChannel.duty=minMicros;
else if(servoChannel.duty>maxMicros)
servoChannel.duty=maxMicros;
servoChannel.duty*=micros2duty; if(!channel)
ledc_channel_config(&servoChannel); return;
double usec=(degrees-minDegrees)*microsPerDegree+minMicros;
if(usec<minMicros)
usec=minMicros;
else if(usec>maxMicros)
usec=maxMicros;
usec*=timer->freq_hz/1e6*(pow(2,(int)timer->duty_resolution)-1);
channel->duty=usec;
ledc_channel_config(channel);
} }
//////////////////////////// ////////////////////////////
const double ServoPin::micros2duty=65535.0/20000.0; ledc_channel_config_t *LedC::channelList[LEDC_CHANNEL_MAX][LEDC_SPEED_MODE_MAX]={};
uint8_t LedPin::numChannels=0; ledc_timer_config_t *LedC::timerList[LEDC_TIMER_MAX][LEDC_SPEED_MODE_MAX]={};
uint8_t ServoPin::numChannels=0;
//*******************************************************
// DEPRECATED - INCLUDED FOR BACKWARDS COMPATIBILITY ONLY
//*******************************************************
PwmPin::PwmPin(uint8_t channel, uint8_t pin){
this->channel=channel & 0x0F;
this->pin=pin;
ledc_timer_config_t ledTimer;
ledTimer.timer_num=LEDC_TIMER_0;
ledTimer.speed_mode=(this->channel)<8?LEDC_HIGH_SPEED_MODE:LEDC_LOW_SPEED_MODE;
ledTimer.duty_resolution=LEDC_TIMER_10_BIT;
ledTimer.freq_hz=5000;
ledc_timer_config(&ledTimer);
ledChannel.gpio_num=pin;
ledChannel.speed_mode=(this->channel)<8?LEDC_HIGH_SPEED_MODE:LEDC_LOW_SPEED_MODE;
ledChannel.channel=(ledc_channel_t)(this->channel&0x07);
ledChannel.intr_type=LEDC_INTR_DISABLE;
ledChannel.timer_sel=LEDC_TIMER_0;
ledChannel.duty=0;
ledChannel.hpoint=0;
ledc_channel_config(&ledChannel);
}
///////////////////
void PwmPin::set(uint8_t channel, uint8_t level){
ledChannel.duty=level*1023;
ledChannel.duty/=100;
ledChannel.duty&=0x03FF;
ledc_channel_config(&ledChannel);
}
///////////////////
void PwmPin::HSVtoRGB(float h, float s, float v, float *r, float *g, float *b ){
// The algorithm below was provided on the web at https://www.cs.rit.edu/~ncs/color/t_convert.html
// h = [0,360]
// s = [0,1]
// v = [0,1]
int i;
float f, p, q, t;
if( s == 0 ){
*r = *g = *b = v;
return;
}
h /= 60;
i = floor( h ) ;
f = h - i;
p = v * ( 1 - s );
q = v * ( 1 - s * f );
t = v * ( 1 - s * ( 1 - f ) );
switch( i % 6 ) {
case 0:
*r = v;
*g = t;
*b = p;
break;
case 1:
*r = q;
*g = v;
*b = p;
break;
case 2:
*r = p;
*g = v;
*b = t;
break;
case 3:
*r = p;
*g = q;
*b = v;
break;
case 4:
*r = t;
*g = p;
*b = v;
break;
case 5:
*r = v;
*g = p;
*b = q;
break;
}
}

View File

@ -11,68 +11,54 @@
// ServoPin(pin) - controls a Servo Motor on specified pin with frequency=50 Hz // ServoPin(pin) - controls a Servo Motor on specified pin with frequency=50 Hz
// - use set(degrees) to set position to degrees // - use set(degrees) to set position to degrees
// //
// Max number of LedPin instantiations: 16
// Max number of ServoPin instantiatons: 8
// Max combined limit (LedPins+ServoPins): 16
//
// Instantiation of an LedPin or ServoPin that causes any of the maximums above to be exceeded throws
// an error message. The object will still be created, but calls to set(level) or set(degrees) are ignored.
// //
///////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////
#include <Arduino.h> #include <Arduino.h>
#include <driver/ledc.h> #include <driver/ledc.h>
#define DEFAULT_PWM_FREQ 5000
///////////////////////////////////// /////////////////////////////////////
class LedPin { class LedC {
boolean enabled=false;
ledc_channel_config_t ledChannel; protected:
static ledc_channel_config_t *channelList[LEDC_CHANNEL_MAX][LEDC_SPEED_MODE_MAX];
static ledc_timer_config_t *timerList[LEDC_TIMER_MAX][LEDC_SPEED_MODE_MAX];
ledc_channel_config_t *channel=NULL;
ledc_timer_config_t *timer;
LedC(uint8_t pin, uint16_t freq);
public: public:
LedPin(uint8_t pin, uint8_t level=0); // assigns pin to be output of one of 16 PWM channels within initial level int getPin(){return(channel?channel->gpio_num:-1);} // returns the pin number
void set(uint8_t level); // sets the PWM duty to level (0-100)
int getPin(){return ledChannel.gpio_num;} // returns the pin number };
/////////////////////////////////////
class LedPin : public LedC {
public:
LedPin(uint8_t pin, float level=0, uint16_t freq=DEFAULT_PWM_FREQ); // assigns pin to be output of one of 16 PWM channels initial level and frequency
void set(float level); // sets the PWM duty to level (0-100)
static uint8_t numChannels;
static void HSVtoRGB(float h, float s, float v, float *r, float *g, float *b ); // converts Hue/Saturation/Brightness to R/G/B static void HSVtoRGB(float h, float s, float v, float *r, float *g, float *b ); // converts Hue/Saturation/Brightness to R/G/B
}; };
///////////////////////////////////// /////////////////////////////////////
class ServoPin { class ServoPin : public LedC {
boolean enabled=false;
uint16_t minMicros; uint16_t minMicros;
uint16_t maxMicros; uint16_t maxMicros;
double minDegrees; double minDegrees;
double microsPerDegree; double microsPerDegree;
ledc_channel_config_t servoChannel;
static const double micros2duty;
public: public:
ServoPin(uint8_t pin, double initDegrees, uint16_t minMicros, uint16_t maxMicros, double minDegrees, double maxDegrees); ServoPin(uint8_t pin, double initDegrees, uint16_t minMicros, uint16_t maxMicros, double minDegrees, double maxDegrees);
ServoPin(uint8_t pin, double initDegrees=0) : ServoPin(pin,initDegrees,1000,2000,-90,90) {}; ServoPin(uint8_t pin, double initDegrees=0) : ServoPin(pin,initDegrees,1000,2000,-90,90) {};
void set(double degrees); // sets the Servo to degrees, where degrees is bounded by [minDegrees,maxDegrees] void set(double degrees); // sets the Servo to degrees, where degrees is bounded by [minDegrees,maxDegrees]
int getPin(){return servoChannel.gpio_num;} // returns the pin number
static uint8_t numChannels;
};
//*******************************************************
// DEPRECATED - INCLUDED FOR BACKWARDS COMPATIBILITY ONLY
//*******************************************************
class PwmPin {
uint8_t channel;
uint8_t pin;
ledc_channel_config_t ledChannel;
public:
PwmPin(uint8_t channel, uint8_t pin); // assigns pin to be output of one of 16 PWM channels (0-15)
void set(uint8_t channel, uint8_t level); // sets the PWM duty to level (0-100)
int getPin(){return pin;} // returns the pin number
static void HSVtoRGB(float h, float s, float v, float *r, float *g, float *b ); // converts Hue/Saturation/Brightness to R/G/B
}; };

View File

@ -1,55 +1,74 @@
#include <Arduino.h> #include <Arduino.h>
#include <soc/rmt_reg.h> #include <soc/rmt_reg.h>
#include <soc/dport_reg.h>
#include "RFControl.h" #include "RFControl.h"
/////////////////// ///////////////////
RFControl::RFControl(int pin){ RFControl::RFControl(uint8_t pin){
if(!configured){ // configure RMT peripheral
DPORT_REG_SET_BIT(DPORT_PERIP_CLK_EN_REG,1<<9); // enable RMT clock by setting bit 9 #ifdef CONFIG_IDF_TARGET_ESP32C3
DPORT_REG_CLR_BIT(DPORT_PERIP_RST_EN_REG,1<<9); // set RMT to normal ("un-reset") mode by clearing bit 9 if(nChannels==RMT_CHANNEL_MAX/2){
REG_SET_BIT(RMT_APB_CONF_REG,3); // enables access to RMT memory and enables wraparound mode (though the latter does not seem to be needed to set continuous TX) #else
REG_WRITE(RMT_INT_ENA_REG,1<<RMT_CH0_TX_END_INT_ENA_S); // enable end-transmission interrupt so that interrupt vector is called if(nChannels==RMT_CHANNEL_MAX){
REG_WRITE(RMT_CH0CONF0_REG,0x08000000); // disable carrier wave; set channel 0 to use all 8 blocks of RMT memory #endif
esp_intr_alloc(ETS_RMT_INTR_SOURCE,0,eot_int,NULL,NULL); // set RMT general interrupt vector Serial.printf("\n*** ERROR: Can't create RFControl(%d) - no open channels ***\n\n",pin);
return;
configured=true;
} }
this->pin=pin; config=new rmt_config_t;
config->rmt_mode=RMT_MODE_TX;
config->tx_config.carrier_en=false;
config->channel=(rmt_channel_t)nChannels;
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)
config->flags=0;
#endif
config->clk_div = 1;
config->mem_block_num=1;
config->gpio_num=(gpio_num_t)pin;
config->tx_config.idle_output_en=false;
config->tx_config.loop_en=false;
rmt_config(config);
rmt_driver_install(config->channel,0,0);
// Below we set the base clock to 1 MHz so tick-units are in microseconds (before CLK_DIV)
#ifdef CONFIG_IDF_TARGET_ESP32C3
REG_SET_FIELD(RMT_SYS_CONF_REG,RMT_SCLK_DIV_NUM,80); // ESP32-C3 does not have a 1 MHz REF Tick Clock, but allows the 80 MHz APB clock to be scaled by an additional RMT-specific divider
#else
rmt_set_source_clk(config->channel,RMT_BASECLK_REF); // use 1 MHz REF Tick Clock for ESP32 and ESP32-S2
#endif
nChannels++;
pinMode(pin,OUTPUT);
REG_WRITE(GPIO_FUNC0_OUT_SEL_CFG_REG+4*pin,87); // set GPIO OUTPUT of pin in GPIO_MATRIX to use RMT Channel-0 Output (=signal 87)
REG_SET_FIELD(GPIO_FUNC0_OUT_SEL_CFG_REG+4*pin,GPIO_FUNC0_OEN_SEL,1); // use GPIO_ENABLE_REG of pin (not RMT) to enable this channel
REG_WRITE(GPIO_ENABLE_W1TC_REG,1<<pin); // disable output on pin - enable only when started
} }
/////////////////// ///////////////////
void RFControl::start(uint8_t _numCycles, uint8_t tickTime){ void RFControl::start(uint8_t nCycles, uint8_t tickTime){ // starts transmission of pulses from internal data structure, repeated for nCycles, where each tick in pulse is tickTime microseconds long
start(data.data(), data.size(), nCycles, tickTime);
}
if(pCount%2==0) // if next entry is lower 16 bits of 32-bit memory ///////////////////
pRMT[pCount/2]=0; // set memory to zero (end-marker)
else void RFControl::start(uint32_t *data, int nData, uint8_t nCycles, uint8_t tickTime){ // starts transmission of pulses from specified data pointer, repeated for nCycles, where each tick in pulse is tickTime microseconds long
pRMT[pCount/2]&=0xFFFF; // else preserve lower 16 bits and zero our upper 16 bits
if(!config || nData==0)
REG_WRITE(GPIO_ENABLE_W1TS_REG,1<<pin); // enable output on pin return;
numCycles=_numCycles; // set number of cycles to repeat transmission
REG_SET_FIELD(RMT_CH0CONF0_REG,RMT_DIV_CNT_CH0,tickTime); // set one tick = 1 microsecond * tickTime (RMT will be set to use 1 MHz REF_TICK, not 80 MHz APB_CLK) rmt_set_clk_div(config->channel,tickTime); // set clock divider
REG_WRITE(RMT_CH0CONF1_REG,0x0000000D); // use REF_TICK clock; reset xmit and receive memory address to start of channel; START TRANSMITTING!
while(numCycles); // wait while transmission in progress for(int i=0;i<nCycles;i++) // loop over nCycles
REG_WRITE(GPIO_ENABLE_W1TC_REG,1<<pin); // disable output on pin rmt_write_items(config->channel, (rmt_item32_t *) data, nData, true); // start transmission and wait until completed before returning
} }
/////////////////// ///////////////////
void RFControl::clear(){ void RFControl::clear(){
pCount=0; data.clear();
} }
/////////////////// ///////////////////
@ -62,42 +81,18 @@ void RFControl::add(uint16_t onTime, uint16_t offTime){
/////////////////// ///////////////////
void RFControl::phase(uint16_t numTicks, uint8_t phase){ void RFControl::phase(uint16_t nTicks, uint8_t phase){
if(pCount==1023){ // maximum number of entries reached (saving one space for end-marker) uint32_t ticks=nTicks&0x7FFF;
Serial.print("\n*** ERROR: Can't add more than 1023 entries to RF Control Module\n\n");
} else
if(numTicks>32767 || numTicks<1){
Serial.print("\n*** ERROR: Request to add RF Control entry with numTicks=");
Serial.print(numTicks);
Serial.print(" is out of allowable range: 1-32767\n\n");
} else {
int index=pCount/2; if(lowWord)
data.push_back(ticks | (phase?(1<<15):0));
if(pCount%2==0) else
pRMT[index]=numTicks | (phase?(1<<15):0); // load entry into lower 16 bits of 32-bit memory data.back()|=ticks<<16 | (phase?(1<<31):0);
else
pRMT[index]=pRMT[index] & 0xFFFF | (numTicks<<16) | (phase?(1<<31):0); // load entry into upper 16 bits of 32-bit memory, preserving lower 16 bits
pCount++;
}
lowWord=!lowWord;
} }
/////////////////// ///////////////////
void RFControl::eot_int(void *arg){ uint8_t RFControl::nChannels=0;
numCycles--;
REG_WRITE(RMT_INT_CLR_REG,~0); // interrupt MUST be cleared first; transmission re-started after (clearing after restart crestes havoc)
if(numCycles)
REG_WRITE(RMT_CH0CONF1_REG,0x0000000D); // use REF_TICK clock; reset xmit and receive memory address to start of channel; re-start transmission
}
///////////////////
boolean RFControl::configured=false;
volatile int RFControl::numCycles;
uint32_t *RFControl::pRMT=(uint32_t *)RMT_CHANNEL_MEM(0);
int RFControl::pCount=0;

View File

@ -3,21 +3,30 @@
// RF Control Module // // RF Control Module //
//////////////////////////////////// ////////////////////////////////////
#include "driver/rmt.h"
#include <vector>
using std::vector;
class RFControl { class RFControl {
private: private:
int pin; rmt_config_t *config=NULL;
static volatile int numCycles; vector<uint32_t> data;
static boolean configured; boolean lowWord=true;
static uint32_t *pRMT; static uint8_t nChannels;
static int pCount;
static void eot_int(void *arg);
public: public:
RFControl(int pin); // creates transmitter on pin RFControl(uint8_t pin); // creates transmitter on pin
static void clear(); // clears transmitter memory
static void add(uint16_t onTime, uint16_t offTime); // adds pulse of onTime ticks HIGH followed by offTime ticks LOW void start(uint32_t *data, int nData, uint8_t nCycles=1, uint8_t tickTime=1); // starts transmission of pulses from specified data pointer, repeated for numCycles, where each tick in pulse is tickTime microseconds long
static void phase(uint16_t numTicks, uint8_t phase); // adds either a HIGH phase or LOW phase lasting numTicks ticks void start(uint8_t nCycles=1, uint8_t tickTime=1); // starts transmission of pulses from internal data structure, repeated for numCycles, where each tick in pulse is tickTime microseconds long
void start(uint8_t _numCycles, uint8_t tickTime=1); // starts transmission of pulses, repeated for numCycles, where each tick in pulse is tickTime microseconds long
void clear(); // clears transmitter memory
void add(uint16_t onTime, uint16_t offTime); // adds pulse of onTime ticks HIGH followed by offTime ticks LOW
void phase(uint16_t nTicks, uint8_t phase); // adds either a HIGH phase or LOW phase lasting numTicks ticks
}; };
// Helper macro for creating your own storage of uint32_t data array elements - used with first variation of start() above
#define RF_PULSE(highTicks,lowTicks) (1 << 15 | highTicks | lowTicks << 16)

View File

@ -1,66 +1,34 @@
/* HomeSpan Remote Control Example */
// This is a placeholder .ino file that allows you to easily edit the contents of this library using the Arduino IDE, #include "RFControl.h" // include RF Control Library
// as well as compile and test from this point. This file is ignored when the library is included in other sketches.
#include "PwmPin.h" void setup() {
void setup(){ Serial.begin(115200); // start the Serial interface
Serial.flush();
delay(1000); // wait for interface to flush
Serial.println("\n\nHomeSpan RF Transmitter Example");
RFControl rf(18); // create an instance of RFControl with signal output to pin 6
#define NPOINTS 3
uint32_t data[NPOINTS];
for(int i=0;i<NPOINTS;i++){
if(i<NPOINTS-1)
data[i]=RF_PULSE(1000,9000);
else
data[i]=RF_PULSE(1000,30000);
}
Serial.begin(115200); rf.start(data,NPOINTS,2,100);
delay(1000);
Serial.println("End Example");
Serial.print("\n\nTest sketch for HomeSpan Extras Library\n\n");
Serial.println("Starting...");
LedPin yellow(16,10);
LedPin d1(19);
LedPin d2(19);
LedPin d3(19);
LedPin d4(19);
LedPin d5(19);
LedPin d6(19);
LedPin d7(19);
LedPin d8(19);
LedPin d9(19);
LedPin d10(19);
LedPin d11(19);
LedPin d12(19);
LedPin red(17);
// ServoPin servo(18,0,500,2200,-90,90);
ServoPin s0(19);
ServoPin servo(18,45);
ServoPin s1(19);
while(1){
for(int i=0;i<100;i++){
yellow.set(i);
delay(10);
}
for(int i=100;i>=0;i--){ } // end of setup()
red.set(i);
delay(10);
}
}
while(1){
double STEP=1;
for(int i=-100*STEP;i<=100*STEP;i++){
servo.set((double)i/STEP);
delay(10);
}
for(int i=100*STEP;i>=-100*STEP;i--){
servo.set((double)i/STEP);
delay(10);
}
}
}
void loop(){ void loop(){
}
} // end of loop()

View File

@ -8,7 +8,9 @@ void setup() {
Serial.begin(115200); Serial.begin(115200);
homeSpan.setLogLevel(1); homeSpan.setLogLevel(2);
homeSpan.setStatusPin(5);
homeSpan.setControlPin(33);
homeSpan.setHostNameSuffix("-lamp1"); homeSpan.setHostNameSuffix("-lamp1");
homeSpan.setPortNum(1201); homeSpan.setPortNum(1201);
@ -21,8 +23,8 @@ void setup() {
new SpanUserCommand('d',"- My Description",userCom1); new SpanUserCommand('d',"- My Description",userCom1);
new SpanUserCommand('e',"- My second Description",userCom2); new SpanUserCommand('e',"- My second Description",userCom2);
homeSpan.enableAutoStartAP(); // homeSpan.enableAutoStartAP();
homeSpan.setApFunction(myWiFiAP); // homeSpan.setApFunction(myWiFiAP);
homeSpan.begin(Category::Lighting,"HomeSpan Lamp Server","homespan"); homeSpan.begin(Category::Lighting,"HomeSpan Lamp Server","homespan");