commit
efb0eba187
52
README.md
52
README.md
|
|
@ -31,6 +31,7 @@ HomeSpan requires version 2.0.0 or later of the [Arduino-ESP32 Board Manager](ht
|
||||||
* Touch pads/sensors connected to an ESP32 pin (for ESP32 devices that support touch pads)
|
* Touch pads/sensors connected to an ESP32 pin (for ESP32 devices that support touch pads)
|
||||||
* Integrated access to the ESP32's on-chip Remote Control peripheral for easy generation of IR and RF signals
|
* Integrated access to the ESP32's on-chip Remote Control peripheral for easy generation of IR and RF signals
|
||||||
* Dedicated classes to control one- and two-wire addressable RGB and RGBW LEDs and LED strips
|
* Dedicated classes to control one- and two-wire addressable RGB and RGBW LEDs and LED strips
|
||||||
|
* Dedicated classes to control stepper motors that can run smoothly in the background without interfering with HomeSpan
|
||||||
* Dedicated class that faciliates seemless point-to-point communication between ESP32 devices using ESP-NOW
|
* Dedicated class that faciliates seemless point-to-point communication between ESP32 devices using ESP-NOW
|
||||||
* Integrated Web Log for user-defined log messages
|
* Integrated Web Log for user-defined log messages
|
||||||
* Extensively-commented Tutorial Sketches taking you from the very basics of HomeSpan through advanced HomeKit topics
|
* Extensively-commented Tutorial Sketches taking you from the very basics of HomeSpan through advanced HomeKit topics
|
||||||
|
|
@ -48,36 +49,40 @@ HomeSpan requires version 2.0.0 or later of the [Arduino-ESP32 Board Manager](ht
|
||||||
* 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.7.2 (4/5/2023)
|
## ❗Latest Update - HomeSpan 1.8.0 (7/8/2023)
|
||||||
|
|
||||||
* **New ability to set OTA password from within sketch**
|
* **New Stepper Motor Control!**
|
||||||
* See the [OTA Page](docs/OTA.md) for details
|
|
||||||
|
|
||||||
* **Added logic to allow duplicates of the same Custom Characteristic to be "defined" in more than one file in a sketch**
|
* adds new **StepperControl** class that allows for smooth, uninterrupted operation of one or more stepper motors running in the background while HomeSpan continues to run simultaneously in the foreground
|
||||||
* Allows the use of the same Custom Characteristic across multiple files in the same sketch without the compiler throwing a "redefinition error"
|
* supports driver boards with or without PWM, including microstepping modes
|
||||||
* See the [API Reference](docs/Reference.md) for details
|
* supports automatic acceleration and deceleration for smooth starts and stops
|
||||||
|
* motors can be set to an absolute position or instructucted to move a specified number of steps
|
||||||
|
* provides options to automatically enter into a "brake" state after motor stops to conserve power
|
||||||
|
* includes a fully worked example of a motorized window shade
|
||||||
|
* see [Stepper Motor Control](docs/Stepper.md) for details
|
||||||
|
|
||||||
|
* **Upgrades to HomeSpan Web Log output**
|
||||||
|
|
||||||
* **Extended functionality of `setValidValues()` to work with more than just UINT8 Characteristics**
|
* adds new method `void homeSpan.setWebLogCSS(const char *css)` that allows you to define *Custom Style Sheets (CSS)* for the Web Log text, tables, and background
|
||||||
* Now works with INT, UINT16, and UINT32 Characteristics, as well as UINT8 Characteristics
|
* adds version numbers for the Sodium and MbedTLS libraries, HomeKit pairing status, and a text description of Reset Reason code
|
||||||
* See the [API Reference](docs/Reference.md) for details
|
* see [Message Logging](docs/Logging.md) for details
|
||||||
|
|
||||||
* **New parameters added to `autoPoll()` that allow the user to set priority and chose CPU**
|
* **Upgrades to Web Log Time Server initialization**
|
||||||
* Provides for enhanced performance on dual-processor chips
|
|
||||||
* See the [API Reference](docs/Reference.md) for details
|
|
||||||
|
|
||||||
* **Automatic LED Fading!**
|
* the process for retrieving the time and date from an NTP server upon booting now runs in the background as a separate task
|
||||||
* Added new methods to LedPin class that enable use of the ESP32's built-in fade controls
|
* HomeSpan is no longer blocked from running during the NTP query
|
||||||
* Allows user to specify speed of fade
|
|
||||||
* Runs in background without consuming any CPU resources
|
|
||||||
* See the [PWM Page](docs/PWM.md) for details
|
|
||||||
|
|
||||||
* **Added ability to Clone the Pairing Data from one device to another**
|
* **Adds new methods to disable HomeSpan's use of the USB Serial port**
|
||||||
* Adds new 'P' and 'C' commands to the CLI
|
|
||||||
* Enables a broken device to be swapped for a new device (running the same sketch) without the need to unpair the old device or pair the new device
|
* new Log Level, -1, causes HomeSpan to suppress all OUTPUT messages
|
||||||
* Avoids loss of automations, scenes, and any other Home App customizations associated with device
|
* new homeSpan method `setSerialInputDisable(boolean val)` disables/re-enables HomeSpan's reading of CLI commands INPUT into the Arduino Serial Monitor
|
||||||
* New and old device can be different chips (e.g. ESP32-S2 versus ESP32-C3)
|
|
||||||
* See the new [Cloning Page](docs/Cloning.md) for details
|
|
||||||
|
|
||||||
|
* **Adds ability to use a non-standard LED as the HomeSpan Status LED**
|
||||||
|
|
||||||
|
* new homeSpan method `setStatusDevice(Blinkable *sDev)` sets the Status LED to the Blinkable object *sDev*
|
||||||
|
* allows an LED connected to a pin expander, or any other non-standard LED controller (such as an inverted LED that lights when a pin is LOW instead of HIGH) to be used as the HomeSpan Status LED
|
||||||
|
* see [Blinkable.md](docs/Blinkable.md) for details (including an example) on how to create Blinkable objects
|
||||||
|
|
||||||
See [Releases](https://github.com/HomeSpan/HomeSpan/releases) for details on all changes and bug fixes included in this update.
|
See [Releases](https://github.com/HomeSpan/HomeSpan/releases) for details on all changes and bug fixes included in this update.
|
||||||
|
|
||||||
# HomeSpan Resources
|
# HomeSpan Resources
|
||||||
|
|
@ -97,6 +102,7 @@ HomeSpan includes the following documentation:
|
||||||
* [HomeSpan PWM](docs/PWM.md) - integrated control of standard LEDs and Servo Motors using the ESP32's on-chip PWM peripheral
|
* [HomeSpan PWM](docs/PWM.md) - integrated control of standard LEDs and Servo Motors using the ESP32's on-chip PWM peripheral
|
||||||
* [HomeSpan RFControl](docs/RMT.md) - easy generation of RF and IR Remote Control signals using the ESP32's on-chip RMT peripheral
|
* [HomeSpan RFControl](docs/RMT.md) - easy generation of RF and IR Remote Control signals using the ESP32's on-chip RMT peripheral
|
||||||
* [HomeSpan Pixels](docs/Pixels.md) - integrated control of addressable one- and two-wire RGB and RGBW LEDs and LED strips
|
* [HomeSpan Pixels](docs/Pixels.md) - integrated control of addressable one- and two-wire RGB and RGBW LEDs and LED strips
|
||||||
|
* [HomeSpan Stepper Motor Control](docs/Stepper.md) - integrated control of stepper motors, including PWM microstepping
|
||||||
* [HomeSpan SpanPoint](docs/NOW.md) - facilitates point-to-point, bi-directional communication between ESP32 Devices using ESP-NOW
|
* [HomeSpan SpanPoint](docs/NOW.md) - facilitates point-to-point, bi-directional communication between ESP32 Devices using ESP-NOW
|
||||||
* [HomeSpan Television Services](docs/TVServices.md) - how to use HomeKit's undocumented Television Services and Characteristics
|
* [HomeSpan Television Services](docs/TVServices.md) - how to use HomeKit's undocumented Television Services and Characteristics
|
||||||
* [HomeSpan Message Logging](docs/Logging.md) - how to generate log messages for display on the Arduino Serial Monitor as well as optionally posted to an integrated Web Log page
|
* [HomeSpan Message Logging](docs/Logging.md) - how to generate log messages for display on the Arduino Serial Monitor as well as optionally posted to an integrated Web Log page
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
# Creating a Custom Status LED with the Blinkable Interface
|
||||||
|
|
||||||
|
The HomeSpan Status LED conveys information about the state of HomeSpan to the user through different blinking patterns. The *homeSpan* `setStatusPin()` and `setStatusPixel()` methods allow you to choose, respectively, either a standard LED or a NeoPixel LED as the Status LED. However, the Status LED can be set to any object that implements the **Blinkable** interface[^1] using the *homeSpan* method `setStatusDevice(Blinkable *sDev)`, where *sDev* is a Blinkable object.
|
||||||
|
|
||||||
|
To create your own Blinkable object, start by creating a child class derived from **Blinkable**. Next, add a constructor that defines the pins and performs any initializations if needed. Finally, define the following *required* methods that **Blinkable** calls to blink the device:
|
||||||
|
|
||||||
|
* `void on()` - turns on the device (e.g. causes an LED to light)
|
||||||
|
* `void off()` - turns off the device (e.g. causes an LED to go dark)
|
||||||
|
* `int getPin()` - returns the pin number of the device (any number is fine; does not have to be an actual ESP32 pin)
|
||||||
|
|
||||||
|
For example, the following defines a Blinkable object for an inverted LED that turns *on* when an ESP32 pin is LOW, and turns *off* when the ESP32 pin is HIGH:
|
||||||
|
|
||||||
|
```C++
|
||||||
|
|
||||||
|
// CREATE THIS STRUCTURE SOMEWHERE NEAR TOP OF SKETCH
|
||||||
|
|
||||||
|
struct invertedLED : Blinkable { // create a child class derived from Blinkable
|
||||||
|
|
||||||
|
int pin; // variable to store the pin number
|
||||||
|
|
||||||
|
invertedLED(int pin) : pin{pin} { // constructor that initializes the pin parameter
|
||||||
|
pinMode(pin,OUTPUT); // set the pin to OUTPUT
|
||||||
|
digitalWrite(pin,HIGH); // set pin HIGH (which is off for an inverted LED)
|
||||||
|
}
|
||||||
|
|
||||||
|
void on() override { digitalWrite(pin,LOW); } // required function on() - sets pin LOW
|
||||||
|
void off() override { digitalWrite(pin,HIGH); } // required function off() - sets pin HIGH
|
||||||
|
int getPin() override { return(pin); } // required function getPin() - returns pin number
|
||||||
|
};
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
// THEN USE THE STRUCTURE IN SETUP() TO SET THE STATUS LED
|
||||||
|
|
||||||
|
void setup(){
|
||||||
|
|
||||||
|
homeSpan.setStatusDevice(new invertedLED(13)); // set Status LED to be a new Blinkable device attached to pin 13
|
||||||
|
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
[^1]: In C++, an *interface* is any abstract class that contains only pure virtual functions. You cannot instantiate an interface, but you can instantiate any derived child classes from the interface provided that you define each of the required virtual functions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[↩️](Reference.md) Back to the Reference API page
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -52,7 +52,7 @@ In addition to being able to configure a HomeSpan device using the [HomeSpan CLI
|
||||||
|
|
||||||
The Control Button should be installed between ground and any pin on the ESP32 that can serve as an input. To inform HomeSpan of which pin you chose, you must call the method `homeSpan.setControlPin(pin)` near the top of your sketch (see the [HomeSpan API Reference](Reference.md) for details), else HomeSpan will assume a Control Button has **not** be installed.
|
The Control Button should be installed between ground and any pin on the ESP32 that can serve as an input. To inform HomeSpan of which pin you chose, you must call the method `homeSpan.setControlPin(pin)` near the top of your sketch (see the [HomeSpan API Reference](Reference.md) for details), else HomeSpan will assume a Control Button has **not** be installed.
|
||||||
|
|
||||||
Similarly, the Status LED can be connected to any pin on the ESP32 that can serve as an output (and grounded through an appropriately-sized current-limiting resistor). To inform HomeSpan of which pin you chose, you must call the method `homeSpan.setStatusPin(pin)` near the top of your sketch, else HomeSpan will assume a Status LED has **not** been installed. Note some ESP32 boards have a built-in LED --- it is fine to use this for the Status LED if it is a simple on/off LED, *not* an addressable color LED that requires a special driver.
|
Similarly, the Status LED can be connected to any pin on the ESP32 that can serve as an output (and grounded through an appropriately-sized current-limiting resistor). To inform HomeSpan of which pin you chose, you must call the method `homeSpan.setStatusPin(pin)` near the top of your sketch, else HomeSpan will assume a Status LED has **not** been installed. Note some ESP32 boards have a built-in LED --- it is fine to use this for the Status LED if it is a simple on/off LED. If your built-in LED is an RGB NeoPixel, or if you would like to use an external RGB NeoPixel for your Status LED, call the method `homeSpan.setStatusPixel(pin)` instead, in which case HomeSpan will use its NeoPixel driver to operate the status LED.
|
||||||
|
|
||||||
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).
|
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).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,10 @@ HomeSpan log messages are typically output directly to the Arduino Serial Monito
|
||||||
|
|
||||||
You can set the *Log Level* in your sketch using the method `homeSpan.setLogLevel(uint8_t level)` as described in the [HomeSpan API Reference](API.md). Level 0 messages are always output; Level 1 messages are only output if the *Log Level* is set to 1 or greater; and Level 2 messages are only output if the *Log Level* is set to to 2. The *Log Level* can also be changed dynamically via the Serial Monitor at any time by typing either the 'L0', 'L1', or 'L2' as described in the [HomeSpan CLI](CLI.md).
|
You can set the *Log Level* in your sketch using the method `homeSpan.setLogLevel(uint8_t level)` as described in the [HomeSpan API Reference](API.md). Level 0 messages are always output; Level 1 messages are only output if the *Log Level* is set to 1 or greater; and Level 2 messages are only output if the *Log Level* is set to to 2. The *Log Level* can also be changed dynamically via the Serial Monitor at any time by typing either the 'L0', 'L1', or 'L2' as described in the [HomeSpan CLI](CLI.md).
|
||||||
|
|
||||||
|
You can also completely suppress all Log messages generated by HomeSpan (as well as all user-defined Log messages - see below) by setting the *Log Level* to -1, either by typing 'L-1' into the HomeSpan CLI or by calling `homeSpan.setLogLevel(-1)` in your sketch. Disabling all Log messages from being output to the Serial Monitor may be useful in cases where a separate Serial peripheral is being controlled by the ESP32. In such cases you may want to implement a physical switch on your device that automatically sets the Log Level to 0 or -1 so you don't have to recompile your sketch every time you want to enable/disable HomeSpan Log Messages.
|
||||||
|
|
||||||
|
Note that the *Log Level* setting has no impact on messages output by any `Serial.print()` or `Serial.printf()` statements used in a sketch. To ensure you can control such messages via the *Log Level* setting, use the `LOG()` macros below. Also note that the *Log Level* setting has no impact on any ESP32 diagnostic messages produced by the ESP32 operating system itself. These messages are controlled according to the *Core Debug Level* specified at compile time under the Tools menu of the Arduino IDE.
|
||||||
|
|
||||||
## User-Defined Log Messages
|
## User-Defined Log Messages
|
||||||
|
|
||||||
You can add your own log messages to any sketch using HomeSpan's **LOG0()**, **LOG1()**, and **LOG2()** macros. Messages created with these macros will be output to the Arduino Serial Monitor according the *Log Level* setting described above. Each **LOGn()** macro (where n=\[0,2\]) is available in two flavors depending on the number of arguments specified:
|
You can add your own log messages to any sketch using HomeSpan's **LOG0()**, **LOG1()**, and **LOG2()** macros. Messages created with these macros will be output to the Arduino Serial Monitor according the *Log Level* setting described above. Each **LOGn()** macro (where n=\[0,2\]) is available in two flavors depending on the number of arguments specified:
|
||||||
|
|
@ -58,6 +62,27 @@ Additional notes:
|
||||||
|
|
||||||
See [Example 19 - WebLog](Tutorials.md#example-19---weblog) for a tutorial sketch demonstrating the use of `homeSpan.enableWebLog()` and the WEBLOG() macro.
|
See [Example 19 - WebLog](Tutorials.md#example-19---weblog) for a tutorial sketch demonstrating the use of `homeSpan.enableWebLog()` and the WEBLOG() macro.
|
||||||
|
|
||||||
|
### Custom Style Sheets (CSS)
|
||||||
|
|
||||||
|
HomeSpan's Web Log normally consists of black text on a light blue background. However, you can set a Custom Style Sheet (CSS) to change the format by calling `homeSpan.setWebLogCSS(const char *css)`, where *css* is constructed using [HTML classes](https://www.w3schools.com/html/html_classes.asp) containing one or more custom style elements. HomeSpan implements the following three class names for the different parts of the Web Log:
|
||||||
|
|
||||||
|
* *bod1* - this class specifies style elements for the main body of the Web Log page, including the background color and the header text at the top (which itself is formatted as \<h2\>)
|
||||||
|
* *tab1* - this class specifies style elements for the status table at the top of the Web Log page
|
||||||
|
* *tab2* - this class specifies style elements for the log entry table at the botom of the Web Log page
|
||||||
|
|
||||||
|
For example, the following CSS changes the background color of the Web Log page to light yellow, the color of the header text to blue, the color of the cells in the top table to light green, and the color of the cells in the botom table to light blue. It also changes the color of the text in the header row (\<th\>) of the second table to red, the color of the data rows (\<td\>) in the second table to dark blue, and the alignment of the text in the data rows to be centered within each table cell:
|
||||||
|
|
||||||
|
```C++
|
||||||
|
homeSpan.setWebLogCSS(".bod1 {background-color:lightyellow;}"
|
||||||
|
".bod1 h2 {color:blue;}"
|
||||||
|
".tab1 {background-color:lightgreen;}"
|
||||||
|
".tab2 {background-color:lightblue;} .tab2 th {color:red;} .tab2 td {color:darkblue; text-align:center;}"
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that HomeSpan outputs the full content of the Web Log HTML, including whatever CSS you may have specified above, to the Serial Monitor whenever the Log Level is set to 1 or greater. Reviewing this output can be helpful when creating your own CSS.
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
[↩️](../README.md) Back to the Welcome page
|
[↩️](../README.md) Back to the Welcome page
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,9 @@ The following PWM resources are available:
|
||||||
|
|
||||||
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 *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.
|
#### Diagnostic Messages
|
||||||
|
|
||||||
|
The **LedPin** and **ServoPin** classes output *Information \[I\]* and *Warning \[W\]* messages to the Serial Monitor based on the *Core Debug Level* selected when compiling the sketch using the Arduino IDE. A non-fatal warning message is produced when insufficient Channel or Timer resources prevent the creation of a new LedPin or ServoPin object. Calls to the `set()` method for objects that failed to be properly created are silently ignored.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,9 @@ Signals are defined as a sequence of HIGH and LOW phases that together form a pu
|
||||||
Since most RF/IR signals repeat the same train of pulses more than once, the duration of the last LOW phase should be extended to account for the delay between repeats of the pulse train. Pulse trains are encoded as sequential arrays of 32-bit words, where each 32-bit word represents an individual pulse using the following protocol:
|
Since most RF/IR signals repeat the same train of pulses more than once, the duration of the last LOW phase should be extended to account for the delay between repeats of the pulse train. Pulse trains are encoded as sequential arrays of 32-bit words, where each 32-bit word represents an individual pulse using the following protocol:
|
||||||
|
|
||||||
* bits 0-14: the duration, in *ticks* from 0-32767, of the first part of the pulse to be transmitted
|
* 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)
|
* bit 15: indicates whether the first part of the pulse to be transmitted 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
|
* 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)
|
* bit 31: indicates whether the second part of the pulse to be transmitted is HIGH (1) or LOW (0)
|
||||||
|
|
||||||
HomeSpan provides two easy methods to create, store, and transmit a pulse train. The first method relies on the fact that each instance of RFControl maintains its own internal memory structure to store a pulse train of arbitrary length. The functions `clear()`, `add()`, and `pulse()`, described below, allow you to create a pulse train using this internal memory structure. The `start()` function is then used to begin transmission of the full pulse train. This method is generally used when pulse trains are to be created on-the-fly as needed, since each RFControl instance can only store a single pulse train at one time.
|
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.
|
||||||
|
|
||||||
|
|
@ -81,6 +81,9 @@ rf.start(4,1000); // start transmission of the pulse train; repeat for 4 cycles
|
||||||
uint32_t pulseTrain[] = {RF_PULSE(100,50), RF_PULSE(100,50), RF_PULSE(25,500)}; // create the same pulse train in an external array
|
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
|
rf.start(pulseTrain,3,4,1000); // start transmission using the same parameters
|
||||||
```
|
```
|
||||||
|
#### Diagnostic Messages
|
||||||
|
|
||||||
|
The **RFControl** class outputs *Warning \[W\]* messages to the Serial Monitor based on the *Core Debug Level* selected when compiling the sketch using the Arduino IDE. A non-fatal warning message is produced when insufficient Channel resources prevent the creation of a new RFControl object. Calls to the `start()` method for objects that failed to be properly created are silently ignored.
|
||||||
|
|
||||||
## Example RFControl Sketch
|
## Example RFControl Sketch
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,10 +37,11 @@ The following **optional** `homeSpan` methods override various HomeSpan initiali
|
||||||
* `void setStatusPin(uint8_t pin)`
|
* `void setStatusPin(uint8_t pin)`
|
||||||
* sets the ESP32 pin to use for the HomeSpan Status LED
|
* sets the ESP32 pin to use for the HomeSpan Status LED
|
||||||
* assumes a standard LED will be connected to *pin*
|
* assumes a standard LED will be connected to *pin*
|
||||||
* if neither this method nor `setStatusPixel()` is called, HomeSpan will assume there is no Status LED
|
* if neither this method nor any equivalent method is called, HomeSpan will assume there is no Status LED
|
||||||
|
|
||||||
* `void setStatusPixel(uint8_t pin, float h=0, float s=100, float v=100)`
|
* `void setStatusPixel(uint8_t pin, float h=0, float s=100, float v=100)`
|
||||||
* sets the ESP32 pin to use for the HomeSpan Status LED
|
* sets the ESP32 pin to use for the HomeSpan Status LED
|
||||||
|
* this method is an *alternative* to using `setStatusPin()` above
|
||||||
* assumes an RGB NeoPixel (or equivalent) will be connected to *pin*
|
* assumes an RGB NeoPixel (or equivalent) will be connected to *pin*
|
||||||
* works well with ESP32 boards that have a built-in NeoPixel LED, though adding an external NeoPixel is fine
|
* works well with ESP32 boards that have a built-in NeoPixel LED, though adding an external NeoPixel is fine
|
||||||
* users can optionally specify the color HomeSpan will use with the NeoPixel by providing the following HSV values:
|
* users can optionally specify the color HomeSpan will use with the NeoPixel by providing the following HSV values:
|
||||||
|
|
@ -49,7 +50,14 @@ The following **optional** `homeSpan` methods override various HomeSpan initiali
|
||||||
* v = Brightness percentage from 0-100
|
* v = Brightness percentage from 0-100
|
||||||
* color defaults to *red* if unspecified
|
* color defaults to *red* if unspecified
|
||||||
* example: `homeSpan.setStatusPixel(8,120,100,20)` sets the Status LED to light green using a NeoPixel attached to pin 8
|
* example: `homeSpan.setStatusPixel(8,120,100,20)` sets the Status LED to light green using a NeoPixel attached to pin 8
|
||||||
* if neither this method nor `setStatusPin()` is called, HomeSpan will assume there is no Status LED
|
* if neither this method nor any equivalent method is called, HomeSpan will assume there is no Status LED
|
||||||
|
|
||||||
|
* `void setStatusDevice(Blinkable *sDev)`
|
||||||
|
* sets the Status LED to a user-specified Blinkable device, *sDev*
|
||||||
|
* this method is an *alternative* to using either `setStatusPin()` or `setStatusPixel()` above
|
||||||
|
* see [Blinkable](Blinkable.md) for details on how to create generic Blinkable devices
|
||||||
|
* useful when using an LED connected to a pin expander, or other specialized driver, as the Status LED
|
||||||
|
* if neither this method nor any equivalent method is called, HomeSpan will assume there is no Status LED
|
||||||
|
|
||||||
* `void setStatusAutoOff(uint16_t duration)`
|
* `void setStatusAutoOff(uint16_t duration)`
|
||||||
* sets Status LED to automatically turn off after *duration* seconds
|
* sets Status LED to automatically turn off after *duration* seconds
|
||||||
|
|
@ -71,11 +79,14 @@ The following **optional** `homeSpan` methods override various HomeSpan initiali
|
||||||
* `void setCommandTimeout(uint16_t nSec)`
|
* `void setCommandTimeout(uint16_t nSec)`
|
||||||
* sets the duration (in seconds) that the HomeSpan End-User Command Mode, once activated, stays alive before timing out (default=120 seconds)
|
* sets the duration (in seconds) that the HomeSpan End-User Command Mode, once activated, stays alive before timing out (default=120 seconds)
|
||||||
|
|
||||||
* `void setLogLevel(uint8_t level)`
|
* `void setLogLevel(int level)`
|
||||||
* sets the logging level for diagnostic messages, where:
|
* sets the logging level for diagnostic messages, where:
|
||||||
* 0 = top-level HomeSpan status messages, and any messages output by the user using `Serial.print()` or `Serial.printf()` (default)
|
* 0 = top-level HomeSpan status messages, and any `LOG0()` messages specified in the sketch by the user (default)
|
||||||
* 1 = all HomeSpan status messages, and any `LOG1()` messages specified in the sketch by the user
|
* 1 = all HomeSpan status messages, and any `LOG1()` messages specified in the sketch by the user
|
||||||
* 2 = all HomeSpan status messages plus all HAP communication packets to and from the HomeSpan device, as well as all `LOG1()` and `LOG2()` messages specified in the sketch by the user
|
* 2 = all HomeSpan status messages plus all HAP communication packets to and from the HomeSpan device, as well as all `LOG1()` and `LOG2()` messages specified in the sketch by the user
|
||||||
|
* -1 = supresses ALL HomeSpan status messages, including all `LOG0()`, `LOG1()`, and `LOG2()` messages specified in the sketch by the user, freeing up the Serial port for other uses
|
||||||
|
* the log level setting has no impact on any `Serial.print()` or `Serial.printf()` statements that may be used in a sketch. Use one of the `LOG()` macros instead of `Serial.print()` or `Serial.printf()` if you want to control the output by setting the HomeSpan log level
|
||||||
|
* the log level setting has no impact on any ESP32 diagnostic messages output by the ESP32 operating system itself. To suppress these mesaages make sure to compile your sketch with the *Core Debug Level* set to "None" in the Tools menu of the Arduino IDE
|
||||||
* note the log level can also be changed at runtime with the 'L' command via the [HomeSpan CLI](CLI.md)
|
* note the log level can also be changed at runtime with the 'L' command via the [HomeSpan CLI](CLI.md)
|
||||||
* see [Message Logging](Logging.md) for complete details
|
* see [Message Logging](Logging.md) for complete details
|
||||||
|
|
||||||
|
|
@ -181,17 +192,21 @@ The following **optional** `homeSpan` methods enable additional features and pro
|
||||||
* can by called from anywhere in a sketch
|
* can by called from anywhere in a sketch
|
||||||
|
|
||||||
* `void enableWebLog(uint16_t maxEntries, const char *timeServerURL, const char *timeZone, const char *logURL)`
|
* `void enableWebLog(uint16_t maxEntries, const char *timeServerURL, const char *timeZone, const char *logURL)`
|
||||||
* enables a rolling web log that displays the most recent *maxEntries* entries created by the user with the `WEBLOG()` macro. Parameters, and their default values if unspecified, are as follows:
|
* enables a rolling Web Log that displays the most recent *maxEntries* entries created by the user with the `WEBLOG()` macro. Parameters, and their default values if unspecified, are as follows:
|
||||||
* *maxEntries* - maximum number of (most recent) entries to save. If unspecified, defaults to 0, in which case the web log will only display status without any log entries
|
* *maxEntries* - maximum number of (most recent) entries to save. If unspecified, defaults to 0, in which case the Web Log will only display status without any log entries
|
||||||
* *timeServerURL* - the URL of a time server that HomeSpan will use to set its clock upon startup after a WiFi connection has been established. If unspecified, default to NULL, in which case HomeSpan skips setting the device clock
|
* *timeServerURL* - the URL of a time server that HomeSpan will use to set its clock upon startup after a WiFi connection has been established. If unspecified, defaults to NULL, in which case HomeSpan skips setting the device clock
|
||||||
* *timeZone* - specifies the time zone to use for setting the clock. Uses standard Unix timezone formatting as interpreted by Espressif IDF. Note the IDF uses a somewhat non-intuitive convention such that a timezone of "UTC+5:00" *subtracts* 5 hours from UTC time, and "UTC-5:00" *adds* 5 hours to UTC time. If *serverURL=NULL* this field is ignored; if *serverURL!=NULL* this field is required
|
* *timeZone* - specifies the time zone to use for setting the clock. Uses standard Unix timezone formatting as interpreted by Espressif IDF. Note the IDF uses a somewhat non-intuitive convention such that a timezone of "UTC+5:00" *subtracts* 5 hours from UTC time, and "UTC-5:00" *adds* 5 hours to UTC time. If *serverURL=NULL* this field is ignored; if *serverURL!=NULL* this field is required
|
||||||
* *logURL* - the URL of the log page for this device. If unspecified, defaults to "status"
|
* *logURL* - the URL of the Web Log page for this device. If unspecified, defaults to "status"
|
||||||
* example: `homeSpan.enableWebLog(50,"pool.ntp.org","UTC-1:00","myLog");` creates a web log at the URL *http<nolink>://HomeSpan-\[DEVICE-ID\].local:\[TCP-PORT\]/myLog* that will display the 50 most-recent log messages produced with the WEBLOG() macro. Upon start-up (after a WiFi connection has been established) HomeSpan will attempt to set the device clock by calling the server "pool.ntp.org" and adjusting the time to be 1 hour ahead of UTC.
|
* example: `homeSpan.enableWebLog(50,"pool.ntp.org","UTC-1:00","myLog");` creates a web log at the URL *http<nolink>://HomeSpan-\[DEVICE-ID\].local:\[TCP-PORT\]/myLog* that will display the 50 most-recent log messages produced with the WEBLOG() macro. Upon start-up (after a WiFi connection has been established) HomeSpan will attempt to set the device clock by calling the server "pool.ntp.org" and adjusting the time to be 1 hour ahead of UTC.
|
||||||
* when attemping to connect to *timeServerURL*, HomeSpan waits 10 seconds for a response. If no response is received after the 10-second timeout period, HomeSpan assumes the server is unreachable and skips the clock-setting procedure. Use `setTimeServerTimeout()` to re-configure the 10-second timeout period to another value
|
* when attemping to connect to *timeServerURL*, HomeSpan waits 120 seconds for a response. This is done in the background and does not block HomeSpan from running as usual while it tries to set the time. If no response is received after the 120-second timeout period, HomeSpan assumes the server is unreachable and skips the clock-setting procedure. Use `setTimeServerTimeout()` to re-configure the 120-second timeout period to another value
|
||||||
* see [Message Logging](Logging.md) for complete details
|
* see [Message Logging](Logging.md) for complete details
|
||||||
|
|
||||||
* `void setTimeServerTimeout(uint32_t tSec)`
|
* `void setTimeServerTimeout(uint32_t tSec)`
|
||||||
* changes the default 10-second timeout period HomeSpan uses when `enableWebLog()` tries set the device clock from an internet time server to *tSec* seconds
|
* changes the default 120-second timeout period HomeSpan uses when `enableWebLog()` tries set the device clock from an internet time server to *tSec* seconds
|
||||||
|
|
||||||
|
* `void setWebLogCSS(const char *css)`
|
||||||
|
* sets the format of the HomeSpan Web Log to the custom style sheet specified by *css*
|
||||||
|
* see [Message Logging](Logging.md) for details on how to construct *css*
|
||||||
|
|
||||||
* `void processSerialCommand(const char *CLIcommand)`
|
* `void processSerialCommand(const char *CLIcommand)`
|
||||||
* processes the *CLIcommand* just as if were typed into the Serial Monitor
|
* processes the *CLIcommand* just as if were typed into the Serial Monitor
|
||||||
|
|
@ -200,6 +215,15 @@ The following **optional** `homeSpan` methods enable additional features and pro
|
||||||
* example: `homeSpan.processSerialCommand("A");` starts the HomeSpan Setup Access Point
|
* example: `homeSpan.processSerialCommand("A");` starts the HomeSpan Setup Access Point
|
||||||
* example: `homeSpan.processSerialCommand("Q HUB3");` changes the HomeKit Setup ID for QR Codes to "HUB3"
|
* example: `homeSpan.processSerialCommand("Q HUB3");` changes the HomeKit Setup ID for QR Codes to "HUB3"
|
||||||
|
|
||||||
|
* `void setSerialInputDisable(boolean val)`
|
||||||
|
* if *val* is true, disables HomeSpan from reading input from the Serial port
|
||||||
|
* if *val* is false, re-enables HomeSpan reading input from the Serial port
|
||||||
|
* useful when the main USB Serial port is needed for reading data from an external Serial peripheral, rather than being used to read input from the Arduino Serial Monitor
|
||||||
|
|
||||||
|
* `boolean getSerialInputDisable()`
|
||||||
|
* returns *true* if HomeSpan reading from the Serial port is currently disabled
|
||||||
|
* returns *false* if HomeSpan is operating normally and will read any CLI commands input into the Arduino Serial Monitor
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
The following **optional** `homeSpan` methods provide additional run-time functionality for more advanced use cases:
|
The following **optional** `homeSpan` methods provide additional run-time functionality for more advanced use cases:
|
||||||
|
|
@ -590,24 +614,6 @@ If REQUIRED is defined in the main sketch *prior* to including the HomeSpan libr
|
||||||
#include "HomeSpan.h"
|
#include "HomeSpan.h"
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### Deprecated functions (available for backwards compatibility with older sketches):
|
|
||||||
|
|
||||||
* `SpanRange(int min, int max, int step)`
|
|
||||||
|
|
||||||
* this legacy class was limited to integer-based parameters and has been re-coded to simply call the more generic `setRange(min, max, step)` method
|
|
||||||
* last supported version: [v1.2.0](https://github.com/HomeSpan/HomeSpan/blob/release-1.2.0/docs/Reference.md#spanrangeint-min-int-max-int-step)
|
|
||||||
* **please use** `setRange(min, max, step)` **for all new sketches**
|
|
||||||
|
|
||||||
* `void homeSpan.setMaxConnections(uint8_t nCon)`
|
|
||||||
* this legacy method was used to set the total number of HAP Controller Connections HomeSpan implements upon start-up to ensure there are still free sockets available for user-defined code requiring separate network resources
|
|
||||||
* last supported version: [v1.4.2](https://github.com/HomeSpan/HomeSpan/blob/release-1.4.2/docs/Reference.md)
|
|
||||||
* this method has been replaced by the more flexible method, `reserveSocketConnections(uint8_t nSockets)`
|
|
||||||
* allows you to simply reserve network sockets for other custom code as needed
|
|
||||||
* upon calling `homespan.begin()`, HomeSpan automatically determines how many sockets are left that it can use for HAP Controller Connections
|
|
||||||
* **please use** `homeSpan.reserveSocketConnections(uint8_t nSockets)` **for all new sketches**
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
[↩️](../README.md) Back to the Welcome page
|
[↩️](../README.md) Back to the Welcome page
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,283 @@
|
||||||
|
# Stepper Motor Control
|
||||||
|
|
||||||
|
HomeSpan includes dedicated classes that provide for easy control of a stepper motor connected to an ESP32 via a stepper motor driver board. These classes allow one or more stepper motors to operate smoothly and asynchronously in the background while HomeSpan continues to run in the foreground. On devices with dual processors, stepper-motor control can be run either on the same or a different processor from HomeSpan.
|
||||||
|
|
||||||
|
The HomeSpan class that contains all the methods to control a stepper motor is called **StepperControl**. However, this is an abstract class and cannot be instantiated directly. Instead you instantiate stepper motor objects using driver-specific child-classes (derived from **StepperControl**) that contain all the logic to configure and operate a particular driver board. Each child class supports one or more constructors allowing you to specify which output pins on your ESP32 device will be connected to the required pins on your driver board:
|
||||||
|
|
||||||
|
* **[Stepper_TB6612](StepperDrivers/Stepper_TB6612.md)**
|
||||||
|
* This class is used to operate stepper motors driven by a [Toshiba TB6612](https://cdn-shop.adafruit.com/datasheets/TB6612FNG_datasheet_en_20121101.pdf) chip, either with or without the use of ESP32 PWM pins
|
||||||
|
* See, for example, the [Adafruit TB6612 1.2A DC/Stepper Motor Driver Breakout Board](https://www.adafruit.com/product/2448)
|
||||||
|
* To use, add the following to the top of your sketch: `#include "extras/Stepper_TB6612.h"`
|
||||||
|
* Constructor 1: `Stepper_TB6612(int AIN1, int AIN2, int BIN1, int BIN2)`
|
||||||
|
* controls the driver board using only 4 digital pins from the ESP32
|
||||||
|
* does not provide ability to microstep the motor
|
||||||
|
* Constructor 2: `Stepper_TB6612(int AIN1, int AIN2, int BIN1, int BIN2, int PWMA, int PWMB)`
|
||||||
|
* controls the driver board using 4 digital pins and 2 PWM pins from the ESP32
|
||||||
|
* the addition of the PWM pins provides the ability to microstep the motor
|
||||||
|
|
||||||
|
* **[Stepper_A3967](StepperDrivers/Stepper_A3967.md)**
|
||||||
|
* This class is used to operate stepper motors driven by an [Allegro A3967](https://cdn.sparkfun.com/datasheets/Robotics/A3967-Datasheet.pdf) chip
|
||||||
|
* See, for example, the [Sparkfun EasyDriver Stepper Motor Board](https://www.sparkfun.com/products/12779)
|
||||||
|
* To use, add the following to the top of your sketch: `#include "extras/Stepper_A3967.h"`
|
||||||
|
* Contructor: `Stepper_A3967(int M1, int M2, int STEP, int DIR, int ENABLE)`
|
||||||
|
* controls the driver board using 5 digital pins from the ESP32
|
||||||
|
* microstepping is built into the driver board (separate ESP32 PWM pins are not needed)
|
||||||
|
|
||||||
|
Click on either of the driver-specific classes above for complete details on how to wire and configure a particular driver board.
|
||||||
|
|
||||||
|
## StepperControl Methods
|
||||||
|
|
||||||
|
The **StepperControl** class provides the following methods to operate and control a stepper motor object instantiated with one of the driver-specific classes described above:
|
||||||
|
|
||||||
|
* `StepperControl *enable()`
|
||||||
|
* enables current flow to the stepper motor coils, actively holding the motor in its position
|
||||||
|
* returns pointer to itself so methods can be daisy-chained
|
||||||
|
* example: `myMotor=(new Stepper_TB6612(23,32,22,14,33,27))->enable();`
|
||||||
|
|
||||||
|
* `StepperControl *disable()`
|
||||||
|
* disables current flow to the stepper motor coils and leaves them in a state of high impedence, allowing the motor to turn freely
|
||||||
|
* returns pointer to itself so methods can be daisy-chained
|
||||||
|
* example: `myMotor=(new Stepper_TB6612(23,32,22,14,33,27))->disable();`
|
||||||
|
|
||||||
|
* `StepperControl *brake()`
|
||||||
|
* disables current flow to the stepper motor coils but leaves them in a state of low impedence, preventing the motor from freely turning
|
||||||
|
* applicable only for driver chips that support a "short brake" mode, otherwise has no effect
|
||||||
|
* returns pointer to itself so methods can be daisy-chained
|
||||||
|
* example: `myMotor=(new Stepper_TB6612(23,32,22,14,33,27))->brake();`
|
||||||
|
|
||||||
|
* `void move(int nSteps, uint32_t msDelay, endAction_t endAction=NONE)`
|
||||||
|
* enables the stepper motor and turns it *nSteps* steps. Note this is a **non-blocking** function and returns immediately after being called while the motor turns for *nSteps* steps in the background
|
||||||
|
|
||||||
|
* *nSteps* - the number of steps to turn. A positive number turns the motor in one direction; a negative number turns the motor in the opposite direction; a value of zero causes the motor to *stop* if it is already turning
|
||||||
|
* *msDelay* - the delay, in milliseconds, to pause between steps. Must be greater than zero. The lower the number, the faster the motor turns, subject to limitations of the motor itself
|
||||||
|
* *endAction* - an optional action to be performed *after* the motor has finished moving *nSteps* steps. Choices include:
|
||||||
|
|
||||||
|
* **StepperControl::NONE** - no action is taken; the stepper motor is left in the enabled state (this is the default)
|
||||||
|
* **StepperControl::DISABLE** - current to the stepper motor is disabled
|
||||||
|
* **StepperControl::BRAKE** - the stepper motor is placed in a brake state
|
||||||
|
* if this method is called while the stepper motor is already turning, the number of steps to turn will be reset to the new *nSteps* value. It is okay to change the sign of *nSteps* to reverse the direction of motor while it is turning, though this may not be desireable depending on what your motor is connected to in the real world
|
||||||
|
* calling this method with a value of *nSteps=0* causes the motor to stop, if it is already turning. If the motor is not turning, calling this method with *nSteps=0* simply enables the motor and the immediately performs the *endAction* (if specified).
|
||||||
|
* example: `myMotor.move(200,5,StepperControl::BRAKE);` starts the motor turning for 200 steps with a delay of 5ms between steps. When the motor has completed all 200 steps, it is placed in the brake state.
|
||||||
|
|
||||||
|
* `int stepsRemaining()`
|
||||||
|
* returns the number of steps remaining to turn
|
||||||
|
* may be positive or negative depending on the direction the motor is turning
|
||||||
|
* returns zero if the motor is not turning
|
||||||
|
* example: `myMotor.move(200,5); while(myMotor.stepsRemaining()!=0); myMotor.move(-300,5);` starts the motor turning, waits until it completes all 200 steps, and then turns the motor in the opposite direction for 300 steps
|
||||||
|
|
||||||
|
* `int position()`
|
||||||
|
* returns the absolute position of the stepper motor, which is defined as the cumulative sum of the all positive and negative steps turned since initial start-up
|
||||||
|
* can be called when the stepper motor is turning or when it is stopped
|
||||||
|
* example: `myMotor.move(-800,5); while(myMotor.stepsRemaining()); myMotor.move(200,5); while(myMotor.stepsRemaining()); Serial.print(myMotor.position())` would print a value of -600 after the motor finishes turning (first one direction for 800 steps, and then the other for 200 steps)
|
||||||
|
|
||||||
|
* `void setPosition(int pos)`
|
||||||
|
* resets the current position counter to *pos*
|
||||||
|
* this method does *not* turn the motor; it only resets the internal position counter as returned by `position()`
|
||||||
|
* this method is only effective when the motor is **not** turning (if called when the motor is turning the internal position counter remainms unchanged)
|
||||||
|
* example: `myMotor.move(300,5); while(myMotor.stepsRemaining()); myMotor.setPosition(-200); myMotor.move(600,5); while(myMotor.stepsRemaining()); Serial.print(myMotor.position())` would print a value of +400 after the motor finishes turning
|
||||||
|
|
||||||
|
* `void moveTo(int nPosition, uint32_t msDelay, endAction_t endAction=NONE)`
|
||||||
|
* enables the stepper motor and turns it to the position *nPosition*. Note this is a **non-blocking** function and returns immediately after being called while the stepper motor turns until it reaches *nPosition*
|
||||||
|
|
||||||
|
* *nPosition* - the position to which the stepper move should turn, where position is defined as the cumulative number of positive and negative steps the motor has turned since initial start-up, as returned by `position()`
|
||||||
|
* *msDelay* - the delay, in milliseconds, to pause between steps. Must be greater than zero. The lower the number, the faster the motor turns, subject to limitations of the motor itself
|
||||||
|
* *endAction* - an optional action to be performed *after* the motor has reached *nPosition*. Choices include:
|
||||||
|
|
||||||
|
* **StepperControl::NONE** - no action is taken; the stepper motor is left in the enabled state (this is the default)
|
||||||
|
* **StepperControl::DISABLE** - current to the stepper motor is disabled
|
||||||
|
* **StepperControl::BRAKE** - the stepper motor is placed in a brake state
|
||||||
|
* it is okay to call this method while the stepper motor is already turning; the motor will either continue turning in the same direction, or reverse direction, until it reaches the *nPosition* specified
|
||||||
|
* calls to `stepsRemaining()` after calling `moveTo()` work as expected - the value returned will be the number of steps remaining until the motor reaches the *nPosition* specified
|
||||||
|
* note that `moveTo(nPosition)` is mathematically the same as `move(nPosition-position())`, but the `moveTo()` method is more accurate since it computes the position of the motor directly inside the task that is actually controlling the motor
|
||||||
|
|
||||||
|
* `StepperControl *setAccel(float accelSize, float accelSteps)`
|
||||||
|
* adds an additional set of delays between steps so that the motor gradually accelerates when it starts and decelerates when it stops
|
||||||
|
|
||||||
|
* *accelSize* - the maximum size of the additional delay, expressed as a factor to be multiplied by the *msDelay* parameter used in `move()` and `moveTo()`. Must be a value greater or equal to 0. The larger the value, the greater the magnitude of the acceleration and deceleration. A value of zero yields no acceleration/deceleration
|
||||||
|
|
||||||
|
* *accelSteps* - the number of steps over which the *accelSize* factor exponentially decays, at which point he motor begins turning at the full speed specified by the *msDelay* parameter. Must be a value greater or equal to 1. The larger the value, the longer the acceleration and deceleration period
|
||||||
|
|
||||||
|
* the total delay between steps (when *stepsRemaining* is not zero) is given by the following formula:
|
||||||
|
$$totalDelay = msDelay \times (1 + accelSize \times (e^{\frac{-\mid nSteps-stepsRemaining \mid}{accelSteps}} + e^{\frac{-(\mid stepsRemaining \mid - 1)}{accelSteps}}))$$
|
||||||
|
|
||||||
|
* example: `myMotor.setAccel(10,20); myMotor.move(200,5);`
|
||||||
|
* yields a 55ms delay after the first step, a 52ms delay after the second step, a 50ms delay after the third step, and so forth, until at step 82 the additional delay has fully decayed such that the delay between steps remains fixed at the 5ms *msDelay* parameter specified. Then, starting at step 118 (with 82 steps remaining) the delay increases to 6ms; at step 134 it further increases to 7ms, and so forth, until the delay reaches its maxmimum of 55ms once again at step 199 just before the motor stops turning at step 200
|
||||||
|
|
||||||
|
* returns pointer to itself so methods can be daisy-chained
|
||||||
|
* example: `myMotor=(new Stepper_TB6612(23,32,22,14,33,27))->setAccel(10,20);`
|
||||||
|
|
||||||
|
* `StepperControl *setStepType(int mode)`
|
||||||
|
* sets the step type of the motor to one of the following *mode* enumerations:
|
||||||
|
|
||||||
|
* **StepperControl::FULL_STEP_ONE_PHASE** (0)
|
||||||
|
* **StepperControl::FULL_STEP_TWO_PHASE** (1)
|
||||||
|
* **StepperControl::HALF_STEP** (2)
|
||||||
|
* **StepperControl::QUARTER_STEP** (4)
|
||||||
|
* **StepperControl::EIGHTH_STEP** (8)
|
||||||
|
* *mode* can be specified using either the name of the enumeration or its integer equivalent
|
||||||
|
* returns pointer to itself so methods can be daisy-chained
|
||||||
|
* example: `myMotor=(new Stepper_TB6612(23,32,22,14,33,27))->setStepType(StepperControl::HALF_STEP);`
|
||||||
|
* smaller step types provide for smoother operation of the motor, but require more steps to turn a complete revolution
|
||||||
|
* not all *modes* are supported by all driver chips
|
||||||
|
* the quarter- and eighth-step modes require microstepping PWM functionality
|
||||||
|
* it is possible, though not recommended, to change the step type *mode* while the motor is turning
|
||||||
|
* see [Stepper Motor Modes](StepperModes.md) for a brief primer on how stepper motors are typically driven
|
||||||
|
|
||||||
|
### CPU and Task Priority
|
||||||
|
|
||||||
|
All **StepperControl** constructors support an *optional* final parameter consisting of a *brace-enclosed* pair of numbers (not shown above). The first number in the braces specifies the *priority* of the background task used to control the stepper motor. The second number in the braces specifies the CPU (0 or 1) that **StepperControl** will use to run the background control task (this number is ignored for single-processor chips). The default (and recommended) value of this optional final parameter is {1,0}. Example:
|
||||||
|
|
||||||
|
* `new Stepper_TB6612(23,32,22,14,{0,1})` instantiates control of a stepper motor driven by a TB6612 chip, where ESP32 pins 23, 32, 22, and 14 are connected to the AIN1, AIN2, BIN1, and BIN2 pins on the TB6612, respectively; the priority of the background task is set to 0; and the task will be run on cpu 1 (applicable only for a dual-processor chip)
|
||||||
|
|
||||||
|
## Example Sketches
|
||||||
|
|
||||||
|
Below is a simple sketch demonstrating the above methods:
|
||||||
|
|
||||||
|
```C++
|
||||||
|
// StepperControl Example using TB6612-based Driver Board with HALF STEP PWM MODE
|
||||||
|
|
||||||
|
#include "Stepper_TB6612.h" // include the driver for a TB6612 chip
|
||||||
|
|
||||||
|
StepperControl *motor; // create a global pointer to StepperControl so it can be accessed in both setup() and loop()
|
||||||
|
|
||||||
|
///////////////////
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
|
||||||
|
Serial.begin(115200);
|
||||||
|
delay(1000);
|
||||||
|
Serial.printf("\nHomeSpan Stepper Control\n\n");
|
||||||
|
|
||||||
|
motor=new Stepper_TB6612(23,32,22,14,33,27); // instantiate the motor object with optional PWM pin specified (33 and 27)
|
||||||
|
|
||||||
|
motor->setStepType(StepperControl::HALF_STEP); // set the mode to HALF STEP, which means 400 steps are needed for a complete revolution of a 200-step motor
|
||||||
|
motor->setAccel(10,20); // add acceleration parameters: extra delay is 10x, decaying over 20 steps
|
||||||
|
|
||||||
|
Serial.printf("Moving motor 400 steps and waiting until motor stops...\n");
|
||||||
|
|
||||||
|
motor->move(-400,5); // move the motor -400 steps (1 revolution), with 5ms between steps.
|
||||||
|
while(motor->stepsRemaining()); // wait until there no remaining steps
|
||||||
|
|
||||||
|
Serial.printf("Moving motor to absolute position of +1200 (i.e reverse direction for 1600 steps, or 4 revolutions) without waiting...\n");
|
||||||
|
|
||||||
|
motor->moveTo(1200,2,StepperControl::BRAKE); // move the motor to an absolute position of 1200 steps with 2ms between steps; enter brake state when done
|
||||||
|
|
||||||
|
// Motor will continue moving in background even once setup() exits and loop() below starts
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////
|
||||||
|
|
||||||
|
void loop(){
|
||||||
|
|
||||||
|
Serial.printf("Motor has %d remaining steps\n",motor->stepsRemaining());
|
||||||
|
|
||||||
|
delay(1000); // motor is unaffected by delay()
|
||||||
|
|
||||||
|
if(motor->position()==1200){
|
||||||
|
Serial.printf("Motor has reached final position and is now stopped.\n");
|
||||||
|
while(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Motorized Window Shade Example
|
||||||
|
|
||||||
|
A fully worked example showing how to use the *StepperControl* class within a complete HomeSpan sketch to control a Motorize Window Shade using both the TB6612 and A3967 driver boards can be found in the Arduino IDE under [*File → Examples → HomeSpan → Other Examples → MotorizedWindowShade*](../examples/Other%20Examples/MotorizedWindowShade).
|
||||||
|
|
||||||
|
## Creating your own **StepperControl** Driver
|
||||||
|
|
||||||
|
If neither of the above motor driver classes works for your specific chip or driver board, it is relatively straightfoward to create a new driver to use in your sketch. This is because all the logic to operate a stepper motor in the background is already embedded in the abstract **StepperControl** class. To create your own driver, start by creating a child class derived from **StepperControl**. Next, add a constructor that defines the pins and performs any initializations if needed. Finally, define the following methods that **StepperControl** calls to operate the motor:
|
||||||
|
|
||||||
|
* `void onStep(boolean direction)` - contains the logic to advance the motor by a single step based on the *direction* parameter
|
||||||
|
* `void onEnable()` - contains the logic that enables the motor driver
|
||||||
|
* `void onDisable()` - contains the logic that disables the motor driver
|
||||||
|
* `void onBrake()` - contains the logic that puts the motor into a short brake state
|
||||||
|
* `StepperControl *setStepType(int mode)` - contains the logic to set the step type mode based on the *mode* parameter
|
||||||
|
|
||||||
|
Only the first method, `onStep()`, is required to be defined. You can leave any of the other methods undefined if they are not applicable for your specific driver board. You can of course create additional methods to reflect any other features your driver board may support.
|
||||||
|
|
||||||
|
As an example, below is the complete code for the **Stepper_A3967** class:
|
||||||
|
|
||||||
|
```C++
|
||||||
|
#include "StepperControl.h"
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
|
||||||
|
struct Stepper_A3967 : StepperControl {
|
||||||
|
|
||||||
|
int m1Pin;
|
||||||
|
int m2Pin;
|
||||||
|
int stepPin;
|
||||||
|
int dirPin;
|
||||||
|
int enablePin;
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
|
||||||
|
Stepper_A3967(int m1Pin, int m2Pin, int stepPin, int dirPin, int enablePin, std::pair<uint32_t, uint32_t> taskParams = {1,0}) : StepperControl(taskParams.first,taskParams.second){
|
||||||
|
this->m1Pin=m1Pin;
|
||||||
|
this->m2Pin=m2Pin;
|
||||||
|
this->stepPin=stepPin;
|
||||||
|
this->dirPin=dirPin;
|
||||||
|
this->enablePin=enablePin;
|
||||||
|
|
||||||
|
pinMode(m1Pin,OUTPUT);
|
||||||
|
pinMode(m2Pin,OUTPUT);
|
||||||
|
pinMode(stepPin,OUTPUT);
|
||||||
|
pinMode(dirPin,OUTPUT);
|
||||||
|
pinMode(enablePin,OUTPUT);
|
||||||
|
|
||||||
|
setStepType(FULL_STEP_TWO_PHASE);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
|
||||||
|
void onStep(boolean direction) override {
|
||||||
|
digitalWrite(dirPin,direction);
|
||||||
|
digitalWrite(stepPin,HIGH);
|
||||||
|
digitalWrite(stepPin,LOW);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
|
||||||
|
void onEnable() override {
|
||||||
|
digitalWrite(enablePin,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
|
||||||
|
void onDisable() override {
|
||||||
|
digitalWrite(enablePin,1);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
|
||||||
|
StepperControl *setStepType(int mode) override {
|
||||||
|
switch(mode){
|
||||||
|
case FULL_STEP_TWO_PHASE:
|
||||||
|
digitalWrite(m1Pin,LOW);
|
||||||
|
digitalWrite(m2Pin,LOW);
|
||||||
|
break;
|
||||||
|
case HALF_STEP:
|
||||||
|
digitalWrite(m1Pin,HIGH);
|
||||||
|
digitalWrite(m2Pin,LOW);
|
||||||
|
break;
|
||||||
|
case QUARTER_STEP:
|
||||||
|
digitalWrite(m1Pin,LOW);
|
||||||
|
digitalWrite(m2Pin,HIGH);
|
||||||
|
break;
|
||||||
|
case EIGHTH_STEP:
|
||||||
|
digitalWrite(m1Pin,HIGH);
|
||||||
|
digitalWrite(m2Pin,HIGH);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ESP_LOGE(STEPPER_TAG,"Unknown StepType=%d",mode);
|
||||||
|
}
|
||||||
|
return(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[↩️](../README.md) Back to the Welcome page
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
# Stepper_A3967
|
||||||
|
|
||||||
|
This is a derived class of **StepperControl** designed to operate stepper motors driven by an [Allegro A3967](https://cdn.sparkfun.com/datasheets/Robotics/A3967-Datasheet.pdf) chip. To use, add `#include "extras/Stepper_A3967.h"` to the top of your sketch.
|
||||||
|
|
||||||
|
The Allegro A3967 is a specialized driver designed for stepper motors. It contains a built-in PWM generator and pre-programmed stepping modes. Wiring for the [Sparkfun EasyDriver Stepper Motor Board](https://learn.sparkfun.com/tutorials/easy-driver-hook-up-guide?_ga=2.152816825.1841726212.1688220137-156607829.1686369274) that uses this chip is as follows:
|
||||||
|
|
||||||
|
#### **Power Connections**
|
||||||
|
* *GND* - connect to GND on the ESP32, and to ground of external DC power supply
|
||||||
|
* *M+* - connect to external DC power supply that will drive stepper motor. An on-board regulator also uses this supply to provide VCC to the rest of the board. For use with an ESP32, you must short the 3.3V/5V jumper with a blob of solder to select 3.3V
|
||||||
|
#### **Motor Connections**
|
||||||
|
* *Motor A* - connect to the "A" coil of the stepper motor
|
||||||
|
* *Motor B* - connect to the "B" coil of the stepper motor
|
||||||
|
#### **Control Connections**
|
||||||
|
* *ENABLE* - connect to a digital pin on the ESP32 - used to enable/disable to motor driver
|
||||||
|
* *STEP, DIR* - connect to two digital pins on the ESP32 - used to step the motor and set the direction
|
||||||
|
* *MS1, MS2* - connect to two digital pins on the ESP32 - used to set the step type mode
|
||||||
|
* *SLEEP, RESET* - already pulled high on the EasyDriver board, so no connection neeed. If using a different driver board, ensure these pins are pulled high, else connect to VCC
|
||||||
|
* *PFD* - not used
|
||||||
|
|
||||||
|
The **Stepper_A3967** class includes the following constructor:
|
||||||
|
* `Stepper_A3967(int M1, int M2, int STEP, int DIR, int ENABLE)`
|
||||||
|
* controls the driver board using 5 digital pins from the ESP32, where the parameters specify the pin numbers. Supports the following step type modes:
|
||||||
|
|
||||||
|
* FULL_STEP_TWO_PHASE
|
||||||
|
* HALF_STEP
|
||||||
|
* QUARTER_STEP
|
||||||
|
* EIGHTH_STEP
|
||||||
|
|
||||||
|
❗Note: The A3967 chip does not support a short brake state. Calls to the `brake()` method, as well as setting the *endAction* parameter in the `move()` and `moveTo()` methods to **StepperControl::BRAKE** have no effect on the motor driver.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[↩️](../Stepper.md) Back to the Stepper Motor Control page
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
# Stepper_TB6612
|
||||||
|
|
||||||
|
This is a derived class of **StepperControl** designed to operate stepper motors driven by a [Toshiba TB6612](https://cdn-shop.adafruit.com/datasheets/TB6612FNG_datasheet_en_20121101.pdf) chip, either with or without the use of ESP32 PWM pins. To use, add `#include "extras/Stepper_TB6612.h"` to the top of your sketch.
|
||||||
|
|
||||||
|
The Toshiba TB6612 is a generic motor driver providing direct control of two full H-bridges. Wiring for the [Adafruit TB6612 1.2A DC/Stepper Motor Driver Breakout Board](https://learn.adafruit.com/adafruit-tb6612-h-bridge-dc-stepper-motor-driver-breakout) that uses this chip is as follows:
|
||||||
|
|
||||||
|
#### **Power Connections**
|
||||||
|
* *VCC* - connect to +3.3V on ESP32
|
||||||
|
* *VM+* - connect to external DC power supply that will drive stepper motor (5-13V)
|
||||||
|
* *GND* - connect to GND on the ESP32, and to ground of external DC power supply
|
||||||
|
#### **Motor Connections**
|
||||||
|
* *MOTORA* - connect to the "A" coil of the stepper motor
|
||||||
|
* *MOTORB* - connect to the "B" coil of the stepper motor
|
||||||
|
#### **Control Connections**
|
||||||
|
* *AIN1, AIN2* - connect to two digital pins on the ESP32 - used to control direction and state of coil *A*
|
||||||
|
* *BIN1, BIN2* - connect to two digital pins on the ESP32 - used to control direction and state of coil *B*
|
||||||
|
* *PWMA, PWMB* - if using PWM, connect to two digital pins on the ESP32; if not, connect to +3.3V on ESP32 to pull high
|
||||||
|
* *STBY* - connect to +3.3V on ESP32 to pull high
|
||||||
|
|
||||||
|
The **Stepper_TB6612** class includes two constructors:
|
||||||
|
* `Stepper_TB6612(int AIN1, int AIN2, int BIN1, int BIN2)`
|
||||||
|
* controls the driver board using only 4 digital pins from the ESP32, where the parameters specify the pin numbers. Supports the following step type modes:
|
||||||
|
|
||||||
|
* FULL_STEP_ONE_PHASE
|
||||||
|
* FULL_STEP_TWO_PHASE
|
||||||
|
* HALF_STEP
|
||||||
|
|
||||||
|
* `Stepper_TB6612(int AIN1, int AIN2, int BIN1, int BIN2, int PWMA, int PWMB)`
|
||||||
|
* controls the driver board using 4 digital pins and 2 PWM pins from the ESP32, where the parameters specify the pin numbers. Supports the following step type modes:
|
||||||
|
|
||||||
|
* FULL_STEP_ONE_PHASE
|
||||||
|
* FULL_STEP_TWO_PHASE
|
||||||
|
* HALF_STEP
|
||||||
|
* QUARTER_STEP
|
||||||
|
* EIGHTH_STEP
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[↩️](../Stepper.md) Back to the Stepper Motor Control page
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
# Stepper Motor Modes
|
||||||
|
|
||||||
|
A typical stepper motor has two sets of coils, *A* and *B*. Applying a current to one or both coils creates a magnetic field that fixes the motor's position. Any changes to the direction and/or magnitude of the current flowing through the coils causes the motor to "step" to a new fixed position. By repeatedly changing the current flow according to a specific pattern the motor can be made to continuously step clockwise or counterclockwise. The specific pattern chosen, known as the *step mode* in the **StepperControl** class, determines the size of the step and overall performance of the motor. The speed at which changes to the current flow are made determines the speed at which the motor rotates. The modes supported by **StepperControl** are described below.
|
||||||
|
|
||||||
|
## FULL STEP TWO PHASE MODE (without PWM)
|
||||||
|
|
||||||
|
In this mode, a constant current is applied to both coils *A* and *B*. The motor is stepped by alternatively flipping the direction of the current flow in each coil as follows:
|
||||||
|
|
||||||
|
<img width="250" alt="image" src="https://github.com/HomeSpan/HomeSpan/assets/68477936/8bea7031-7325-4ded-8ebd-5554d8f1e13d"><br>
|
||||||
|
|
||||||
|
Note that though the pattern repeats after four steps, it is not symmetrical - running the pattern from left to right causes the motor to rotate in one direction, whereas running the pattern from right to left will cause it to rotate in the opposite direction. Many stepper motors are constructed to have 200 full steps, which means you need to repeat the above pattern 25 times to cause the motor to complete a single revolution.
|
||||||
|
|
||||||
|
Since in this mode each coil has only two possible states (i.e. the direction of the current flow), only one digital signal per coil is required to implement the stepping pattern. However, fully flipping the direction of the current flow in a coil changes the magnetic fields very rapidly, which creates a rather clunky motion when stepping the motor.
|
||||||
|
|
||||||
|
## FULL STEP ONE PHASE MODE (without PWM)
|
||||||
|
|
||||||
|
In this mode, a constant current is alternatively applied to either *A* **or** *B*, while also flipping its direction as follows:
|
||||||
|
|
||||||
|
<img width="250" alt="image" src="https://github.com/HomeSpan/HomeSpan/assets/68477936/cbf2fea5-072e-4fef-9231-504bb483b0c0"><br>
|
||||||
|
|
||||||
|
This mode uses only half the power as the FULL STEP TWO PHASE mode since current only flows through one coil at a time. Also, though the step size is the same, the transtition from one step to another is not as harsh since the current to each coil transtions from one direction to zero before flipping to the other direction. However, since in this mode each coil has three possible states for current flow (positive, negative, and off), two digital signals per coil are required to implement the stepping pattern.
|
||||||
|
|
||||||
|
## HALF STEP MODE (without PWM)
|
||||||
|
|
||||||
|
Though the the step sizes for the two modes above are the same, the set of positions they represent as the motor rotates are out of sync by 1/2 step. This means that by interleaving the four steps of the two modes together, we can create the following 8-step pattern where the step size is *half* that of a full step:
|
||||||
|
|
||||||
|
<img width="400" alt="image" src="https://github.com/HomeSpan/HomeSpan/assets/68477936/ec317c77-fbd9-4641-9d50-d822b477c9ec"><br>
|
||||||
|
|
||||||
|
The advantage of this mode is that the motor moves much more smoothly, and can be stopped in half-step increments for more precise control. However, it takes twice as many steps for the motor to complete a full rotation (and thus moves twice as slow given the same delay between steps).
|
||||||
|
|
||||||
|
One other disadvantage of this mode is that in four of the steps both coils are powered, whereas in the other four only one coil is powered. This results in uneven motor torque as the motor rotates, as well as different holding torques depending on which step the motor lies when stopped. Depending on your specific application (raising a shade, closing a door, etc.) this may or may not be of concern.
|
||||||
|
|
||||||
|
## QUARTER STEP and EIGHTH STEP MODES (including PWM versions of the above modes)
|
||||||
|
|
||||||
|
In the above modes, current to each coil is either "fully on" in one direction, fully on in the other direction, or completely off. As a result, there is a trade-off between step granularity and smoothness of rotation versus power and torque consistency. The solution to this problem is to drive the current in each coil with a PWM signal (or an equivalent limiting mechanism) that allows the *magnitude* of the current flow to be controlled in addition to its direction.
|
||||||
|
|
||||||
|
Typically this is done by setting the magnitude of the current in each coil based on sinusoidal patterns offset by 90° as follows:
|
||||||
|
|
||||||
|
<img width="505" alt="image" src="https://github.com/HomeSpan/HomeSpan/assets/68477936/75a6176b-b5b4-4b85-a394-a4d6e1f9bf3d"><br>
|
||||||
|
|
||||||
|
Here, the blue dots perfectly replicate the steps of the FULL STEP ONE PHASE mode (e.g. positive/off/negative/off for coil *A*), the red dots replicate the steps of the FULL STEP TWO PHASE MODE, and the combined set of both the red blue and red dots taken together replicate the steps of the HALF STEP mode. Each non-PWM mode above can therefore be replicated using a single set of PWM-based sinusoidal curves, but with one important difference: the PWM method yields the same power for every step in every mode, avoiding the problems of inconsistent torque described above for the non-PWM modes. This is because power is typically proportional to the *square* of the magnitude of the current running through a coil; coil *A* follows a cosine curve; coil *B* follows a sine curve; and and $cos^2+sin^2=1$, which is a constant.
|
||||||
|
|
||||||
|
It is generally preferable to use the PWM method when running a motor in any of the three modes above, rather than running with just a constant current applied to each coil, provided that PWM signals are available. Fortunately, many stepper motor driver boards contain a built-in PWM module. And if they don't, you can have **StepperControl** generate the required PWM signals using the ESP32's built-in PWM peripheral.
|
||||||
|
|
||||||
|
In addition to providing better motor performance, the PWM method also provides for even more granular stepping modes by adding "microsteps" to the step pattern. For example, adding 8 additional steps between the half-steps, as indicated on the curves by the 8 black diamonds placed between the blue and red dots, yields a QUARTER STEP mode. This mode is twice as "smooth" as the HALF STEP mode, but requires twice as many steps for the motor to complete a full rotation. Halving the steps even further (not shown on the diagram) yields the EIGHTH STEP mode where each cycle contains 32 individual stepping points. **StepperControl** supports both the QUARTER STEP and EIGHTH STEP modes, though you can easily extend this further by adding modes with 64 or 128 steps per cycle. For motors designed to operate a window shade, such granularity is generally not needed, and too fine a granularity can cause misteps depending on the specific characteristics of the motor.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[↩️](Stepper.md) Back to the Stepper Motor Control page
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -138,6 +138,9 @@ Demonstrates how *SpanPoint* can be used to transmit messages from battery-power
|
||||||
### [FadingLED](../examples/Other%20Examples/FadingLED)
|
### [FadingLED](../examples/Other%20Examples/FadingLED)
|
||||||
Demonstrates how the *LedPin* class can use the ESP32's built-in fading control to automatically fade an LED from from one level of brightness to another over a specified period of time. See the [LedPin](PWM.md#pulse-width-modulation-pwm) page for full details
|
Demonstrates how the *LedPin* class can use the ESP32's built-in fading control to automatically fade an LED from from one level of brightness to another over a specified period of time. See the [LedPin](PWM.md#pulse-width-modulation-pwm) page for full details
|
||||||
|
|
||||||
|
### [MotorizedWindowShade](../examples/Other%20Examples/MotorizedWindowShade)
|
||||||
|
Demonstrates how to use the *StepperControl* class to operate a stepper motor. Implements a motorized window shade based on [Example 13](../examples/13-TargetStates) above. See the [Stepper Motor Control](Stepper.md) page for full details
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
[↩️](../README.md) Back to the Welcome page
|
[↩️](../README.md) Back to the Welcome page
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,16 @@
|
||||||
# HomeSpan User Guide
|
# HomeSpan User Guide
|
||||||
|
1. [Introduction](#introduction)
|
||||||
|
2. [Getting Started](#getting-started)
|
||||||
|
3. [HomeSpan Device States](#homespan-device-states)
|
||||||
|
4. [Device Configuration Mode](#device-configuration-mode)
|
||||||
|
5. [Setting HomeSpan’s WiFi Credentials and Setup Code](#setting-homespans-wifi-credentials-and-setup-code)
|
||||||
|
6. [Pairing to HomeKit](#pairing-to-homekit)
|
||||||
|
7. [Factory Reset](#factory-reset)
|
||||||
|
8. [Creating a Scannable Tag](#creating-a-scannable-tag)
|
||||||
|
9. [Status LED Flashing Reference](#status-led-flashing-reference)
|
||||||
|
|
||||||
|
|
||||||
|
## Introduction
|
||||||
This guide explains how to set up and configure a HomeSpan device that has already been programmed to operate one or more appliances, including how to:
|
This guide explains how to set up and configure a HomeSpan device that has already been programmed to operate one or more appliances, including how to:
|
||||||
|
|
||||||
* Determine the state of the device by observing the HomeSpan Status LED
|
* Determine the state of the device by observing the HomeSpan Status LED
|
||||||
|
|
@ -157,6 +168,20 @@ The box can be bigger or smaller as long as you keep the same proportions. For
|
||||||
|
|
||||||
Note that if you can’t find *Scancardium* listed as a font choice in either the *Pages* or *Keynote* font dropdown boxes, select Format → Font → Show Fonts from the menu bar of the *Pages* or *Keynote* application to bring up a list of all installed fonts where you can search for, and select, *Scancardium*.
|
Note that if you can’t find *Scancardium* listed as a font choice in either the *Pages* or *Keynote* font dropdown boxes, select Format → Font → Show Fonts from the menu bar of the *Pages* or *Keynote* application to bring up a list of all installed fonts where you can search for, and select, *Scancardium*.
|
||||||
|
|
||||||
|
## Status LED Flashing Reference
|
||||||
|
|
||||||
|
If a status LED has been connected and configured, HomeSpan will use flashing code to indicate different status.
|
||||||
|
|
||||||
|
| Flash pattern | Description |
|
||||||
|
| --------------------------- | -------------------------------------------------------------------------- |
|
||||||
|
| LED off, no flashing | By default, no power, unless `setStatusAutoOff(timeout)` has been defined. |
|
||||||
|
| LED on, no flashing | All good. Device is connected to WiFi and paired. See [PAIRED](#paired). |
|
||||||
|
| 1-second flash every second | Attempting to connect to WiFi. See [CONNECTING](#connecting). |
|
||||||
|
| 1 flash every 3 seconds | No WiFi. See [NO-WIFI](#nowifi). |
|
||||||
|
| 2 flashes every 3 seconds | See [READY-TO-PAIR](#readytopair). |
|
||||||
|
| 10 flashes per second | The Control button is pressed. Once released, HomeSpan will either<br>- Enter [Device Configuration Mode](#device-configuration-mode).<br>- Exit _Device Configuration Mode_ executing the action selected based on the number of flashes.|
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
[↩️](../README.md) Back to the Welcome page
|
[↩️](../README.md) Back to the Welcome page
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,143 @@
|
||||||
|
/*********************************************************************************
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2023 Gregg E. Berman
|
||||||
|
*
|
||||||
|
* https://github.com/HomeSpan/HomeSpan
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*
|
||||||
|
********************************************************************************/
|
||||||
|
|
||||||
|
// This example demonstrates how to control real-world Stepper Motors using HomeSpan's
|
||||||
|
// StepperControl Class through the implemention of a Motorized Window Shade Accessory.
|
||||||
|
|
||||||
|
// The sketch below is based on the more-fully commented WindowShade Accessory included in
|
||||||
|
// Tutorial Example 13 (this sketch only contains comments related to the use of the stepper motors).
|
||||||
|
|
||||||
|
// The Accessory we will use two stepper motors:
|
||||||
|
|
||||||
|
// * one motor to open/close the window shade, driven by an Adafruit TB6612 driver board (https://www.adafruit.com/product/2448)
|
||||||
|
// * one motor to tilt the window shade slats, driven by a SparkFun A3967 driver board (https://www.sparkfun.com/products/12779)
|
||||||
|
|
||||||
|
// See HomeSpan's StepperControl documentation for details on the classes used to control these driver boards,
|
||||||
|
// as well as for instructions on how you can easily extend StepperControl to create a custom driver for any board.
|
||||||
|
|
||||||
|
#include "HomeSpan.h"
|
||||||
|
#include "extras/Stepper_TB6612.h" // this contains HomeSpan's StepperControl Class for the Adafruit TB6612 driver board
|
||||||
|
#include "extras/Stepper_A3967.h" // this contains HomeSpan's StepperControl Class for the Sparkfun A3967 driver board
|
||||||
|
|
||||||
|
////////////////////////////////////
|
||||||
|
|
||||||
|
struct DEV_WindowShade : Service::WindowCovering {
|
||||||
|
|
||||||
|
Characteristic::CurrentPosition currentPos{0,true};
|
||||||
|
Characteristic::TargetPosition targetPos{0,true};
|
||||||
|
Characteristic::CurrentHorizontalTiltAngle currentTilt{0,true};
|
||||||
|
Characteristic::TargetHorizontalTiltAngle targetTilt{0,true};
|
||||||
|
|
||||||
|
StepperControl *mainMotor; // motor to open/close shade
|
||||||
|
StepperControl *slatMotor; // motor to tilt shade slats
|
||||||
|
|
||||||
|
DEV_WindowShade(StepperControl *mainMotor, StepperControl *slatMotor) : Service::WindowCovering(){
|
||||||
|
|
||||||
|
this->mainMotor=mainMotor; // save pointers to the motors
|
||||||
|
this->slatMotor=slatMotor;
|
||||||
|
|
||||||
|
mainMotor->setAccel(10,20); // set acceleration parameters for main motor
|
||||||
|
mainMotor->setStepType(StepperControl::HALF_STEP); // set step type to HALF STEP for main motor
|
||||||
|
|
||||||
|
LOG0("Initial Open/Close Position: %d\n",currentPos.getVal());
|
||||||
|
LOG0("Initial Slat Position: %d\n",currentTilt.getVal());
|
||||||
|
|
||||||
|
mainMotor->setPosition(currentPos.getVal()*20); // define initial position of main motor
|
||||||
|
slatMotor->setPosition(currentTilt.getVal()*11.47); // define initial position of slat motor
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////
|
||||||
|
|
||||||
|
boolean update(){
|
||||||
|
|
||||||
|
if(targetPos.updated()){
|
||||||
|
|
||||||
|
// Move motor to absolute position, assuming 400 steps per revolution and 5 revolutions for full open/close travel,
|
||||||
|
// for a total of 2000 steps of full travel. Specify that motor should enter the BRAKE state upon reaching to desired position.
|
||||||
|
// Must multiply targetPos, which ranges from 0-100, by 20 to scale to number of motor steps needed
|
||||||
|
|
||||||
|
mainMotor->moveTo(targetPos.getNewVal()*20,5,StepperControl::BRAKE);
|
||||||
|
LOG1("Setting Shade Position=%d\n",targetPos.getNewVal());
|
||||||
|
}
|
||||||
|
|
||||||
|
if(targetTilt.updated()){
|
||||||
|
|
||||||
|
// Move motor to absolute position, assuming 2064 steps per revolution and 1/2 revolution for full travel of slat tilt in either direction
|
||||||
|
// Must multiply targetPos, which ranges from -90 to 90, by 11.47 to scale number of motor steps needed
|
||||||
|
// Note this driver board for this motor does not support a "short brake" state
|
||||||
|
|
||||||
|
slatMotor->moveTo(targetTilt.getNewVal()*11.47,5);
|
||||||
|
LOG1("Setting Shade Position=%d\n",targetTilt.getNewVal());
|
||||||
|
}
|
||||||
|
|
||||||
|
return(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////
|
||||||
|
|
||||||
|
void loop(){
|
||||||
|
|
||||||
|
// If the current window shade position or tilt does NOT equal the target position, BUT the motor has stopped moving,
|
||||||
|
// we must have reached the target position, so set the current position equal to the target position
|
||||||
|
|
||||||
|
if(currentPos.getVal()!=targetPos.getVal() && !mainMotor->stepsRemaining()){
|
||||||
|
currentPos.setVal(targetPos.getVal());
|
||||||
|
LOG1("Main Motor Stopped at Shade Position=%d\n",currentPos.getVal());
|
||||||
|
}
|
||||||
|
|
||||||
|
if(currentTilt.getVal()!=targetTilt.getVal() && !slatMotor->stepsRemaining()){
|
||||||
|
currentTilt.setVal(targetTilt.getVal());
|
||||||
|
LOG1("Slat Motor Stopped at Shade Tilt=%d\n",currentTilt.getVal());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
////////////////////////////////////
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
|
||||||
|
Serial.begin(115200);
|
||||||
|
|
||||||
|
homeSpan.begin(Category::WindowCoverings,"Motorized Shade");
|
||||||
|
|
||||||
|
// MAKE SURE TO CHANGE THE PINS NUMBERS BELOW TO MATCH YOUR ESP32 DEVICE!!!
|
||||||
|
// THE PINS NUMBER SPECIFIED IN THIS EXAMPLE WORK WITH THE ORIGINAL ESP32, BUT WILL LIKELY CRASH AN ESP32-S2, -S3, or -C3.
|
||||||
|
|
||||||
|
new SpanAccessory();
|
||||||
|
new Service::AccessoryInformation();
|
||||||
|
new Characteristic::Identify();
|
||||||
|
new DEV_WindowShade(new Stepper_TB6612(23,32,22,14,33,27), new Stepper_A3967(18,21,5,4,19)); // instantiate drivers for each board and specify pins used on ESP32
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////
|
||||||
|
|
||||||
|
void loop(){
|
||||||
|
|
||||||
|
homeSpan.poll();
|
||||||
|
}
|
||||||
|
|
@ -58,7 +58,7 @@
|
||||||
#define DIAGNOSTIC_MODE
|
#define DIAGNOSTIC_MODE
|
||||||
|
|
||||||
#define SAMPLE_TIME 30000 // Time between temperature samples (in milliseconds)
|
#define SAMPLE_TIME 30000 // Time between temperature samples (in milliseconds)
|
||||||
#define I2C_ADD 0x48 // ICS Address to use for the Adafruit ADT7410
|
#define I2C_ADD 0x48 // I2C Address to use for the Adafruit ADT7410
|
||||||
|
|
||||||
SpanPoint *mainDevice;
|
SpanPoint *mainDevice;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
name=HomeSpan
|
name=HomeSpan
|
||||||
version=1.7.2
|
version=1.8.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.
|
||||||
|
|
|
||||||
254
src/HAP.cpp
254
src/HAP.cpp
|
|
@ -28,6 +28,7 @@
|
||||||
#include <ESPmDNS.h>
|
#include <ESPmDNS.h>
|
||||||
#include <sodium.h>
|
#include <sodium.h>
|
||||||
#include <MD5Builder.h>
|
#include <MD5Builder.h>
|
||||||
|
#include <mbedtls/version.h>
|
||||||
|
|
||||||
#include "HAP.h"
|
#include "HAP.h"
|
||||||
|
|
||||||
|
|
@ -61,16 +62,11 @@ void HAPClient::init(){
|
||||||
nvs_get_blob(srpNVS,"VERIFYDATA",&verifyData,&len); // retrieve data
|
nvs_get_blob(srpNVS,"VERIFYDATA",&verifyData,&len); // retrieve data
|
||||||
srp.loadVerifyCode(verifyData.verifyCode,verifyData.salt); // load verification code and salt into SRP structure
|
srp.loadVerifyCode(verifyData.verifyCode,verifyData.salt); // load verification code and salt into SRP structure
|
||||||
} else {
|
} else {
|
||||||
|
LOG0("Generating SRP verification data for default Setup Code: %.3s-%.2s-%.3s\n",homeSpan.defaultSetupCode,homeSpan.defaultSetupCode+3,homeSpan.defaultSetupCode+5);
|
||||||
char c[128];
|
|
||||||
sprintf(c,"Generating SRP verification data for default Setup Code: %.3s-%.2s-%.3s\n",homeSpan.defaultSetupCode,homeSpan.defaultSetupCode+3,homeSpan.defaultSetupCode+5);
|
|
||||||
Serial.print(c);
|
|
||||||
srp.createVerifyCode(homeSpan.defaultSetupCode,verifyData.verifyCode,verifyData.salt); // create verification code from default Setup Code and random salt
|
srp.createVerifyCode(homeSpan.defaultSetupCode,verifyData.verifyCode,verifyData.salt); // create verification code from default Setup Code and random salt
|
||||||
nvs_set_blob(srpNVS,"VERIFYDATA",&verifyData,sizeof(verifyData)); // update data
|
nvs_set_blob(srpNVS,"VERIFYDATA",&verifyData,sizeof(verifyData)); // update data
|
||||||
nvs_commit(srpNVS); // commit to NVS
|
nvs_commit(srpNVS); // commit to NVS
|
||||||
Serial.print("Setup Payload for Optional QR Code: ");
|
LOG0("Setup Payload for Optional QR Code: %s\n\n",homeSpan.qrCode.get(atoi(homeSpan.defaultSetupCode),homeSpan.qrID,atoi(homeSpan.category)));
|
||||||
Serial.print(homeSpan.qrCode.get(atoi(homeSpan.defaultSetupCode),homeSpan.qrID,atoi(homeSpan.category)));
|
|
||||||
Serial.print("\n\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!strlen(homeSpan.qrID)){ // Setup ID has not been specified in sketch
|
if(!strlen(homeSpan.qrID)){ // Setup ID has not been specified in sketch
|
||||||
|
|
@ -84,7 +80,7 @@ void HAPClient::init(){
|
||||||
if(!nvs_get_blob(hapNVS,"ACCESSORY",NULL,&len)){ // if found long-term Accessory data in NVS
|
if(!nvs_get_blob(hapNVS,"ACCESSORY",NULL,&len)){ // if found long-term Accessory data in NVS
|
||||||
nvs_get_blob(hapNVS,"ACCESSORY",&accessory,&len); // retrieve data
|
nvs_get_blob(hapNVS,"ACCESSORY",&accessory,&len); // retrieve data
|
||||||
} else {
|
} else {
|
||||||
Serial.print("Generating new random Accessory ID and Long-Term Ed25519 Signature Keys...\n");
|
LOG0("Generating new random Accessory ID and Long-Term Ed25519 Signature Keys...\n");
|
||||||
uint8_t buf[6];
|
uint8_t buf[6];
|
||||||
char cBuf[18];
|
char cBuf[18];
|
||||||
|
|
||||||
|
|
@ -102,7 +98,7 @@ void HAPClient::init(){
|
||||||
if(!nvs_get_blob(hapNVS,"CONTROLLERS",NULL,&len)){ // if found long-term Controller Pairings data from NVS
|
if(!nvs_get_blob(hapNVS,"CONTROLLERS",NULL,&len)){ // if found long-term Controller Pairings data from NVS
|
||||||
nvs_get_blob(hapNVS,"CONTROLLERS",controllers,&len); // retrieve data
|
nvs_get_blob(hapNVS,"CONTROLLERS",controllers,&len); // retrieve data
|
||||||
} else {
|
} else {
|
||||||
Serial.print("Initializing storage for Paired Controllers data...\n\n");
|
LOG0("Initializing storage for Paired Controllers data...\n\n");
|
||||||
|
|
||||||
HAPClient::removeControllers(); // clear all Controller data
|
HAPClient::removeControllers(); // clear all Controller data
|
||||||
|
|
||||||
|
|
@ -110,11 +106,11 @@ void HAPClient::init(){
|
||||||
nvs_commit(hapNVS); // commit to NVS
|
nvs_commit(hapNVS); // commit to NVS
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.print("Accessory ID: ");
|
LOG0("Accessory ID: ");
|
||||||
charPrintRow(accessory.ID,17);
|
charPrintRow(accessory.ID,17);
|
||||||
Serial.print(" LTPK: ");
|
LOG0(" LTPK: ");
|
||||||
hexPrintRow(accessory.LTPK,32);
|
hexPrintRow(accessory.LTPK,32);
|
||||||
Serial.print("\n");
|
LOG0("\n");
|
||||||
|
|
||||||
printControllers();
|
printControllers();
|
||||||
|
|
||||||
|
|
@ -138,17 +134,19 @@ void HAPClient::init(){
|
||||||
if(!nvs_get_blob(hapNVS,"HAPHASH",NULL,&len)){ // if found HAP HASH structure
|
if(!nvs_get_blob(hapNVS,"HAPHASH",NULL,&len)){ // if found HAP HASH structure
|
||||||
nvs_get_blob(hapNVS,"HAPHASH",&homeSpan.hapConfig,&len); // retrieve data
|
nvs_get_blob(hapNVS,"HAPHASH",&homeSpan.hapConfig,&len); // retrieve data
|
||||||
} else {
|
} else {
|
||||||
Serial.print("Resetting Database Hash...\n");
|
LOG0("Resetting Database Hash...\n");
|
||||||
nvs_set_blob(hapNVS,"HAPHASH",&homeSpan.hapConfig,sizeof(homeSpan.hapConfig)); // save data (will default to all zero values, which will then be updated below)
|
nvs_set_blob(hapNVS,"HAPHASH",&homeSpan.hapConfig,sizeof(homeSpan.hapConfig)); // save data (will default to all zero values, which will then be updated below)
|
||||||
nvs_commit(hapNVS); // commit to NVS
|
nvs_commit(hapNVS); // commit to NVS
|
||||||
}
|
}
|
||||||
|
|
||||||
if(homeSpan.updateDatabase(false)) // create Configuration Number and Loop vector
|
if(homeSpan.updateDatabase(false)){ // create Configuration Number and Loop vector
|
||||||
Serial.printf("\nAccessory configuration has changed. Updating configuration number to %d\n",homeSpan.hapConfig.configNumber);
|
LOG0("\nAccessory configuration has changed. Updating configuration number to %d\n",homeSpan.hapConfig.configNumber);
|
||||||
else
|
}
|
||||||
Serial.printf("\nAccessory configuration number: %d\n",homeSpan.hapConfig.configNumber);
|
else{
|
||||||
|
LOG0("\nAccessory configuration number: %d\n",homeSpan.hapConfig.configNumber);
|
||||||
|
}
|
||||||
|
|
||||||
Serial.print("\n");
|
LOG0("\n");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -179,7 +177,7 @@ void HAPClient::processRequest(){
|
||||||
|
|
||||||
if(nBytes>MAX_HTTP){ // exceeded maximum number of bytes allowed
|
if(nBytes>MAX_HTTP){ // exceeded maximum number of bytes allowed
|
||||||
badRequestError();
|
badRequestError();
|
||||||
Serial.print("\n*** ERROR: Exceeded maximum HTTP message length\n\n");
|
LOG0("\n*** ERROR: Exceeded maximum HTTP message length\n\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -192,7 +190,7 @@ void HAPClient::processRequest(){
|
||||||
|
|
||||||
if(!(p=strstr((char *)httpBuf,"\r\n\r\n"))){
|
if(!(p=strstr((char *)httpBuf,"\r\n\r\n"))){
|
||||||
badRequestError();
|
badRequestError();
|
||||||
Serial.print("\n*** ERROR: Malformed HTTP request (can't find blank line indicating end of BODY)\n\n");
|
LOG0("\n*** ERROR: Malformed HTTP request (can't find blank line indicating end of BODY)\n\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -204,7 +202,7 @@ void HAPClient::processRequest(){
|
||||||
cLen=atoi(p+16);
|
cLen=atoi(p+16);
|
||||||
if(nBytes!=strlen(body)+4+cLen){
|
if(nBytes!=strlen(body)+4+cLen){
|
||||||
badRequestError();
|
badRequestError();
|
||||||
Serial.print("\n*** ERROR: Malformed HTTP request (Content-Length plus Body Length does not equal total number of bytes read)\n\n");
|
LOG0("\n*** ERROR: Malformed HTTP request (Content-Length plus Body Length does not equal total number of bytes read)\n\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -215,14 +213,14 @@ void HAPClient::processRequest(){
|
||||||
|
|
||||||
if(cLen==0){
|
if(cLen==0){
|
||||||
badRequestError();
|
badRequestError();
|
||||||
Serial.print("\n*** ERROR: HTTP POST request contains no Content\n\n");
|
LOG0("\n*** ERROR: HTTP POST request contains no Content\n\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!strncmp(body,"POST /pair-setup ",17) && // POST PAIR-SETUP
|
if(!strncmp(body,"POST /pair-setup ",17) && // POST PAIR-SETUP
|
||||||
strstr(body,"Content-Type: application/pairing+tlv8") && // check that content is TLV8
|
strstr(body,"Content-Type: application/pairing+tlv8") && // check that content is TLV8
|
||||||
tlv8.unpack(content,cLen)){ // read TLV content
|
tlv8.unpack(content,cLen)){ // read TLV content
|
||||||
if(homeSpan.logLevel>1) tlv8.print(); // print TLV records in form "TAG(INT) LENGTH(INT) VALUES(HEX)"
|
tlv8.print(2); // print TLV records in form "TAG(INT) LENGTH(INT) VALUES(HEX)"
|
||||||
LOG2("------------ END TLVS! ------------\n");
|
LOG2("------------ END TLVS! ------------\n");
|
||||||
|
|
||||||
postPairSetupURL(); // process URL
|
postPairSetupURL(); // process URL
|
||||||
|
|
@ -232,7 +230,7 @@ void HAPClient::processRequest(){
|
||||||
if(!strncmp(body,"POST /pair-verify ",18) && // POST PAIR-VERIFY
|
if(!strncmp(body,"POST /pair-verify ",18) && // POST PAIR-VERIFY
|
||||||
strstr(body,"Content-Type: application/pairing+tlv8") && // check that content is TLV8
|
strstr(body,"Content-Type: application/pairing+tlv8") && // check that content is TLV8
|
||||||
tlv8.unpack(content,cLen)){ // read TLV content
|
tlv8.unpack(content,cLen)){ // read TLV content
|
||||||
if(homeSpan.logLevel>1) tlv8.print(); // print TLV records in form "TAG(INT) LENGTH(INT) VALUES(HEX)"
|
tlv8.print(2); // print TLV records in form "TAG(INT) LENGTH(INT) VALUES(HEX)"
|
||||||
LOG2("------------ END TLVS! ------------\n");
|
LOG2("------------ END TLVS! ------------\n");
|
||||||
|
|
||||||
postPairVerifyURL(); // process URL
|
postPairVerifyURL(); // process URL
|
||||||
|
|
@ -242,7 +240,7 @@ void HAPClient::processRequest(){
|
||||||
if(!strncmp(body,"POST /pairings ",15) && // POST PAIRINGS
|
if(!strncmp(body,"POST /pairings ",15) && // POST PAIRINGS
|
||||||
strstr(body,"Content-Type: application/pairing+tlv8") && // check that content is TLV8
|
strstr(body,"Content-Type: application/pairing+tlv8") && // check that content is TLV8
|
||||||
tlv8.unpack(content,cLen)){ // read TLV content
|
tlv8.unpack(content,cLen)){ // read TLV content
|
||||||
if(homeSpan.logLevel>1) tlv8.print(); // print TLV records in form "TAG(INT) LENGTH(INT) VALUES(HEX)"
|
tlv8.print(2); // print TLV records in form "TAG(INT) LENGTH(INT) VALUES(HEX)"
|
||||||
LOG2("------------ END TLVS! ------------\n");
|
LOG2("------------ END TLVS! ------------\n");
|
||||||
|
|
||||||
postPairingsURL(); // process URL
|
postPairingsURL(); // process URL
|
||||||
|
|
@ -252,7 +250,7 @@ void HAPClient::processRequest(){
|
||||||
if(!strncmp(body,"POST /pairings ",15) && // POST PAIRINGS
|
if(!strncmp(body,"POST /pairings ",15) && // POST PAIRINGS
|
||||||
strstr(body,"Content-Type: application/pairing+tlv8") && // check that content is TLV8
|
strstr(body,"Content-Type: application/pairing+tlv8") && // check that content is TLV8
|
||||||
tlv8.unpack(content,cLen)){ // read TLV content
|
tlv8.unpack(content,cLen)){ // read TLV content
|
||||||
if(homeSpan.logLevel>1) tlv8.print(); // print TLV records in form "TAG(INT) LENGTH(INT) VALUES(HEX)"
|
tlv8.print(2); // print TLV records in form "TAG(INT) LENGTH(INT) VALUES(HEX)"
|
||||||
LOG2("------------ END TLVS! ------------\n");
|
LOG2("------------ END TLVS! ------------\n");
|
||||||
|
|
||||||
postPairingsURL(); // process URL
|
postPairingsURL(); // process URL
|
||||||
|
|
@ -260,7 +258,7 @@ void HAPClient::processRequest(){
|
||||||
}
|
}
|
||||||
|
|
||||||
notFoundError();
|
notFoundError();
|
||||||
Serial.print("\n*** ERROR: Bad POST request - URL not found\n\n");
|
LOG0("\n*** ERROR: Bad POST request - URL not found\n\n");
|
||||||
return;
|
return;
|
||||||
|
|
||||||
} // POST request
|
} // POST request
|
||||||
|
|
@ -269,7 +267,7 @@ void HAPClient::processRequest(){
|
||||||
|
|
||||||
if(cLen==0){
|
if(cLen==0){
|
||||||
badRequestError();
|
badRequestError();
|
||||||
Serial.print("\n*** ERROR: HTTP PUT request contains no Content\n\n");
|
LOG0("\n*** ERROR: HTTP PUT request contains no Content\n\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -296,7 +294,7 @@ void HAPClient::processRequest(){
|
||||||
}
|
}
|
||||||
|
|
||||||
notFoundError();
|
notFoundError();
|
||||||
Serial.print("\n*** ERROR: Bad PUT request - URL not found\n\n");
|
LOG0("\n*** ERROR: Bad PUT request - URL not found\n\n");
|
||||||
return;
|
return;
|
||||||
|
|
||||||
} // PUT request
|
} // PUT request
|
||||||
|
|
@ -319,13 +317,13 @@ void HAPClient::processRequest(){
|
||||||
}
|
}
|
||||||
|
|
||||||
notFoundError();
|
notFoundError();
|
||||||
Serial.print("\n*** ERROR: Bad GET request - URL not found\n\n");
|
LOG0("\n*** ERROR: Bad GET request - URL not found\n\n");
|
||||||
return;
|
return;
|
||||||
|
|
||||||
} // GET request
|
} // GET request
|
||||||
|
|
||||||
badRequestError();
|
badRequestError();
|
||||||
Serial.print("\n*** ERROR: Unknown or malformed HTTP request\n\n");
|
LOG0("\n*** ERROR: Unknown or malformed HTTP request\n\n");
|
||||||
|
|
||||||
} // processHAP
|
} // processHAP
|
||||||
|
|
||||||
|
|
@ -393,13 +391,13 @@ int HAPClient::postPairSetupURL(){
|
||||||
char buf[64];
|
char buf[64];
|
||||||
|
|
||||||
if(tlvState==-1){ // missing STATE TLV
|
if(tlvState==-1){ // missing STATE TLV
|
||||||
Serial.print("\n*** ERROR: Missing <M#> State TLV\n\n");
|
LOG0("\n*** ERROR: Missing <M#> State TLV\n\n");
|
||||||
badRequestError(); // return with 400 error, which closes connection
|
badRequestError(); // return with 400 error, which closes connection
|
||||||
return(0);
|
return(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(nAdminControllers()){ // error: Device already paired (i.e. there is at least one admin Controller). We should not be receiving any requests for Pair-Setup!
|
if(nAdminControllers()){ // error: Device already paired (i.e. there is at least one admin Controller). We should not be receiving any requests for Pair-Setup!
|
||||||
Serial.print("\n*** ERROR: Device already paired!\n\n");
|
LOG0("\n*** ERROR: Device already paired!\n\n");
|
||||||
tlv8.clear(); // clear TLV records
|
tlv8.clear(); // clear TLV records
|
||||||
tlv8.val(kTLVType_State,tlvState+1); // set response STATE to requested state+1 (which should match the state that was expected by the controller)
|
tlv8.val(kTLVType_State,tlvState+1); // set response STATE to requested state+1 (which should match the state that was expected by the controller)
|
||||||
tlv8.val(kTLVType_Error,tagError_Unavailable); // set Error=Unavailable
|
tlv8.val(kTLVType_Error,tagError_Unavailable); // set Error=Unavailable
|
||||||
|
|
@ -411,7 +409,7 @@ int HAPClient::postPairSetupURL(){
|
||||||
LOG2(buf);
|
LOG2(buf);
|
||||||
|
|
||||||
if(tlvState!=pairStatus){ // error: Device is not yet paired, but out-of-sequence pair-setup STATE was received
|
if(tlvState!=pairStatus){ // error: Device is not yet paired, but out-of-sequence pair-setup STATE was received
|
||||||
Serial.print("\n*** ERROR: Out-of-Sequence Pair-Setup request!\n\n");
|
LOG0("\n*** ERROR: Out-of-Sequence Pair-Setup request!\n\n");
|
||||||
tlv8.clear(); // clear TLV records
|
tlv8.clear(); // clear TLV records
|
||||||
tlv8.val(kTLVType_State,tlvState+1); // set response STATE to requested state+1 (which should match the state that was expected by the controller)
|
tlv8.val(kTLVType_State,tlvState+1); // set response STATE to requested state+1 (which should match the state that was expected by the controller)
|
||||||
tlv8.val(kTLVType_Error,tagError_Unknown); // set Error=Unknown (there is no specific error type for out-of-sequence steps)
|
tlv8.val(kTLVType_Error,tagError_Unknown); // set Error=Unknown (there is no specific error type for out-of-sequence steps)
|
||||||
|
|
@ -425,7 +423,7 @@ int HAPClient::postPairSetupURL(){
|
||||||
case pairState_M1: // 'SRP Start Request'
|
case pairState_M1: // 'SRP Start Request'
|
||||||
|
|
||||||
if(tlv8.val(kTLVType_Method)!=0){ // error: "Pair Setup" method must always be 0 to indicate setup without MiFi Authentification (HAP Table 5-3)
|
if(tlv8.val(kTLVType_Method)!=0){ // error: "Pair Setup" method must always be 0 to indicate setup without MiFi Authentification (HAP Table 5-3)
|
||||||
Serial.print("\n*** ERROR: Pair Method not set to 0\n\n");
|
LOG0("\n*** ERROR: Pair Method not set to 0\n\n");
|
||||||
tlv8.clear(); // clear TLV records
|
tlv8.clear(); // clear TLV records
|
||||||
tlv8.val(kTLVType_State,pairState_M2); // set State=<M2>
|
tlv8.val(kTLVType_State,pairState_M2); // set State=<M2>
|
||||||
tlv8.val(kTLVType_Error,tagError_Unavailable); // set Error=Unavailable
|
tlv8.val(kTLVType_Error,tagError_Unavailable); // set Error=Unavailable
|
||||||
|
|
@ -450,7 +448,7 @@ int HAPClient::postPairSetupURL(){
|
||||||
if(!srp.writeTLV(kTLVType_PublicKey,&srp.A) || // try to write TLVs into mpi structures
|
if(!srp.writeTLV(kTLVType_PublicKey,&srp.A) || // try to write TLVs into mpi structures
|
||||||
!srp.writeTLV(kTLVType_Proof,&srp.M1)){
|
!srp.writeTLV(kTLVType_Proof,&srp.M1)){
|
||||||
|
|
||||||
Serial.print("\n*** ERROR: One or both of the required 'PublicKey' and 'Proof' TLV records for this step is bad or missing\n\n");
|
LOG0("\n*** ERROR: One or both of the required 'PublicKey' and 'Proof' TLV records for this step is bad or missing\n\n");
|
||||||
tlv8.clear(); // clear TLV records
|
tlv8.clear(); // clear TLV records
|
||||||
tlv8.val(kTLVType_State,pairState_M4); // set State=<M4>
|
tlv8.val(kTLVType_State,pairState_M4); // set State=<M4>
|
||||||
tlv8.val(kTLVType_Error,tagError_Unknown); // set Error=Unknown (there is no specific error type for missing/bad TLV data)
|
tlv8.val(kTLVType_Error,tagError_Unknown); // set Error=Unknown (there is no specific error type for missing/bad TLV data)
|
||||||
|
|
@ -462,7 +460,7 @@ int HAPClient::postPairSetupURL(){
|
||||||
srp.createSessionKey(); // create session key, K, from receipt of HAP Client public key, A
|
srp.createSessionKey(); // create session key, K, from receipt of HAP Client public key, A
|
||||||
|
|
||||||
if(!srp.verifyProof()){ // verify proof, M1, received from HAP Client
|
if(!srp.verifyProof()){ // verify proof, M1, received from HAP Client
|
||||||
Serial.print("\n*** ERROR: SRP Proof Verification Failed\n\n");
|
LOG0("\n*** ERROR: SRP Proof Verification Failed\n\n");
|
||||||
tlv8.clear(); // clear TLV records
|
tlv8.clear(); // clear TLV records
|
||||||
tlv8.val(kTLVType_State,pairState_M4); // set State=<M4>
|
tlv8.val(kTLVType_State,pairState_M4); // set State=<M4>
|
||||||
tlv8.val(kTLVType_Error,tagError_Authentication); // set Error=Authentication
|
tlv8.val(kTLVType_Error,tagError_Authentication); // set Error=Authentication
|
||||||
|
|
@ -485,7 +483,7 @@ int HAPClient::postPairSetupURL(){
|
||||||
case pairState_M5: // 'Exchange Request'
|
case pairState_M5: // 'Exchange Request'
|
||||||
|
|
||||||
if(!tlv8.buf(kTLVType_EncryptedData)){
|
if(!tlv8.buf(kTLVType_EncryptedData)){
|
||||||
Serial.print("\n*** ERROR: Required 'EncryptedData' TLV record for this step is bad or missing\n\n");
|
LOG0("\n*** ERROR: Required 'EncryptedData' TLV record for this step is bad or missing\n\n");
|
||||||
tlv8.clear(); // clear TLV records
|
tlv8.clear(); // clear TLV records
|
||||||
tlv8.val(kTLVType_State,pairState_M6); // set State=<M6>
|
tlv8.val(kTLVType_State,pairState_M6); // set State=<M6>
|
||||||
tlv8.val(kTLVType_Error,tagError_Unknown); // set Error=Unknown (there is no specific error type for missing/bad TLV data)
|
tlv8.val(kTLVType_Error,tagError_Unknown); // set Error=Unknown (there is no specific error type for missing/bad TLV data)
|
||||||
|
|
@ -512,7 +510,7 @@ int HAPClient::postPairSetupURL(){
|
||||||
tlv8.buf(kTLVType_EncryptedData), tlv8.len(kTLVType_EncryptedData), NULL, 0,
|
tlv8.buf(kTLVType_EncryptedData), tlv8.len(kTLVType_EncryptedData), NULL, 0,
|
||||||
(unsigned char *)"\x00\x00\x00\x00PS-Msg05", sessionKey)==-1){
|
(unsigned char *)"\x00\x00\x00\x00PS-Msg05", sessionKey)==-1){
|
||||||
|
|
||||||
Serial.print("\n*** ERROR: Exchange-Request Authentication Failed\n\n");
|
LOG0("\n*** ERROR: Exchange-Request Authentication Failed\n\n");
|
||||||
tlv8.clear(); // clear TLV records
|
tlv8.clear(); // clear TLV records
|
||||||
tlv8.val(kTLVType_State,pairState_M6); // set State=<M6>
|
tlv8.val(kTLVType_State,pairState_M6); // set State=<M6>
|
||||||
tlv8.val(kTLVType_Error,tagError_Authentication); // set Error=Authentication
|
tlv8.val(kTLVType_Error,tagError_Authentication); // set Error=Authentication
|
||||||
|
|
@ -522,7 +520,7 @@ int HAPClient::postPairSetupURL(){
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!tlv8.unpack(decrypted,decryptedLen)){
|
if(!tlv8.unpack(decrypted,decryptedLen)){
|
||||||
Serial.print("\n*** ERROR: Can't parse decrypted data into separate TLV records\n\n");
|
LOG0("\n*** ERROR: Can't parse decrypted data into separate TLV records\n\n");
|
||||||
tlv8.clear(); // clear TLV records
|
tlv8.clear(); // clear TLV records
|
||||||
tlv8.val(kTLVType_State,pairState_M6); // set State=<M6>
|
tlv8.val(kTLVType_State,pairState_M6); // set State=<M6>
|
||||||
tlv8.val(kTLVType_Error,tagError_Unknown); // set Error=Unknown (there is no specific error type for missing/bad TLV data)
|
tlv8.val(kTLVType_Error,tagError_Unknown); // set Error=Unknown (there is no specific error type for missing/bad TLV data)
|
||||||
|
|
@ -531,11 +529,11 @@ int HAPClient::postPairSetupURL(){
|
||||||
return(0);
|
return(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(homeSpan.logLevel>1) tlv8.print(); // print decrypted TLV data
|
tlv8.print(2); // print decrypted TLV data
|
||||||
LOG2("------- END DECRYPTED TLVS! -------\n");
|
LOG2("------- END DECRYPTED TLVS! -------\n");
|
||||||
|
|
||||||
if(!tlv8.buf(kTLVType_Identifier) || !tlv8.buf(kTLVType_PublicKey) || !tlv8.buf(kTLVType_Signature)){
|
if(!tlv8.buf(kTLVType_Identifier) || !tlv8.buf(kTLVType_PublicKey) || !tlv8.buf(kTLVType_Signature)){
|
||||||
Serial.print("\n*** ERROR: One or more of required 'Identifier,' 'PublicKey,' and 'Signature' TLV records for this step is bad or missing\n\n");
|
LOG0("\n*** ERROR: One or more of required 'Identifier,' 'PublicKey,' and 'Signature' TLV records for this step is bad or missing\n\n");
|
||||||
tlv8.clear(); // clear TLV records
|
tlv8.clear(); // clear TLV records
|
||||||
tlv8.val(kTLVType_State,pairState_M6); // set State=<M6>
|
tlv8.val(kTLVType_State,pairState_M6); // set State=<M6>
|
||||||
tlv8.val(kTLVType_Error,tagError_Unknown); // set Error=Unknown (there is no specific error type for missing/bad TLV data)
|
tlv8.val(kTLVType_Error,tagError_Unknown); // set Error=Unknown (there is no specific error type for missing/bad TLV data)
|
||||||
|
|
@ -569,7 +567,7 @@ int HAPClient::postPairSetupURL(){
|
||||||
uint8_t *iosDeviceSignature = tlv8.buf(kTLVType_Signature); // set iosDeviceSignature from TLV record (an Ed25519 should always be 64 bytes)
|
uint8_t *iosDeviceSignature = tlv8.buf(kTLVType_Signature); // set iosDeviceSignature from TLV record (an Ed25519 should always be 64 bytes)
|
||||||
|
|
||||||
if(crypto_sign_verify_detached(iosDeviceSignature, iosDeviceInfo, iosDeviceInfoLen, iosDeviceLTPK) != 0){ // verify signature of iosDeviceInfo using iosDeviceLTPK
|
if(crypto_sign_verify_detached(iosDeviceSignature, iosDeviceInfo, iosDeviceInfoLen, iosDeviceLTPK) != 0){ // verify signature of iosDeviceInfo using iosDeviceLTPK
|
||||||
Serial.print("\n*** ERROR: LPTK Signature Verification Failed\n\n");
|
LOG0("\n*** ERROR: LPTK Signature Verification Failed\n\n");
|
||||||
tlv8.clear(); // clear TLV records
|
tlv8.clear(); // clear TLV records
|
||||||
tlv8.val(kTLVType_State,pairState_M6); // set State=<M6>
|
tlv8.val(kTLVType_State,pairState_M6); // set State=<M6>
|
||||||
tlv8.val(kTLVType_Error,tagError_Authentication); // set Error=Authentication
|
tlv8.val(kTLVType_Error,tagError_Authentication); // set Error=Authentication
|
||||||
|
|
@ -611,7 +609,7 @@ int HAPClient::postPairSetupURL(){
|
||||||
|
|
||||||
LOG2("------- ENCRYPTING SUB-TLVS -------\n");
|
LOG2("------- ENCRYPTING SUB-TLVS -------\n");
|
||||||
|
|
||||||
if(homeSpan.logLevel>1) tlv8.print();
|
tlv8.print(2);
|
||||||
|
|
||||||
size_t subTLVLen=tlv8.pack(NULL); // get size of buffer needed to store sub-TLV
|
size_t subTLVLen=tlv8.pack(NULL); // get size of buffer needed to store sub-TLV
|
||||||
uint8_t subTLV[subTLVLen];
|
uint8_t subTLV[subTLVLen];
|
||||||
|
|
@ -666,13 +664,13 @@ int HAPClient::postPairVerifyURL(){
|
||||||
int tlvState=tlv8.val(kTLVType_State);
|
int tlvState=tlv8.val(kTLVType_State);
|
||||||
|
|
||||||
if(tlvState==-1){ // missing STATE TLV
|
if(tlvState==-1){ // missing STATE TLV
|
||||||
Serial.print("\n*** ERROR: Missing <M#> State TLV\n\n");
|
LOG0("\n*** ERROR: Missing <M#> State TLV\n\n");
|
||||||
badRequestError(); // return with 400 error, which closes connection
|
badRequestError(); // return with 400 error, which closes connection
|
||||||
return(0);
|
return(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!nAdminControllers()){ // error: Device not yet paired - we should not be receiving any requests for Pair-Verify!
|
if(!nAdminControllers()){ // error: Device not yet paired - we should not be receiving any requests for Pair-Verify!
|
||||||
Serial.print("\n*** ERROR: Device not yet paired!\n\n");
|
LOG0("\n*** ERROR: Device not yet paired!\n\n");
|
||||||
tlv8.clear(); // clear TLV records
|
tlv8.clear(); // clear TLV records
|
||||||
tlv8.val(kTLVType_State,tlvState+1); // set response STATE to requested state+1 (which should match the state that was expected by the controller)
|
tlv8.val(kTLVType_State,tlvState+1); // set response STATE to requested state+1 (which should match the state that was expected by the controller)
|
||||||
tlv8.val(kTLVType_Error,tagError_Unknown); // set Error=Unknown
|
tlv8.val(kTLVType_Error,tagError_Unknown); // set Error=Unknown
|
||||||
|
|
@ -688,7 +686,7 @@ int HAPClient::postPairVerifyURL(){
|
||||||
case pairState_M1: // 'Verify Start Request'
|
case pairState_M1: // 'Verify Start Request'
|
||||||
|
|
||||||
if(!tlv8.buf(kTLVType_PublicKey)){
|
if(!tlv8.buf(kTLVType_PublicKey)){
|
||||||
Serial.print("\n*** ERROR: Required 'PublicKey' TLV record for this step is bad or missing\n\n");
|
LOG0("\n*** ERROR: Required 'PublicKey' TLV record for this step is bad or missing\n\n");
|
||||||
tlv8.clear(); // clear TLV records
|
tlv8.clear(); // clear TLV records
|
||||||
tlv8.val(kTLVType_State,pairState_M2); // set State=<M2>
|
tlv8.val(kTLVType_State,pairState_M2); // set State=<M2>
|
||||||
tlv8.val(kTLVType_Error,tagError_Unknown); // set Error=Unknown (there is no specific error type for missing/bad TLV data)
|
tlv8.val(kTLVType_Error,tagError_Unknown); // set Error=Unknown (there is no specific error type for missing/bad TLV data)
|
||||||
|
|
@ -723,7 +721,7 @@ int HAPClient::postPairVerifyURL(){
|
||||||
|
|
||||||
LOG2("------- ENCRYPTING SUB-TLVS -------\n");
|
LOG2("------- ENCRYPTING SUB-TLVS -------\n");
|
||||||
|
|
||||||
if(homeSpan.logLevel>1) tlv8.print();
|
tlv8.print(2);
|
||||||
|
|
||||||
size_t subTLVLen=tlv8.pack(NULL); // get size of buffer needed to store sub-TLV
|
size_t subTLVLen=tlv8.pack(NULL); // get size of buffer needed to store sub-TLV
|
||||||
uint8_t subTLV[subTLVLen];
|
uint8_t subTLV[subTLVLen];
|
||||||
|
|
@ -754,7 +752,7 @@ int HAPClient::postPairVerifyURL(){
|
||||||
case pairState_M3: // 'Verify Finish Request'
|
case pairState_M3: // 'Verify Finish Request'
|
||||||
|
|
||||||
if(!tlv8.buf(kTLVType_EncryptedData)){
|
if(!tlv8.buf(kTLVType_EncryptedData)){
|
||||||
Serial.print("\n*** ERROR: Required 'EncryptedData' TLV record for this step is bad or missing\n\n");
|
LOG0("\n*** ERROR: Required 'EncryptedData' TLV record for this step is bad or missing\n\n");
|
||||||
tlv8.clear(); // clear TLV records
|
tlv8.clear(); // clear TLV records
|
||||||
tlv8.val(kTLVType_State,pairState_M4); // set State=<M4>
|
tlv8.val(kTLVType_State,pairState_M4); // set State=<M4>
|
||||||
tlv8.val(kTLVType_Error,tagError_Unknown); // set Error=Unknown (there is no specific error type for missing/bad TLV data)
|
tlv8.val(kTLVType_Error,tagError_Unknown); // set Error=Unknown (there is no specific error type for missing/bad TLV data)
|
||||||
|
|
@ -770,7 +768,7 @@ int HAPClient::postPairVerifyURL(){
|
||||||
tlv8.buf(kTLVType_EncryptedData), tlv8.len(kTLVType_EncryptedData), NULL, 0,
|
tlv8.buf(kTLVType_EncryptedData), tlv8.len(kTLVType_EncryptedData), NULL, 0,
|
||||||
(unsigned char *)"\x00\x00\x00\x00PV-Msg03", sessionKey)==-1){
|
(unsigned char *)"\x00\x00\x00\x00PV-Msg03", sessionKey)==-1){
|
||||||
|
|
||||||
Serial.print("\n*** ERROR: Verify Authentication Failed\n\n");
|
LOG0("\n*** ERROR: Verify Authentication Failed\n\n");
|
||||||
tlv8.clear(); // clear TLV records
|
tlv8.clear(); // clear TLV records
|
||||||
tlv8.val(kTLVType_State,pairState_M4); // set State=<M4>
|
tlv8.val(kTLVType_State,pairState_M4); // set State=<M4>
|
||||||
tlv8.val(kTLVType_Error,tagError_Authentication); // set Error=Authentication
|
tlv8.val(kTLVType_Error,tagError_Authentication); // set Error=Authentication
|
||||||
|
|
@ -779,7 +777,7 @@ int HAPClient::postPairVerifyURL(){
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!tlv8.unpack(decrypted,decryptedLen)){
|
if(!tlv8.unpack(decrypted,decryptedLen)){
|
||||||
Serial.print("\n*** ERROR: Can't parse decrypted data into separate TLV records\n\n");
|
LOG0("\n*** ERROR: Can't parse decrypted data into separate TLV records\n\n");
|
||||||
tlv8.clear(); // clear TLV records
|
tlv8.clear(); // clear TLV records
|
||||||
tlv8.val(kTLVType_State,pairState_M4); // set State=<M4>
|
tlv8.val(kTLVType_State,pairState_M4); // set State=<M4>
|
||||||
tlv8.val(kTLVType_Error,tagError_Unknown); // set Error=Unknown (there is no specific error type for missing/bad TLV data)
|
tlv8.val(kTLVType_Error,tagError_Unknown); // set Error=Unknown (there is no specific error type for missing/bad TLV data)
|
||||||
|
|
@ -787,11 +785,11 @@ int HAPClient::postPairVerifyURL(){
|
||||||
return(0);
|
return(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(homeSpan.logLevel>1) tlv8.print(); // print decrypted TLV data
|
tlv8.print(2); // print decrypted TLV data
|
||||||
LOG2("------- END DECRYPTED TLVS! -------\n");
|
LOG2("------- END DECRYPTED TLVS! -------\n");
|
||||||
|
|
||||||
if(!tlv8.buf(kTLVType_Identifier) || !tlv8.buf(kTLVType_Signature)){
|
if(!tlv8.buf(kTLVType_Identifier) || !tlv8.buf(kTLVType_Signature)){
|
||||||
Serial.print("\n*** ERROR: One or more of required 'Identifier,' and 'Signature' TLV records for this step is bad or missing\n\n");
|
LOG0("\n*** ERROR: One or more of required 'Identifier,' and 'Signature' TLV records for this step is bad or missing\n\n");
|
||||||
tlv8.clear(); // clear TLV records
|
tlv8.clear(); // clear TLV records
|
||||||
tlv8.val(kTLVType_State,pairState_M4); // set State=<M4>
|
tlv8.val(kTLVType_State,pairState_M4); // set State=<M4>
|
||||||
tlv8.val(kTLVType_Error,tagError_Unknown); // set Error=Unknown (there is no specific error type for missing/bad TLV data)
|
tlv8.val(kTLVType_Error,tagError_Unknown); // set Error=Unknown (there is no specific error type for missing/bad TLV data)
|
||||||
|
|
@ -802,7 +800,7 @@ int HAPClient::postPairVerifyURL(){
|
||||||
Controller *tPair; // temporary pointer to Controller
|
Controller *tPair; // temporary pointer to Controller
|
||||||
|
|
||||||
if(!(tPair=findController(tlv8.buf(kTLVType_Identifier)))){
|
if(!(tPair=findController(tlv8.buf(kTLVType_Identifier)))){
|
||||||
Serial.print("\n*** ERROR: Unrecognized Controller PairingID\n\n");
|
LOG0("\n*** ERROR: Unrecognized Controller PairingID\n\n");
|
||||||
tlv8.clear(); // clear TLV records
|
tlv8.clear(); // clear TLV records
|
||||||
tlv8.val(kTLVType_State,pairState_M4); // set State=<M4>
|
tlv8.val(kTLVType_State,pairState_M4); // set State=<M4>
|
||||||
tlv8.val(kTLVType_Error,tagError_Authentication); // set Error=Authentication
|
tlv8.val(kTLVType_Error,tagError_Authentication); // set Error=Authentication
|
||||||
|
|
@ -818,7 +816,7 @@ int HAPClient::postPairVerifyURL(){
|
||||||
memcpy(iosDeviceInfo+32+36,publicCurveKey,32);
|
memcpy(iosDeviceInfo+32+36,publicCurveKey,32);
|
||||||
|
|
||||||
if(crypto_sign_verify_detached(tlv8.buf(kTLVType_Signature), iosDeviceInfo, iosDeviceInfoLen, tPair->LTPK) != 0){ // verify signature of iosDeviceInfo using iosDeviceLTPK
|
if(crypto_sign_verify_detached(tlv8.buf(kTLVType_Signature), iosDeviceInfo, iosDeviceInfoLen, tPair->LTPK) != 0){ // verify signature of iosDeviceInfo using iosDeviceLTPK
|
||||||
Serial.print("\n*** ERROR: LPTK Signature Verification Failed\n\n");
|
LOG0("\n*** ERROR: LPTK Signature Verification Failed\n\n");
|
||||||
tlv8.clear(); // clear TLV records
|
tlv8.clear(); // clear TLV records
|
||||||
tlv8.val(kTLVType_State,pairState_M4); // set State=<M4>
|
tlv8.val(kTLVType_State,pairState_M4); // set State=<M4>
|
||||||
tlv8.val(kTLVType_Error,tagError_Authentication); // set Error=Authentication
|
tlv8.val(kTLVType_Error,tagError_Authentication); // set Error=Authentication
|
||||||
|
|
@ -903,7 +901,7 @@ int HAPClient::postPairingsURL(){
|
||||||
LOG1(")...");
|
LOG1(")...");
|
||||||
|
|
||||||
if(tlv8.val(kTLVType_State)!=1){
|
if(tlv8.val(kTLVType_State)!=1){
|
||||||
Serial.print("\n*** ERROR: 'State' TLV record is either missing or not set to <M1> as required\n\n");
|
LOG0("\n*** ERROR: 'State' TLV record is either missing or not set to <M1> as required\n\n");
|
||||||
badRequestError(); // return with 400 error, which closes connection
|
badRequestError(); // return with 400 error, which closes connection
|
||||||
return(0);
|
return(0);
|
||||||
}
|
}
|
||||||
|
|
@ -914,7 +912,7 @@ int HAPClient::postPairingsURL(){
|
||||||
LOG1("Add...\n");
|
LOG1("Add...\n");
|
||||||
|
|
||||||
if(!tlv8.buf(kTLVType_Identifier) || !tlv8.buf(kTLVType_PublicKey) || !tlv8.buf(kTLVType_Permissions)){
|
if(!tlv8.buf(kTLVType_Identifier) || !tlv8.buf(kTLVType_PublicKey) || !tlv8.buf(kTLVType_Permissions)){
|
||||||
Serial.print("\n*** ERROR: One or more of required 'Identifier,' 'PublicKey,' and 'Permissions' TLV records for this step is bad or missing\n\n");
|
LOG0("\n*** ERROR: One or more of required 'Identifier,' 'PublicKey,' and 'Permissions' TLV records for this step is bad or missing\n\n");
|
||||||
tlv8.clear(); // clear TLV records
|
tlv8.clear(); // clear TLV records
|
||||||
tlv8.val(kTLVType_State,pairState_M2); // set State=<M2>
|
tlv8.val(kTLVType_State,pairState_M2); // set State=<M2>
|
||||||
tlv8.val(kTLVType_Error,tagError_Unknown); // set Error=Unknown (there is no specific error type for missing/bad TLV data)
|
tlv8.val(kTLVType_Error,tagError_Unknown); // set Error=Unknown (there is no specific error type for missing/bad TLV data)
|
||||||
|
|
@ -922,7 +920,7 @@ int HAPClient::postPairingsURL(){
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!cPair->admin){
|
if(!cPair->admin){
|
||||||
Serial.print("\n*** ERROR: Controller making request does not have admin privileges to add/update other Controllers\n\n");
|
LOG0("\n*** ERROR: Controller making request does not have admin privileges to add/update other Controllers\n\n");
|
||||||
tlv8.clear(); // clear TLV records
|
tlv8.clear(); // clear TLV records
|
||||||
tlv8.val(kTLVType_State,pairState_M2); // set State=<M2>
|
tlv8.val(kTLVType_State,pairState_M2); // set State=<M2>
|
||||||
tlv8.val(kTLVType_Error,tagError_Authentication); // set Error=Authentication
|
tlv8.val(kTLVType_Error,tagError_Authentication); // set Error=Authentication
|
||||||
|
|
@ -930,7 +928,7 @@ int HAPClient::postPairingsURL(){
|
||||||
}
|
}
|
||||||
|
|
||||||
if((newCont=findController(tlv8.buf(kTLVType_Identifier))) && memcmp(tlv8.buf(kTLVType_PublicKey),newCont->LTPK,32)){ // requested Controller already exists, but LTPKs don't match
|
if((newCont=findController(tlv8.buf(kTLVType_Identifier))) && memcmp(tlv8.buf(kTLVType_PublicKey),newCont->LTPK,32)){ // requested Controller already exists, but LTPKs don't match
|
||||||
Serial.print("\n*** ERROR: Invalid request to update the LTPK of an exsiting Controller\n\n");
|
LOG0("\n*** ERROR: Invalid request to update the LTPK of an exsiting Controller\n\n");
|
||||||
tlv8.clear(); // clear TLV records
|
tlv8.clear(); // clear TLV records
|
||||||
tlv8.val(kTLVType_State,pairState_M2); // set State=<M2>
|
tlv8.val(kTLVType_State,pairState_M2); // set State=<M2>
|
||||||
tlv8.val(kTLVType_Error,tagError_Unknown); // set Error=Unknown
|
tlv8.val(kTLVType_Error,tagError_Unknown); // set Error=Unknown
|
||||||
|
|
@ -938,9 +936,7 @@ int HAPClient::postPairingsURL(){
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!addController(tlv8.buf(kTLVType_Identifier),tlv8.buf(kTLVType_PublicKey),tlv8.val(kTLVType_Permissions)==1?true:false)){
|
if(!addController(tlv8.buf(kTLVType_Identifier),tlv8.buf(kTLVType_PublicKey),tlv8.val(kTLVType_Permissions)==1?true:false)){
|
||||||
Serial.print("\n*** ERROR: Can't pair more than ");
|
LOG0("\n*** ERROR: Can't pair more than %d Controllers\n\n",MAX_CONTROLLERS);
|
||||||
Serial.print(MAX_CONTROLLERS);
|
|
||||||
Serial.print(" Controllers\n\n");
|
|
||||||
tlv8.clear(); // clear TLV records
|
tlv8.clear(); // clear TLV records
|
||||||
tlv8.val(kTLVType_State,pairState_M2); // set State=<M2>
|
tlv8.val(kTLVType_State,pairState_M2); // set State=<M2>
|
||||||
tlv8.val(kTLVType_Error,tagError_MaxPeers); // set Error=MaxPeers
|
tlv8.val(kTLVType_Error,tagError_MaxPeers); // set Error=MaxPeers
|
||||||
|
|
@ -955,7 +951,7 @@ int HAPClient::postPairingsURL(){
|
||||||
LOG1("Remove...\n");
|
LOG1("Remove...\n");
|
||||||
|
|
||||||
if(!tlv8.buf(kTLVType_Identifier)){
|
if(!tlv8.buf(kTLVType_Identifier)){
|
||||||
Serial.print("\n*** ERROR: Required 'Identifier' TLV record for this step is bad or missing\n\n");
|
LOG0("\n*** ERROR: Required 'Identifier' TLV record for this step is bad or missing\n\n");
|
||||||
tlv8.clear(); // clear TLV records
|
tlv8.clear(); // clear TLV records
|
||||||
tlv8.val(kTLVType_State,pairState_M2); // set State=<M2>
|
tlv8.val(kTLVType_State,pairState_M2); // set State=<M2>
|
||||||
tlv8.val(kTLVType_Error,tagError_Unknown); // set Error=Unknown (there is no specific error type for missing/bad TLV data)
|
tlv8.val(kTLVType_Error,tagError_Unknown); // set Error=Unknown (there is no specific error type for missing/bad TLV data)
|
||||||
|
|
@ -963,7 +959,7 @@ int HAPClient::postPairingsURL(){
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!cPair->admin){
|
if(!cPair->admin){
|
||||||
Serial.print("\n*** ERROR: Controller making request does not have admin privileges to remove Controllers\n\n");
|
LOG0("\n*** ERROR: Controller making request does not have admin privileges to remove Controllers\n\n");
|
||||||
tlv8.clear(); // clear TLV records
|
tlv8.clear(); // clear TLV records
|
||||||
tlv8.val(kTLVType_State,pairState_M2); // set State=<M2>
|
tlv8.val(kTLVType_State,pairState_M2); // set State=<M2>
|
||||||
tlv8.val(kTLVType_Error,tagError_Authentication); // set Error=Authentication
|
tlv8.val(kTLVType_Error,tagError_Authentication); // set Error=Authentication
|
||||||
|
|
@ -986,7 +982,7 @@ int HAPClient::postPairingsURL(){
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
Serial.print("\n*** ERROR: 'Method' TLV record is either missing or not set to either 3, 4, or 5 as required\n\n");
|
LOG0("\n*** ERROR: 'Method' TLV record is either missing or not set to either 3, 4, or 5 as required\n\n");
|
||||||
badRequestError(); // return with 400 error, which closes connection
|
badRequestError(); // return with 400 error, which closes connection
|
||||||
return(0);
|
return(0);
|
||||||
break;
|
break;
|
||||||
|
|
@ -1242,19 +1238,57 @@ int HAPClient::getStatusURL(){
|
||||||
|
|
||||||
sprintf(uptime,"%d:%02d:%02d:%02d",days,hours,mins,secs);
|
sprintf(uptime,"%d:%02d:%02d:%02d",days,hours,mins,secs);
|
||||||
|
|
||||||
String response="HTTP/1.1 200 OK\r\nContent-type: text/html\r\n\r\n";
|
String response="HTTP/1.1 200 OK\r\nContent-type: text/html; charset=utf-8\r\n\r\n";
|
||||||
|
|
||||||
response+="<html><head><title>" + String(homeSpan.displayName) + "</title>\n";
|
response+="<html><head><title>" + String(homeSpan.displayName) + "</title>\n";
|
||||||
response+="<style>th, td {padding-right: 10px; padding-left: 10px; border:1px solid black;}";
|
response+="<style>body {background-color:lightblue;} th, td {padding-right: 10px; padding-left: 10px; border:1px solid black;}" + homeSpan.webLog.css + "</style></head>\n";
|
||||||
response+="</style></head>\n";
|
response+="<body class=bod1><h2>" + String(homeSpan.displayName) + "</h2>\n";
|
||||||
response+="<body style=\"background-color:lightblue;\">\n";
|
|
||||||
response+="<p><b>" + String(homeSpan.displayName) + "</b></p>\n";
|
|
||||||
|
|
||||||
response+="<table>\n";
|
response+="<table class=tab1>\n";
|
||||||
response+="<tr><td>Up Time:</td><td>" + String(uptime) + "</td></tr>\n";
|
response+="<tr><td>Up Time:</td><td>" + String(uptime) + "</td></tr>\n";
|
||||||
response+="<tr><td>Current Time:</td><td>" + String(clocktime) + "</td></tr>\n";
|
response+="<tr><td>Current Time:</td><td>" + String(clocktime) + "</td></tr>\n";
|
||||||
response+="<tr><td>Boot Time:</td><td>" + String(homeSpan.webLog.bootTime) + "</td></tr>\n";
|
response+="<tr><td>Boot Time:</td><td>" + String(homeSpan.webLog.bootTime) + "</td></tr>\n";
|
||||||
response+="<tr><td>Reset Reason Code:</td><td>" + String(esp_reset_reason()) + "</td></tr>\n";
|
|
||||||
|
response+="<tr><td>Reset Reason:</td><td>";
|
||||||
|
switch(esp_reset_reason()) {
|
||||||
|
case ESP_RST_UNKNOWN:
|
||||||
|
response += "Cannot be determined";
|
||||||
|
break;
|
||||||
|
case ESP_RST_POWERON:
|
||||||
|
response += "Power-on event";
|
||||||
|
break;
|
||||||
|
case ESP_RST_EXT:
|
||||||
|
response += "External pin";
|
||||||
|
break;
|
||||||
|
case ESP_RST_SW:
|
||||||
|
response += "Software reboot via esp_restart";
|
||||||
|
break;
|
||||||
|
case ESP_RST_PANIC:
|
||||||
|
response += "Software Exception/Panic";
|
||||||
|
break;
|
||||||
|
case ESP_RST_INT_WDT:
|
||||||
|
response += "Interrupt watchdog";
|
||||||
|
break;
|
||||||
|
case ESP_RST_TASK_WDT:
|
||||||
|
response += "Task watchdog";
|
||||||
|
break;
|
||||||
|
case ESP_RST_WDT:
|
||||||
|
response += "Other watchdogs";
|
||||||
|
break;
|
||||||
|
case ESP_RST_DEEPSLEEP:
|
||||||
|
response += "Exiting deep sleep mode";
|
||||||
|
break;
|
||||||
|
case ESP_RST_BROWNOUT:
|
||||||
|
response += "Brownout";
|
||||||
|
break;
|
||||||
|
case ESP_RST_SDIO:
|
||||||
|
response += "SDIO";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
response += "Unknown Reset Code";
|
||||||
|
}
|
||||||
|
response+=" (" + String(esp_reset_reason()) + ")</td></tr>\n";
|
||||||
|
|
||||||
response+="<tr><td>WiFi Disconnects:</td><td>" + String(homeSpan.connected/2) + "</td></tr>\n";
|
response+="<tr><td>WiFi Disconnects:</td><td>" + String(homeSpan.connected/2) + "</td></tr>\n";
|
||||||
response+="<tr><td>WiFi Signal:</td><td>" + String(WiFi.RSSI()) + " dBm</td></tr>\n";
|
response+="<tr><td>WiFi Signal:</td><td>" + String(WiFi.RSSI()) + " dBm</td></tr>\n";
|
||||||
response+="<tr><td>WiFi Gateway:</td><td>" + WiFi.gatewayIP().toString() + "</td></tr>\n";
|
response+="<tr><td>WiFi Gateway:</td><td>" + WiFi.gatewayIP().toString() + "</td></tr>\n";
|
||||||
|
|
@ -1263,12 +1297,19 @@ int HAPClient::getStatusURL(){
|
||||||
response+="<tr><td>ESP-IDF Version:</td><td>" + String(ESP_IDF_VERSION_MAJOR) + "." + String(ESP_IDF_VERSION_MINOR) + "." + String(ESP_IDF_VERSION_PATCH) + "</td></tr>\n";
|
response+="<tr><td>ESP-IDF Version:</td><td>" + String(ESP_IDF_VERSION_MAJOR) + "." + String(ESP_IDF_VERSION_MINOR) + "." + String(ESP_IDF_VERSION_PATCH) + "</td></tr>\n";
|
||||||
response+="<tr><td>HomeSpan Version:</td><td>" + String(HOMESPAN_VERSION) + "</td></tr>\n";
|
response+="<tr><td>HomeSpan Version:</td><td>" + String(HOMESPAN_VERSION) + "</td></tr>\n";
|
||||||
response+="<tr><td>Sketch Version:</td><td>" + String(homeSpan.getSketchVersion()) + "</td></tr>\n";
|
response+="<tr><td>Sketch Version:</td><td>" + String(homeSpan.getSketchVersion()) + "</td></tr>\n";
|
||||||
|
response+="<tr><td>Sodium Version:</td><td>" + String(sodium_version_string()) + " Lib " + String(sodium_library_version_major()) + "." + String(sodium_library_version_minor()) +"</td></tr>\n";
|
||||||
|
|
||||||
|
char mbtlsv[64];
|
||||||
|
mbedtls_version_get_string_full(mbtlsv);
|
||||||
|
response+="<tr><td>MbedTLS Version:</td><td>" + String(mbtlsv) + "</td></tr>\n";
|
||||||
|
|
||||||
|
response+="<tr><td>HomeKit Status:</td><td>" + String(nAdminControllers()?"PAIRED":"NOT PAIRED") + "</td></tr>\n";
|
||||||
response+="<tr><td>Max Log Entries:</td><td>" + String(homeSpan.webLog.maxEntries) + "</td></tr>\n";
|
response+="<tr><td>Max Log Entries:</td><td>" + String(homeSpan.webLog.maxEntries) + "</td></tr>\n";
|
||||||
response+="</table>\n";
|
response+="</table>\n";
|
||||||
response+="<p></p>";
|
response+="<p></p>";
|
||||||
|
|
||||||
if(homeSpan.webLog.maxEntries>0){
|
if(homeSpan.webLog.maxEntries>0){
|
||||||
response+="<table><tr><th>Entry</th><th>Up Time</th><th>Log Time</th><th>Client</th><th>Message</th></tr>\n";
|
response+="<table class=tab2><tr><th>Entry</th><th>Up Time</th><th>Log Time</th><th>Client</th><th>Message</th></tr>\n";
|
||||||
int lastIndex=homeSpan.webLog.nEntries-homeSpan.webLog.maxEntries;
|
int lastIndex=homeSpan.webLog.nEntries-homeSpan.webLog.maxEntries;
|
||||||
if(lastIndex<0)
|
if(lastIndex<0)
|
||||||
lastIndex=0;
|
lastIndex=0;
|
||||||
|
|
@ -1388,7 +1429,7 @@ void HAPClient::tlvRespond(){
|
||||||
LOG2(client.remoteIP());
|
LOG2(client.remoteIP());
|
||||||
LOG2(" >>>>>>>>>>\n");
|
LOG2(" >>>>>>>>>>\n");
|
||||||
LOG2(body);
|
LOG2(body);
|
||||||
if(homeSpan.logLevel>1) tlv8.print();
|
tlv8.print(2);
|
||||||
|
|
||||||
if(!cPair){ // unverified, unencrypted session
|
if(!cPair){ // unverified, unencrypted session
|
||||||
client.print(body);
|
client.print(body);
|
||||||
|
|
@ -1412,17 +1453,17 @@ int HAPClient::receiveEncrypted(){
|
||||||
int n=buf[0]+buf[1]*256; // compute number of bytes expected in encoded message
|
int n=buf[0]+buf[1]*256; // compute number of bytes expected in encoded message
|
||||||
|
|
||||||
if(nBytes+n>MAX_HTTP){ // exceeded maximum number of bytes allowed in plaintext message
|
if(nBytes+n>MAX_HTTP){ // exceeded maximum number of bytes allowed in plaintext message
|
||||||
Serial.print("\n\n*** ERROR: Exceeded maximum HTTP message length\n\n");
|
LOG0("\n\n*** ERROR: Exceeded maximum HTTP message length\n\n");
|
||||||
return(0);
|
return(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(client.read(buf+2,n+16)!=n+16){ // read expected number of total bytes = n bytes in encoded message + 16 bytes for appended authentication tag
|
if(client.read(buf+2,n+16)!=n+16){ // read expected number of total bytes = n bytes in encoded message + 16 bytes for appended authentication tag
|
||||||
Serial.print("\n\n*** ERROR: Malformed encrypted message frame\n\n");
|
LOG0("\n\n*** ERROR: Malformed encrypted message frame\n\n");
|
||||||
return(0);
|
return(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(crypto_aead_chacha20poly1305_ietf_decrypt(httpBuf+nBytes, NULL, NULL, buf+2, n+16, buf, 2, c2aNonce.get(), c2aKey)==-1){
|
if(crypto_aead_chacha20poly1305_ietf_decrypt(httpBuf+nBytes, NULL, NULL, buf+2, n+16, buf, 2, c2aNonce.get(), c2aKey)==-1){
|
||||||
Serial.print("\n\n*** ERROR: Can't Decrypt Message\n\n");
|
LOG0("\n\n*** ERROR: Can't Decrypt Message\n\n");
|
||||||
return(0);
|
return(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1490,41 +1531,35 @@ void HAPClient::sendEncrypted(char *body, uint8_t *dataBuf, int dataLen){
|
||||||
/////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
/////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
void HAPClient::hexPrintColumn(uint8_t *buf, int n){
|
void HAPClient::hexPrintColumn(uint8_t *buf, int n, int minLogLevel){
|
||||||
|
|
||||||
char c[16];
|
if(homeSpan.logLevel<minLogLevel)
|
||||||
|
return;
|
||||||
|
|
||||||
for(int i=0;i<n;i++){
|
for(int i=0;i<n;i++)
|
||||||
sprintf(c,"%d) %02X",i,buf[i]);
|
Serial.printf("%d) %02X\n",i,buf[i]);
|
||||||
Serial.println(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////////////////////////////////
|
//////////////////////////////////////
|
||||||
|
|
||||||
void HAPClient::hexPrintRow(uint8_t *buf, int n){
|
void HAPClient::hexPrintRow(uint8_t *buf, int n, int minLogLevel){
|
||||||
|
|
||||||
char c[16];
|
if(homeSpan.logLevel<minLogLevel)
|
||||||
|
return;
|
||||||
for(int i=0;i<n;i++){
|
|
||||||
sprintf(c,"%02X",buf[i]);
|
|
||||||
Serial.print(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
for(int i=0;i<n;i++)
|
||||||
|
Serial.printf("%02X",buf[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////////////////////////////////
|
//////////////////////////////////////
|
||||||
|
|
||||||
void HAPClient::charPrintRow(uint8_t *buf, int n){
|
void HAPClient::charPrintRow(uint8_t *buf, int n, int minLogLevel){
|
||||||
|
|
||||||
char c[16];
|
if(homeSpan.logLevel<minLogLevel)
|
||||||
|
return;
|
||||||
|
|
||||||
for(int i=0;i<n;i++){
|
for(int i=0;i<n;i++)
|
||||||
sprintf(c,"%c",buf[i]);
|
Serial.printf("%c",buf[i]);
|
||||||
Serial.print(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////////////////////////////////
|
//////////////////////////////////////
|
||||||
|
|
@ -1561,8 +1596,7 @@ Controller *HAPClient::addController(uint8_t *id, uint8_t *ltpk, boolean admin){
|
||||||
memcpy(slot->LTPK,ltpk,32);
|
memcpy(slot->LTPK,ltpk,32);
|
||||||
slot->admin=admin;
|
slot->admin=admin;
|
||||||
LOG2("\n*** Updated Controller: ");
|
LOG2("\n*** Updated Controller: ");
|
||||||
if(homeSpan.logLevel>1)
|
charPrintRow(id,36,2);
|
||||||
charPrintRow(id,36);
|
|
||||||
LOG2(slot->admin?" (admin)\n\n":" (regular)\n\n");
|
LOG2(slot->admin?" (admin)\n\n":" (regular)\n\n");
|
||||||
return(slot);
|
return(slot);
|
||||||
}
|
}
|
||||||
|
|
@ -1573,8 +1607,7 @@ Controller *HAPClient::addController(uint8_t *id, uint8_t *ltpk, boolean admin){
|
||||||
memcpy(slot->LTPK,ltpk,32);
|
memcpy(slot->LTPK,ltpk,32);
|
||||||
slot->admin=admin;
|
slot->admin=admin;
|
||||||
LOG2("\n*** Added Controller: ");
|
LOG2("\n*** Added Controller: ");
|
||||||
if(homeSpan.logLevel>1)
|
charPrintRow(id,36,2);
|
||||||
charPrintRow(id,36);
|
|
||||||
LOG2(slot->admin?" (admin)\n\n":" (regular)\n\n");
|
LOG2(slot->admin?" (admin)\n\n":" (regular)\n\n");
|
||||||
return(slot);
|
return(slot);
|
||||||
}
|
}
|
||||||
|
|
@ -1611,8 +1644,7 @@ void HAPClient::removeController(uint8_t *id){
|
||||||
|
|
||||||
if((slot=findController(id))){ // remove controller if found
|
if((slot=findController(id))){ // remove controller if found
|
||||||
LOG2("\n***Removed Controller: ");
|
LOG2("\n***Removed Controller: ");
|
||||||
if(homeSpan.logLevel>1)
|
charPrintRow(id,36,2);
|
||||||
charPrintRow(id,36);
|
|
||||||
LOG2(slot->admin?" (admin)\n":" (regular)\n");
|
LOG2(slot->admin?" (admin)\n":" (regular)\n");
|
||||||
slot->allocated=false;
|
slot->allocated=false;
|
||||||
|
|
||||||
|
|
@ -1634,24 +1666,26 @@ void HAPClient::removeController(uint8_t *id){
|
||||||
|
|
||||||
//////////////////////////////////////
|
//////////////////////////////////////
|
||||||
|
|
||||||
void HAPClient::printControllers(){
|
void HAPClient::printControllers(int minLogLevel){
|
||||||
|
|
||||||
|
if(homeSpan.logLevel<minLogLevel)
|
||||||
|
return;
|
||||||
|
|
||||||
int n=0;
|
int n=0;
|
||||||
|
|
||||||
for(int i=0;i<MAX_CONTROLLERS;i++){ // loop over all controller slots
|
for(int i=0;i<MAX_CONTROLLERS;i++){ // loop over all controller slots
|
||||||
if(controllers[i].allocated){
|
if(controllers[i].allocated){
|
||||||
Serial.print("Paired Controller: ");
|
Serial.printf("Paired Controller: ");
|
||||||
charPrintRow(controllers[i].ID,36);
|
charPrintRow(controllers[i].ID,36);
|
||||||
Serial.print(controllers[i].admin?" (admin)":" (regular)");
|
Serial.printf("%s LTPK: ",controllers[i].admin?" (admin)":" (regular)");
|
||||||
Serial.print(" LTPK: ");
|
|
||||||
hexPrintRow(controllers[i].LTPK,32);
|
hexPrintRow(controllers[i].LTPK,32);
|
||||||
Serial.print("\n");
|
Serial.printf("\n");
|
||||||
n++;
|
n++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(n==0)
|
if(n==0)
|
||||||
Serial.print("No Paired Controllers\n");
|
Serial.printf("No Paired Controllers\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////////////////////////////////
|
//////////////////////////////////////
|
||||||
|
|
|
||||||
|
|
@ -131,9 +131,9 @@ struct HAPClient {
|
||||||
|
|
||||||
static void init(); // initialize HAP after start-up
|
static void init(); // initialize HAP after start-up
|
||||||
|
|
||||||
static void hexPrintColumn(uint8_t *buf, int n); // prints 'n' bytes of *buf as HEX, one byte per row. For diagnostics/debugging only
|
static void hexPrintColumn(uint8_t *buf, int n, int minLogLevel=0); // prints 'n' bytes of *buf as HEX, one byte per row, subject to specified minimum log level
|
||||||
static void hexPrintRow(uint8_t *buf, int n); // prints 'n' bytes of *buf as HEX, all on one row
|
static void hexPrintRow(uint8_t *buf, int n, int minLogLevel=0); // prints 'n' bytes of *buf as HEX, all on one row, subject to specified minimum log level
|
||||||
static void charPrintRow(uint8_t *buf, int n); // prints 'n' bytes of *buf as CHAR, all on one row
|
static void charPrintRow(uint8_t *buf, int n, int minLogLevel=0); // prints 'n' bytes of *buf as CHAR, all on one row, subject to specified minimum log level
|
||||||
|
|
||||||
static Controller *findController(uint8_t *id); // returns pointer to controller with mathching ID (or NULL if no match)
|
static Controller *findController(uint8_t *id); // returns pointer to controller with mathching ID (or NULL if no match)
|
||||||
static Controller *getFreeController(); // return pointer to next free controller slot (or NULL if no free slots)
|
static Controller *getFreeController(); // return pointer to next free controller slot (or NULL if no free slots)
|
||||||
|
|
@ -141,7 +141,7 @@ struct HAPClient {
|
||||||
static int nAdminControllers(); // returns number of admin Controllers stored
|
static int nAdminControllers(); // returns number of admin Controllers stored
|
||||||
static void removeControllers(); // removes all Controllers (sets allocated flags to false for all slots)
|
static void removeControllers(); // removes all Controllers (sets allocated flags to false for all slots)
|
||||||
static void removeController(uint8_t *id); // removes specific Controller. If no remaining admin Controllers, remove all others (if any) as per HAP requirements.
|
static void removeController(uint8_t *id); // removes specific Controller. If no remaining admin Controllers, remove all others (if any) as per HAP requirements.
|
||||||
static void printControllers(); // prints IDs of all allocated (paired) Controller
|
static void printControllers(int minLogLevel=0); // prints IDs of all allocated (paired) Controller, subject to specified minimum log level
|
||||||
static void checkNotifications(); // checks for Event Notifications and reports to controllers as needed (HAP Section 6.8)
|
static void checkNotifications(); // checks for Event Notifications and reports to controllers as needed (HAP Section 6.8)
|
||||||
static void checkTimedWrites(); // checks for expired Timed Write PIDs, and clears any found (HAP Section 6.7.2.4)
|
static void checkTimedWrites(); // checks for expired Timed Write PIDs, and clears any found (HAP Section 6.7.2.4)
|
||||||
static void eventNotify(SpanBuf *pObj, int nObj, int ignoreClient=-1); // transmits EVENT Notifications for nObj SpanBuf objects, pObj, with optional flag to ignore a specific client
|
static void eventNotify(SpanBuf *pObj, int nObj, int ignoreClient=-1); // transmits EVENT Notifications for nObj SpanBuf objects, pObj, with optional flag to ignore a specific client
|
||||||
|
|
|
||||||
596
src/HomeSpan.cpp
596
src/HomeSpan.cpp
File diff suppressed because it is too large
Load Diff
|
|
@ -110,8 +110,6 @@ struct SpanUserCommand;
|
||||||
|
|
||||||
extern Span homeSpan;
|
extern Span homeSpan;
|
||||||
|
|
||||||
#include "HAP.h"
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////
|
||||||
// INTERNAL HOMESPAN STRUCTURES - NOT FOR USER ACCESS //
|
// INTERNAL HOMESPAN STRUCTURES - NOT FOR USER ACCESS //
|
||||||
////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////
|
||||||
|
|
@ -150,7 +148,8 @@ struct SpanWebLog{ // optional web status/log data
|
||||||
boolean timeInit=false; // flag to indicate time has been initialized
|
boolean timeInit=false; // flag to indicate time has been initialized
|
||||||
char bootTime[33]="Unknown"; // boot time
|
char bootTime[33]="Unknown"; // boot time
|
||||||
String statusURL; // URL of status log
|
String statusURL; // URL of status log
|
||||||
uint32_t waitTime=10000; // number of milliseconds to wait for initial connection to time server
|
uint32_t waitTime=120000; // number of milliseconds to wait for initial connection to time server
|
||||||
|
String css=""; // optional user-defined style sheet for web log
|
||||||
|
|
||||||
struct log_t { // log entry type
|
struct log_t { // log entry type
|
||||||
uint64_t upTime; // number of seconds since booting
|
uint64_t upTime; // number of seconds since booting
|
||||||
|
|
@ -160,7 +159,7 @@ struct SpanWebLog{ // optional web status/log data
|
||||||
} *log=NULL; // array of log entries
|
} *log=NULL; // array of log entries
|
||||||
|
|
||||||
void init(uint16_t maxEntries, const char *serv, const char *tz, const char *url);
|
void init(uint16_t maxEntries, const char *serv, const char *tz, const char *url);
|
||||||
void initTime();
|
static void initTime(void *args);
|
||||||
void vLog(boolean sysMsg, const char *fmr, va_list ap);
|
void vLog(boolean sysMsg, const char *fmr, va_list ap);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -217,6 +216,7 @@ class Span{
|
||||||
char pairingCodeCommand[12]=""; // user-specified Pairing Code - only needed if Pairing Setup Code is specified in sketch using setPairingCode()
|
char pairingCodeCommand[12]=""; // user-specified Pairing Code - only needed if Pairing Setup Code is specified in sketch using setPairingCode()
|
||||||
String lastClientIP="0.0.0.0"; // IP address of last client accessing device through encrypted channel
|
String lastClientIP="0.0.0.0"; // IP address of last client accessing device through encrypted channel
|
||||||
boolean newCode; // flag indicating new application code has been loaded (based on keeping track of app SHA256)
|
boolean newCode; // flag indicating new application code has been loaded (based on keeping track of app SHA256)
|
||||||
|
boolean serialInputDisabled=false; // flag indiating that serial input is disabled
|
||||||
|
|
||||||
int connected=0; // WiFi connection status (increments upon each connect and disconnect)
|
int connected=0; // WiFi connection status (increments upon each connect and disconnect)
|
||||||
unsigned long waitTime=60000; // time to wait (in milliseconds) between WiFi connection attempts
|
unsigned long waitTime=60000; // time to wait (in milliseconds) between WiFi connection attempts
|
||||||
|
|
@ -224,7 +224,7 @@ class Span{
|
||||||
|
|
||||||
const char *defaultSetupCode=DEFAULT_SETUP_CODE; // Setup Code used for pairing
|
const char *defaultSetupCode=DEFAULT_SETUP_CODE; // Setup Code used for pairing
|
||||||
uint16_t autoOffLED=0; // automatic turn-off duration (in seconds) for Status LED
|
uint16_t autoOffLED=0; // automatic turn-off duration (in seconds) for Status LED
|
||||||
uint8_t logLevel=DEFAULT_LOG_LEVEL; // level for writing out log messages to serial monitor
|
int logLevel=DEFAULT_LOG_LEVEL; // level for writing out log messages to serial monitor
|
||||||
uint8_t maxConnections=CONFIG_LWIP_MAX_SOCKETS-2; // maximum number of allowed simultaneous HAP connections
|
uint8_t maxConnections=CONFIG_LWIP_MAX_SOCKETS-2; // maximum number of allowed simultaneous HAP connections
|
||||||
uint8_t requestedMaxCon=CONFIG_LWIP_MAX_SOCKETS-2; // requested maximum number of simultaneous HAP connections
|
uint8_t requestedMaxCon=CONFIG_LWIP_MAX_SOCKETS-2; // requested maximum 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
|
||||||
|
|
@ -263,7 +263,7 @@ class Span{
|
||||||
|
|
||||||
int sprintfAttributes(char *cBuf, int flags=GET_VALUE|GET_META|GET_PERMS|GET_TYPE|GET_DESC); // prints Attributes JSON database into buf, unless buf=NULL; return number of characters printed, excluding null terminator
|
int sprintfAttributes(char *cBuf, int flags=GET_VALUE|GET_META|GET_PERMS|GET_TYPE|GET_DESC); // prints Attributes JSON database into buf, unless buf=NULL; return number of characters printed, excluding null terminator
|
||||||
|
|
||||||
void prettyPrint(char *buf, int nsp=2); // print arbitrary JSON from buf to serial monitor, formatted with indentions of 'nsp' spaces
|
void prettyPrint(char *buf, int nsp=2, int minLogLevel=0); // print arbitrary JSON from buf to serial monitor, formatted with indentions of 'nsp' spaces, subject to specified minimum log level
|
||||||
SpanCharacteristic *find(uint32_t aid, int iid); // return Characteristic with matching aid and iid (else NULL if not found)
|
SpanCharacteristic *find(uint32_t aid, int iid); // return Characteristic with matching aid and iid (else NULL if not found)
|
||||||
int countCharacteristics(char *buf); // return number of characteristic objects referenced in PUT /characteristics JSON request
|
int countCharacteristics(char *buf); // return number of characteristic objects referenced in PUT /characteristics JSON request
|
||||||
int updateCharacteristics(char *buf, SpanBuf *pObj); // parses PUT /characteristics JSON request 'buf into 'pObj' and updates referenced characteristics; returns 1 on success, 0 on fail
|
int updateCharacteristics(char *buf, SpanBuf *pObj); // parses PUT /characteristics JSON request 'buf into 'pObj' and updates referenced characteristics; returns 1 on success, 0 on fail
|
||||||
|
|
@ -308,8 +308,10 @@ class Span{
|
||||||
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)
|
||||||
void setCommandTimeout(uint16_t nSec){comModeLife=nSec*1000;} // sets Command Mode Timeout (seconds)
|
void setCommandTimeout(uint16_t nSec){comModeLife=nSec*1000;} // sets Command Mode Timeout (seconds)
|
||||||
void setLogLevel(uint8_t level){logLevel=level;} // sets Log Level for log messages (0=baseline, 1=intermediate, 2=all)
|
void setLogLevel(int level){logLevel=level;} // sets Log Level for log messages (0=baseline, 1=intermediate, 2=all, -1=disable all serial input/output)
|
||||||
int getLogLevel(){return(logLevel);} // get Log Level
|
int getLogLevel(){return(logLevel);} // get Log Level
|
||||||
|
void setSerialInputDisable(boolean val){serialInputDisabled=val;} // sets whether serial input is disabled (true) or enabled (false)
|
||||||
|
boolean getSerialInputDisable(){return(serialInputDisabled);} // returns true if serial input is disabled, or false if serial input in enabled
|
||||||
void reserveSocketConnections(uint8_t n){maxConnections-=n;} // reserves n socket connections *not* to be used for HAP
|
void reserveSocketConnections(uint8_t n){maxConnections-=n;} // reserves n socket connections *not* to be used for HAP
|
||||||
void setHostNameSuffix(const char *suffix){hostNameSuffix=suffix;} // sets the hostName suffix to be used instead of the 6-byte AccessoryID
|
void setHostNameSuffix(const char *suffix){hostNameSuffix=suffix;} // sets the hostName suffix to be used instead of the 6-byte AccessoryID
|
||||||
void setPortNum(uint16_t port){tcpPortNum=port;} // sets the TCP port number to use for communications between HomeKit and HomeSpan
|
void setPortNum(uint16_t port){tcpPortNum=port;} // sets the TCP port number to use for communications between HomeKit and HomeSpan
|
||||||
|
|
@ -341,9 +343,11 @@ class Span{
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setWebLogCSS(const char *css){webLog.css="\n" + String(css) + "\n";}
|
||||||
|
|
||||||
void autoPoll(uint32_t stackSize=8192, uint32_t priority=1, uint32_t cpu=0){ // start pollTask()
|
void autoPoll(uint32_t stackSize=8192, uint32_t priority=1, uint32_t cpu=0){ // start pollTask()
|
||||||
xTaskCreateUniversal([](void *parms){for(;;)homeSpan.pollTask();}, "pollTask", stackSize, NULL, priority, &pollTaskHandle, cpu);
|
xTaskCreateUniversal([](void *parms){for(;;)homeSpan.pollTask();}, "pollTask", stackSize, NULL, priority, &pollTaskHandle, cpu);
|
||||||
Serial.printf("\n*** AutoPolling Task started with priority=%d\n\n",uxTaskPriorityGet(pollTaskHandle));
|
LOG0("\n*** AutoPolling Task started with priority=%d\n\n",uxTaskPriorityGet(pollTaskHandle));
|
||||||
}
|
}
|
||||||
|
|
||||||
void setTimeServerTimeout(uint32_t tSec){webLog.waitTime=tSec*1000;} // sets wait time (in seconds) for optional web log time server to connect
|
void setTimeServerTimeout(uint32_t tSec){webLog.waitTime=tSec*1000;} // sets wait time (in seconds) for optional web log time server to connect
|
||||||
|
|
@ -632,7 +636,7 @@ class SpanCharacteristic{
|
||||||
void setString(const char *val){
|
void setString(const char *val){
|
||||||
|
|
||||||
if((perms & EV) == 0){
|
if((perms & EV) == 0){
|
||||||
Serial.printf("\n*** WARNING: Attempt to update Characteristic::%s with setString() ignored. No NOTIFICATION permission on this characteristic\n\n",hapName);
|
LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setString() ignored. No NOTIFICATION permission on this characteristic\n\n",hapName);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -666,9 +670,9 @@ class SpanCharacteristic{
|
||||||
return(olen);
|
return(olen);
|
||||||
|
|
||||||
if(ret==MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL)
|
if(ret==MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL)
|
||||||
Serial.printf("\n*** WARNING: Can't decode Characteristic::%s with getData(). Destination buffer is too small (%d out of %d bytes needed)\n\n",hapName,len,olen);
|
LOG0("\n*** WARNING: Can't decode Characteristic::%s with getData(). Destination buffer is too small (%d out of %d bytes needed)\n\n",hapName,len,olen);
|
||||||
else if(ret==MBEDTLS_ERR_BASE64_INVALID_CHARACTER)
|
else if(ret==MBEDTLS_ERR_BASE64_INVALID_CHARACTER)
|
||||||
Serial.printf("\n*** WARNING: Can't decode Characteristic::%s with getData(). Data is not in base-64 format\n\n",hapName);
|
LOG0("\n*** WARNING: Can't decode Characteristic::%s with getData(). Data is not in base-64 format\n\n",hapName);
|
||||||
|
|
||||||
return(olen);
|
return(olen);
|
||||||
}
|
}
|
||||||
|
|
@ -684,9 +688,9 @@ class SpanCharacteristic{
|
||||||
return(olen);
|
return(olen);
|
||||||
|
|
||||||
if(ret==MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL)
|
if(ret==MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL)
|
||||||
Serial.printf("\n*** WARNING: Can't decode Characteristic::%s with getData(). Destination buffer is too small (%d out of %d bytes needed)\n\n",hapName,len,olen);
|
LOG0("\n*** WARNING: Can't decode Characteristic::%s with getData(). Destination buffer is too small (%d out of %d bytes needed)\n\n",hapName,len,olen);
|
||||||
else if(ret==MBEDTLS_ERR_BASE64_INVALID_CHARACTER)
|
else if(ret==MBEDTLS_ERR_BASE64_INVALID_CHARACTER)
|
||||||
Serial.printf("\n*** WARNING: Can't decode Characteristic::%s with getData(). Data is not in base-64 format\n\n",hapName);
|
LOG0("\n*** WARNING: Can't decode Characteristic::%s with getData(). Data is not in base-64 format\n\n",hapName);
|
||||||
|
|
||||||
return(olen);
|
return(olen);
|
||||||
}
|
}
|
||||||
|
|
@ -694,12 +698,12 @@ class SpanCharacteristic{
|
||||||
void setData(uint8_t *data, size_t len){
|
void setData(uint8_t *data, size_t len){
|
||||||
|
|
||||||
if((perms & EV) == 0){
|
if((perms & EV) == 0){
|
||||||
Serial.printf("\n*** WARNING: Attempt to update Characteristic::%s with setData() ignored. No NOTIFICATION permission on this characteristic\n\n",hapName);
|
LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setData() ignored. No NOTIFICATION permission on this characteristic\n\n",hapName);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(len<1){
|
if(len<1){
|
||||||
Serial.printf("\n*** WARNING: Attempt to update Characteristic::%s with setData() ignored. Size of data buffer must be greater than zero\n\n",hapName);
|
LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setData() ignored. Size of data buffer must be greater than zero\n\n",hapName);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -713,12 +717,12 @@ class SpanCharacteristic{
|
||||||
template <typename T> void setVal(T val, boolean notify=true){
|
template <typename T> void setVal(T val, boolean notify=true){
|
||||||
|
|
||||||
if((perms & EV) == 0){
|
if((perms & EV) == 0){
|
||||||
Serial.printf("\n*** WARNING: Attempt to update Characteristic::%s with setVal() ignored. No NOTIFICATION permission on this characteristic\n\n",hapName);
|
LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setVal() ignored. No NOTIFICATION permission on this characteristic\n\n",hapName);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(val < uvGet<T>(minValue) || val > uvGet<T>(maxValue)){
|
if(val < uvGet<T>(minValue) || val > uvGet<T>(maxValue)){
|
||||||
Serial.printf("\n*** WARNING: Attempt to update Characteristic::%s with setVal(%g) is out of range [%g,%g]. This may cause device to become non-reponsive!\n\n",
|
LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setVal(%g) is out of range [%g,%g]. This may cause device to become non-reponsive!\n\n",
|
||||||
hapName,(double)val,uvGet<double>(minValue),uvGet<double>(maxValue));
|
hapName,(double)val,uvGet<double>(minValue),uvGet<double>(maxValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -814,11 +818,11 @@ class SpanButton : public PushButton {
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
enum buttonType_t {
|
enum buttonType_t {
|
||||||
BUTTON,
|
HS_BUTTON,
|
||||||
TOGGLE
|
HS_TOGGLE
|
||||||
};
|
};
|
||||||
|
|
||||||
buttonType_t buttonType=BUTTON; // type of SpanButton
|
buttonType_t buttonType=HS_BUTTON; // type of SpanButton
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
|
@ -842,7 +846,7 @@ class SpanToggle : public SpanButton {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
SpanToggle(int pin, triggerType_t triggerType=TRIGGER_ON_LOW, uint16_t toggleTime=5) : SpanButton(pin,triggerType,toggleTime){buttonType=TOGGLE;};
|
SpanToggle(int pin, triggerType_t triggerType=TRIGGER_ON_LOW, uint16_t toggleTime=5) : SpanButton(pin,triggerType,toggleTime){buttonType=HS_TOGGLE;};
|
||||||
int position(){return(pressType);}
|
int position(){return(pressType);}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,33 +65,26 @@ void Network::serialConfigure(){
|
||||||
wifiData.ssid[0]='\0';
|
wifiData.ssid[0]='\0';
|
||||||
wifiData.pwd[0]='\0';
|
wifiData.pwd[0]='\0';
|
||||||
|
|
||||||
Serial.print("*** WiFi Setup - Scanning for Networks...\n\n");
|
LOG0("*** WiFi Setup - Scanning for Networks...\n\n");
|
||||||
|
|
||||||
scan(); // scan for networks
|
scan(); // scan for networks
|
||||||
|
|
||||||
for(int i=0;i<numSSID;i++){
|
for(int i=0;i<numSSID;i++)
|
||||||
Serial.print(" ");
|
LOG0(" %d) %s\n",i+1,ssidList[i]);
|
||||||
Serial.print(i+1);
|
|
||||||
Serial.print(") ");
|
|
||||||
Serial.print(ssidList[i]);
|
|
||||||
Serial.print("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
while(!strlen(wifiData.ssid)){
|
while(!strlen(wifiData.ssid)){
|
||||||
Serial.print("\n>>> WiFi SSID: ");
|
LOG0("\n>>> WiFi SSID: ");
|
||||||
readSerial(wifiData.ssid,MAX_SSID);
|
readSerial(wifiData.ssid,MAX_SSID);
|
||||||
if(atoi(wifiData.ssid)>0 && atoi(wifiData.ssid)<=numSSID){
|
if(atoi(wifiData.ssid)>0 && atoi(wifiData.ssid)<=numSSID){
|
||||||
strcpy(wifiData.ssid,ssidList[atoi(wifiData.ssid)-1]);
|
strcpy(wifiData.ssid,ssidList[atoi(wifiData.ssid)-1]);
|
||||||
}
|
}
|
||||||
Serial.print(wifiData.ssid);
|
LOG0("%s\n",wifiData.ssid);
|
||||||
Serial.print("\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
while(!strlen(wifiData.pwd)){
|
while(!strlen(wifiData.pwd)){
|
||||||
Serial.print(">>> WiFi PASS: ");
|
LOG0(">>> WiFi PASS: ");
|
||||||
readSerial(wifiData.pwd,MAX_PWD);
|
readSerial(wifiData.pwd,MAX_PWD);
|
||||||
Serial.print(mask(wifiData.pwd,2));
|
LOG0("%s\n",mask(wifiData.pwd,2).c_str());
|
||||||
Serial.print("\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
@ -110,25 +103,16 @@ boolean Network::allowedCode(char *s){
|
||||||
|
|
||||||
void Network::apConfigure(){
|
void Network::apConfigure(){
|
||||||
|
|
||||||
Serial.print("*** Starting Access Point: ");
|
LOG0("*** Starting Access Point: %s / %s\n",apSSID,apPassword);
|
||||||
Serial.print(apSSID);
|
|
||||||
Serial.print(" / ");
|
|
||||||
Serial.print(apPassword);
|
|
||||||
Serial.print("\n");
|
|
||||||
|
|
||||||
STATUS_UPDATE(start(LED_AP_STARTED),HS_AP_STARTED)
|
STATUS_UPDATE(start(LED_AP_STARTED),HS_AP_STARTED)
|
||||||
|
|
||||||
Serial.print("\nScanning for Networks...\n\n");
|
LOG0("\nScanning for Networks...\n\n");
|
||||||
|
|
||||||
scan(); // scan for networks
|
scan(); // scan for networks
|
||||||
|
|
||||||
for(int i=0;i<numSSID;i++){
|
for(int i=0;i<numSSID;i++)
|
||||||
Serial.print(" ");
|
LOG0(" %d) %s\n",i+1,ssidList[i]);
|
||||||
Serial.print(i+1);
|
|
||||||
Serial.print(") ");
|
|
||||||
Serial.print(ssidList[i]);
|
|
||||||
Serial.print("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
WiFiServer apServer(80);
|
WiFiServer apServer(80);
|
||||||
client=0;
|
client=0;
|
||||||
|
|
@ -148,12 +132,12 @@ void Network::apConfigure(){
|
||||||
alarmTimeOut=millis()+lifetime; // Access Point will shut down when alarmTimeOut is reached
|
alarmTimeOut=millis()+lifetime; // Access Point will shut down when alarmTimeOut is reached
|
||||||
apStatus=0; // status will be "timed out" unless changed
|
apStatus=0; // status will be "timed out" unless changed
|
||||||
|
|
||||||
Serial.print("\nReady.\n");
|
LOG0("\nReady.\n");
|
||||||
|
|
||||||
while(1){ // loop until we get timed out (which will be accelerated if save/cancel selected)
|
while(1){ // loop until we get timed out (which will be accelerated if save/cancel selected)
|
||||||
|
|
||||||
if(homeSpan.controlButton && homeSpan.controlButton->triggered(9999,3000)){
|
if(homeSpan.controlButton && homeSpan.controlButton->triggered(9999,3000)){
|
||||||
Serial.print("\n*** Access Point Terminated. Restarting...\n\n");
|
LOG0("\n*** Access Point Terminated. Restarting...\n\n");
|
||||||
STATUS_UPDATE(start(LED_ALERT),HS_AP_TERMINATED)
|
STATUS_UPDATE(start(LED_ALERT),HS_AP_TERMINATED)
|
||||||
homeSpan.controlButton->wait();
|
homeSpan.controlButton->wait();
|
||||||
homeSpan.reboot();
|
homeSpan.reboot();
|
||||||
|
|
@ -163,17 +147,14 @@ void Network::apConfigure(){
|
||||||
WiFi.softAPdisconnect(true); // terminate connections and shut down captive access point
|
WiFi.softAPdisconnect(true); // terminate connections and shut down captive access point
|
||||||
delay(100);
|
delay(100);
|
||||||
if(apStatus==1){
|
if(apStatus==1){
|
||||||
Serial.print("\n*** Access Point: Exiting and Saving Settings\n\n");
|
LOG0("\n*** Access Point: Exiting and Saving Settings\n\n");
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
if(apStatus==0){
|
if(apStatus==0)
|
||||||
Serial.print("\n*** Access Point: Timed Out (");
|
LOG0("\n*** Access Point: Timed Out (%ld seconds).",lifetime/1000);
|
||||||
Serial.print(lifetime/1000);
|
else
|
||||||
Serial.print(" seconds).");
|
LOG0("\n*** Access Point: Configuration Cancelled.");
|
||||||
} else {
|
LOG0(" Restarting...\n\n");
|
||||||
Serial.print("\n*** Access Point: Configuration Cancelled.");
|
|
||||||
}
|
|
||||||
Serial.print(" Restarting...\n\n");
|
|
||||||
STATUS_UPDATE(start(LED_ALERT),HS_AP_TERMINATED)
|
STATUS_UPDATE(start(LED_ALERT),HS_AP_TERMINATED)
|
||||||
homeSpan.reboot();
|
homeSpan.reboot();
|
||||||
}
|
}
|
||||||
|
|
@ -202,7 +183,7 @@ void Network::apConfigure(){
|
||||||
|
|
||||||
if(nBytes>MAX_HTTP){ // exceeded maximum number of bytes allowed
|
if(nBytes>MAX_HTTP){ // exceeded maximum number of bytes allowed
|
||||||
badRequestError();
|
badRequestError();
|
||||||
Serial.print("\n*** ERROR: Exceeded maximum HTTP message length\n\n");
|
LOG0("\n*** ERROR: Exceeded maximum HTTP message length\n\n");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -212,7 +193,7 @@ void Network::apConfigure(){
|
||||||
|
|
||||||
if(!(p=strstr((char *)httpBuf,"\r\n\r\n"))){
|
if(!(p=strstr((char *)httpBuf,"\r\n\r\n"))){
|
||||||
badRequestError();
|
badRequestError();
|
||||||
Serial.print("\n*** ERROR: Malformed HTTP request (can't find blank line indicating end of BODY)\n\n");
|
LOG0("\n*** ERROR: Malformed HTTP request (can't find blank line indicating end of BODY)\n\n");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -224,7 +205,7 @@ void Network::apConfigure(){
|
||||||
cLen=atoi(p+16);
|
cLen=atoi(p+16);
|
||||||
if(nBytes!=strlen(body)+4+cLen){
|
if(nBytes!=strlen(body)+4+cLen){
|
||||||
badRequestError();
|
badRequestError();
|
||||||
Serial.print("\n*** ERROR: Malformed HTTP request (Content-Length plus Body Length does not equal total number of bytes read)\n\n");
|
LOG0("\n*** ERROR: Malformed HTTP request (Content-Length plus Body Length does not equal total number of bytes read)\n\n");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
11
src/SRP.cpp
11
src/SRP.cpp
|
|
@ -266,16 +266,17 @@ int SRP6A::writeTLV(kTLVType tag, mbedtls_mpi *mpi){
|
||||||
|
|
||||||
//////////////////////////////////////
|
//////////////////////////////////////
|
||||||
|
|
||||||
void SRP6A::print(mbedtls_mpi *mpi){
|
void SRP6A::print(mbedtls_mpi *mpi, int minLogLevel){
|
||||||
|
|
||||||
|
if(homeSpan.getLogLevel()<minLogLevel)
|
||||||
|
return;
|
||||||
|
|
||||||
char sBuf[2000];
|
char sBuf[2000];
|
||||||
size_t sLen;
|
size_t sLen;
|
||||||
|
|
||||||
mbedtls_mpi_write_string(mpi,16,sBuf,2000,&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.printf("%d %s\n",(sLen-1)/2,sBuf); // subtract 1 for null-terminator, and then divide by 2 to get number of bytes (e.g. 4F = 2 characters, but represents just one mpi byte)
|
||||||
Serial.print(" ");
|
|
||||||
Serial.println(sBuf);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////////////////////////////////
|
//////////////////////////////////////
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,6 @@ struct SRP6A {
|
||||||
int verifyProof(); // verify M1 SRP6A Proof received from HAP client (return 1 on success, 0 on failure)
|
int verifyProof(); // verify M1 SRP6A Proof received from HAP client (return 1 on success, 0 on failure)
|
||||||
void createProof(); // create M2 server-side SRP6A Proof based on M1 as received from HAP Client
|
void createProof(); // create M2 server-side SRP6A Proof based on M1 as received from HAP Client
|
||||||
|
|
||||||
void print(mbedtls_mpi *mpi); // prints size of mpi (in bytes), followed by the mpi itself (as a hex charcter string) - for diagnostic purposes only
|
void print(mbedtls_mpi *mpi, int minLogLevel=0); // prints size of mpi (in bytes), followed by the mpi itself (as a hex charcter string), subject to specified minimum log level
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -35,8 +35,8 @@
|
||||||
// HomeSpan Version //
|
// HomeSpan Version //
|
||||||
|
|
||||||
#define HS_MAJOR 1
|
#define HS_MAJOR 1
|
||||||
#define HS_MINOR 7
|
#define HS_MINOR 8
|
||||||
#define HS_PATCH 2
|
#define HS_PATCH 0
|
||||||
|
|
||||||
#define STRINGIFY(x) _STR(x)
|
#define STRINGIFY(x) _STR(x)
|
||||||
#define _STR(x) #x
|
#define _STR(x) #x
|
||||||
|
|
@ -104,11 +104,11 @@
|
||||||
// Message Log Level Control Macros //
|
// Message Log Level Control Macros //
|
||||||
// 0=Minimal, 1=Informative, 2=All //
|
// 0=Minimal, 1=Informative, 2=All //
|
||||||
|
|
||||||
#define LOG0(format,...) Serial.print ##__VA_OPT__(f)(format __VA_OPT__(,) __VA_ARGS__)
|
#define LOG0(format,...) do{ if(homeSpan.getLogLevel()>=0)Serial.print ##__VA_OPT__(f)(format __VA_OPT__(,) __VA_ARGS__); }while(0)
|
||||||
#define LOG1(format,...) if(homeSpan.getLogLevel()>0)Serial.print ##__VA_OPT__(f)(format __VA_OPT__(,) __VA_ARGS__)
|
#define LOG1(format,...) do{ if(homeSpan.getLogLevel()>=1)Serial.print ##__VA_OPT__(f)(format __VA_OPT__(,) __VA_ARGS__); }while(0)
|
||||||
#define LOG2(format,...) if(homeSpan.getLogLevel()>1)Serial.print ##__VA_OPT__(f)(format __VA_OPT__(,) __VA_ARGS__)
|
#define LOG2(format,...) do{ if(homeSpan.getLogLevel()>=2)Serial.print ##__VA_OPT__(f)(format __VA_OPT__(,) __VA_ARGS__); }while(0)
|
||||||
|
|
||||||
#define WEBLOG(format,...) homeSpan.addWebLog(false, format __VA_OPT__(,) __VA_ARGS__)
|
#define WEBLOG(format,...) homeSpan.addWebLog(false, format __VA_OPT__(,) __VA_ARGS__);
|
||||||
|
|
||||||
//////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////
|
||||||
// Types of Accessory Categories //
|
// Types of Accessory Categories //
|
||||||
|
|
|
||||||
22
src/TLV.h
22
src/TLV.h
|
|
@ -56,7 +56,7 @@ public:
|
||||||
uint8_t *buf(tagType tag); // returns VAL Buffer for TLV with matching TAG (or NULL if no match)
|
uint8_t *buf(tagType tag); // returns VAL Buffer for TLV with matching TAG (or NULL if no match)
|
||||||
uint8_t *buf(tagType tag, int len); // set length and returns VAL Buffer for TLV with matching TAG (or NULL if no match or if LEN>MAX)
|
uint8_t *buf(tagType tag, int len); // set length and returns VAL Buffer for TLV with matching TAG (or NULL if no match or if LEN>MAX)
|
||||||
int len(tagType tag); // returns LEN for TLV matching TAG (or 0 if TAG is found but LEN not yet set; -1 if no match at all)
|
int len(tagType tag); // returns LEN for TLV matching TAG (or 0 if TAG is found but LEN not yet set; -1 if no match at all)
|
||||||
void print(); // prints all defined TLVs (those with length>0). For diagnostics/debugging only
|
void print(int minLogLevel=0); // prints all defined TLVs (those with length>0), subject to specified minimum log level
|
||||||
int unpack(uint8_t *tlvBuf, int nBytes); // unpacks nBytes of TLV content from single byte buffer into individual TLV records (return 1 on success, 0 if fail)
|
int unpack(uint8_t *tlvBuf, int nBytes); // unpacks nBytes of TLV content from single byte buffer into individual TLV records (return 1 on success, 0 if fail)
|
||||||
int pack(uint8_t *tlvBuf); // if tlvBuf!=NULL, packs all defined TLV records (LEN>0) into a single byte buffer, spitting large TLVs into separate 255-byte chunks. Returns number of bytes (that would be) stored in buffer
|
int pack(uint8_t *tlvBuf); // if tlvBuf!=NULL, packs all defined TLV records (LEN>0) into a single byte buffer, spitting large TLVs into separate 255-byte chunks. Returns number of bytes (that would be) stored in buffer
|
||||||
int pack_old(uint8_t *buf); // packs all defined TLV records (LEN>0) into a single byte buffer, spitting large TLVs into separate 255-byte records. Returns number of bytes stored in buffer
|
int pack_old(uint8_t *buf); // packs all defined TLV records (LEN>0) into a single byte buffer, spitting large TLVs into separate 255-byte records. Returns number of bytes stored in buffer
|
||||||
|
|
@ -192,24 +192,20 @@ uint8_t *TLV<tagType, maxTags>::buf(tagType tag, int len){
|
||||||
// TLV print()
|
// TLV print()
|
||||||
|
|
||||||
template<class tagType, int maxTags>
|
template<class tagType, int maxTags>
|
||||||
void TLV<tagType, maxTags>::print(){
|
void TLV<tagType, maxTags>::print(int minLogLevel){
|
||||||
|
|
||||||
char buf[3];
|
|
||||||
|
|
||||||
|
if(homeSpan.getLogLevel()<minLogLevel)
|
||||||
|
return;
|
||||||
|
|
||||||
for(int i=0;i<numTags;i++){
|
for(int i=0;i<numTags;i++){
|
||||||
|
|
||||||
if(tlv[i].len>0){
|
if(tlv[i].len>0){
|
||||||
Serial.print(tlv[i].name);
|
Serial.printf("%s(%d) ",tlv[i].name,tlv[i].len);
|
||||||
Serial.print("(");
|
|
||||||
Serial.print(tlv[i].len);
|
|
||||||
Serial.print(") ");
|
|
||||||
|
|
||||||
for(int j=0;j<tlv[i].len;j++){
|
for(int j=0;j<tlv[i].len;j++)
|
||||||
sprintf(buf,"%02X",tlv[i].val[j]);
|
Serial.printf("%02X",tlv[i].val[j]);
|
||||||
Serial.print(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
Serial.print("\n");
|
Serial.printf("\n");
|
||||||
|
|
||||||
} // len>0
|
} // len>0
|
||||||
} // loop over all TLVs
|
} // loop over all TLVs
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@
|
||||||
********************************************************************************/
|
********************************************************************************/
|
||||||
|
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
|
#include "HomeSpan.h"
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
|
|
@ -39,6 +40,12 @@
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
char *Utils::readSerial(char *c, int max){
|
char *Utils::readSerial(char *c, int max){
|
||||||
|
|
||||||
|
if(homeSpan.getSerialInputDisable()){
|
||||||
|
c[0]='\0';
|
||||||
|
return(c);
|
||||||
|
}
|
||||||
|
|
||||||
int i=0;
|
int i=0;
|
||||||
char buf;
|
char buf;
|
||||||
|
|
||||||
|
|
@ -104,10 +111,10 @@ PushButton::PushButton(int pin, triggerType_t triggerType){
|
||||||
threshold/=calibCount;
|
threshold/=calibCount;
|
||||||
#if SOC_TOUCH_VERSION_1
|
#if SOC_TOUCH_VERSION_1
|
||||||
threshold/=2;
|
threshold/=2;
|
||||||
Serial.printf("Touch Sensor at pin=%d used for calibration. Triggers when sensor reading < %d.\n",pin,threshold);
|
LOG0("Touch Sensor at pin=%d used for calibration. Triggers when sensor reading < %d.\n",pin,threshold);
|
||||||
#elif SOC_TOUCH_VERSION_2
|
#elif SOC_TOUCH_VERSION_2
|
||||||
threshold*=2;
|
threshold*=2;
|
||||||
Serial.printf("Touch Sensor at pin=%d used for calibration. Triggers when sensor reading > %d.\n",pin,threshold);
|
LOG0("Touch Sensor at pin=%d used for calibration. Triggers when sensor reading > %d.\n",pin,threshold);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*********************************************************************************
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-2023 Gregg E. Berman
|
||||||
|
*
|
||||||
|
* https://github.com/HomeSpan/HomeSpan
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*
|
||||||
|
********************************************************************************/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../src/extras/StepperControl.h"
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*********************************************************************************
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-2022 Gregg E. Berman
|
||||||
|
*
|
||||||
|
* https://github.com/HomeSpan/HomeSpan
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*
|
||||||
|
********************************************************************************/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../src/extras/Stepper_A3967.h"
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*********************************************************************************
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-2022 Gregg E. Berman
|
||||||
|
*
|
||||||
|
* https://github.com/HomeSpan/HomeSpan
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*
|
||||||
|
********************************************************************************/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../src/extras/Stepper_TB6612.h"
|
||||||
75
src/src.ino
75
src/src.ino
|
|
@ -25,47 +25,68 @@
|
||||||
*
|
*
|
||||||
********************************************************************************/
|
********************************************************************************/
|
||||||
|
|
||||||
// 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 "HomeSpan.h"
|
#include "HomeSpan.h"
|
||||||
|
|
||||||
CUSTOM_CHAR(CharFloat, 00000001-0001-0001-0001-46637266EA00, PR+PW+EV, FLOAT, 0, 0, 100, false);
|
struct LED_Service : Service::LightBulb {
|
||||||
CUSTOM_CHAR(CharUInt8, 00000009-0001-0001-0001-46637266EA00, PR+PW+EV, UINT8, 0, 0, 100, false);
|
|
||||||
CUSTOM_CHAR(CharUInt16, 00000016-0001-0001-0001-46637266EA00, PR+PW+EV, UINT16, 0, 0, 100, false);
|
int ledPin;
|
||||||
CUSTOM_CHAR(CharUInt32, 00000032-0001-0001-0001-46637266EA00, PR+PW+EV, UINT32, 0, 0, 100, false);
|
SpanCharacteristic *power;
|
||||||
CUSTOM_CHAR(CharInt, 00000002-0001-0001-0001-46637266EA00, PR+PW+EV, INT, 0, 0, 100, false);
|
|
||||||
|
LED_Service(int ledPin) : Service::LightBulb(){
|
||||||
|
power=new Characteristic::On();
|
||||||
|
this->ledPin=ledPin;
|
||||||
|
pinMode(ledPin,OUTPUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean update(){
|
||||||
|
digitalWrite(ledPin,power->getNewVal());
|
||||||
|
return(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
//////////////////////////////////////
|
||||||
|
|
||||||
|
struct invertedLED : Blinkable { // create a child class derived from Blinkable
|
||||||
|
|
||||||
|
int pin; // variable to store the pin number
|
||||||
|
|
||||||
|
invertedLED(int pin) : pin{pin} { // constructor that initializes the pin parameter
|
||||||
|
pinMode(pin,OUTPUT); // set the pin to OUTPUT
|
||||||
|
digitalWrite(pin,HIGH); // set pin HIGH (which is off for an inverted LED)
|
||||||
|
}
|
||||||
|
|
||||||
|
void on() override { digitalWrite(pin,LOW); } // required function on() - sets pin LOW
|
||||||
|
void off() override { digitalWrite(pin,HIGH); } // required function off() - sets pin HIGH
|
||||||
|
int getPin() override { return(pin); } // required function getPin() - returns pin number
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
//////////////////////////////////////
|
//////////////////////////////////////
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
|
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
|
|
||||||
homeSpan.setLogLevel(1);
|
// homeSpan.setLogLevel(-1);
|
||||||
|
// homeSpan.setSerialInputDisable(true);
|
||||||
|
homeSpan.enableOTA();
|
||||||
|
|
||||||
homeSpan.begin(Category::Other,"HomeSpan Test");
|
homeSpan.setStatusDevice(new invertedLED(13)); // set Status LED to be a new Blinkable device attached to pin 13
|
||||||
|
homeSpan.setStatusAutoOff(30);
|
||||||
|
|
||||||
new SpanAccessory();
|
homeSpan.begin(Category::Lighting,"HomeSpan LED");
|
||||||
new Service::AccessoryInformation();
|
|
||||||
new Characteristic::Identify();
|
|
||||||
new Service::LightBulb();
|
|
||||||
new Characteristic::On();
|
|
||||||
|
|
||||||
(new Characteristic::CharFloat())->setValidValues(5,0,1,2,6,7,8);
|
|
||||||
(new Characteristic::CharUInt8())->setValidValues(5,0,1,2,6,7,8);
|
|
||||||
(new Characteristic::CharUInt16())->setValidValues(5,0,1<<8,1<<16,0xFFFFFFFF,-1);
|
|
||||||
(new Characteristic::CharUInt32())->setValidValues(5,0,1<<8,1<<16,0xFFFFFFFF,-1);
|
|
||||||
(new Characteristic::CharInt())->setValidValues(5,0,255,2000000000,-2000000000,-1)->setValidValues(1,2);
|
|
||||||
|
|
||||||
} // end of setup()
|
new SpanAccessory();
|
||||||
|
new Service::AccessoryInformation();
|
||||||
|
new Characteristic::Identify();
|
||||||
|
new LED_Service(13);
|
||||||
|
}
|
||||||
|
|
||||||
//////////////////////////////////////
|
//////////////////////////////////////
|
||||||
|
|
||||||
void loop(){
|
void loop(){
|
||||||
|
|
||||||
homeSpan.poll();
|
homeSpan.poll();
|
||||||
|
}
|
||||||
} // end of loop()
|
|
||||||
|
|
||||||
//////////////////////////////////////
|
//////////////////////////////////////
|
||||||
|
|
|
||||||
|
|
@ -134,7 +134,7 @@ void Blinker::check(){
|
||||||
if(pauseDuration==0 || isPaused || (millis()-pauseTime)<pauseDuration)
|
if(pauseDuration==0 || isPaused || (millis()-pauseTime)<pauseDuration)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Serial.print("Pausing LED\n");
|
ESP_LOGI(BLINKER_TAG,"Pausing LED");
|
||||||
off();
|
off();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,8 @@
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <driver/timer.h>
|
#include <driver/timer.h>
|
||||||
|
|
||||||
|
[[maybe_unused]] static const char* BLINKER_TAG = "Blinker";
|
||||||
|
|
||||||
////////////////////////////////
|
////////////////////////////////
|
||||||
// Blinkable Interface //
|
// Blinkable Interface //
|
||||||
////////////////////////////////
|
////////////////////////////////
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,8 @@
|
||||||
#include "PwmPin.h"
|
#include "PwmPin.h"
|
||||||
#include "Blinker.h"
|
#include "Blinker.h"
|
||||||
|
|
||||||
|
[[maybe_unused]] static const char* PIXEL_TAG = "Pixel";
|
||||||
|
|
||||||
////////////////////////////////////////////
|
////////////////////////////////////////////
|
||||||
// Single-Wire RGB/RGBW NeoPixels //
|
// Single-Wire RGB/RGBW NeoPixels //
|
||||||
////////////////////////////////////////////
|
////////////////////////////////////////////
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ LedC::LedC(uint8_t pin, uint16_t freq, boolean invert){
|
||||||
|
|
||||||
timerList[nTimer][nMode]->duty_resolution=(ledc_timer_bit_t)res;
|
timerList[nTimer][nMode]->duty_resolution=(ledc_timer_bit_t)res;
|
||||||
if(ledc_timer_config(timerList[nTimer][nMode])!=0){
|
if(ledc_timer_config(timerList[nTimer][nMode])!=0){
|
||||||
Serial.printf("\n*** ERROR: Frequency=%d Hz is out of allowed range ---",freq);
|
ESP_LOGE(PWM_TAG,"Frequency=%d Hz is out of allowed range ---",freq);
|
||||||
delete timerList[nTimer][nMode];
|
delete timerList[nTimer][nMode];
|
||||||
timerList[nTimer][nMode]=NULL;
|
timerList[nTimer][nMode]=NULL;
|
||||||
return;
|
return;
|
||||||
|
|
@ -82,10 +82,12 @@ LedC::LedC(uint8_t pin, uint16_t freq, boolean invert){
|
||||||
|
|
||||||
LedPin::LedPin(uint8_t pin, float level, uint16_t freq, boolean invert) : LedC(pin, freq, invert){
|
LedPin::LedPin(uint8_t pin, float level, uint16_t freq, boolean invert) : LedC(pin, freq, invert){
|
||||||
|
|
||||||
if(!channel)
|
if(!channel){
|
||||||
Serial.printf("\n*** ERROR: Can't create LedPin(%d) - no open PWM channels and/or Timers ***\n\n",pin);
|
ESP_LOGE(PWM_TAG,"Can't create LedPin(%d) - no open PWM channels and/or Timers",pin);
|
||||||
|
return;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
Serial.printf("LedPin=%d: mode=%d, channel=%d, timer=%d, freq=%d Hz, resolution=%d bits %s\n",
|
ESP_LOGI(PWM_TAG,"LedPin=%d: mode=%d, channel=%d, timer=%d, freq=%d Hz, resolution=%d bits %s",
|
||||||
channel->gpio_num,
|
channel->gpio_num,
|
||||||
channel->speed_mode,
|
channel->speed_mode,
|
||||||
channel->channel,
|
channel->channel,
|
||||||
|
|
@ -222,9 +224,9 @@ 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) : LedC(pin, 50){
|
ServoPin::ServoPin(uint8_t pin, double initDegrees, uint16_t minMicros, uint16_t maxMicros, double minDegrees, double maxDegrees) : LedC(pin, 50){
|
||||||
|
|
||||||
if(!channel)
|
if(!channel)
|
||||||
Serial.printf("\n*** ERROR: Can't create ServoPin(%d) - no open PWM channels and/or Timers ***\n\n",pin);
|
ESP_LOGE(PWM_TAG,"Can't create ServoPin(%d) - no open PWM channels and/or Timers",pin);
|
||||||
else
|
else
|
||||||
Serial.printf("ServoPin=%d: mode=%d channel=%d, timer=%d, freq=%d Hz, resolution=%d bits\n",
|
ESP_LOGI(PWM_TAG,"ServoPin=%d: mode=%d channel=%d, timer=%d, freq=%d Hz, resolution=%d bits",
|
||||||
channel->gpio_num,
|
channel->gpio_num,
|
||||||
channel->speed_mode,
|
channel->speed_mode,
|
||||||
channel->channel,
|
channel->channel,
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,8 @@
|
||||||
#include <driver/ledc.h>
|
#include <driver/ledc.h>
|
||||||
#include "Blinker.h"
|
#include "Blinker.h"
|
||||||
|
|
||||||
|
[[maybe_unused]] static const char* PWM_TAG = "PwmPin";
|
||||||
|
|
||||||
#define DEFAULT_PWM_FREQ 5000
|
#define DEFAULT_PWM_FREQ 5000
|
||||||
|
|
||||||
/////////////////////////////////////
|
/////////////////////////////////////
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ RFControl::RFControl(uint8_t pin, boolean refClock, boolean installDriver){
|
||||||
#else
|
#else
|
||||||
if(nChannels==RMT_CHANNEL_MAX){
|
if(nChannels==RMT_CHANNEL_MAX){
|
||||||
#endif
|
#endif
|
||||||
Serial.printf("\n*** ERROR: Can't create RFControl(%d) - no open channels ***\n\n",pin);
|
ESP_LOGE(RFControl_TAG,"Can't create RFControl(%d) - no open channels",pin);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -139,17 +139,17 @@ void RFControl::enableCarrier(uint32_t freq, float duty){
|
||||||
uint32_t lowTime=period*(1.0-duty)+0.5;
|
uint32_t lowTime=period*(1.0-duty)+0.5;
|
||||||
|
|
||||||
if(highTime>0xFFFF || lowTime>0xFFFF){
|
if(highTime>0xFFFF || lowTime>0xFFFF){
|
||||||
Serial.printf("\n*** ERROR: Can't enable carrier frequency=%d Hz for RF Control pin=%d, duty=%0.2f. Frequency is too low!\n\n",freq,config->gpio_num,duty);
|
ESP_LOGE(RFControl_TAG,"Can't enable carrier frequency=%d Hz for RF Control pin=%d, duty=%0.2f. Frequency is too low!",freq,config->gpio_num,duty);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(highTime==0){
|
if(highTime==0){
|
||||||
Serial.printf("\n*** ERROR: Can't enable carrier frequency=%d Hz for RF Control pin=%d, duty=%0.2f. Duty is too low or frequency is too high!\n\n",freq,config->gpio_num,duty);
|
ESP_LOGE(RFControl_TAG,"Can't enable carrier frequency=%d Hz for RF Control pin=%d, duty=%0.2f. Duty is too low or frequency is too high!",freq,config->gpio_num,duty);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(lowTime==0){
|
if(lowTime==0){
|
||||||
Serial.printf("\n*** ERROR: Can't enable carrier frequency=%d Hz for RF Control pin=%d, duty=%0.2f. Duty is too high or frequency is too high!\n\n",freq,config->gpio_num,duty);
|
ESP_LOGE(RFControl_TAG,"Can't enable carrier frequency=%d Hz for RF Control pin=%d, duty=%0.2f. Duty is too high or frequency is too high!",freq,config->gpio_num,duty);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,8 @@
|
||||||
#include "driver/rmt.h"
|
#include "driver/rmt.h"
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
[[maybe_unused]] static const char* RFControl_TAG = "RFControl";
|
||||||
|
|
||||||
using std::vector;
|
using std::vector;
|
||||||
|
|
||||||
class RFControl {
|
class RFControl {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,205 @@
|
||||||
|
/*********************************************************************************
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2023 Gregg E. Berman
|
||||||
|
*
|
||||||
|
* https://github.com/HomeSpan/HomeSpan
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*
|
||||||
|
********************************************************************************/
|
||||||
|
|
||||||
|
#include "StepperControl.h"
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
|
||||||
|
StepperControl::StepperControl(uint32_t priority, uint32_t cpu){
|
||||||
|
|
||||||
|
upLinkQueue = xQueueCreate(1,sizeof(upLink_t));
|
||||||
|
downLinkQueue = xQueueCreate(1,sizeof(downLink_t));
|
||||||
|
xTaskCreateUniversal(motorTask, "motorTaskHandle", 8096, this, priority, &motorTaskHandle, cpu);
|
||||||
|
ESP_LOGI(STEPPER_TAG,"motor task started with priority %d on cpu %d",priority,cpu);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
|
||||||
|
StepperControl *StepperControl::setAccel(float accelSize, float accelSteps){
|
||||||
|
if(accelSize<0.0){
|
||||||
|
ESP_LOGE(STEPPER_TAG,"accelSize cannot be less than 0.0");
|
||||||
|
return(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(accelSteps<1.0){
|
||||||
|
ESP_LOGE(STEPPER_TAG,"accelSteps cannot be less than 1.0");
|
||||||
|
return(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->accelSize=accelSize;
|
||||||
|
this->accelSteps=accelSteps;
|
||||||
|
return(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
|
||||||
|
void StepperControl::move(int nSteps, uint32_t msDelay, endAction_t endAction){
|
||||||
|
if(msDelay==0){
|
||||||
|
ESP_LOGE(STEPPER_TAG,"msDelay must be greater than zero");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
upLink_t upLinkData = { .nSteps=nSteps, .msDelay=msDelay, .action=MOVE, .endAction=endAction };
|
||||||
|
xQueueOverwrite(upLinkQueue,&upLinkData);
|
||||||
|
waitForAck();
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
|
||||||
|
void StepperControl::moveTo(int nPosition, uint32_t msDelay, endAction_t endAction){
|
||||||
|
if(msDelay==0){
|
||||||
|
ESP_LOGE(STEPPER_TAG,"msDelay must be greater than zero");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
upLink_t upLinkData = { .nSteps=nPosition, .msDelay=msDelay, .action=MOVETO, .endAction=endAction };
|
||||||
|
xQueueOverwrite(upLinkQueue,&upLinkData);
|
||||||
|
waitForAck();
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
|
||||||
|
int StepperControl::stepsRemaining(){
|
||||||
|
xQueuePeek(downLinkQueue,&downLinkData,0);
|
||||||
|
return(downLinkData.stepsRemaining);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
|
||||||
|
int StepperControl::position(){
|
||||||
|
xQueuePeek(downLinkQueue,&downLinkData,0);
|
||||||
|
return(downLinkData.position);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
|
||||||
|
void StepperControl::setPosition(int pos){
|
||||||
|
if(!stepsRemaining()){
|
||||||
|
upLink_t upLinkData = { .nSteps=pos, .msDelay=10, .action=SET_POSITION, .endAction=NONE };
|
||||||
|
xQueueOverwrite(upLinkQueue,&upLinkData);
|
||||||
|
waitForAck();
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(STEPPER_TAG,"can't set position while motor is running");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
|
||||||
|
void StepperControl::waitForAck(){
|
||||||
|
downLinkData.ack=false;
|
||||||
|
while(downLinkData.ack==false)
|
||||||
|
xQueueReceive(downLinkQueue,&downLinkData,0);
|
||||||
|
};
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
|
||||||
|
StepperControl *StepperControl::brake(){
|
||||||
|
move(0,10,BRAKE);
|
||||||
|
while(stepsRemaining());
|
||||||
|
return(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
|
||||||
|
StepperControl *StepperControl::disable(){
|
||||||
|
move(0,10,DISABLE);
|
||||||
|
while(stepsRemaining());
|
||||||
|
return(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
|
||||||
|
StepperControl *StepperControl::enable(){
|
||||||
|
move(0,10);
|
||||||
|
while(stepsRemaining());
|
||||||
|
return(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
|
||||||
|
void StepperControl::motorTask(void *args){
|
||||||
|
StepperControl *motor = (StepperControl *)args;
|
||||||
|
upLink_t upLinkData = { .nSteps=0, .msDelay=10, .action=MOVE, .endAction=NONE };
|
||||||
|
downLink_t downLinkData;
|
||||||
|
boolean running=false;
|
||||||
|
|
||||||
|
for(;;){
|
||||||
|
|
||||||
|
if(xQueueReceive(motor->upLinkQueue, &upLinkData, 0)){
|
||||||
|
switch(upLinkData.action){
|
||||||
|
case SET_POSITION:
|
||||||
|
downLinkData.position=upLinkData.nSteps;
|
||||||
|
break;
|
||||||
|
case MOVETO:
|
||||||
|
upLinkData.nSteps-=downLinkData.position;
|
||||||
|
[[fallthrough]];
|
||||||
|
case MOVE:
|
||||||
|
downLinkData.stepsRemaining=upLinkData.nSteps;
|
||||||
|
motor->onEnable();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
running=true;
|
||||||
|
downLinkData.ack=true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t msDelay=upLinkData.msDelay;
|
||||||
|
|
||||||
|
if(running==false){
|
||||||
|
vTaskDelay(msDelay);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(downLinkData.stepsRemaining!=0)
|
||||||
|
msDelay+=msDelay * motor->accelSize * (exp(-fabs(upLinkData.nSteps-downLinkData.stepsRemaining)/motor->accelSteps) + exp(-(fabs(downLinkData.stepsRemaining)-1.0)/motor->accelSteps));
|
||||||
|
|
||||||
|
ESP_LOGD(STEPPER_TAG,"Position: %d Steps Remaining: %d Delay=%d ms",downLinkData.position,downLinkData.stepsRemaining,(int)(msDelay));
|
||||||
|
|
||||||
|
int dStep=0;
|
||||||
|
|
||||||
|
if(downLinkData.stepsRemaining>0){
|
||||||
|
dStep=-1;
|
||||||
|
motor->onStep(1);
|
||||||
|
} else if(downLinkData.stepsRemaining<0){
|
||||||
|
dStep=1;
|
||||||
|
motor->onStep(0);
|
||||||
|
} else {
|
||||||
|
if(upLinkData.endAction==DISABLE)
|
||||||
|
motor->onDisable();
|
||||||
|
else if(upLinkData.endAction==BRAKE)
|
||||||
|
motor->onBrake();
|
||||||
|
running=false;
|
||||||
|
}
|
||||||
|
|
||||||
|
xQueueOverwrite(motor->downLinkQueue,&downLinkData);
|
||||||
|
downLinkData.stepsRemaining+=dStep;
|
||||||
|
downLinkData.position-=dStep;
|
||||||
|
downLinkData.ack=false;
|
||||||
|
vTaskDelay(msDelay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
|
@ -0,0 +1,105 @@
|
||||||
|
/*********************************************************************************
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2023 Gregg E. Berman
|
||||||
|
*
|
||||||
|
* https://github.com/HomeSpan/HomeSpan
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*
|
||||||
|
********************************************************************************/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
[[maybe_unused]] static const char* STEPPER_TAG = "StepperControl";
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
|
||||||
|
class StepperControl {
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
enum {
|
||||||
|
FULL_STEP_ONE_PHASE=0,
|
||||||
|
FULL_STEP_TWO_PHASE=1,
|
||||||
|
HALF_STEP=2,
|
||||||
|
QUARTER_STEP=4,
|
||||||
|
EIGHTH_STEP=8
|
||||||
|
};
|
||||||
|
|
||||||
|
enum endAction_t {
|
||||||
|
NONE,
|
||||||
|
DISABLE,
|
||||||
|
BRAKE
|
||||||
|
};
|
||||||
|
|
||||||
|
enum action_t {
|
||||||
|
MOVE,
|
||||||
|
MOVETO,
|
||||||
|
SET_POSITION
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
struct upLink_t {
|
||||||
|
int nSteps;
|
||||||
|
uint32_t msDelay;
|
||||||
|
action_t action;
|
||||||
|
endAction_t endAction;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct downLink_t {
|
||||||
|
int stepsRemaining=0;
|
||||||
|
int position=0;
|
||||||
|
boolean ack=false;
|
||||||
|
};
|
||||||
|
|
||||||
|
float accelSteps=1;
|
||||||
|
float accelSize=0;
|
||||||
|
downLink_t downLinkData;
|
||||||
|
|
||||||
|
TaskHandle_t motorTaskHandle;
|
||||||
|
QueueHandle_t upLinkQueue;
|
||||||
|
QueueHandle_t downLinkQueue;
|
||||||
|
|
||||||
|
void waitForAck();
|
||||||
|
virtual void onStep(boolean direction)=0;
|
||||||
|
virtual void onEnable(){};
|
||||||
|
virtual void onDisable(){};
|
||||||
|
virtual void onBrake(){};
|
||||||
|
static void motorTask(void *args);
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
StepperControl(uint32_t priority=1, uint32_t cpu=0);
|
||||||
|
virtual StepperControl *setStepType(int mode){return(this);};
|
||||||
|
StepperControl *setAccel(float accelSize, float accelSteps);
|
||||||
|
void move(int nSteps, uint32_t msDelay, endAction_t endAction=NONE);
|
||||||
|
void moveTo(int nPosition, uint32_t msDelay, endAction_t endAction=NONE);
|
||||||
|
int position();
|
||||||
|
void setPosition(int pos);
|
||||||
|
int stepsRemaining();
|
||||||
|
StepperControl *enable();
|
||||||
|
StepperControl *disable();
|
||||||
|
StepperControl *brake();
|
||||||
|
};
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
|
@ -0,0 +1,120 @@
|
||||||
|
/*********************************************************************************
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2023 Gregg E. Berman
|
||||||
|
*
|
||||||
|
* https://github.com/HomeSpan/HomeSpan
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*
|
||||||
|
********************************************************************************/
|
||||||
|
|
||||||
|
// Implementation of StepperControl for a Sparkfun A3967 EasyDriver Stepper Motor Driver
|
||||||
|
// Breakout Board (https://www.sparkfun.com/products/12779)
|
||||||
|
|
||||||
|
// This implementation uses the driver board's MS1, MS2, STEP, DIR, and ENABLE pins.
|
||||||
|
// Separate PWM pins on the ESP32 are NOT needed since the driver board contains its own
|
||||||
|
// waveform generation. Supported modes include FULL_STEP (double-phase only), HALF_STEP,
|
||||||
|
// QUARTER_STEP, and EIGHTH_STEP.
|
||||||
|
|
||||||
|
// The motor outputs can be enabled (current running through the coils) or
|
||||||
|
// disabled (no current / high impedence). The EasyDriver board does NOT support
|
||||||
|
// the short brake mode.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "StepperControl.h"
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
|
||||||
|
struct Stepper_A3967 : StepperControl {
|
||||||
|
|
||||||
|
int m1Pin;
|
||||||
|
int m2Pin;
|
||||||
|
int stepPin;
|
||||||
|
int dirPin;
|
||||||
|
int enablePin;
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
|
||||||
|
Stepper_A3967(int m1Pin, int m2Pin, int stepPin, int dirPin, int enablePin, std::pair<uint32_t, uint32_t> taskParams = {1,0}) : StepperControl(taskParams.first,taskParams.second){
|
||||||
|
this->m1Pin=m1Pin;
|
||||||
|
this->m2Pin=m2Pin;
|
||||||
|
this->stepPin=stepPin;
|
||||||
|
this->dirPin=dirPin;
|
||||||
|
this->enablePin=enablePin;
|
||||||
|
|
||||||
|
pinMode(m1Pin,OUTPUT);
|
||||||
|
pinMode(m2Pin,OUTPUT);
|
||||||
|
pinMode(stepPin,OUTPUT);
|
||||||
|
pinMode(dirPin,OUTPUT);
|
||||||
|
pinMode(enablePin,OUTPUT);
|
||||||
|
|
||||||
|
setStepType(FULL_STEP_TWO_PHASE);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
|
||||||
|
void onStep(boolean direction) override {
|
||||||
|
digitalWrite(dirPin,direction);
|
||||||
|
digitalWrite(stepPin,HIGH);
|
||||||
|
digitalWrite(stepPin,LOW);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
|
||||||
|
void onEnable() override {
|
||||||
|
digitalWrite(enablePin,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
|
||||||
|
void onDisable() override {
|
||||||
|
digitalWrite(enablePin,1);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
|
||||||
|
StepperControl *setStepType(int mode) override {
|
||||||
|
switch(mode){
|
||||||
|
case FULL_STEP_TWO_PHASE:
|
||||||
|
digitalWrite(m1Pin,LOW);
|
||||||
|
digitalWrite(m2Pin,LOW);
|
||||||
|
break;
|
||||||
|
case HALF_STEP:
|
||||||
|
digitalWrite(m1Pin,HIGH);
|
||||||
|
digitalWrite(m2Pin,LOW);
|
||||||
|
break;
|
||||||
|
case QUARTER_STEP:
|
||||||
|
digitalWrite(m1Pin,LOW);
|
||||||
|
digitalWrite(m2Pin,HIGH);
|
||||||
|
break;
|
||||||
|
case EIGHTH_STEP:
|
||||||
|
digitalWrite(m1Pin,HIGH);
|
||||||
|
digitalWrite(m2Pin,HIGH);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ESP_LOGE(STEPPER_TAG,"Unknown StepType=%d",mode);
|
||||||
|
}
|
||||||
|
return(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
|
@ -0,0 +1,182 @@
|
||||||
|
/*********************************************************************************
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2023 Gregg E. Berman
|
||||||
|
*
|
||||||
|
* https://github.com/HomeSpan/HomeSpan
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*
|
||||||
|
********************************************************************************/
|
||||||
|
|
||||||
|
// Implementation of StepperControl for an Adafruit TB6612 1.2A DC/Stepper Motor Driver
|
||||||
|
// Breakout Board (https://www.adafruit.com/product/2448)
|
||||||
|
|
||||||
|
// This implementation supports two constructors reflecting implementations both with and
|
||||||
|
// without the use of PWM pins. The first operates the driver board using only its
|
||||||
|
// four digital control pins: AIN1, AIN2, BIN1, BIN2. In this configuration the
|
||||||
|
// PWMA, PWMB, and STBY pins on the driver board should be directly connected to Vcc.
|
||||||
|
|
||||||
|
// The second configuration uses the four digital control pins (AIN1, AIN2, BIN1, and BIN2)
|
||||||
|
// as well as the PWMA and PWMB pins. In this configuration only the STBY pin on the
|
||||||
|
// driver board should be directly connected to Vcc.
|
||||||
|
|
||||||
|
// The first configuration supports both single-phase and double-phase FULL_STEP modes,
|
||||||
|
// as well as a HALF_STEP mode. The second configuration also includes QUARTER_STEP
|
||||||
|
// and EIGHTH_STEP modes, made possible by the use of the PWM pins to micro-step the motor.
|
||||||
|
|
||||||
|
// In either configuration the motor outputs can be enabled (current running through the coils)
|
||||||
|
// disabled (no current / high impedence) or set to a short brake.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "StepperControl.h"
|
||||||
|
#include "PwmPin.h"
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
|
||||||
|
struct Stepper_TB6612 : StepperControl {
|
||||||
|
|
||||||
|
int ain1, ain2, bin1, bin2;
|
||||||
|
uint8_t phase, nPhases;
|
||||||
|
double offset;
|
||||||
|
LedPin *pwmA=NULL, *pwmB;
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
|
||||||
|
Stepper_TB6612(int AIN1, int AIN2, int BIN1, int BIN2, std::pair<uint32_t, uint32_t> taskParams = {1,0}) : StepperControl(taskParams.first,taskParams.second){
|
||||||
|
|
||||||
|
ain1=AIN1;
|
||||||
|
ain2=AIN2;
|
||||||
|
bin1=BIN1;
|
||||||
|
bin2=BIN2;
|
||||||
|
|
||||||
|
pinMode(ain1,OUTPUT);
|
||||||
|
pinMode(ain2,OUTPUT);
|
||||||
|
pinMode(bin1,OUTPUT);
|
||||||
|
pinMode(bin2,OUTPUT);
|
||||||
|
|
||||||
|
setStepType(FULL_STEP_TWO_PHASE);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
|
||||||
|
Stepper_TB6612(int AIN1, int AIN2, int BIN1, int BIN2, int PWMA, int PWMB, std::pair<uint32_t, uint32_t> taskParams = {1,0}) : Stepper_TB6612(AIN1,AIN2,BIN1,BIN2,taskParams){
|
||||||
|
|
||||||
|
pwmA=new LedPin(PWMA,0,50000);
|
||||||
|
pwmB=new LedPin(PWMB,0,50000);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
|
||||||
|
void onEnable() override {
|
||||||
|
setPins();
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
|
||||||
|
void onDisable() override {
|
||||||
|
digitalWrite(ain1,0);
|
||||||
|
digitalWrite(ain2,0);
|
||||||
|
digitalWrite(bin1,0);
|
||||||
|
digitalWrite(bin2,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
|
||||||
|
void onBrake() override {
|
||||||
|
digitalWrite(ain1,1);
|
||||||
|
digitalWrite(ain2,1);
|
||||||
|
digitalWrite(bin1,1);
|
||||||
|
digitalWrite(bin2,1);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
|
||||||
|
void onStep(boolean direction) override {
|
||||||
|
if(direction)
|
||||||
|
phase=(phase+1)%nPhases;
|
||||||
|
else
|
||||||
|
phase=(phase+nPhases-1)%nPhases;
|
||||||
|
|
||||||
|
setPins();
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
|
||||||
|
void setPins(){
|
||||||
|
float levelA=cos(phase*TWO_PI/nPhases+offset)*100.0;
|
||||||
|
float levelB=sin(phase*TWO_PI/nPhases+offset)*100.0;
|
||||||
|
digitalWrite(ain1,levelA>0.01);
|
||||||
|
digitalWrite(ain2,levelA<-0.01);
|
||||||
|
digitalWrite(bin1,levelB>0.01);
|
||||||
|
digitalWrite(bin2,levelB<-0.01);
|
||||||
|
if(pwmA){
|
||||||
|
pwmA->set(fabs(levelA));
|
||||||
|
pwmB->set(fabs(levelB));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
|
||||||
|
StepperControl *setStepType(int mode) override {
|
||||||
|
|
||||||
|
switch(mode){
|
||||||
|
case FULL_STEP_ONE_PHASE:
|
||||||
|
phase=0;
|
||||||
|
nPhases=4;
|
||||||
|
offset=0;
|
||||||
|
break;
|
||||||
|
case FULL_STEP_TWO_PHASE:
|
||||||
|
phase=0;
|
||||||
|
nPhases=4;
|
||||||
|
offset=TWO_PI/8.0;
|
||||||
|
break;
|
||||||
|
case HALF_STEP:
|
||||||
|
phase=0;
|
||||||
|
nPhases=8;
|
||||||
|
offset=0;
|
||||||
|
break;
|
||||||
|
case QUARTER_STEP:
|
||||||
|
if(!pwmA){
|
||||||
|
ESP_LOGE(STEPPER_TAG,"QUARTER_STEP requires PWM pins");
|
||||||
|
return(this);
|
||||||
|
}
|
||||||
|
phase=0;
|
||||||
|
nPhases=16;
|
||||||
|
offset=0;
|
||||||
|
break;
|
||||||
|
case EIGHTH_STEP:
|
||||||
|
if(!pwmA){
|
||||||
|
ESP_LOGE(STEPPER_TAG,"EIGHTH_STEP requires PWM pins");
|
||||||
|
return(this);
|
||||||
|
}
|
||||||
|
phase=0;
|
||||||
|
nPhases=32;
|
||||||
|
offset=0;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ESP_LOGE(STEPPER_TAG,"Unknown StepType=%d",mode);
|
||||||
|
}
|
||||||
|
return(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
|
@ -28,42 +28,46 @@
|
||||||
// This is a placeholder .ino file that allows you to easily edit the contents of this files using the Arduino IDE,
|
// This is a placeholder .ino file that allows you to easily edit the contents of this files using the Arduino IDE,
|
||||||
// as well as compile and test from this point. This file is ignored when the library is included in other sketches.
|
// as well as compile and test from this point. This file is ignored when the library is included in other sketches.
|
||||||
|
|
||||||
#include "PwmPin.h"
|
#include "Stepper_TB6612.h" // include the driver for a TB6612 chip
|
||||||
|
#include "Stepper_A3967.h"
|
||||||
|
|
||||||
void setup() {
|
StepperControl *bigMotor;
|
||||||
|
StepperControl *smallMotor;
|
||||||
Serial.begin(115200); // start the Serial interface
|
|
||||||
Serial.flush();
|
|
||||||
delay(1000); // wait for interface to flush
|
|
||||||
|
|
||||||
Serial.println("\n\nHomeSpan LED Fade Test\n");
|
#define BIG_MOTOR_POSITION 1600
|
||||||
|
#define SMALL_MOTOR_POSITION 2064
|
||||||
|
|
||||||
LedPin red(33,0);
|
///////////////////
|
||||||
LedPin green(32,0);
|
|
||||||
LedPin blue(14,0);
|
|
||||||
|
|
||||||
int redLevel=0;
|
void setup() {
|
||||||
|
|
||||||
for(int i=100;i<=100;i+=10){
|
Serial.begin(115200);
|
||||||
while(red.fadeStatus()==LedPin::FADING);
|
delay(1000);
|
||||||
red.fade(i,1000,LedPin::PROPORTIONAL);
|
Serial.printf("\nHomeSpan Stepper Control\n\n");
|
||||||
}
|
|
||||||
|
|
||||||
while(1);
|
bigMotor=(new Stepper_TB6612(23,32,22,14,33,27))->setStepType(StepperControl::HALF_STEP)->setAccel(10,20);
|
||||||
|
smallMotor=new Stepper_A3967(18,21,5,4,19);
|
||||||
|
|
||||||
|
// smallMotor->setStepType(StepperControl::EIGHTH_STEP);
|
||||||
while(1){
|
|
||||||
delay(1000);
|
// bigMotor->setStepType(StepperControl::HALF_STEP);
|
||||||
if(red.fade(redLevel,5000))
|
// bigMotor->setAccel(10,20);
|
||||||
Serial.printf("Failed\n");
|
|
||||||
else{
|
|
||||||
Serial.printf("Success\n");
|
|
||||||
redLevel=100-redLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////////////
|
||||||
|
|
||||||
void loop(){
|
void loop(){
|
||||||
|
|
||||||
|
if(smallMotor->position()==0)
|
||||||
|
smallMotor->moveTo(SMALL_MOTOR_POSITION,2);
|
||||||
|
else if(smallMotor->position()==SMALL_MOTOR_POSITION)
|
||||||
|
smallMotor->moveTo(0,2);
|
||||||
|
|
||||||
|
if(bigMotor->position()==0)
|
||||||
|
bigMotor->moveTo(BIG_MOTOR_POSITION,4);
|
||||||
|
else if(bigMotor->position()==BIG_MOTOR_POSITION)
|
||||||
|
bigMotor->moveTo(0,4);
|
||||||
|
|
||||||
|
delay(1000);
|
||||||
|
Serial.printf("Small Motor: %d Big Motor %d\n",smallMotor->stepsRemaining(),bigMotor->stepsRemaining());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue