commit
88c4bb5c9f
|
|
@ -1 +1,2 @@
|
|||
.development
|
||||
.DS_Store
|
||||
|
|
|
|||
108
docs/Extras.md
108
docs/Extras.md
|
|
@ -4,21 +4,27 @@ HomeSpan includes integrated access to a number of ESP32 features you'll likely
|
|||
|
||||
## 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"`
|
||||
|
||||
### *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)
|
||||
|
||||
* `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).
|
||||
|
||||
|
|
@ -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:
|
||||
|
||||
* `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*.
|
||||
|
||||
* `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).
|
||||
|
||||
Resource limitations:
|
||||
### PWM Resource Allocation and Limitations
|
||||
|
||||
* A maximum of 16 LedPin objects can be instantiated
|
||||
* 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)
|
||||
The following PWM resources are available:
|
||||
|
||||
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
|
||||
|
||||
|
|
@ -72,40 +82,70 @@ The ESP32 has an on-chip signal-generator peripheral designed to drive an RF or
|
|||
|
||||
### *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.
|
||||
|
||||

|
||||
|
||||
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
|
||||
|
||||
* 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
|
||||
|
||||
* `static void clear()`
|
||||
|
||||
* clears the pulse train memory buffer
|
||||
* 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
|
||||
|
||||
* `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
|
||||
|
||||
* 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).
|
||||
|
||||
```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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
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?
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
> :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
|
||||
|
||||
|
|
@ -41,17 +41,34 @@ HomeSpan provides a microcontroller-focused implementation of [Apple's HomeKit A
|
|||
* Launch the WiFi Access Point
|
||||
* A standalone, detailed End-User Guide
|
||||
|
||||
## Latest Update - HomeSpan 1.3.0 (6/20/2021)
|
||||
## ❗Latest Update - HomeSpan 1.4.0 (10/9/2021)
|
||||
|
||||
This update brings a number of new features and enhancements:
|
||||
**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**
|
||||
|
||||
* The PWM library has been—
|
||||
* 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!
|
||||
* 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!
|
||||
* The HomeSpan CLI can now be customized — extend the CLI with your own functions and commands!
|
||||
* Enable the automatic launch of HomeSpan's WiFi Access Point upon start-up whenever WiFi Credentials are not found.
|
||||
* For advanced users: create your own custom WiFi Access Point and set your WiFi Credentials programmatically.
|
||||
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.
|
||||
|
||||
Also included in HomeSpan 1.4.0 are the following new features and enhancements:
|
||||
|
||||
* **The PWM library has been upgraded**
|
||||
* users can specify a custom PWM frequency for each instance of LedPin() (subject to chip-specific resource limitations)
|
||||
* users can set the duty cycle of an LedPin() as a decimal floating number (e.g., 34.56), instead of just an integer
|
||||
* the library automatically sets the duty resolution to the maximum allowable value for a chosen PWM frequency
|
||||
* 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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
* `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)`
|
||||
* 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)`
|
||||
* 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
|
||||
* 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)*
|
||||
|
||||
Creating an instance of this **class** attaches a pushbutton handler to the ESP32 *pin* specified.
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
name=HomeSpan
|
||||
version=1.3.0
|
||||
version=1.4.0
|
||||
author=Gregg <homespan@icloud.com>
|
||||
maintainer=Gregg <homespan@icloud.com>
|
||||
sentence=A robust and extremely easy-to-use HomeKit implementation for the Espressif ESP32 running on the Arduino IDE.
|
||||
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
|
||||
architectures=esp32
|
||||
includes=HomeSpan.h
|
||||
|
|
|
|||
|
|
@ -719,7 +719,7 @@ int HAPClient::postPairVerifyURL(){
|
|||
|
||||
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
|
||||
size_t accessoryPairingIDLen = 17;
|
||||
|
|
|
|||
|
|
@ -31,6 +31,9 @@
|
|||
#include <WiFi.h>
|
||||
#include <ArduinoOTA.h>
|
||||
#include <esp_ota_ops.h>
|
||||
#include <driver/ledc.h>
|
||||
#include <mbedtls/version.h>
|
||||
#include <esp_task_wdt.h>
|
||||
|
||||
#include "HomeSpan.h"
|
||||
#include "HAP.h"
|
||||
|
|
@ -52,6 +55,8 @@ void Span::begin(Category catID, const char *displayName, const char *hostNameBa
|
|||
this->modelName=modelName;
|
||||
sprintf(this->category,"%d",(int)catID);
|
||||
|
||||
esp_task_wdt_delete(xTaskGetIdleTaskHandleForCPU(0)); // required to avoid watchdog timeout messages from ESP32-C3
|
||||
|
||||
controlButton.init(controlPin);
|
||||
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(logLevel);
|
||||
Serial.print("\nStatus LED: Pin ");
|
||||
if(statusPin>=0)
|
||||
Serial.print(statusPin);
|
||||
else
|
||||
Serial.print("- *** WARNING: Status LED Pin is UNDEFINED");
|
||||
Serial.print("\nDevice Control: Pin ");
|
||||
if(controlPin>=0)
|
||||
Serial.print(controlPin);
|
||||
else
|
||||
Serial.print("- *** WARNING: Device Control Pin is UNDEFINED");
|
||||
Serial.print("\nSketch Version: ");
|
||||
Serial.print(getSketchVersion());
|
||||
Serial.print("\nHomeSpan Version: ");
|
||||
Serial.print(HOMESPAN_VERSION);
|
||||
Serial.print("\nESP-IDF Version: ");
|
||||
Serial.print(esp_get_idf_version());
|
||||
Serial.print("\nArduino-ESP Ver.: ");
|
||||
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
|
||||
Serial.print("\nESP32 Board: ");
|
||||
Serial.print(ARDUINO_VARIANT);
|
||||
#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(__DATE__);
|
||||
Serial.print(" ");
|
||||
|
|
@ -1598,6 +1620,10 @@ int SpanCharacteristic::sprintfAttributes(char *cBuf, int flags){
|
|||
if(uvGet<float>(stepValue)>0)
|
||||
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)){
|
||||
|
|
@ -1718,6 +1744,41 @@ unsigned long SpanCharacteristic::timeVal(){
|
|||
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 //
|
||||
///////////////////////////////
|
||||
|
|
|
|||
|
|
@ -31,8 +31,11 @@
|
|||
#error ERROR: HOMESPAN IS ONLY AVAILABLE FOR ESP32 MICROCONTROLLERS!
|
||||
#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 <unordered_map>
|
||||
#include <vector>
|
||||
#include <nvs.h>
|
||||
|
||||
#include "Settings.h"
|
||||
|
|
@ -115,8 +118,8 @@ struct Span{
|
|||
unsigned long alarmConnect=0; // time after which WiFi connection attempt should be tried again
|
||||
|
||||
const char *defaultSetupCode=DEFAULT_SETUP_CODE; // Setup Code used for pairing
|
||||
uint8_t statusPin=DEFAULT_STATUS_PIN; // pin for status LED
|
||||
uint8_t controlPin=DEFAULT_CONTROL_PIN; // pin for Control Pushbutton
|
||||
int statusPin=DEFAULT_STATUS_PIN; // pin for status LED
|
||||
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 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
|
||||
|
|
@ -168,7 +171,7 @@ struct Span{
|
|||
|
||||
void setControlPin(uint8_t pin){controlPin=pin;} // sets Control 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 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)
|
||||
|
|
@ -256,8 +259,9 @@ struct SpanCharacteristic{
|
|||
UVal minValue; // Characteristic minimum (not applicable for STRING)
|
||||
UVal maxValue; // Characteristic maximum (not applicable for STRING)
|
||||
UVal stepValue; // Characteristic step size (not applicable for STRING)
|
||||
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
|
||||
const char *validValues=NULL; // Optional JSON array of valid values. Applicable only to uint8 Characteristics
|
||||
boolean *ev; // Characteristic Event Notify Enable (per-connection)
|
||||
char *nvsKey=NULL; // key for NVS storage of Characteristic value
|
||||
|
||||
|
|
@ -275,6 +279,8 @@ struct SpanCharacteristic{
|
|||
boolean updated(){return(isUpdated);} // returns isUpdated
|
||||
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){
|
||||
char c[64];
|
||||
switch(format){
|
||||
|
|
@ -298,7 +304,8 @@ struct SpanCharacteristic{
|
|||
sprintf(c,"\"%s\"",u.STRING);
|
||||
return(String(c));
|
||||
} // switch
|
||||
} // str()
|
||||
return(String()); // included to prevent compiler warnings
|
||||
}
|
||||
|
||||
void uvSet(UVal &u, const char *val){
|
||||
u.STRING=val;
|
||||
|
|
@ -328,7 +335,7 @@ struct SpanCharacteristic{
|
|||
u.FLOAT=(double)val;
|
||||
break;
|
||||
} // switch
|
||||
} // set()
|
||||
}
|
||||
|
||||
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");
|
||||
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){
|
||||
|
||||
|
|
@ -493,7 +501,7 @@ struct SpanCharacteristic{
|
|||
|
||||
///////////////////////////////
|
||||
|
||||
struct SpanRange{
|
||||
struct [[deprecated("Please use Characteristic::setRange() method instead.")]] SpanRange{
|
||||
SpanRange(int min, int max, int step);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -403,7 +403,8 @@ int Network::getFormValue(char *formData, const char *tag, char *value, int maxS
|
|||
sscanf(v,"%2x",(unsigned int *)value++);
|
||||
v+=2;
|
||||
} else {
|
||||
*value++=*v++;
|
||||
*value++=(*v=='+'?' ':*v); // HTML Forms use '+' for spaces (and '+' signs are escaped)
|
||||
v++;
|
||||
}
|
||||
len++;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -144,8 +144,8 @@ void SRP6A::createPublicKey(){
|
|||
void SRP6A::getPrivateKey(){
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
@ -167,7 +167,8 @@ void SRP6A::createSessionKey(){
|
|||
|
||||
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_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 )
|
||||
|
||||
|
|
@ -267,10 +268,10 @@ int SRP6A::writeTLV(kTLVType tag, mbedtls_mpi *mpi){
|
|||
|
||||
void SRP6A::print(mbedtls_mpi *mpi){
|
||||
|
||||
char sBuf[1000];
|
||||
char sBuf[2000];
|
||||
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(" ");
|
||||
|
|
|
|||
|
|
@ -27,13 +27,15 @@
|
|||
|
||||
// USER-DEFINED SETTINGS AND REFERENCE ENUMERATION CLASSES
|
||||
|
||||
#include <core_version.h>
|
||||
|
||||
#pragma once
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
// HomeSpan Version //
|
||||
|
||||
#define HS_MAJOR 1
|
||||
#define HS_MINOR 3
|
||||
#define HS_MINOR 4
|
||||
#define HS_PATCH 0
|
||||
|
||||
#define STRINGIFY(x) _STR(x)
|
||||
|
|
@ -51,6 +53,8 @@
|
|||
#error THIS SKETCH REQUIRES A LATER VERISON OF THE HOMESPAN LIBRARY
|
||||
#endif
|
||||
|
||||
#define ARDUINO_ESP_VERSION STRINGIFY(ARDUINO_ESP32_GIT_DESC)
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
// DEFAULT SETTINGS //
|
||||
|
||||
|
|
@ -63,8 +67,8 @@
|
|||
|
||||
#define DEFAULT_QR_ID "HSPN" // change with homeSpan.setQRID(qrID);
|
||||
|
||||
#define DEFAULT_CONTROL_PIN 21 // change with homeSpan.setControlPin(pin)
|
||||
#define DEFAULT_STATUS_PIN 13 // change with homeSpan.setStatusPin(pin)
|
||||
#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)
|
||||
|
|
@ -78,7 +82,6 @@
|
|||
#define DEFAULT_MAX_CONNECTIONS 8 // change with homeSpan.setMaxConnections(num);
|
||||
#define DEFAULT_TCP_PORT 80 // change with homeSpan.setPort(port);
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
// STATUS LED SETTINGS //
|
||||
|
||||
|
|
|
|||
|
|
@ -381,7 +381,7 @@ namespace Service {
|
|||
// 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) \
|
||||
struct HAPCHAR : SpanCharacteristic { HAPCHAR(TYPE val=DEFVAL, boolean nvsStore=false) : SpanCharacteristic {&hapChars.HAPCHAR} { init(val,nvsStore,(TYPE)MINVAL,(TYPE)MAXVAL); } };
|
||||
|
|
|
|||
|
|
@ -89,13 +89,17 @@ PushButton::PushButton(){}
|
|||
|
||||
//////////////////////////////////////
|
||||
|
||||
PushButton::PushButton(uint8_t pin){
|
||||
PushButton::PushButton(int pin){
|
||||
init(pin);
|
||||
}
|
||||
|
||||
//////////////////////////////////////
|
||||
|
||||
void PushButton::init(uint8_t pin){
|
||||
void PushButton::init(int pin){
|
||||
|
||||
if(pin<0)
|
||||
return;
|
||||
|
||||
status=0;
|
||||
doubleCheck=false;
|
||||
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){
|
||||
|
||||
if(pin<0)
|
||||
return(false);
|
||||
|
||||
unsigned long cTime=millis();
|
||||
|
||||
switch(status){
|
||||
|
|
@ -183,6 +190,9 @@ boolean PushButton::triggered(uint16_t singleTime, uint16_t longTime, uint16_t d
|
|||
|
||||
boolean PushButton::primed(){
|
||||
|
||||
if(pin<0)
|
||||
return(false);
|
||||
|
||||
if(millis()>singleAlarm && status==1){
|
||||
status=2;
|
||||
return(true);
|
||||
|
|
@ -200,6 +210,10 @@ int PushButton::type(){
|
|||
//////////////////////////////////////
|
||||
|
||||
void PushButton::wait(){
|
||||
|
||||
if(pin<0)
|
||||
return;
|
||||
|
||||
while(!digitalRead(pin));
|
||||
}
|
||||
|
||||
|
|
@ -225,12 +239,21 @@ Blinker::Blinker(int pin, int timerNum){
|
|||
//////////////////////////////////////
|
||||
|
||||
void Blinker::init(int pin, int timerNum){
|
||||
|
||||
this->pin=pin;
|
||||
if(pin<0)
|
||||
return;
|
||||
|
||||
pinMode(pin,OUTPUT);
|
||||
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;
|
||||
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;
|
||||
conf.alarm_en=TIMER_ALARM_EN;
|
||||
|
|
@ -238,7 +261,11 @@ void Blinker::init(int pin, int timerNum){
|
|||
conf.intr_type=TIMER_INTR_LEVEL;
|
||||
conf.counter_dir=TIMER_COUNT_UP;
|
||||
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_isr_register(group,idx,Blinker::isrTimer,(void *)this,0,NULL);
|
||||
|
|
@ -252,6 +279,7 @@ void Blinker::isrTimer(void *arg){
|
|||
|
||||
Blinker *b=(Blinker *)arg;
|
||||
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
if(b->group){
|
||||
if(b->idx)
|
||||
TIMERG1.int_clr_timers.t1=1;
|
||||
|
|
@ -263,6 +291,25 @@ void Blinker::isrTimer(void *arg){
|
|||
else
|
||||
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)){
|
||||
digitalWrite(b->pin,1);
|
||||
|
|
@ -279,6 +326,7 @@ void Blinker::isrTimer(void *arg){
|
|||
}
|
||||
|
||||
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){
|
||||
|
||||
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;
|
||||
onTime=dutyCycle*period;
|
||||
offTime=period-onTime;
|
||||
|
|
@ -306,12 +359,20 @@ void Blinker::start(int period, float dutyCycle, int nBlinks, int delayTime){
|
|||
//////////////////////////////////////
|
||||
|
||||
void Blinker::stop(){
|
||||
|
||||
if(pin<0)
|
||||
return;
|
||||
|
||||
timer_pause(group,idx);
|
||||
}
|
||||
|
||||
//////////////////////////////////////
|
||||
|
||||
void Blinker::on(){
|
||||
|
||||
if(pin<0)
|
||||
return;
|
||||
|
||||
stop();
|
||||
digitalWrite(pin,1);
|
||||
}
|
||||
|
|
@ -319,6 +380,10 @@ void Blinker::on(){
|
|||
//////////////////////////////////////
|
||||
|
||||
void Blinker::off(){
|
||||
|
||||
if(pin<0)
|
||||
return;
|
||||
|
||||
stop();
|
||||
digitalWrite(pin,0);
|
||||
}
|
||||
|
|
|
|||
14
src/Utils.h
14
src/Utils.h
|
|
@ -74,7 +74,7 @@ struct TempBuffer {
|
|||
class PushButton{
|
||||
|
||||
int status;
|
||||
uint8_t pin;
|
||||
int pin;
|
||||
boolean doubleCheck;
|
||||
uint32_t singleAlarm;
|
||||
uint32_t doubleAlarm;
|
||||
|
|
@ -90,7 +90,7 @@ class PushButton{
|
|||
};
|
||||
|
||||
PushButton();
|
||||
PushButton(uint8_t pin);
|
||||
PushButton(int pin);
|
||||
|
||||
// Creates generic pushbutton functionality on specified pin
|
||||
// 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
|
||||
|
||||
void init(uint8_t pin);
|
||||
void init(int pin);
|
||||
|
||||
// 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().
|
||||
//
|
||||
// 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);
|
||||
|
||||
// Initializes Blinker, if not configured during instantiation.
|
||||
//
|
||||
// pin: Pin mumber to control. Blinker will set pinMode to OUTPUT automatically
|
||||
// timerNum: ESP32 Alarm Timer to use. 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);
|
||||
|
||||
|
|
|
|||
|
|
@ -3,56 +3,93 @@
|
|||
|
||||
///////////////////
|
||||
|
||||
LedPin::LedPin(uint8_t pin, uint8_t level){
|
||||
if(numChannels+ServoPin::numChannels>15){
|
||||
Serial.printf("\n*** ERROR: Can't create LedPin(%d) - no open PWM channels ***\n\n",pin);
|
||||
ledChannel.gpio_num=0;
|
||||
LedC::LedC(uint8_t pin, uint16_t freq){
|
||||
|
||||
if(freq==0)
|
||||
freq=DEFAULT_PWM_FREQ;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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){
|
||||
if(!enabled)
|
||||
LedPin::LedPin(uint8_t pin, float level, uint16_t freq) : LedC(pin, freq){
|
||||
|
||||
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;
|
||||
|
||||
ledChannel.duty=level*1023;
|
||||
ledChannel.duty/=100;
|
||||
ledChannel.duty&=0x03FF;
|
||||
ledc_channel_config(&ledChannel);
|
||||
if(level>100)
|
||||
level=100;
|
||||
|
||||
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){
|
||||
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;
|
||||
}
|
||||
ServoPin::ServoPin(uint8_t pin, double initDegrees, uint16_t minMicros, uint16_t maxMicros, double minDegrees, double maxDegrees) : LedC(pin, 50){
|
||||
|
||||
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->maxMicros=maxMicros;
|
||||
this->minDegrees=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);
|
||||
|
||||
}
|
||||
|
||||
///////////////////
|
||||
|
||||
void ServoPin::set(double degrees){
|
||||
if(!enabled)
|
||||
|
||||
if(!channel)
|
||||
return;
|
||||
|
||||
servoChannel.duty=(degrees-minDegrees)*microsPerDegree+minMicros;
|
||||
double usec=(degrees-minDegrees)*microsPerDegree+minMicros;
|
||||
|
||||
if(servoChannel.duty<minMicros)
|
||||
servoChannel.duty=minMicros;
|
||||
else if(servoChannel.duty>maxMicros)
|
||||
servoChannel.duty=maxMicros;
|
||||
if(usec<minMicros)
|
||||
usec=minMicros;
|
||||
else if(usec>maxMicros)
|
||||
usec=maxMicros;
|
||||
|
||||
servoChannel.duty*=micros2duty;
|
||||
ledc_channel_config(&servoChannel);
|
||||
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;
|
||||
uint8_t LedPin::numChannels=0;
|
||||
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;
|
||||
}
|
||||
}
|
||||
ledc_channel_config_t *LedC::channelList[LEDC_CHANNEL_MAX][LEDC_SPEED_MODE_MAX]={};
|
||||
ledc_timer_config_t *LedC::timerList[LEDC_TIMER_MAX][LEDC_SPEED_MODE_MAX]={};
|
||||
|
|
|
|||
|
|
@ -11,68 +11,54 @@
|
|||
// ServoPin(pin) - controls a Servo Motor on specified pin with frequency=50 Hz
|
||||
// - 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 <driver/ledc.h>
|
||||
|
||||
#define DEFAULT_PWM_FREQ 5000
|
||||
|
||||
/////////////////////////////////////
|
||||
|
||||
class LedPin {
|
||||
boolean enabled=false;
|
||||
ledc_channel_config_t ledChannel;
|
||||
class LedC {
|
||||
|
||||
protected:
|
||||
static ledc_channel_config_t *channelList[LEDC_CHANNEL_MAX][LEDC_SPEED_MODE_MAX];
|
||||
static ledc_timer_config_t *timerList[LEDC_TIMER_MAX][LEDC_SPEED_MODE_MAX];
|
||||
|
||||
ledc_channel_config_t *channel=NULL;
|
||||
ledc_timer_config_t *timer;
|
||||
|
||||
LedC(uint8_t pin, uint16_t freq);
|
||||
|
||||
public:
|
||||
LedPin(uint8_t pin, uint8_t level=0); // assigns pin to be output of one of 16 PWM channels within initial level
|
||||
void set(uint8_t level); // sets the PWM duty to level (0-100)
|
||||
int getPin(){return ledChannel.gpio_num;} // returns the pin number
|
||||
int getPin(){return(channel?channel->gpio_num:-1);} // 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
|
||||
};
|
||||
|
||||
/////////////////////////////////////
|
||||
|
||||
class ServoPin {
|
||||
boolean enabled=false;
|
||||
class ServoPin : public LedC {
|
||||
uint16_t minMicros;
|
||||
uint16_t maxMicros;
|
||||
double minDegrees;
|
||||
double microsPerDegree;
|
||||
ledc_channel_config_t servoChannel;
|
||||
|
||||
static const double micros2duty;
|
||||
|
||||
public:
|
||||
ServoPin(uint8_t pin, double initDegrees, uint16_t minMicros, uint16_t maxMicros, double minDegrees, double maxDegrees);
|
||||
ServoPin(uint8_t pin, double initDegrees=0) : ServoPin(pin,initDegrees,1000,2000,-90,90) {};
|
||||
|
||||
void set(double degrees); // sets the Servo to degrees, where degrees is bounded by [minDegrees,maxDegrees]
|
||||
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
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,55 +1,74 @@
|
|||
|
||||
#include <Arduino.h>
|
||||
#include <soc/rmt_reg.h>
|
||||
#include <soc/dport_reg.h>
|
||||
|
||||
#include "RFControl.h"
|
||||
|
||||
///////////////////
|
||||
|
||||
RFControl::RFControl(int pin){
|
||||
if(!configured){ // configure RMT peripheral
|
||||
RFControl::RFControl(uint8_t pin){
|
||||
|
||||
DPORT_REG_SET_BIT(DPORT_PERIP_CLK_EN_REG,1<<9); // enable RMT clock by setting bit 9
|
||||
DPORT_REG_CLR_BIT(DPORT_PERIP_RST_EN_REG,1<<9); // set RMT to normal ("un-reset") mode by clearing bit 9
|
||||
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)
|
||||
REG_WRITE(RMT_INT_ENA_REG,1<<RMT_CH0_TX_END_INT_ENA_S); // enable end-transmission interrupt so that interrupt vector is called
|
||||
REG_WRITE(RMT_CH0CONF0_REG,0x08000000); // disable carrier wave; set channel 0 to use all 8 blocks of RMT memory
|
||||
esp_intr_alloc(ETS_RMT_INTR_SOURCE,0,eot_int,NULL,NULL); // set RMT general interrupt vector
|
||||
|
||||
configured=true;
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32C3
|
||||
if(nChannels==RMT_CHANNEL_MAX/2){
|
||||
#else
|
||||
if(nChannels==RMT_CHANNEL_MAX){
|
||||
#endif
|
||||
Serial.printf("\n*** ERROR: Can't create RFControl(%d) - no open channels ***\n\n",pin);
|
||||
return;
|
||||
}
|
||||
|
||||
this->pin=pin;
|
||||
config=new rmt_config_t;
|
||||
|
||||
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
|
||||
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++;
|
||||
|
||||
}
|
||||
|
||||
///////////////////
|
||||
|
||||
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
|
||||
pRMT[pCount/2]&=0xFFFF; // else preserve lower 16 bits and zero our upper 16 bits
|
||||
///////////////////
|
||||
|
||||
REG_WRITE(GPIO_ENABLE_W1TS_REG,1<<pin); // enable output on pin
|
||||
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)
|
||||
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
|
||||
REG_WRITE(GPIO_ENABLE_W1TC_REG,1<<pin); // disable output on pin
|
||||
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
|
||||
|
||||
if(!config || nData==0)
|
||||
return;
|
||||
|
||||
rmt_set_clk_div(config->channel,tickTime); // set clock divider
|
||||
|
||||
for(int i=0;i<nCycles;i++) // loop over nCycles
|
||||
rmt_write_items(config->channel, (rmt_item32_t *) data, nData, true); // start transmission and wait until completed before returning
|
||||
}
|
||||
|
||||
///////////////////
|
||||
|
||||
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)
|
||||
Serial.print("\n*** ERROR: Can't add more than 1023 entries to RF Control Module\n\n");
|
||||
} else
|
||||
uint32_t ticks=nTicks&0x7FFF;
|
||||
|
||||
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(pCount%2==0)
|
||||
pRMT[index]=numTicks | (phase?(1<<15):0); // load entry into lower 16 bits of 32-bit memory
|
||||
if(lowWord)
|
||||
data.push_back(ticks | (phase?(1<<15):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++;
|
||||
}
|
||||
data.back()|=ticks<<16 | (phase?(1<<31):0);
|
||||
|
||||
lowWord=!lowWord;
|
||||
}
|
||||
|
||||
///////////////////
|
||||
|
||||
void RFControl::eot_int(void *arg){
|
||||
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;
|
||||
uint8_t RFControl::nChannels=0;
|
||||
|
|
|
|||
|
|
@ -3,21 +3,30 @@
|
|||
// RF Control Module //
|
||||
////////////////////////////////////
|
||||
|
||||
#include "driver/rmt.h"
|
||||
#include <vector>
|
||||
|
||||
using std::vector;
|
||||
|
||||
class RFControl {
|
||||
private:
|
||||
int pin;
|
||||
static volatile int numCycles;
|
||||
static boolean configured;
|
||||
static uint32_t *pRMT;
|
||||
static int pCount;
|
||||
static void eot_int(void *arg);
|
||||
rmt_config_t *config=NULL;
|
||||
vector<uint32_t> data;
|
||||
boolean lowWord=true;
|
||||
static uint8_t nChannels;
|
||||
|
||||
public:
|
||||
RFControl(int 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
|
||||
static void phase(uint16_t numTicks, uint8_t phase); // adds either a HIGH phase or LOW phase lasting numTicks ticks
|
||||
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
|
||||
RFControl(uint8_t pin); // creates transmitter on pin
|
||||
|
||||
void start(uint32_t *data, int nData, uint8_t nCycles=1, uint8_t tickTime=1); // starts transmission of pulses from specified data pointer, repeated for numCycles, where each tick in pulse is tickTime microseconds long
|
||||
void start(uint8_t nCycles=1, uint8_t tickTime=1); // starts transmission of pulses from internal data structure, repeated for numCycles, where each tick in pulse is tickTime microseconds long
|
||||
|
||||
void clear(); // clears transmitter memory
|
||||
void add(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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
// as well as compile and test from this point. This file is ignored when the library is included in other sketches.
|
||||
#include "RFControl.h" // include RF Control Library
|
||||
|
||||
#include "PwmPin.h"
|
||||
void setup() {
|
||||
|
||||
void setup(){
|
||||
Serial.begin(115200); // start the Serial interface
|
||||
Serial.flush();
|
||||
delay(1000); // wait for interface to flush
|
||||
|
||||
Serial.begin(115200);
|
||||
delay(1000);
|
||||
Serial.println("\n\nHomeSpan RF Transmitter Example");
|
||||
|
||||
Serial.print("\n\nTest sketch for HomeSpan Extras Library\n\n");
|
||||
RFControl rf(18); // create an instance of RFControl with signal output to pin 6
|
||||
|
||||
Serial.println("Starting...");
|
||||
#define NPOINTS 3
|
||||
|
||||
uint32_t data[NPOINTS];
|
||||
|
||||
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=0;i<NPOINTS;i++){
|
||||
if(i<NPOINTS-1)
|
||||
data[i]=RF_PULSE(1000,9000);
|
||||
else
|
||||
data[i]=RF_PULSE(1000,30000);
|
||||
}
|
||||
|
||||
for(int i=100;i>=0;i--){
|
||||
red.set(i);
|
||||
delay(10);
|
||||
}
|
||||
}
|
||||
rf.start(data,NPOINTS,2,100);
|
||||
|
||||
while(1){
|
||||
double STEP=1;
|
||||
Serial.println("End Example");
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} // end of setup()
|
||||
|
||||
void loop(){
|
||||
}
|
||||
|
||||
} // end of loop()
|
||||
|
|
|
|||
|
|
@ -8,7 +8,9 @@ void setup() {
|
|||
|
||||
Serial.begin(115200);
|
||||
|
||||
homeSpan.setLogLevel(1);
|
||||
homeSpan.setLogLevel(2);
|
||||
homeSpan.setStatusPin(5);
|
||||
homeSpan.setControlPin(33);
|
||||
|
||||
homeSpan.setHostNameSuffix("-lamp1");
|
||||
homeSpan.setPortNum(1201);
|
||||
|
|
@ -21,8 +23,8 @@ void setup() {
|
|||
new SpanUserCommand('d',"- My Description",userCom1);
|
||||
new SpanUserCommand('e',"- My second Description",userCom2);
|
||||
|
||||
homeSpan.enableAutoStartAP();
|
||||
homeSpan.setApFunction(myWiFiAP);
|
||||
// homeSpan.enableAutoStartAP();
|
||||
// homeSpan.setApFunction(myWiFiAP);
|
||||
|
||||
homeSpan.begin(Category::Lighting,"HomeSpan Lamp Server","homespan");
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue