diff --git a/README.md b/README.md
index 1c5d140..c92f99d 100644
--- a/README.md
+++ b/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)
* Integrated access to the ESP32's on-chip Remote Control peripheral for easy generation of IR and RF signals
* Dedicated classes to control one- and two-wire addressable RGB and RGBW LEDs and LED strips
+* Dedicated classes to control stepper motors that can run smoothly in the background without interfering with HomeSpan
* Dedicated class that faciliates seemless point-to-point communication between ESP32 devices using ESP-NOW
* Integrated Web Log for user-defined log messages
* Extensively-commented Tutorial Sketches taking you from the very basics of HomeSpan through advanced HomeKit topics
@@ -48,36 +49,40 @@ HomeSpan requires version 2.0.0 or later of the [Arduino-ESP32 Board Manager](ht
* Launch the WiFi Access Point
* A standalone, detailed End-User Guide
-## ❗Latest Update - HomeSpan 1.7.2 (4/5/2023)
+## ❗Latest Update - HomeSpan 1.8.0 (7/8/2023)
-* **New ability to set OTA password from within sketch**
- * See the [OTA Page](docs/OTA.md) for details
+* **New Stepper Motor Control!**
-* **Added logic to allow duplicates of the same Custom Characteristic to be "defined" in more than one file in a sketch**
- * Allows the use of the same Custom Characteristic across multiple files in the same sketch without the compiler throwing a "redefinition error"
- * See the [API Reference](docs/Reference.md) for details
+ * 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
+ * supports driver boards with or without PWM, including microstepping modes
+ * 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**
- * Now works with INT, UINT16, and UINT32 Characteristics, as well as UINT8 Characteristics
- * See the [API Reference](docs/Reference.md) for details
+ * 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
+ * adds version numbers for the Sodium and MbedTLS libraries, HomeKit pairing status, and a text description of Reset Reason code
+ * see [Message Logging](docs/Logging.md) for details
-* **New parameters added to `autoPoll()` that allow the user to set priority and chose CPU**
- * Provides for enhanced performance on dual-processor chips
- * See the [API Reference](docs/Reference.md) for details
+* **Upgrades to Web Log Time Server initialization**
-* **Automatic LED Fading!**
- * Added new methods to LedPin class that enable use of the ESP32's built-in fade controls
- * Allows user to specify speed of fade
- * Runs in background without consuming any CPU resources
- * See the [PWM Page](docs/PWM.md) for details
+ * the process for retrieving the time and date from an NTP server upon booting now runs in the background as a separate task
+ * HomeSpan is no longer blocked from running during the NTP query
-* **Added ability to Clone the Pairing Data from one device to another**
- * 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
- * Avoids loss of automations, scenes, and any other Home App customizations associated with device
- * 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 new methods to disable HomeSpan's use of the USB Serial port**
+
+ * new Log Level, -1, causes HomeSpan to suppress all OUTPUT messages
+ * new homeSpan method `setSerialInputDisable(boolean val)` disables/re-enables HomeSpan's reading of CLI commands INPUT into the Arduino Serial Monitor
+* **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.
# 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 RFControl](docs/RMT.md) - easy generation of RF and IR Remote Control signals using the ESP32's on-chip RMT peripheral
* [HomeSpan Pixels](docs/Pixels.md) - integrated control of addressable one- and two-wire RGB and RGBW LEDs and LED strips
+* [HomeSpan Stepper Motor Control](docs/Stepper.md) - integrated control of stepper motors, including PWM microstepping
* [HomeSpan SpanPoint](docs/NOW.md) - facilitates point-to-point, bi-directional communication between ESP32 Devices using ESP-NOW
* [HomeSpan Television Services](docs/TVServices.md) - how to use HomeKit's undocumented Television Services and Characteristics
* [HomeSpan Message Logging](docs/Logging.md) - how to generate log messages for display on the Arduino Serial Monitor as well as optionally posted to an integrated Web Log page
diff --git a/docs/Blinkable.md b/docs/Blinkable.md
new file mode 100644
index 0000000..5362609
--- /dev/null
+++ b/docs/Blinkable.md
@@ -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
+
+
diff --git a/docs/GettingStarted.md b/docs/GettingStarted.md
index 09b84d0..bf53f36 100644
--- a/docs/GettingStarted.md
+++ b/docs/GettingStarted.md
@@ -52,7 +52,7 @@ In addition to being able to configure a HomeSpan device using the [HomeSpan CLI
The Control Button should be installed between ground and any pin on the ESP32 that can serve as an input. To inform HomeSpan of which pin you chose, you must call the method `homeSpan.setControlPin(pin)` near the top of your sketch (see the [HomeSpan API Reference](Reference.md) for details), else HomeSpan will assume a Control Button has **not** be installed.
-Similarly, the Status LED can be connected to any pin on the ESP32 that can serve as an output (and grounded through an appropriately-sized current-limiting resistor). To inform HomeSpan of which pin you chose, you must call the method `homeSpan.setStatusPin(pin)` near the top of your sketch, else HomeSpan will assume a Status LED has **not** been installed. Note some ESP32 boards have a built-in LED --- it is fine to use this for the Status LED if it is a simple on/off LED, *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).
diff --git a/docs/Logging.md b/docs/Logging.md
index 6f40b99..66d09f0 100644
--- a/docs/Logging.md
+++ b/docs/Logging.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 also completely suppress all Log messages generated by HomeSpan (as well as all user-defined Log messages - see below) by setting the *Log Level* to -1, either by typing 'L-1' into the HomeSpan CLI or by calling `homeSpan.setLogLevel(-1)` in your sketch. Disabling all Log messages from being output to the Serial Monitor may be useful in cases where a separate Serial peripheral is being controlled by the ESP32. In such cases you may want to implement a physical switch on your device that automatically sets the Log Level to 0 or -1 so you don't have to recompile your sketch every time you want to enable/disable HomeSpan Log Messages.
+
+Note that the *Log Level* setting has no impact on messages output by any `Serial.print()` or `Serial.printf()` statements used in a sketch. To ensure you can control such messages via the *Log Level* setting, use the `LOG()` macros below. Also note that the *Log Level* setting has no impact on any ESP32 diagnostic messages produced by the ESP32 operating system itself. These messages are controlled according to the *Core Debug Level* specified at compile time under the Tools menu of the Arduino IDE.
+
## User-Defined Log Messages
You can add your own log messages to any sketch using HomeSpan's **LOG0()**, **LOG1()**, and **LOG2()** macros. Messages created with these macros will be output to the Arduino Serial Monitor according the *Log Level* setting described above. Each **LOGn()** macro (where n=\[0,2\]) is available in two flavors depending on the number of arguments specified:
@@ -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.
+### 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 \
)
+ * *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 (\| ) of the second table to red, the color of the data rows (\ | ) 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
diff --git a/docs/PWM.md b/docs/PWM.md
index dbd67dc..c0a647b 100644
--- a/docs/PWM.md
+++ b/docs/PWM.md
@@ -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 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.
---
diff --git a/docs/RMT.md b/docs/RMT.md
index f75cf09..5be3d6e 100644
--- a/docs/RMT.md
+++ b/docs/RMT.md
@@ -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:
* 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
- * 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.
@@ -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
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
diff --git a/docs/Reference.md b/docs/Reference.md
index 98f651b..472c8cb 100644
--- a/docs/Reference.md
+++ b/docs/Reference.md
@@ -37,10 +37,11 @@ The following **optional** `homeSpan` methods override various HomeSpan initiali
* `void setStatusPin(uint8_t pin)`
* sets the ESP32 pin to use for the HomeSpan Status LED
* assumes a standard LED will be connected to *pin*
- * if neither this method nor `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)`
* sets the ESP32 pin to use for the HomeSpan Status LED
+ * this method is an *alternative* to using `setStatusPin()` above
* assumes an RGB NeoPixel (or equivalent) will be connected to *pin*
* works well with ESP32 boards that have a built-in NeoPixel LED, though adding an external NeoPixel is fine
* users can optionally specify the color HomeSpan will use with the NeoPixel by providing the following HSV values:
@@ -49,7 +50,14 @@ The following **optional** `homeSpan` methods override various HomeSpan initiali
* v = Brightness percentage from 0-100
* color defaults to *red* if unspecified
* example: `homeSpan.setStatusPixel(8,120,100,20)` sets the Status LED to light green using a NeoPixel attached to pin 8
- * if neither this method nor `setStatusPin()` is called, HomeSpan will assume there is no Status LED
+ * 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)`
* 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)`
* 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:
- * 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
* 2 = all HomeSpan status messages plus all HAP communication packets to and from the HomeSpan device, as well as all `LOG1()` and `LOG2()` messages specified in the sketch by the user
+ * -1 = supresses ALL HomeSpan status messages, including all `LOG0()`, `LOG1()`, and `LOG2()` messages specified in the sketch by the user, freeing up the Serial port for other uses
+ * the log level setting has no impact on any `Serial.print()` or `Serial.printf()` statements that may be used in a sketch. Use one of the `LOG()` macros instead of `Serial.print()` or `Serial.printf()` if you want to control the output by setting the HomeSpan log level
+ * the log level setting has no impact on any ESP32 diagnostic messages output by the ESP32 operating system itself. To suppress these mesaages make sure to compile your sketch with the *Core Debug Level* set to "None" in the Tools menu of the Arduino IDE
* note the log level can also be changed at runtime with the 'L' command via the [HomeSpan CLI](CLI.md)
* see [Message Logging](Logging.md) for complete details
@@ -181,17 +192,21 @@ The following **optional** `homeSpan` methods enable additional features and pro
* can by called from anywhere in a sketch
* `void enableWebLog(uint16_t maxEntries, const char *timeServerURL, const char *timeZone, const char *logURL)`
- * enables a rolling web log that displays the most recent *maxEntries* entries created by the user with the `WEBLOG()` macro. Parameters, and their default values if unspecified, are as follows:
- * *maxEntries* - maximum number of (most recent) entries to save. If unspecified, defaults to 0, in which case the web log will only display status without any log entries
- * *timeServerURL* - the URL of a time server that HomeSpan will use to set its clock upon startup after a WiFi connection has been established. If unspecified, default to NULL, in which case HomeSpan skips setting the device clock
+ * enables a rolling Web Log that displays the most recent *maxEntries* entries created by the user with the `WEBLOG()` macro. Parameters, and their default values if unspecified, are as follows:
+ * *maxEntries* - maximum number of (most recent) entries to save. If unspecified, defaults to 0, in which case the Web Log will only display status without any log entries
+ * *timeServerURL* - the URL of a time server that HomeSpan will use to set its clock upon startup after a WiFi connection has been established. If unspecified, 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
- * *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://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
* `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)`
* 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("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:
@@ -590,24 +614,6 @@ If REQUIRED is defined in the main sketch *prior* to including the HomeSpan libr
#include "HomeSpan.h"
```
- ---
-
-#### Deprecated functions (available for backwards compatibility with older sketches):
-
-* `SpanRange(int min, int max, int step)`
-
- * this legacy class was limited to integer-based parameters and has been re-coded to simply call the more generic `setRange(min, max, step)` method
- * last supported version: [v1.2.0](https://github.com/HomeSpan/HomeSpan/blob/release-1.2.0/docs/Reference.md#spanrangeint-min-int-max-int-step)
- * **please use** `setRange(min, max, step)` **for all new sketches**
-
-* `void homeSpan.setMaxConnections(uint8_t nCon)`
- * this legacy method was used to set the total number of HAP Controller Connections HomeSpan implements upon start-up to ensure there are still free sockets available for user-defined code requiring separate network resources
- * last supported version: [v1.4.2](https://github.com/HomeSpan/HomeSpan/blob/release-1.4.2/docs/Reference.md)
- * this method has been replaced by the more flexible method, `reserveSocketConnections(uint8_t nSockets)`
- * allows you to simply reserve network sockets for other custom code as needed
- * upon calling `homespan.begin()`, HomeSpan automatically determines how many sockets are left that it can use for HAP Controller Connections
- * **please use** `homeSpan.reserveSocketConnections(uint8_t nSockets)` **for all new sketches**
-
---
[↩️](../README.md) Back to the Welcome page
diff --git a/docs/Stepper.md b/docs/Stepper.md
new file mode 100644
index 0000000..3f89bea
--- /dev/null
+++ b/docs/Stepper.md
@@ -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 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
diff --git a/docs/StepperDrivers/Stepper_A3967.md b/docs/StepperDrivers/Stepper_A3967.md
new file mode 100644
index 0000000..7d0ef6a
--- /dev/null
+++ b/docs/StepperDrivers/Stepper_A3967.md
@@ -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
diff --git a/docs/StepperDrivers/Stepper_TB6612.md b/docs/StepperDrivers/Stepper_TB6612.md
new file mode 100644
index 0000000..960679e
--- /dev/null
+++ b/docs/StepperDrivers/Stepper_TB6612.md
@@ -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
diff --git a/docs/StepperModes.md b/docs/StepperModes.md
new file mode 100644
index 0000000..56aa328
--- /dev/null
+++ b/docs/StepperModes.md
@@ -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:
+
+
+
+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:
+
+
+
+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:
+
+
+
+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:
+
+
+
+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
+
+
+
+
+
+
diff --git a/docs/Tutorials.md b/docs/Tutorials.md
index 558f9a4..9b38147 100644
--- a/docs/Tutorials.md
+++ b/docs/Tutorials.md
@@ -138,6 +138,9 @@ Demonstrates how *SpanPoint* can be used to transmit messages from battery-power
### [FadingLED](../examples/Other%20Examples/FadingLED)
Demonstrates how the *LedPin* class can use the ESP32's built-in fading control to automatically fade an LED from from one level of brightness to another over a specified period of time. See the [LedPin](PWM.md#pulse-width-modulation-pwm) page for full details
+### [MotorizedWindowShade](../examples/Other%20Examples/MotorizedWindowShade)
+Demonstrates how to use the *StepperControl* class to operate a stepper motor. Implements a motorized window shade based on [Example 13](../examples/13-TargetStates) above. See the [Stepper Motor Control](Stepper.md) page for full details
+
---
[↩️](../README.md) Back to the Welcome page
diff --git a/docs/UserGuide.md b/docs/UserGuide.md
index a2cb90d..4e2aa38 100644
--- a/docs/UserGuide.md
+++ b/docs/UserGuide.md
@@ -1,5 +1,16 @@
# 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:
* 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*.
+## 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 - Enter [Device Configuration Mode](#device-configuration-mode). - Exit _Device Configuration Mode_ executing the action selected based on the number of flashes.|
+
+
---
[↩️](../README.md) Back to the Welcome page
diff --git a/documents/HomeSpanDiagrams.key b/documents/HomeSpanDiagrams.key
index d8bd531..cd164e0 100755
Binary files a/documents/HomeSpanDiagrams.key and b/documents/HomeSpanDiagrams.key differ
diff --git a/examples/Other Examples/MotorizedWindowShade/MotorizedWindowShade.ino b/examples/Other Examples/MotorizedWindowShade/MotorizedWindowShade.ino
new file mode 100644
index 0000000..39314ff
--- /dev/null
+++ b/examples/Other Examples/MotorizedWindowShade/MotorizedWindowShade.ino
@@ -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();
+}
diff --git a/examples/Other Examples/RemoteSensors/RemoteTempSensor/RemoteTempSensor.ino b/examples/Other Examples/RemoteSensors/RemoteTempSensor/RemoteTempSensor.ino
index 4157707..63d5931 100644
--- a/examples/Other Examples/RemoteSensors/RemoteTempSensor/RemoteTempSensor.ino
+++ b/examples/Other Examples/RemoteSensors/RemoteTempSensor/RemoteTempSensor.ino
@@ -58,7 +58,7 @@
#define DIAGNOSTIC_MODE
#define SAMPLE_TIME 30000 // Time between temperature samples (in milliseconds)
-#define I2C_ADD 0x48 // ICS Address to use for the Adafruit ADT7410
+#define I2C_ADD 0x48 // I2C Address to use for the Adafruit ADT7410
SpanPoint *mainDevice;
diff --git a/library.properties b/library.properties
index d531b0c..1209647 100644
--- a/library.properties
+++ b/library.properties
@@ -1,5 +1,5 @@
name=HomeSpan
-version=1.7.2
+version=1.8.0
author=Gregg
maintainer=Gregg
sentence=A robust and extremely easy-to-use HomeKit implementation for the Espressif ESP32 running on the Arduino IDE.
diff --git a/src/HAP.cpp b/src/HAP.cpp
index 3db9f4a..39bed60 100644
--- a/src/HAP.cpp
+++ b/src/HAP.cpp
@@ -28,6 +28,7 @@
#include
#include
#include
+#include
#include "HAP.h"
@@ -61,16 +62,11 @@ void HAPClient::init(){
nvs_get_blob(srpNVS,"VERIFYDATA",&verifyData,&len); // retrieve data
srp.loadVerifyCode(verifyData.verifyCode,verifyData.salt); // load verification code and salt into SRP structure
} else {
-
- 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);
+ LOG0("Generating SRP verification data for default Setup Code: %.3s-%.2s-%.3s\n",homeSpan.defaultSetupCode,homeSpan.defaultSetupCode+3,homeSpan.defaultSetupCode+5);
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_commit(srpNVS); // commit to NVS
- Serial.print("Setup Payload for Optional QR Code: ");
- Serial.print(homeSpan.qrCode.get(atoi(homeSpan.defaultSetupCode),homeSpan.qrID,atoi(homeSpan.category)));
- Serial.print("\n\n");
+ LOG0("Setup Payload for Optional QR Code: %s\n\n",homeSpan.qrCode.get(atoi(homeSpan.defaultSetupCode),homeSpan.qrID,atoi(homeSpan.category)));
}
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
nvs_get_blob(hapNVS,"ACCESSORY",&accessory,&len); // retrieve data
} 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];
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
nvs_get_blob(hapNVS,"CONTROLLERS",controllers,&len); // retrieve data
} 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
@@ -110,11 +106,11 @@ void HAPClient::init(){
nvs_commit(hapNVS); // commit to NVS
}
- Serial.print("Accessory ID: ");
+ LOG0("Accessory ID: ");
charPrintRow(accessory.ID,17);
- Serial.print(" LTPK: ");
+ LOG0(" LTPK: ");
hexPrintRow(accessory.LTPK,32);
- Serial.print("\n");
+ LOG0("\n");
printControllers();
@@ -138,17 +134,19 @@ void HAPClient::init(){
if(!nvs_get_blob(hapNVS,"HAPHASH",NULL,&len)){ // if found HAP HASH structure
nvs_get_blob(hapNVS,"HAPHASH",&homeSpan.hapConfig,&len); // retrieve data
} 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_commit(hapNVS); // commit to NVS
}
- 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);
- else
- Serial.printf("\nAccessory configuration number: %d\n",homeSpan.hapConfig.configNumber);
+ if(homeSpan.updateDatabase(false)){ // create Configuration Number and Loop vector
+ LOG0("\nAccessory configuration has changed. Updating configuration number to %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
badRequestError();
- Serial.print("\n*** ERROR: Exceeded maximum HTTP message length\n\n");
+ LOG0("\n*** ERROR: Exceeded maximum HTTP message length\n\n");
return;
}
@@ -192,7 +190,7 @@ void HAPClient::processRequest(){
if(!(p=strstr((char *)httpBuf,"\r\n\r\n"))){
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;
}
@@ -204,7 +202,7 @@ void HAPClient::processRequest(){
cLen=atoi(p+16);
if(nBytes!=strlen(body)+4+cLen){
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;
}
@@ -215,14 +213,14 @@ void HAPClient::processRequest(){
if(cLen==0){
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;
}
if(!strncmp(body,"POST /pair-setup ",17) && // POST PAIR-SETUP
strstr(body,"Content-Type: application/pairing+tlv8") && // check that content is TLV8
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");
postPairSetupURL(); // process URL
@@ -232,7 +230,7 @@ void HAPClient::processRequest(){
if(!strncmp(body,"POST /pair-verify ",18) && // POST PAIR-VERIFY
strstr(body,"Content-Type: application/pairing+tlv8") && // check that content is TLV8
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");
postPairVerifyURL(); // process URL
@@ -242,7 +240,7 @@ void HAPClient::processRequest(){
if(!strncmp(body,"POST /pairings ",15) && // POST PAIRINGS
strstr(body,"Content-Type: application/pairing+tlv8") && // check that content is TLV8
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");
postPairingsURL(); // process URL
@@ -252,7 +250,7 @@ void HAPClient::processRequest(){
if(!strncmp(body,"POST /pairings ",15) && // POST PAIRINGS
strstr(body,"Content-Type: application/pairing+tlv8") && // check that content is TLV8
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");
postPairingsURL(); // process URL
@@ -260,7 +258,7 @@ void HAPClient::processRequest(){
}
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;
} // POST request
@@ -269,7 +267,7 @@ void HAPClient::processRequest(){
if(cLen==0){
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;
}
@@ -296,7 +294,7 @@ void HAPClient::processRequest(){
}
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;
} // PUT request
@@ -319,13 +317,13 @@ void HAPClient::processRequest(){
}
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;
} // GET request
badRequestError();
- Serial.print("\n*** ERROR: Unknown or malformed HTTP request\n\n");
+ LOG0("\n*** ERROR: Unknown or malformed HTTP request\n\n");
} // processHAP
@@ -393,13 +391,13 @@ int HAPClient::postPairSetupURL(){
char buf[64];
if(tlvState==-1){ // missing STATE TLV
- Serial.print("\n*** ERROR: Missing State TLV\n\n");
+ LOG0("\n*** ERROR: Missing State TLV\n\n");
badRequestError(); // return with 400 error, which closes connection
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!
- Serial.print("\n*** ERROR: Device already paired!\n\n");
+ LOG0("\n*** ERROR: Device already paired!\n\n");
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_Error,tagError_Unavailable); // set Error=Unavailable
@@ -411,7 +409,7 @@ int HAPClient::postPairSetupURL(){
LOG2(buf);
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.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)
@@ -425,7 +423,7 @@ int HAPClient::postPairSetupURL(){
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)
- 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.val(kTLVType_State,pairState_M2); // set State=
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
!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.val(kTLVType_State,pairState_M4); // set State=
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
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.val(kTLVType_State,pairState_M4); // set State=
tlv8.val(kTLVType_Error,tagError_Authentication); // set Error=Authentication
@@ -485,7 +483,7 @@ int HAPClient::postPairSetupURL(){
case pairState_M5: // 'Exchange Request'
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.val(kTLVType_State,pairState_M6); // set State=
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,
(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.val(kTLVType_State,pairState_M6); // set State=
tlv8.val(kTLVType_Error,tagError_Authentication); // set Error=Authentication
@@ -522,7 +520,7 @@ int HAPClient::postPairSetupURL(){
}
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.val(kTLVType_State,pairState_M6); // set State=
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);
}
- if(homeSpan.logLevel>1) tlv8.print(); // print decrypted TLV data
+ tlv8.print(2); // print decrypted TLV data
LOG2("------- END DECRYPTED TLVS! -------\n");
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.val(kTLVType_State,pairState_M6); // set State=
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)
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.val(kTLVType_State,pairState_M6); // set State=
tlv8.val(kTLVType_Error,tagError_Authentication); // set Error=Authentication
@@ -611,7 +609,7 @@ int HAPClient::postPairSetupURL(){
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
uint8_t subTLV[subTLVLen];
@@ -666,13 +664,13 @@ int HAPClient::postPairVerifyURL(){
int tlvState=tlv8.val(kTLVType_State);
if(tlvState==-1){ // missing STATE TLV
- Serial.print("\n*** ERROR: Missing State TLV\n\n");
+ LOG0("\n*** ERROR: Missing State TLV\n\n");
badRequestError(); // return with 400 error, which closes connection
return(0);
}
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.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
@@ -688,7 +686,7 @@ int HAPClient::postPairVerifyURL(){
case pairState_M1: // 'Verify Start Request'
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.val(kTLVType_State,pairState_M2); // set State=
tlv8.val(kTLVType_Error,tagError_Unknown); // set Error=Unknown (there is no specific error type for missing/bad TLV data)
@@ -723,7 +721,7 @@ int HAPClient::postPairVerifyURL(){
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
uint8_t subTLV[subTLVLen];
@@ -754,7 +752,7 @@ int HAPClient::postPairVerifyURL(){
case pairState_M3: // 'Verify Finish Request'
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.val(kTLVType_State,pairState_M4); // set State=
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,
(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.val(kTLVType_State,pairState_M4); // set State=
tlv8.val(kTLVType_Error,tagError_Authentication); // set Error=Authentication
@@ -779,7 +777,7 @@ int HAPClient::postPairVerifyURL(){
}
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.val(kTLVType_State,pairState_M4); // set State=
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);
}
- if(homeSpan.logLevel>1) tlv8.print(); // print decrypted TLV data
+ tlv8.print(2); // print decrypted TLV data
LOG2("------- END DECRYPTED TLVS! -------\n");
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.val(kTLVType_State,pairState_M4); // set State=
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
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.val(kTLVType_State,pairState_M4); // set State=
tlv8.val(kTLVType_Error,tagError_Authentication); // set Error=Authentication
@@ -818,7 +816,7 @@ int HAPClient::postPairVerifyURL(){
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
- 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.val(kTLVType_State,pairState_M4); // set State=
tlv8.val(kTLVType_Error,tagError_Authentication); // set Error=Authentication
@@ -903,7 +901,7 @@ int HAPClient::postPairingsURL(){
LOG1(")...");
if(tlv8.val(kTLVType_State)!=1){
- Serial.print("\n*** ERROR: 'State' TLV record is either missing or not set to as required\n\n");
+ LOG0("\n*** ERROR: 'State' TLV record is either missing or not set to as required\n\n");
badRequestError(); // return with 400 error, which closes connection
return(0);
}
@@ -914,7 +912,7 @@ int HAPClient::postPairingsURL(){
LOG1("Add...\n");
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.val(kTLVType_State,pairState_M2); // set State=
tlv8.val(kTLVType_Error,tagError_Unknown); // set Error=Unknown (there is no specific error type for missing/bad TLV data)
@@ -922,7 +920,7 @@ int HAPClient::postPairingsURL(){
}
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.val(kTLVType_State,pairState_M2); // set State=
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
- 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.val(kTLVType_State,pairState_M2); // set State=
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)){
- Serial.print("\n*** ERROR: Can't pair more than ");
- Serial.print(MAX_CONTROLLERS);
- Serial.print(" Controllers\n\n");
+ LOG0("\n*** ERROR: Can't pair more than %d Controllers\n\n",MAX_CONTROLLERS);
tlv8.clear(); // clear TLV records
tlv8.val(kTLVType_State,pairState_M2); // set State=
tlv8.val(kTLVType_Error,tagError_MaxPeers); // set Error=MaxPeers
@@ -955,7 +951,7 @@ int HAPClient::postPairingsURL(){
LOG1("Remove...\n");
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.val(kTLVType_State,pairState_M2); // set State=
tlv8.val(kTLVType_Error,tagError_Unknown); // set Error=Unknown (there is no specific error type for missing/bad TLV data)
@@ -963,7 +959,7 @@ int HAPClient::postPairingsURL(){
}
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.val(kTLVType_State,pairState_M2); // set State=
tlv8.val(kTLVType_Error,tagError_Authentication); // set Error=Authentication
@@ -986,7 +982,7 @@ int HAPClient::postPairingsURL(){
break;
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
return(0);
break;
@@ -1242,19 +1238,57 @@ int HAPClient::getStatusURL(){
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+="" + String(homeSpan.displayName) + "\n";
- response+="\n";
- response+="\n";
- response+="" + String(homeSpan.displayName) + " \n";
+ response+="\n";
+ response+="" + String(homeSpan.displayName) + "\n";
- response+="\n";
+ response+="\n";
response+="| Up Time: | " + String(uptime) + " | \n";
response+="| Current Time: | " + String(clocktime) + " | \n";
response+="| Boot Time: | " + String(homeSpan.webLog.bootTime) + " | \n";
- response+="| Reset Reason Code: | " + String(esp_reset_reason()) + " | \n";
+
+ response+="| Reset Reason: | ";
+ 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()) + ") | \n";
+
response+="| WiFi Disconnects: | " + String(homeSpan.connected/2) + " | \n";
response+="| WiFi Signal: | " + String(WiFi.RSSI()) + " dBm | \n";
response+="| WiFi Gateway: | " + WiFi.gatewayIP().toString() + " | \n";
@@ -1263,12 +1297,19 @@ int HAPClient::getStatusURL(){
response+="| ESP-IDF Version: | " + String(ESP_IDF_VERSION_MAJOR) + "." + String(ESP_IDF_VERSION_MINOR) + "." + String(ESP_IDF_VERSION_PATCH) + " | \n";
response+="| HomeSpan Version: | " + String(HOMESPAN_VERSION) + " | \n";
response+="| Sketch Version: | " + String(homeSpan.getSketchVersion()) + " | \n";
+ response+="| Sodium Version: | " + String(sodium_version_string()) + " Lib " + String(sodium_library_version_major()) + "." + String(sodium_library_version_minor()) +" | \n";
+
+ char mbtlsv[64];
+ mbedtls_version_get_string_full(mbtlsv);
+ response+="| MbedTLS Version: | " + String(mbtlsv) + " | \n";
+
+ response+="| HomeKit Status: | " + String(nAdminControllers()?"PAIRED":"NOT PAIRED") + " | \n";
response+="| Max Log Entries: | " + String(homeSpan.webLog.maxEntries) + " | \n";
response+=" \n";
response+="";
if(homeSpan.webLog.maxEntries>0){
- response+="| Entry | Up Time | Log Time | Client | Message | \n";
+ response+="| Entry | Up Time | Log Time | Client | Message | \n";
int lastIndex=homeSpan.webLog.nEntries-homeSpan.webLog.maxEntries;
if(lastIndex<0)
lastIndex=0;
@@ -1388,7 +1429,7 @@ void HAPClient::tlvRespond(){
LOG2(client.remoteIP());
LOG2(" >>>>>>>>>>\n");
LOG2(body);
- if(homeSpan.logLevel>1) tlv8.print();
+ tlv8.print(2);
if(!cPair){ // unverified, unencrypted session
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
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);
}
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);
}
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);
}
@@ -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.logLevelLTPK,ltpk,32);
slot->admin=admin;
LOG2("\n*** Updated Controller: ");
- if(homeSpan.logLevel>1)
- charPrintRow(id,36);
+ charPrintRow(id,36,2);
LOG2(slot->admin?" (admin)\n\n":" (regular)\n\n");
return(slot);
}
@@ -1573,8 +1607,7 @@ Controller *HAPClient::addController(uint8_t *id, uint8_t *ltpk, boolean admin){
memcpy(slot->LTPK,ltpk,32);
slot->admin=admin;
LOG2("\n*** Added Controller: ");
- if(homeSpan.logLevel>1)
- charPrintRow(id,36);
+ charPrintRow(id,36,2);
LOG2(slot->admin?" (admin)\n\n":" (regular)\n\n");
return(slot);
}
@@ -1611,8 +1644,7 @@ void HAPClient::removeController(uint8_t *id){
if((slot=findController(id))){ // remove controller if found
LOG2("\n***Removed Controller: ");
- if(homeSpan.logLevel>1)
- charPrintRow(id,36);
+ charPrintRow(id,36,2);
LOG2(slot->admin?" (admin)\n":" (regular)\n");
slot->allocated=false;
@@ -1634,24 +1666,26 @@ void HAPClient::removeController(uint8_t *id){
//////////////////////////////////////
-void HAPClient::printControllers(){
+void HAPClient::printControllers(int minLogLevel){
+ if(homeSpan.logLevel\n\n");
- Serial.print("Message Logs: Level ");
- Serial.print(logLevel);
- Serial.print("\nStatus LED: Pin ");
+ LOG0("Message Logs: Level %d",logLevel);
+ LOG0("\nStatus LED: Pin ");
if(getStatusPin()>=0){
- Serial.print(getStatusPin());
+ LOG0(getStatusPin());
if(autoOffLED>0)
- Serial.printf(" (Auto Off=%d sec)",autoOffLED);
+ LOG0(" (Auto Off=%d sec)",autoOffLED);
}
else
- Serial.print("- *** WARNING: Status LED Pin is UNDEFINED");
- Serial.print("\nDevice Control: Pin ");
- if(getControlPin()>=0)
- Serial.print(getControlPin());
- else
- Serial.print("- *** WARNING: Device Control Pin is UNDEFINED");
- Serial.print("\nSketch Version: ");
- Serial.print(getSketchVersion());
- Serial.print("\nHomeSpan Version: ");
- Serial.print(HOMESPAN_VERSION);
- Serial.print("\nArduino-ESP Ver.: ");
- Serial.print(ARDUINO_ESP_VERSION);
- Serial.printf("\nESP-IDF Version: %d.%d.%d",ESP_IDF_VERSION_MAJOR,ESP_IDF_VERSION_MINOR,ESP_IDF_VERSION_PATCH);
- Serial.printf("\nESP32 Chip: %s Rev %d %s-core %dMB Flash", ESP.getChipModel(),ESP.getChipRevision(),
+ LOG0("- *** WARNING: Status LED Pin is UNDEFINED");
+ LOG0("\nDevice Control: Pin ");
+ if(getControlPin()>=0){
+ LOG0(getControlPin());
+ }
+ else{
+ LOG0("- *** WARNING: Device Control Pin is UNDEFINED");
+ }
+ LOG0("\nSketch Version: %s",getSketchVersion());
+ LOG0("\nHomeSpan Version: %s",HOMESPAN_VERSION);
+ LOG0("\nArduino-ESP Ver.: %s",ARDUINO_ESP_VERSION);
+ LOG0("\nESP-IDF Version: %d.%d.%d",ESP_IDF_VERSION_MAJOR,ESP_IDF_VERSION_MINOR,ESP_IDF_VERSION_PATCH);
+ LOG0("\nESP32 Chip: %s Rev %d %s-core %dMB Flash", ESP.getChipModel(),ESP.getChipRevision(),
ESP.getChipCores()==1?"single":"dual",ESP.getFlashChipSize()/1024/1024);
#ifdef ARDUINO_VARIANT
- Serial.print("\nESP32 Board: ");
- Serial.print(ARDUINO_VARIANT);
+ LOG0("\nESP32 Board: ");
+ LOG0(ARDUINO_VARIANT);
#endif
- Serial.printf("\nPWM Resources: %d channels, %d timers, max %d-bit duty resolution",
+ LOG0("\nPWM Resources: %d channels, %d timers, max %d-bit duty resolution",
LEDC_SPEED_MODE_MAX*LEDC_CHANNEL_MAX,LEDC_SPEED_MODE_MAX*LEDC_TIMER_MAX,LEDC_TIMER_BIT_MAX-1);
- Serial.printf("\nSodium Version: %s Lib %d.%d",sodium_version_string(),sodium_library_version_major(),sodium_library_version_minor());
+ LOG0("\nSodium Version: %s Lib %d.%d",sodium_version_string(),sodium_library_version_major(),sodium_library_version_minor());
char mbtlsv[64];
mbedtls_version_get_string_full(mbtlsv);
- Serial.printf("\nMbedTLS Version: %s",mbtlsv);
+ LOG0("\nMbedTLS Version: %s",mbtlsv);
- Serial.print("\nSketch Compiled: ");
- Serial.print(__DATE__);
- Serial.print(" ");
- Serial.print(__TIME__);
-
- Serial.printf("\nPartition: %s",esp_ota_get_running_partition()->label);
- Serial.printf("\nMAC Address: %s",WiFi.macAddress().c_str());
+ LOG0("\nSketch Compiled: %s %s",__DATE__,__TIME__);
+ LOG0("\nPartition: %s",esp_ota_get_running_partition()->label);
+ LOG0("\nMAC Address: %s",WiFi.macAddress().c_str());
- Serial.print("\n\nDevice Name: ");
- Serial.print(displayName);
- Serial.print("\n\n");
+ LOG0("\n\nDevice Name: %s\n\n",displayName);
uint8_t otaRequired=0;
nvs_get_u8(otaNVS,"OTA_REQUIRED",&otaRequired);
nvs_set_u8(otaNVS,"OTA_REQUIRED",0);
nvs_commit(otaNVS);
if(otaRequired && !spanOTA.enabled){
- Serial.printf("\n\n*** OTA SAFE MODE ALERT: OTA REQUIRED BUT NOT ENABLED. ROLLING BACK TO PREVIOUS APPLICATION ***\n\n");
+ LOG0("\n\n*** OTA SAFE MODE ALERT: OTA REQUIRED BUT NOT ENABLED. ROLLING BACK TO PREVIOUS APPLICATION ***\n\n");
delay(100);
esp_ota_mark_app_invalid_rollback_and_reboot();
}
@@ -164,7 +156,7 @@ void Span::begin(Category catID, const char *displayName, const char *hostNameBa
void Span::poll() {
if(pollTaskHandle){
- Serial.print("\n** FATAL ERROR: Do not call homeSpan.poll() directly if homeSpan.start() is used!\n** PROGRAM HALTED **\n\n");
+ LOG0("\n** FATAL ERROR: Do not call homeSpan.poll() directly if homeSpan.start() is used!\n** PROGRAM HALTED **\n\n");
vTaskDelete(pollTaskHandle);
while(1);
}
@@ -177,7 +169,7 @@ void Span::poll() {
void Span::pollTask() {
if(!strlen(category)){
- Serial.print("\n** FATAL ERROR: Cannot start homeSpan polling without an initial call to homeSpan.begin()!\n** PROGRAM HALTED **\n\n");
+ LOG0("\n** FATAL ERROR: Cannot start homeSpan polling without an initial call to homeSpan.begin()!\n** PROGRAM HALTED **\n\n");
while(1);
}
@@ -188,12 +180,12 @@ void Span::pollTask() {
HAPClient::init(); // read NVS and load HAP settings
if(!strlen(network.wifiData.ssid)){
- Serial.print("*** WIFI CREDENTIALS DATA NOT FOUND. ");
+ LOG0("*** WIFI CREDENTIALS DATA NOT FOUND. ");
if(autoStartAPEnabled){
- Serial.print("AUTO-START OF ACCESS POINT ENABLED...\n\n");
+ LOG0("AUTO-START OF ACCESS POINT ENABLED...\n\n");
processSerialCommand("A");
} else {
- Serial.print("YOU MAY CONFIGURE BY TYPING 'W '.\n\n");
+ LOG0("YOU MAY CONFIGURE BY TYPING 'W '.\n\n");
STATUS_UPDATE(start(LED_WIFI_NEEDED),HS_WIFI_NEEDED)
}
} else {
@@ -203,8 +195,7 @@ void Span::pollTask() {
if(controlButton)
controlButton->reset();
- Serial.print(displayName);
- Serial.print(" is READY!\n\n");
+ LOG0("%s is READY!\n\n",displayName);
isInitialized=true;
} // isInitialized
@@ -215,7 +206,7 @@ void Span::pollTask() {
char cBuf[65]="?";
- if(Serial.available()){
+ if(!serialInputDisabled && Serial.available()){
readSerial(cBuf,64);
processSerialCommand(cBuf);
}
@@ -331,11 +322,11 @@ int Span::getFreeSlot(){
void Span::commandMode(){
if(!statusDevice && !statusCallback){
- Serial.print("*** ERROR: CAN'T ENTER COMMAND MODE WITHOUT A DEFINED STATUS LED OR EVENT HANDLER CALLBACK***\n\n");
+ LOG0("*** ERROR: CAN'T ENTER COMMAND MODE WITHOUT A DEFINED STATUS LED OR EVENT HANDLER CALLBACK***\n\n");
return;
}
- Serial.print("*** COMMAND MODE ***\n\n");
+ LOG0("*** COMMAND MODE ***\n\n");
int mode=1;
boolean done=false;
STATUS_UPDATE(start(500,0.3,mode,1000),static_cast(HS_ENTERING_CONFIG_MODE+mode))
@@ -343,9 +334,7 @@ void Span::commandMode(){
while(!done){
if(millis()>alarmTime){
- Serial.print("*** Command Mode: Timed Out (");
- Serial.print(comModeLife/1000);
- Serial.print(" seconds).\n\n");
+ LOG0("*** Command Mode: Timed Out (%ld seconds)",comModeLife/1000);
mode=1;
done=true;
} else
@@ -388,7 +377,7 @@ void Span::commandMode(){
} // switch
- Serial.print("*** EXITING COMMAND MODE ***\n\n");
+ LOG0("*** EXITING COMMAND MODE ***\n\n");
}
//////////////////////////////////////
@@ -416,9 +405,7 @@ void Span::checkConnect(){
waitTime*=2;
if(waitTime==32000){
- Serial.print("\n*** Can't connect to ");
- Serial.print(network.wifiData.ssid);
- Serial.print(". You may type 'W ' to re-configure WiFi, or 'X ' to erase WiFi credentials. Will try connecting again in 60 seconds.\n\n");
+ LOG0("\n*** Can't connect to %s. You may type 'W ' to re-configure WiFi, or 'X ' to erase WiFi credentials. Will try connecting again in 60 seconds.\n\n",network.wifiData.ssid);
waitTime=60000;
} else {
addWebLog(true,"Trying to connect to %s. Waiting %d sec...",network.wifiData.ssid,waitTime/1000);
@@ -462,25 +449,18 @@ void Span::checkConnect(){
sscanf(hostName,"%[A-Za-z0-9-]",d);
if(strlen(hostName)>255|| hostName[0]=='-' || hostName[strlen(hostName)-1]=='-' || strlen(hostName)!=strlen(d)){
- Serial.printf("\n*** Error: Can't start MDNS due to invalid hostname '%s'.\n",hostName);
- Serial.print("*** Hostname must consist of 255 or less alphanumeric characters or a hyphen, except that the hyphen cannot be the first or last character.\n");
- Serial.print("*** PROGRAM HALTED!\n\n");
+ LOG0("\n*** Error: Can't start MDNS due to invalid hostname '%s'.\n",hostName);
+ LOG0("*** Hostname must consist of 255 or less alphanumeric characters or a hyphen, except that the hyphen cannot be the first or last character.\n");
+ LOG0("*** PROGRAM HALTED!\n\n");
while(1);
}
- Serial.print("\nStarting MDNS...\n\n");
- Serial.print("HostName: ");
- Serial.print(hostName);
- Serial.print(".local:");
- Serial.print(tcpPortNum);
- Serial.print("\nDisplay Name: ");
- Serial.print(displayName);
- Serial.print("\nModel Name: ");
- Serial.print(modelName);
- Serial.print("\nSetup ID: ");
- Serial.print(qrID);
- Serial.print("\n\n");
-
+ LOG0("\nStarting MDNS...\n\n");
+ LOG0("HostName: %s.local:%d\n",hostName,tcpPortNum);
+ LOG0("Display Name: %s\n",displayName);
+ LOG0("Model Name: %s\n",modelName);
+ LOG0("Setup ID: %s\n\n",qrID);
+
MDNS.begin(hostName); // set server host name (.local implied)
MDNS.setInstanceName(displayName); // set server display name
MDNS.addService("_hap","_tcp",tcpPortNum); // advertise HAP service on specified port
@@ -529,12 +509,8 @@ void Span::checkConnect(){
ArduinoOTA.onStart(spanOTA.start).onEnd(spanOTA.end).onProgress(spanOTA.progress).onError(spanOTA.error);
ArduinoOTA.begin();
- Serial.print("Starting OTA Server: ");
- Serial.print(displayName);
- Serial.print(" at ");
- Serial.print(WiFi.localIP());
- Serial.print("\nAuthorization Password: ");
- Serial.print(spanOTA.auth?"Enabled\n\n":"DISABLED!\n\n");
+ LOG0("Starting OTA Server: %s at %s\n",displayName,WiFi.localIP().toString().c_str());
+ LOG0("Authorization Password: %s",spanOTA.auth?"Enabled\n\n":"DISABLED!\n\n");
}
mdns_service_txt_item_set("_hap","_tcp","ota",spanOTA.enabled?"yes":"no"); // OTA status (info only - NOT used by HAP)
@@ -542,18 +518,19 @@ void Span::checkConnect(){
if(webLog.isEnabled){
mdns_service_txt_item_set("_hap","_tcp","logURL",webLog.statusURL.c_str()+4); // Web Log status (info only - NOT used by HAP)
- Serial.printf("Web Logging enabled at http://%s.local:%d%swith max number of entries=%d\n\n",hostName,tcpPortNum,webLog.statusURL.c_str()+4,webLog.maxEntries);
- webLog.initTime();
+ LOG0("Web Logging enabled at http://%s.local:%d%swith max number of entries=%d\n\n",hostName,tcpPortNum,webLog.statusURL.c_str()+4,webLog.maxEntries);
+ if(webLog.timeServer)
+ xTaskCreateUniversal(webLog.initTime, "timeSeverTaskHandle", 8096, &webLog, 1, NULL, 0);
}
- Serial.printf("Starting HAP Server on port %d supporting %d simultaneous HomeKit Controller Connections...\n",tcpPortNum,maxConnections);
+ LOG0("Starting HAP Server on port %d supporting %d simultaneous HomeKit Controller Connections...\n\n",tcpPortNum,maxConnections);
hapServer->begin();
- Serial.print("\n");
+ LOG0("\n");
if(!HAPClient::nAdminControllers())
- Serial.print("DEVICE NOT YET PAIRED -- PLEASE PAIR WITH HOMEKIT APP\n\n");
+ LOG0("DEVICE NOT YET PAIRED -- PLEASE PAIR WITH HOMEKIT APP\n\n");
if(wifiCallback)
wifiCallback();
@@ -581,48 +558,40 @@ void Span::processSerialCommand(const char *c){
case 's': {
- Serial.print("\n*** HomeSpan Status ***\n\n");
+ LOG0("\n*** HomeSpan Status ***\n\n");
- Serial.print("IP Address: ");
- Serial.print(WiFi.localIP());
- Serial.print("\n\n");
- Serial.print("Accessory ID: ");
+ LOG0("IP Address: %s\n\n",WiFi.localIP().toString().c_str());
+ LOG0("Accessory ID: ");
HAPClient::charPrintRow(HAPClient::accessory.ID,17);
- Serial.print(" LTPK: ");
+ LOG0(" LTPK: ");
HAPClient::hexPrintRow(HAPClient::accessory.LTPK,32);
- Serial.print("\n");
+ LOG0("\n");
HAPClient::printControllers();
- Serial.print("\n");
+ LOG0("\n");
for(int i=0;iclient){
- Serial.print(hap[i]->client.remoteIP());
- Serial.print(" on Socket ");
- Serial.print(hap[i]->client.fd()-LWIP_SOCKET_OFFSET+1);
- Serial.print("/");
- Serial.print(CONFIG_LWIP_MAX_SOCKETS);
+ LOG0("%s on Socket %d/%d",hap[i]->client.remoteIP().toString().c_str(),hap[i]->client.fd()-LWIP_SOCKET_OFFSET+1,CONFIG_LWIP_MAX_SOCKETS);
if(hap[i]->cPair){
- Serial.print(" ID=");
+ LOG0(" ID=");
HAPClient::charPrintRow(hap[i]->cPair->ID,36);
- Serial.print(hap[i]->cPair->admin?" (admin)":" (regular)");
+ LOG0(hap[i]->cPair->admin?" (admin)":" (regular)");
} else {
- Serial.print(" (unverified)");
+ LOG0(" (unverified)");
}
} else {
- Serial.print("(unconnected)");
+ LOG0("(unconnected)");
}
- Serial.print("\n");
+ LOG0("\n");
}
- Serial.print("\n*** End Status ***\n\n");
+ LOG0("\n*** End Status ***\n\n");
}
break;
@@ -631,13 +600,9 @@ void Span::processSerialCommand(const char *c){
TempBuffer qBuf(sprintfAttributes(NULL)+1);
sprintfAttributes(qBuf.buf);
- Serial.print("\n*** Attributes Database: size=");
- Serial.print(qBuf.len()-1);
- Serial.print(" configuration=");
- Serial.print(hapConfig.configNumber);
- Serial.print(" ***\n\n");
+ LOG0("\n*** Attributes Database: size=%d configuration=%d ***\n\n",qBuf.len()-1,hapConfig.configNumber);
prettyPrint(qBuf.buf);
- Serial.print("\n*** End Database ***\n\n");
+ LOG0("\n*** End Database ***\n\n");
}
break;
@@ -648,15 +613,11 @@ void Span::processSerialCommand(const char *c){
if(strlen(s)==4 && strlen(tBuf)==4){
sprintf(qrID,"%s",tBuf);
- Serial.print("\nChanging default Setup ID for QR Code to: '");
- Serial.print(qrID);
- Serial.print("'. Will take effect after next restart.\n\n");
- nvs_set_str(HAPClient::hapNVS,"SETUPID",qrID); // update data
+ LOG0("\nChanging default Setup ID for QR Code to: '%s'. Will take effect after next restart.\n\n",qrID);
+ nvs_set_str(HAPClient::hapNVS,"SETUPID",qrID);
nvs_commit(HAPClient::hapNVS);
} else {
- Serial.print("\n*** Invalid request to change Setup ID for QR Code to: '");
- Serial.print(s);
- Serial.print("'. Setup ID must be exactly 4 alphanumeric characters (0-9, A-Z, and a-z).\n\n");
+ LOG0("\n*** Invalid request to change Setup ID for QR Code to: '%s'. Setup ID must be exactly 4 alphanumeric characters (0-9, A-Z, and a-z).\n\n",s);
}
}
break;
@@ -665,35 +626,33 @@ void Span::processSerialCommand(const char *c){
char textPwd[34]="\0";
- Serial.print("\n>>> New OTA Password, or to cancel request: ");
+ LOG0("\n>>> New OTA Password, or to cancel request: ");
readSerial(textPwd,33);
if(strlen(textPwd)==0){
- Serial.print("(cancelled)\n\n");
+ LOG0("(cancelled)\n\n");
return;
}
if(strlen(textPwd)==33){
- Serial.print("\n*** Sorry, 32 character limit - request cancelled\n\n");
+ LOG0("\n*** Sorry, 32 character limit - request cancelled\n\n");
return;
}
- Serial.print(mask(textPwd,2));
- Serial.print("\n");
+ LOG0("%s\n",mask(textPwd,2).c_str());
spanOTA.setPassword(textPwd);
nvs_set_str(otaNVS,"OTADATA",spanOTA.otaPwd); // update data
nvs_commit(otaNVS);
- Serial.print("... Accepted! Password change will take effect after next restart.\n");
+ LOG0("... Accepted! Password change will take effect after next restart.\n");
if(!spanOTA.enabled)
- Serial.print("... Note: OTA has not been enabled in this sketch.\n");
- Serial.print("\n");
+ LOG0("... Note: OTA has not been enabled in this sketch.\n");
+ LOG0("\n");
}
break;
case 'S': {
- char buf[128];
char setupCode[10];
struct { // temporary structure to hold SRP verification code and salt stored in NVS
@@ -704,23 +663,19 @@ void Span::processSerialCommand(const char *c){
sscanf(c+1," %9[0-9]",setupCode);
if(strlen(setupCode)!=8){
- Serial.print("\n*** Invalid request to change Setup Code. Code must be exactly 8 digits.\n\n");
+ LOG0("\n*** Invalid request to change Setup Code. Code must be exactly 8 digits.\n\n");
} else
if(!network.allowedCode(setupCode)){
- Serial.print("\n*** Invalid request to change Setup Code. Code too simple.\n\n");
+ LOG0("\n*** Invalid request to change Setup Code. Code too simple.\n\n");
} else {
- sprintf(buf,"\n\nGenerating SRP verification data for new Setup Code: %.3s-%.2s-%.3s ... ",setupCode,setupCode+3,setupCode+5);
- Serial.print(buf);
+ LOG0("\nGenerating SRP verification data for new Setup Code: %.3s-%.2s-%.3s ... ",setupCode,setupCode+3,setupCode+5);
HAPClient::srp.createVerifyCode(setupCode,verifyData.verifyCode,verifyData.salt); // create verification code from default Setup Code and random salt
nvs_set_blob(HAPClient::srpNVS,"VERIFYDATA",&verifyData,sizeof(verifyData)); // update data
nvs_commit(HAPClient::srpNVS); // commit to NVS
- Serial.print("New Code Saved!\n");
-
- Serial.print("Setup Payload for Optional QR Code: ");
- Serial.print(qrCode.get(atoi(setupCode),qrID,atoi(category)));
- Serial.print("\n\n");
+ LOG0("New Code Saved!\n");
+ LOG0("Setup Payload for Optional QR Code: %s\n\n",qrCode.get(atoi(setupCode),qrID,atoi(category)));
}
}
break;
@@ -730,7 +685,7 @@ void Span::processSerialCommand(const char *c){
HAPClient::removeControllers(); // clear all Controller data
nvs_set_blob(HAPClient::hapNVS,"CONTROLLERS",HAPClient::controllers,sizeof(HAPClient::controllers)); // update data
nvs_commit(HAPClient::hapNVS); // commit to NVS
- Serial.print("\n*** HomeSpan Pairing Data DELETED ***\n\n");
+ LOG0("\n*** HomeSpan Pairing Data DELETED ***\n\n");
for(int i=0;iclient){ // if slot is connected
@@ -741,7 +696,7 @@ void Span::processSerialCommand(const char *c){
}
}
- Serial.print("\nDEVICE NOT YET PAIRED -- PLEASE PAIR WITH HOMEKIT APP\n\n");
+ LOG0("\nDEVICE NOT YET PAIRED -- PLEASE PAIR WITH HOMEKIT APP\n\n");
mdns_service_txt_item_set("_hap","_tcp","sf","1"); // set Status Flag = 1 (Table 6-8)
if(homeSpan.pairCallback)
@@ -753,8 +708,11 @@ void Span::processSerialCommand(const char *c){
case 'W': {
+ if(serialInputDisabled || logLevel<0) // do not proceed if serial input/output is not fully enabled
+ return;
+
if(strlen(network.wifiData.ssid)>0){
- Serial.print("*** Stopping all current WiFi services...\n\n");
+ LOG0("*** Stopping all current WiFi services...\n\n");
hapServer->end();
MDNS.end();
WiFi.disconnect();
@@ -763,7 +721,7 @@ void Span::processSerialCommand(const char *c){
network.serialConfigure();
nvs_set_blob(wifiNVS,"WIFIDATA",&network.wifiData,sizeof(network.wifiData)); // update data
nvs_commit(wifiNVS); // commit to NVS
- Serial.print("\n*** WiFi Credentials SAVED! Restarting ***\n\n");
+ LOG0("\n*** WiFi Credentials SAVED! Restarting ***\n\n");
reboot();
}
break;
@@ -771,7 +729,7 @@ void Span::processSerialCommand(const char *c){
case 'A': {
if(strlen(network.wifiData.ssid)>0){
- Serial.print("*** Stopping all current WiFi services...\n\n");
+ LOG0("*** Stopping all current WiFi services...\n\n");
hapServer->end();
MDNS.end();
WiFi.disconnect();
@@ -785,16 +743,16 @@ void Span::processSerialCommand(const char *c){
network.apConfigure();
nvs_set_blob(wifiNVS,"WIFIDATA",&network.wifiData,sizeof(network.wifiData)); // update data
nvs_commit(wifiNVS); // commit to NVS
- Serial.print("\n*** Credentials saved!\n");
+ LOG0("\n*** Credentials saved!\n");
if(strlen(network.setupCode)){
char s[10];
sprintf(s,"S%s",network.setupCode);
processSerialCommand(s);
} else {
- Serial.print("*** Setup Code Unchanged\n");
+ LOG0("*** Setup Code Unchanged\n");
}
- Serial.print("\n*** Restarting...\n\n");
+ LOG0("\n*** Restarting...\n\n");
STATUS_UPDATE(start(LED_ALERT),HS_AP_TERMINATED)
reboot();
}
@@ -805,7 +763,7 @@ void Span::processSerialCommand(const char *c){
nvs_erase_all(wifiNVS);
nvs_commit(wifiNVS);
WiFi.begin("none");
- Serial.print("\n*** WiFi Credentials ERASED! Restarting...\n\n");
+ LOG0("\n*** WiFi Credentials ERASED! Restarting...\n\n");
reboot();
}
break;
@@ -814,7 +772,7 @@ void Span::processSerialCommand(const char *c){
nvs_erase_all(charNVS);
nvs_commit(charNVS);
- Serial.print("\n*** Values for all saved Characteristics erased!\n\n");
+ LOG0("\n*** Values for all saved Characteristics erased!\n\n");
}
break;
@@ -822,7 +780,7 @@ void Span::processSerialCommand(const char *c){
nvs_erase_all(HAPClient::hapNVS);
nvs_commit(HAPClient::hapNVS);
- Serial.print("\n*** HomeSpan Device ID and Pairing Data DELETED! Restarting...\n\n");
+ LOG0("\n*** HomeSpan Device ID and Pairing Data DELETED! Restarting...\n\n");
reboot();
}
break;
@@ -844,7 +802,7 @@ void Span::processSerialCommand(const char *c){
nvs_erase_all(otaNVS);
nvs_commit(otaNVS);
WiFi.begin("none");
- Serial.print("\n*** FACTORY RESET! Restarting...\n\n");
+ LOG0("\n*** FACTORY RESET! Restarting...\n\n");
reboot();
}
break;
@@ -852,7 +810,7 @@ void Span::processSerialCommand(const char *c){
case 'E': {
nvs_flash_erase();
- Serial.print("\n*** ALL DATA ERASED! Restarting...\n\n");
+ LOG0("\n*** ALL DATA ERASED! Restarting...\n\n");
reboot();
}
break;
@@ -862,27 +820,30 @@ void Span::processSerialCommand(const char *c){
int level=0;
sscanf(c+1,"%d",&level);
- if(level<0)
- level=0;
+ if(level<-1)
+ level=-1;
if(level>2)
level=2;
- Serial.print("\n*** Log Level set to ");
- Serial.print(level);
- Serial.print("\n\n");
- delay(1000);
setLogLevel(level);
+ LOG0("\n*** Log Level set to %d\n\n",level);
}
break;
case 'm': {
- Serial.printf("Free Memory: %d bytes\n",heap_caps_get_free_size(MALLOC_CAP_DEFAULT));
+ multi_heap_info_t heapInfo;
+ heap_caps_get_info(&heapInfo,MALLOC_CAP_INTERNAL);
+ LOG0("Total Heap=%d ",heapInfo.total_free_bytes);
+ heap_caps_get_info(&heapInfo,MALLOC_CAP_DEFAULT);
+ LOG0("DRAM-Capable=%d ",heapInfo.total_free_bytes);
+ heap_caps_get_info(&heapInfo,MALLOC_CAP_EXEC);
+ LOG0("IRAM-Capable=%d\n",heapInfo.total_free_bytes);
}
break;
case 'i':{
- Serial.print("\n*** HomeSpan Info ***\n\n");
+ LOG0("\n*** HomeSpan Info ***\n\n");
int nErrors=0;
int nWarnings=0;
@@ -891,25 +852,24 @@ void Span::processSerialCommand(const char *c){
char pNames[][7]={"PR","PW","EV","AA","TW","HD","WR"};
for(auto acc=Accessories.begin(); acc!=Accessories.end(); acc++){
- Serial.printf("\u27a4 Accessory: AID=%d\n",(*acc)->aid);
+ LOG0("\u27a4 Accessory: AID=%d\n",(*acc)->aid);
boolean foundInfo=false;
if(acc==Accessories.begin() && (*acc)->aid!=1)
- Serial.printf(" *** ERROR #%d! AID of first Accessory must always be 1 ***\n",++nErrors);
+ LOG0(" *** ERROR #%d! AID of first Accessory must always be 1 ***\n",++nErrors);
if(aidValues.find((*acc)->aid)!=aidValues.end())
- Serial.printf(" *** ERROR #%d! AID already in use for another Accessory ***\n",++nErrors);
+ LOG0(" *** ERROR #%d! AID already in use for another Accessory ***\n",++nErrors);
aidValues.insert((*acc)->aid);
for(auto svc=(*acc)->Services.begin(); svc!=(*acc)->Services.end(); svc++){
- Serial.printf(" \u279f Service %s: IID=%d, %sUUID=\"%s\"",(*svc)->hapName,(*svc)->iid,(*svc)->isCustom?"Custom-":"",(*svc)->type);
- Serial.printf("\n");
+ LOG0(" \u279f Service %s: IID=%d, %sUUID=\"%s\"\n",(*svc)->hapName,(*svc)->iid,(*svc)->isCustom?"Custom-":"",(*svc)->type);
if(!strcmp((*svc)->type,"3E")){
foundInfo=true;
if((*svc)->iid!=1)
- Serial.printf(" *** ERROR #%d! The Accessory Information Service must be defined before any other Services in an Accessory ***\n",++nErrors);
+ LOG0(" *** ERROR #%d! The Accessory Information Service must be defined before any other Services in an Accessory ***\n",++nErrors);
}
else if((*acc)->aid==1) // this is an Accessory with aid=1, but it has more than just AccessoryInfo. So...
isBridge=false; // ...this is not a bridge device
@@ -917,45 +877,45 @@ void Span::processSerialCommand(const char *c){
unordered_set hapChar;
for(auto chr=(*svc)->Characteristics.begin(); chr!=(*svc)->Characteristics.end(); chr++){
- Serial.printf(" \u21e8 Characteristic %s(%s): IID=%d, %sUUID=\"%s\", %sPerms=",
+ LOG0(" \u21e8 Characteristic %s(%s): IID=%d, %sUUID=\"%s\", %sPerms=",
(*chr)->hapName,(*chr)->uvPrint((*chr)->value).c_str(),(*chr)->iid,(*chr)->isCustom?"Custom-":"",(*chr)->type,(*chr)->perms!=(*chr)->hapChar->perms?"Custom-":"");
int foundPerms=0;
for(uint8_t i=0;i<7;i++){
if((*chr)->perms & (1<format!=FORMAT::STRING && (*chr)->format!=FORMAT::BOOL && (*chr)->format!=FORMAT::DATA){
if((*chr)->validValues)
- Serial.printf(", Valid Values=%s",(*chr)->validValues);
+ LOG0(", Valid Values=%s",(*chr)->validValues);
else if((*chr)->uvGet((*chr)->stepValue)>0)
- Serial.printf(", %sRange=[%s,%s,%s]",(*chr)->customRange?"Custom-":"",(*chr)->uvPrint((*chr)->minValue).c_str(),(*chr)->uvPrint((*chr)->maxValue).c_str(),(*chr)->uvPrint((*chr)->stepValue).c_str());
+ LOG0(", %sRange=[%s,%s,%s]",(*chr)->customRange?"Custom-":"",(*chr)->uvPrint((*chr)->minValue).c_str(),(*chr)->uvPrint((*chr)->maxValue).c_str(),(*chr)->uvPrint((*chr)->stepValue).c_str());
else
- Serial.printf(", %sRange=[%s,%s]",(*chr)->customRange?"Custom-":"",(*chr)->uvPrint((*chr)->minValue).c_str(),(*chr)->uvPrint((*chr)->maxValue).c_str());
+ LOG0(", %sRange=[%s,%s]",(*chr)->customRange?"Custom-":"",(*chr)->uvPrint((*chr)->minValue).c_str(),(*chr)->uvPrint((*chr)->maxValue).c_str());
}
if((*chr)->nvsKey)
- Serial.printf(" (nvs)");
- Serial.printf("\n");
+ LOG0(" (nvs)");
+ LOG0("\n");
if(!(*chr)->isCustom && !(*svc)->isCustom && (*svc)->req.find((*chr)->hapChar)==(*svc)->req.end() && (*svc)->opt.find((*chr)->hapChar)==(*svc)->opt.end())
- Serial.printf(" *** WARNING #%d! Service does not support this Characteristic ***\n",++nWarnings);
+ LOG0(" *** WARNING #%d! Service does not support this Characteristic ***\n",++nWarnings);
else
if(invalidUUID((*chr)->type,(*chr)->isCustom))
- Serial.printf(" *** ERROR #%d! Format of UUID is invalid ***\n",++nErrors);
+ LOG0(" *** ERROR #%d! Format of UUID is invalid ***\n",++nErrors);
else
if(hapChar.find((*chr)->hapChar)!=hapChar.end())
- Serial.printf(" *** ERROR #%d! Characteristic already defined for this Service ***\n",++nErrors);
+ LOG0(" *** ERROR #%d! Characteristic already defined for this Service ***\n",++nErrors);
if((*chr)->setRangeError)
- Serial.printf(" *** WARNING #%d! Attempt to set Custom Range for this Characteristic ignored ***\n",++nWarnings);
+ LOG0(" *** WARNING #%d! Attempt to set Custom Range for this Characteristic ignored ***\n",++nWarnings);
if((*chr)->setValidValuesError)
- Serial.printf(" *** WARNING #%d! Attempt to set Custom Valid Values for this Characteristic ignored ***\n",++nWarnings);
+ LOG0(" *** WARNING #%d! Attempt to set Custom Valid Values for this Characteristic ignored ***\n",++nWarnings);
if((*chr)->format!=STRING && ((*chr)->uvGet((*chr)->value) < (*chr)->uvGet((*chr)->minValue) || (*chr)->uvGet((*chr)->value) > (*chr)->uvGet((*chr)->maxValue)))
- Serial.printf(" *** WARNING #%d! Value of %g is out of range [%g,%g] ***\n",++nWarnings,(*chr)->uvGet((*chr)->value),(*chr)->uvGet((*chr)->minValue),(*chr)->uvGet((*chr)->maxValue));
+ LOG0(" *** WARNING #%d! Value of %g is out of range [%g,%g] ***\n",++nWarnings,(*chr)->uvGet((*chr)->value),(*chr)->uvGet((*chr)->minValue),(*chr)->uvGet((*chr)->maxValue));
hapChar.insert((*chr)->hapChar);
@@ -963,65 +923,65 @@ void Span::processSerialCommand(const char *c){
for(auto req=(*svc)->req.begin(); req!=(*svc)->req.end(); req++){
if(hapChar.find(*req)==hapChar.end())
- Serial.printf(" *** WARNING #%d! Required '%s' Characteristic for this Service not found ***\n",++nWarnings,(*req)->hapName);
+ LOG0(" *** WARNING #%d! Required '%s' Characteristic for this Service not found ***\n",++nWarnings,(*req)->hapName);
}
for(auto button=PushButtons.begin(); button!=PushButtons.end(); button++){
if((*button)->service==(*svc)){
- if((*button)->buttonType==SpanButton::BUTTON)
- Serial.printf(" \u25bc SpanButton: Pin=%d, Single=%ums, Double=%ums, Long=%ums, Type=",(*button)->pin,(*button)->singleTime,(*button)->doubleTime,(*button)->longTime);
+ if((*button)->buttonType==SpanButton::HS_BUTTON)
+ LOG0(" \u25bc SpanButton: Pin=%d, Single=%ums, Double=%ums, Long=%ums, Type=",(*button)->pin,(*button)->singleTime,(*button)->doubleTime,(*button)->longTime);
else
- Serial.printf(" \u25bc SpanToggle: Pin=%d, Toggle=%ums, Type=",(*button)->pin,(*button)->longTime);
+ LOG0(" \u25bc SpanToggle: Pin=%d, Toggle=%ums, Type=",(*button)->pin,(*button)->longTime);
if((*button)->triggerType==PushButton::TRIGGER_ON_LOW)
- Serial.printf("TRIGGER_ON_LOW\n");
+ LOG0("TRIGGER_ON_LOW\n");
else if((*button)->triggerType==PushButton::TRIGGER_ON_HIGH)
- Serial.printf("TRIGGER_ON_HIGH\n");
+ LOG0("TRIGGER_ON_HIGH\n");
#if SOC_TOUCH_SENSOR_NUM > 0
else if((*button)->triggerType==PushButton::TRIGGER_ON_TOUCH)
- Serial.printf("TRIGGER_ON_TOUCH\n");
+ LOG0("TRIGGER_ON_TOUCH\n");
#endif
else
- Serial.printf("USER-DEFINED\n");
+ LOG0("USER-DEFINED\n");
if((void(*)(int,int))((*svc)->*(&SpanService::button))==(void(*)(int,int))(&SpanService::button))
- Serial.printf(" *** WARNING #%d! No button() method defined in this Service ***\n",++nWarnings);
+ LOG0(" *** WARNING #%d! No button() method defined in this Service ***\n",++nWarnings);
}
}
} // Services
if(!foundInfo)
- Serial.printf(" *** ERROR #%d! Required 'AccessoryInformation' Service not found ***\n",++nErrors);
+ LOG0(" *** ERROR #%d! Required 'AccessoryInformation' Service not found ***\n",++nErrors);
} // Accessories
- Serial.printf("\nConfigured as Bridge: %s\n",isBridge?"YES":"NO");
+ LOG0("\nConfigured as Bridge: %s\n",isBridge?"YES":"NO");
if(hapConfig.configNumber>0)
- Serial.printf("Configuration Number: %d\n",hapConfig.configNumber);
- Serial.printf("\nDatabase Validation: Warnings=%d, Errors=%d\n\n",nWarnings,nErrors);
+ LOG0("Configuration Number: %d\n",hapConfig.configNumber);
+ LOG0("\nDatabase Validation: Warnings=%d, Errors=%d\n\n",nWarnings,nErrors);
char d[]="------------------------------";
- Serial.printf("%-30s %8s %10s %s %s %s %s %s\n","Service","UUID","AID","IID","Update","Loop","Button","Linked Services");
- Serial.printf("%.30s %.8s %.10s %.3s %.6s %.4s %.6s %.15s\n",d,d,d,d,d,d,d,d);
+ LOG0("%-30s %8s %10s %s %s %s %s %s\n","Service","UUID","AID","IID","Update","Loop","Button","Linked Services");
+ LOG0("%.30s %.8s %.10s %.3s %.6s %.4s %.6s %.15s\n",d,d,d,d,d,d,d,d);
for(int i=0;iServices.size();j++){
SpanService *s=Accessories[i]->Services[j];
- Serial.printf("%-30s %8.8s %10u %3d %6s %4s %6s ",s->hapName,s->type,Accessories[i]->aid,s->iid,
+ LOG0("%-30s %8.8s %10u %3d %6s %4s %6s ",s->hapName,s->type,Accessories[i]->aid,s->iid,
(void(*)())(s->*(&SpanService::update))!=(void(*)())(&SpanService::update)?"YES":"NO",
(void(*)())(s->*(&SpanService::loop))!=(void(*)())(&SpanService::loop)?"YES":"NO",
(void(*)(int,boolean))(s->*(&SpanService::button))!=(void(*)(int,boolean))(&SpanService::button)?"YES":"NO"
);
if(s->linkedServices.empty())
- Serial.print("-");
+ LOG0("-");
for(int k=0;klinkedServices.size();k++){
- Serial.print(s->linkedServices[k]->iid);
+ LOG0("%d",s->linkedServices[k]->iid);
if(klinkedServices.size()-1)
- Serial.print(",");
+ LOG0(",");
}
- Serial.print("\n");
+ LOG0("\n");
}
}
@@ -1029,75 +989,75 @@ void Span::processSerialCommand(const char *c){
uint8_t channel;
wifi_second_chan_t channel2;
esp_wifi_get_channel(&channel,&channel2);
- Serial.printf("\nFound %d SpanPoint Links:\n\n",SpanPoint::SpanPoints.size());
- Serial.printf("%-17s %18s %7s %7s %7s\n","Local MAC Address","Remote MAC Address","Send","Receive","Depth");
- Serial.printf("%.17s %.18s %.7s %.7s %.7s\n",d,d,d,d,d);
+ LOG0("\nFound %d SpanPoint Links:\n\n",SpanPoint::SpanPoints.size());
+ LOG0("%-17s %18s %7s %7s %7s\n","Local MAC Address","Remote MAC Address","Send","Receive","Depth");
+ LOG0("%.17s %.18s %.7s %.7s %.7s\n",d,d,d,d,d);
for(auto it=SpanPoint::SpanPoints.begin();it!=SpanPoint::SpanPoints.end();it++)
- Serial.printf("%-18s %02X:%02X:%02X:%02X:%02X:%02X %7d %7d %7d\n",(*it)->peerInfo.ifidx==WIFI_IF_AP?WiFi.softAPmacAddress().c_str():WiFi.macAddress().c_str(),
+ LOG0("%-18s %02X:%02X:%02X:%02X:%02X:%02X %7d %7d %7d\n",(*it)->peerInfo.ifidx==WIFI_IF_AP?WiFi.softAPmacAddress().c_str():WiFi.macAddress().c_str(),
(*it)->peerInfo.peer_addr[0],(*it)->peerInfo.peer_addr[1],(*it)->peerInfo.peer_addr[2],(*it)->peerInfo.peer_addr[3],(*it)->peerInfo.peer_addr[4],(*it)->peerInfo.peer_addr[5],
(*it)->sendSize,(*it)->receiveSize,uxQueueSpacesAvailable((*it)->receiveQueue));
- Serial.printf("\nSpanPoint using WiFi Channel %d%s\n",channel,WiFi.status()!=WL_CONNECTED?" (subject to change once WiFi connection established)":"");
+ LOG0("\nSpanPoint using WiFi Channel %d%s\n",channel,WiFi.status()!=WL_CONNECTED?" (subject to change once WiFi connection established)":"");
}
- Serial.print("\n*** End Info ***\n\n");
+ LOG0("\n*** End Info ***\n\n");
}
break;
case 'P': {
- Serial.printf("\n*** Pairing Data used for Cloning another Device\n\n");
+ LOG0("\n*** Pairing Data used for Cloning another Device\n\n");
size_t olen;
TempBuffer tBuf(256);
mbedtls_base64_encode((uint8_t *)tBuf.buf,256,&olen,(uint8_t *)&HAPClient::accessory,sizeof(struct Accessory));
- Serial.printf("Accessory data: %s\n",tBuf.buf);
+ LOG0("Accessory data: %s\n",tBuf.buf);
for(int i=0;i tBuf(200);
size_t olen;
tBuf.buf[0]='\0';
- Serial.print(">>> Accessory data: ");
+ LOG0(">>> Accessory data: ");
readSerial(tBuf.buf,199);
if(strlen(tBuf.buf)==0){
- Serial.printf("(cancelled)\n\n");
+ LOG0("(cancelled)\n\n");
return;
}
mbedtls_base64_decode((uint8_t *)&HAPClient::accessory,sizeof(struct Accessory),&olen,(uint8_t *)tBuf.buf,strlen(tBuf.buf));
if(olen!=sizeof(struct Accessory)){
- Serial.printf("\n*** Error in size of Accessory data - cloning cancelled. Restarting...\n\n");
+ LOG0("\n*** Error in size of Accessory data - cloning cancelled. Restarting...\n\n");
reboot();
} else {
HAPClient::charPrintRow(HAPClient::accessory.ID,17);
- Serial.printf("\n");
+ LOG0("\n");
}
for(int i=0;i>> Controller data: ");
+ LOG0(">>> Controller data: ");
readSerial(tBuf.buf,199);
if(strlen(tBuf.buf)==0){
- Serial.printf("(done)\n");
+ LOG0("(done)\n");
while(i - change the HomeKit Pairing Setup Code to \n");
- Serial.print(" Q - change the HomeKit Setup ID for QR Codes to \n");
- Serial.print(" O - change the OTA password\n");
- Serial.print(" A - start the HomeSpan Setup Access Point\n");
- Serial.print("\n");
- Serial.print(" V - delete value settings for all saved Characteristics\n");
- Serial.print(" U - unpair device by deleting all Controller data\n");
- Serial.print(" H - delete HomeKit Device ID as well as all Controller data and restart\n");
- Serial.print("\n");
- Serial.print(" P - output Pairing Data that can be saved offline to clone a new device\n");
- Serial.print(" C - clone Pairing Data previously saved offline from another device\n");
- Serial.print("\n");
- Serial.print(" R - restart device\n");
- Serial.print(" F - factory reset and restart\n");
- Serial.print(" E - erase ALL stored data and restart\n");
- Serial.print("\n");
- Serial.print(" L - change the Log Level setting to \n");
- Serial.print("\n");
+ LOG0("\n*** HomeSpan Commands ***\n\n");
+ LOG0(" s - print connection status\n");
+ LOG0(" i - print summary information about the HAP Database\n");
+ LOG0(" d - print the full HAP Accessory Attributes Database in JSON format\n");
+ LOG0(" m - print free heap memory\n");
+ LOG0("\n");
+ LOG0(" W - configure WiFi Credentials and restart\n");
+ LOG0(" X - delete WiFi Credentials and restart\n");
+ LOG0(" S - change the HomeKit Pairing Setup Code to \n");
+ LOG0(" Q - change the HomeKit Setup ID for QR Codes to \n");
+ LOG0(" O - change the OTA password\n");
+ LOG0(" A - start the HomeSpan Setup Access Point\n");
+ LOG0("\n");
+ LOG0(" V - delete value settings for all saved Characteristics\n");
+ LOG0(" U - unpair device by deleting all Controller data\n");
+ LOG0(" H - delete HomeKit Device ID as well as all Controller data and restart\n");
+ LOG0("\n");
+ LOG0(" P - output Pairing Data that can be saved offline to clone a new device\n");
+ LOG0(" C - clone Pairing Data previously saved offline from another device\n");
+ LOG0("\n");
+ LOG0(" R - restart device\n");
+ LOG0(" F - factory reset and restart\n");
+ LOG0(" E - erase ALL stored data and restart\n");
+ LOG0("\n");
+ LOG0(" L - change the Log Level setting to \n");
+ LOG0("\n");
for(auto uCom=homeSpan.UserCommands.begin(); uCom!=homeSpan.UserCommands.end(); uCom++) // loop over all UserCommands using an iterator
- Serial.printf(" @%c %s\n",uCom->first,uCom->second->s);
+ LOG0(" @%c %s\n",uCom->first,uCom->second->s);
if(!homeSpan.UserCommands.empty())
- Serial.print("\n");
+ LOG0("\n");
- Serial.print(" ? - print this list of commands\n\n");
- Serial.print("*** End Commands ***\n\n");
+ LOG0(" ? - print this list of commands\n\n");
+ LOG0("*** End Commands ***\n\n");
}
break;
@@ -1173,17 +1133,13 @@ void Span::processSerialCommand(const char *c){
else
uCom->second->userFunction2(c+1,uCom->second->userArg);
} else {
- Serial.print("*** Undefined user command: '");
- Serial.print(c);
- Serial.print("'. Type '?' for list of commands.\n");
+ LOG0("*** Undefined user command: '%s'. Type '?' for list of commands.\n",c);
}
}
break;
default:
- Serial.print("*** Unknown command: '");
- Serial.print(c);
- Serial.print("'. Type '?' for list of commands.\n");
+ LOG0("*** Unknown command: '%s'. Type '?' for list of commands.\n",c);
break;
} // switch
@@ -1270,7 +1226,11 @@ int Span::sprintfAttributes(char *cBuf, int flags){
///////////////////////////////
-void Span::prettyPrint(char *buf, int nsp){
+void Span::prettyPrint(char *buf, int nsp, int minLogLevel){
+
+ if(logLevelTimedWrites[pid]){
- Serial.print("\n*** ERROR: Timed Write Expired\n\n");
+ LOG0("\n*** ERROR: Timed Write Expired\n\n");
twFail=true;
}
} else {
- Serial.print("\n*** ERROR: Problems parsing JSON characteristics object - unexpected property \"");
- Serial.print(t2);
- Serial.print("\"\n\n");
+ LOG0("\n*** ERROR: Problems parsing JSON characteristics object - unexpected property \"%s\"\n\n",t2);
return(0);
}
} // parse property tokens
@@ -1432,7 +1388,7 @@ int Span::updateCharacteristics(char *buf, SpanBuf *pObj){
if(okay==7 || okay==11 || okay==15){ // all required properties found
nObj++; // increment number of characteristic objects found
} else {
- Serial.print("\n*** ERROR: Problems parsing JSON characteristics object - missing required properties\n\n");
+ LOG0("\n*** ERROR: Problems parsing JSON characteristics object - missing required properties\n\n");
return(0);
}
}
@@ -1661,9 +1617,7 @@ SpanAccessory::SpanAccessory(uint32_t aid){
if(!homeSpan.Accessories.empty()){
if(homeSpan.Accessories.size()==HAPClient::MAX_ACCESSORIES){
- Serial.print("\n\n*** FATAL ERROR: Can't create more than ");
- Serial.print(HAPClient::MAX_ACCESSORIES);
- Serial.print(" Accessories. Program Halting.\n\n");
+ LOG0("\n\n*** FATAL ERROR: Can't create more than %d Accessories. Program Halting.\n\n",HAPClient::MAX_ACCESSORIES);
while(1);
}
@@ -1720,8 +1674,8 @@ int SpanAccessory::sprintfAttributes(char *cBuf, int flags){
SpanService::SpanService(const char *type, const char *hapName, boolean isCustom){
if(homeSpan.Accessories.empty()){
- Serial.printf("\nFATAL ERROR! Can't create new Service '%s' without a defined Accessory ***\n",hapName);
- Serial.printf("\n=== PROGRAM HALTED ===");
+ LOG0("\nFATAL ERROR! Can't create new Service '%s' without a defined Accessory ***\n",hapName);
+ LOG0("\n=== PROGRAM HALTED ===");
while(1);
}
@@ -1838,8 +1792,8 @@ SpanCharacteristic::SpanCharacteristic(HapChar *hapChar, boolean isCustom){
this->hapChar=hapChar;
if(homeSpan.Accessories.empty() || homeSpan.Accessories.back()->Services.empty()){
- Serial.printf("\nFATAL ERROR! Can't create new Characteristic '%s' without a defined Service ***\n",hapName);
- Serial.printf("\n=== PROGRAM HALTED ===");
+ LOG0("\nFATAL ERROR! Can't create new Characteristic '%s' without a defined Service ***\n",hapName);
+ LOG0("\n=== PROGRAM HALTED ===");
while(1);
}
@@ -2103,8 +2057,8 @@ SpanCharacteristic *SpanCharacteristic::setValidValues(int n, ...){
SpanRange::SpanRange(int min, int max, int step){
if(homeSpan.Accessories.empty() || homeSpan.Accessories.back()->Services.empty() || homeSpan.Accessories.back()->Services.back()->Characteristics.empty() ){
- Serial.printf("\nFATAL ERROR! Can't create new SpanRange(%d,%d,%d) without a defined Characteristic ***\n",min,max,step);
- Serial.printf("\n=== PROGRAM HALTED ===");
+ LOG0("\nFATAL ERROR! Can't create new SpanRange(%d,%d,%d) without a defined Characteristic ***\n",min,max,step);
+ LOG0("\n=== PROGRAM HALTED ===");
while(1);
} else {
homeSpan.Accessories.back()->Services.back()->Characteristics.back()->setRange(min,max,step);
@@ -2118,12 +2072,12 @@ SpanRange::SpanRange(int min, int max, int step){
SpanButton::SpanButton(int pin, uint16_t longTime, uint16_t singleTime, uint16_t doubleTime, triggerType_t triggerType) : PushButton(pin, triggerType){
if(homeSpan.Accessories.empty() || homeSpan.Accessories.back()->Services.empty()){
- if(buttonType==BUTTON)
- Serial.printf("\nFATAL ERROR! Can't create new SpanButton(%d,%u,%u,%u) without a defined Service ***\n",pin,longTime,singleTime,doubleTime);
+ if(buttonType==HS_BUTTON)
+ LOG0("\nFATAL ERROR! Can't create new SpanButton(%d,%u,%u,%u) without a defined Service ***\n",pin,longTime,singleTime,doubleTime);
else
- Serial.printf("\nFATAL ERROR! Can't create new SpanToggle(%d,%u) without a defined Service ***\n",pin,longTime);
+ LOG0("\nFATAL ERROR! Can't create new SpanToggle(%d,%u) without a defined Service ***\n",pin,longTime);
- Serial.printf("\n=== PROGRAM HALTED ===");
+ LOG0("\n=== PROGRAM HALTED ===");
while(1);
}
@@ -2139,8 +2093,8 @@ SpanButton::SpanButton(int pin, uint16_t longTime, uint16_t singleTime, uint16_t
void SpanButton::check(){
- if( (buttonType==BUTTON && triggered(singleTime,longTime,doubleTime)) ||
- (buttonType==TOGGLE && toggled(longTime)) ) // if the underlying PushButton is triggered/toggled
+ if( (buttonType==HS_BUTTON && triggered(singleTime,longTime,doubleTime)) ||
+ (buttonType==HS_TOGGLE && toggled(longTime)) ) // if the underlying PushButton is triggered/toggled
service->button(pin,type()); // call the Service's button() routine with pin and type as parameters
}
@@ -2182,20 +2136,22 @@ void SpanWebLog::init(uint16_t maxEntries, const char *serv, const char *tz, con
///////////////////////////////
-void SpanWebLog::initTime(){
- if(!timeServer)
- return;
-
- Serial.printf("Acquiring Time from %s (%s). Waiting %d second(s) for response... ",timeServer,timeZone,waitTime/1000);
- configTzTime(timeZone,timeServer);
+void SpanWebLog::initTime(void *args){
+ SpanWebLog *wLog = (SpanWebLog *)args;
+
+ WEBLOG("Acquiring Time from %s (%s)",wLog->timeServer,wLog->timeZone,wLog->waitTime/1000);
+ configTzTime(wLog->timeZone,wLog->timeServer);
struct tm timeinfo;
- if(getLocalTime(&timeinfo,waitTime)){
- strftime(bootTime,sizeof(bootTime),"%c",&timeinfo);
- Serial.printf("%s\n\n",bootTime);
- timeInit=true;
+ if(getLocalTime(&timeinfo,wLog->waitTime)){
+ strftime(wLog->bootTime,sizeof(wLog->bootTime),"%c",&timeinfo);
+ wLog->timeInit=true;
+ WEBLOG("Time Acquired: %s",wLog->bootTime);
} else {
- Serial.printf("Can't access Time Server - time-keeping not initialized!\n\n");
+ WEBLOG("Can't access Time Server after %d seconds",wLog->waitTime/1000);
}
+
+ vTaskDelete(NULL);
+
}
///////////////////////////////
@@ -2206,9 +2162,9 @@ void SpanWebLog::vLog(boolean sysMsg, const char *fmt, va_list ap){
vasprintf(&buf,fmt,ap);
if(sysMsg)
- Serial.printf("%s\n",buf);
- else if(homeSpan.logLevel>0)
- Serial.printf("WEBLOG: %s\n",buf);
+ LOG0("%s\n",buf);
+ else
+ LOG1("WEBLOG: %s\n",buf);
if(maxEntries>0){
int index=nEntries%maxEntries;
@@ -2235,7 +2191,7 @@ void SpanWebLog::vLog(boolean sysMsg, const char *fmt, va_list ap){
int SpanOTA::init(boolean _auth, boolean _safeLoad, const char *pwd){
if(esp_ota_get_running_partition()==esp_ota_get_next_update_partition(NULL)){
- Serial.print("\n*** WARNING: Can't start OTA Server - Partition table used to compile this sketch is not configured for OTA.\n\n");
+ LOG0("\n*** WARNING: Can't start OTA Server - Partition table used to compile this sketch is not configured for OTA.\n\n");
return(-1);
}
@@ -2252,7 +2208,7 @@ int SpanOTA::init(boolean _auth, boolean _safeLoad, const char *pwd){
int SpanOTA::setPassword(const char *pwd){
if(strlen(pwd)<1 || strlen(pwd)>32){
- Serial.printf("\n*** WARNING: Cannot change OTA password to '%s'. Password length must be between 1 and 32 characters.\n\n",pwd);
+ LOG0("\n*** WARNING: Cannot change OTA password to '%s'. Password length must be between 1 and 32 characters.\n\n",pwd);
return(-1);
}
@@ -2267,7 +2223,7 @@ int SpanOTA::setPassword(const char *pwd){
///////////////////////////////
void SpanOTA::start(){
- Serial.printf("\n*** Current Partition: %s\n*** New Partition: %s\n*** OTA Starting..",
+ LOG0("\n*** Current Partition: %s\n*** New Partition: %s\n*** OTA Starting..",
esp_ota_get_running_partition()->label,esp_ota_get_next_update_partition(NULL)->label);
otaPercent=0;
STATUS_UPDATE(start(LED_OTA_STARTED),HS_OTA_STARTED)
@@ -2278,7 +2234,7 @@ void SpanOTA::start(){
void SpanOTA::end(){
nvs_set_u8(homeSpan.otaNVS,"OTA_REQUIRED",safeLoad);
nvs_commit(homeSpan.otaNVS);
- Serial.printf(" DONE! Rebooting...\n");
+ LOG0(" DONE! Rebooting...\n");
homeSpan.reboot();
}
@@ -2288,13 +2244,13 @@ void SpanOTA::progress(uint32_t progress, uint32_t total){
int percent=progress*100/total;
if(percent/10 != otaPercent/10){
otaPercent=percent;
- Serial.printf("%d%%..",progress*100/total);
+ LOG0("%d%%..",progress*100/total);
}
if(safeLoad && progress==total){
SpanPartition newSpanPartition;
esp_partition_read(esp_ota_get_next_update_partition(NULL), sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t), &newSpanPartition, sizeof(newSpanPartition));
- Serial.printf("Checking for HomeSpan Magic Cookie: %s..",newSpanPartition.magicCookie);
+ LOG0("Checking for HomeSpan Magic Cookie: %s..",newSpanPartition.magicCookie);
if(strcmp(newSpanPartition.magicCookie,spanPartition.magicCookie))
Update.abort();
}
@@ -2303,12 +2259,12 @@ void SpanOTA::progress(uint32_t progress, uint32_t total){
///////////////////////////////
void SpanOTA::error(ota_error_t err){
- Serial.printf("*** OTA Error[%u]: ", err);
- if (err == OTA_AUTH_ERROR) Serial.println("Auth Failed\n");
- else if (err == OTA_BEGIN_ERROR) Serial.println("Begin Failed\n");
- else if (err == OTA_CONNECT_ERROR) Serial.println("Connect Failed\n");
- else if (err == OTA_RECEIVE_ERROR) Serial.println("Receive Failed\n");
- else if (err == OTA_END_ERROR) Serial.println("End Failed\n");
+ LOG0("*** OTA Error[%u]: ", err);
+ if (err == OTA_AUTH_ERROR) LOG0("Auth Failed\n\n");
+ else if (err == OTA_BEGIN_ERROR) LOG0("Begin Failed\n\n");
+ else if (err == OTA_CONNECT_ERROR) LOG0("Connect Failed\n\n");
+ else if (err == OTA_RECEIVE_ERROR) LOG0("Receive Failed\n\n");
+ else if (err == OTA_END_ERROR) LOG0("End Failed\n\n");
}
///////////////////////////////
@@ -2325,14 +2281,14 @@ boolean SpanOTA::auth;
SpanPoint::SpanPoint(const char *macAddress, int sendSize, int receiveSize, int queueDepth, boolean useAPaddress){
if(sscanf(macAddress,"%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",peerInfo.peer_addr,peerInfo.peer_addr+1,peerInfo.peer_addr+2,peerInfo.peer_addr+3,peerInfo.peer_addr+4,peerInfo.peer_addr+5)!=6){
- Serial.printf("\nFATAL ERROR! Can't create new SpanPoint(\"%s\") - Invalid MAC Address ***\n",macAddress);
- Serial.printf("\n=== PROGRAM HALTED ===");
+ LOG0("\nFATAL ERROR! Can't create new SpanPoint(\"%s\") - Invalid MAC Address ***\n",macAddress);
+ LOG0("\n=== PROGRAM HALTED ===");
while(1);
}
if(sendSize<0 || sendSize>200 || receiveSize<0 || receiveSize>200 || queueDepth<1 || (sendSize==0 && receiveSize==0)){
- Serial.printf("\nFATAL ERROR! Can't create new SpanPoint(\"%s\",%d,%d,%d) - one or more invalid parameters ***\n",macAddress,sendSize,receiveSize,queueDepth);
- Serial.printf("\n=== PROGRAM HALTED ===");
+ LOG0("\nFATAL ERROR! Can't create new SpanPoint(\"%s\",%d,%d,%d) - one or more invalid parameters ***\n",macAddress,sendSize,receiveSize,queueDepth);
+ LOG0("\n=== PROGRAM HALTED ===");
while(1);
}
@@ -2413,8 +2369,8 @@ void SpanPoint::setChannelMask(uint16_t mask){
channel=(channelMask & (1<receiveSize){
- Serial.printf("SpanPoint Warning! %d bytes received from %02X:%02X:%02X:%02X:%02X:%02X does not match %d-byte queue size\n",len,mac[0],mac[1],mac[2],mac[3],mac[4],mac[5],(*it)->receiveSize);
+ LOG0("SpanPoint Warning! %d bytes received from %02X:%02X:%02X:%02X:%02X:%02X does not match %d-byte queue size\n",len,mac[0],mac[1],mac[2],mac[3],mac[4],mac[5],(*it)->receiveSize);
return;
}
diff --git a/src/HomeSpan.h b/src/HomeSpan.h
index 3b4a20d..5a22b59 100644
--- a/src/HomeSpan.h
+++ b/src/HomeSpan.h
@@ -110,8 +110,6 @@ struct SpanUserCommand;
extern Span homeSpan;
-#include "HAP.h"
-
////////////////////////////////////////////////////////
// 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
char bootTime[33]="Unknown"; // boot time
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
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
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);
};
@@ -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()
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 serialInputDisabled=false; // flag indiating that serial input is disabled
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
@@ -224,7 +224,7 @@ class Span{
const char *defaultSetupCode=DEFAULT_SETUP_CODE; // Setup Code used for pairing
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 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
@@ -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
- 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)
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
@@ -308,8 +308,10 @@ class Span{
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 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
+ 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 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
@@ -341,9 +343,11 @@ class Span{
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()
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
@@ -632,7 +636,7 @@ class SpanCharacteristic{
void setString(const char *val){
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;
}
@@ -666,9 +670,9 @@ class SpanCharacteristic{
return(olen);
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)
- 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);
}
@@ -684,9 +688,9 @@ class SpanCharacteristic{
return(olen);
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)
- 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);
}
@@ -694,12 +698,12 @@ class SpanCharacteristic{
void setData(uint8_t *data, size_t len){
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;
}
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;
}
@@ -713,12 +717,12 @@ class SpanCharacteristic{
template void setVal(T val, boolean notify=true){
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;
}
if(val < uvGet(minValue) || val > uvGet(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(minValue),uvGet(maxValue));
}
@@ -814,11 +818,11 @@ class SpanButton : public PushButton {
protected:
enum buttonType_t {
- BUTTON,
- TOGGLE
+ HS_BUTTON,
+ HS_TOGGLE
};
- buttonType_t buttonType=BUTTON; // type of SpanButton
+ buttonType_t buttonType=HS_BUTTON; // type of SpanButton
public:
@@ -842,7 +846,7 @@ class SpanToggle : public SpanButton {
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);}
};
diff --git a/src/Network.cpp b/src/Network.cpp
index a5f452f..bcfe676 100644
--- a/src/Network.cpp
+++ b/src/Network.cpp
@@ -65,33 +65,26 @@ void Network::serialConfigure(){
wifiData.ssid[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
- for(int i=0;i>> WiFi SSID: ");
+ LOG0("\n>>> WiFi SSID: ");
readSerial(wifiData.ssid,MAX_SSID);
if(atoi(wifiData.ssid)>0 && atoi(wifiData.ssid)<=numSSID){
strcpy(wifiData.ssid,ssidList[atoi(wifiData.ssid)-1]);
}
- Serial.print(wifiData.ssid);
- Serial.print("\n");
+ LOG0("%s\n",wifiData.ssid);
}
while(!strlen(wifiData.pwd)){
- Serial.print(">>> WiFi PASS: ");
+ LOG0(">>> WiFi PASS: ");
readSerial(wifiData.pwd,MAX_PWD);
- Serial.print(mask(wifiData.pwd,2));
- Serial.print("\n");
+ LOG0("%s\n",mask(wifiData.pwd,2).c_str());
}
return;
@@ -110,25 +103,16 @@ boolean Network::allowedCode(char *s){
void Network::apConfigure(){
- Serial.print("*** Starting Access Point: ");
- Serial.print(apSSID);
- Serial.print(" / ");
- Serial.print(apPassword);
- Serial.print("\n");
+ LOG0("*** Starting Access Point: %s / %s\n",apSSID,apPassword);
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
- for(int i=0;itriggered(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)
homeSpan.controlButton->wait();
homeSpan.reboot();
@@ -163,17 +147,14 @@ void Network::apConfigure(){
WiFi.softAPdisconnect(true); // terminate connections and shut down captive access point
delay(100);
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;
} else {
- if(apStatus==0){
- Serial.print("\n*** Access Point: Timed Out (");
- Serial.print(lifetime/1000);
- Serial.print(" seconds).");
- } else {
- Serial.print("\n*** Access Point: Configuration Cancelled.");
- }
- Serial.print(" Restarting...\n\n");
+ if(apStatus==0)
+ LOG0("\n*** Access Point: Timed Out (%ld seconds).",lifetime/1000);
+ else
+ LOG0("\n*** Access Point: Configuration Cancelled.");
+ LOG0(" Restarting...\n\n");
STATUS_UPDATE(start(LED_ALERT),HS_AP_TERMINATED)
homeSpan.reboot();
}
@@ -202,7 +183,7 @@ void Network::apConfigure(){
if(nBytes>MAX_HTTP){ // exceeded maximum number of bytes allowed
badRequestError();
- Serial.print("\n*** ERROR: Exceeded maximum HTTP message length\n\n");
+ LOG0("\n*** ERROR: Exceeded maximum HTTP message length\n\n");
continue;
}
@@ -212,7 +193,7 @@ void Network::apConfigure(){
if(!(p=strstr((char *)httpBuf,"\r\n\r\n"))){
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;
}
@@ -224,7 +205,7 @@ void Network::apConfigure(){
cLen=atoi(p+16);
if(nBytes!=strlen(body)+4+cLen){
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;
}
diff --git a/src/SRP.cpp b/src/SRP.cpp
index bc03cd7..aaa1e27 100644
--- a/src/SRP.cpp
+++ b/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()0)Serial.print ##__VA_OPT__(f)(format __VA_OPT__(,) __VA_ARGS__)
-#define LOG2(format,...) if(homeSpan.getLogLevel()>1)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,...) do{ if(homeSpan.getLogLevel()>=1)Serial.print ##__VA_OPT__(f)(format __VA_OPT__(,) __VA_ARGS__); }while(0)
+#define LOG2(format,...) do{ if(homeSpan.getLogLevel()>=2)Serial.print ##__VA_OPT__(f)(format __VA_OPT__(,) __VA_ARGS__); }while(0)
-#define 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 //
diff --git a/src/TLV.h b/src/TLV.h
index 4b7ae96..505c4f8 100644
--- a/src/TLV.h
+++ b/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, 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)
- 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 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
@@ -192,24 +192,20 @@ uint8_t *TLV::buf(tagType tag, int len){
// TLV print()
template
-void TLV::print(){
-
- char buf[3];
+void TLV::print(int minLogLevel){
+ if(homeSpan.getLogLevel()0){
- Serial.print(tlv[i].name);
- Serial.print("(");
- Serial.print(tlv[i].len);
- Serial.print(") ");
+ Serial.printf("%s(%d) ",tlv[i].name,tlv[i].len);
- for(int j=0;j0
} // loop over all TLVs
diff --git a/src/Utils.cpp b/src/Utils.cpp
index 94dd131..7af158e 100644
--- a/src/Utils.cpp
+++ b/src/Utils.cpp
@@ -26,6 +26,7 @@
********************************************************************************/
#include "Utils.h"
+#include "HomeSpan.h"
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
@@ -39,6 +40,12 @@
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
char *Utils::readSerial(char *c, int max){
+
+ if(homeSpan.getSerialInputDisable()){
+ c[0]='\0';
+ return(c);
+ }
+
int i=0;
char buf;
@@ -104,10 +111,10 @@ PushButton::PushButton(int pin, triggerType_t triggerType){
threshold/=calibCount;
#if SOC_TOUCH_VERSION_1
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
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
diff --git a/src/extras/StepperControl.h b/src/extras/StepperControl.h
new file mode 100644
index 0000000..0e685ca
--- /dev/null
+++ b/src/extras/StepperControl.h
@@ -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"
diff --git a/src/extras/Stepper_A3967.h b/src/extras/Stepper_A3967.h
new file mode 100644
index 0000000..a5fb2b0
--- /dev/null
+++ b/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_A3967.h"
diff --git a/src/extras/Stepper_TB6612.h b/src/extras/Stepper_TB6612.h
new file mode 100644
index 0000000..14365d9
--- /dev/null
+++ b/src/extras/Stepper_TB6612.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"
diff --git a/src/src.ino b/src/src.ino
index 3bb8cfd..d82cf30 100644
--- a/src/src.ino
+++ b/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"
-CUSTOM_CHAR(CharFloat, 00000001-0001-0001-0001-46637266EA00, PR+PW+EV, FLOAT, 0, 0, 100, false);
-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);
-CUSTOM_CHAR(CharUInt32, 00000032-0001-0001-0001-46637266EA00, PR+PW+EV, UINT32, 0, 0, 100, false);
-CUSTOM_CHAR(CharInt, 00000002-0001-0001-0001-46637266EA00, PR+PW+EV, INT, 0, 0, 100, false);
+struct LED_Service : Service::LightBulb {
+
+ int ledPin;
+ SpanCharacteristic *power;
+
+ 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() {
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();
- 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);
+ homeSpan.begin(Category::Lighting,"HomeSpan LED");
-} // end of setup()
+ new SpanAccessory();
+ new Service::AccessoryInformation();
+ new Characteristic::Identify();
+ new LED_Service(13);
+}
//////////////////////////////////////
-void loop(){
-
+void loop(){
homeSpan.poll();
-
-} // end of loop()
+}
//////////////////////////////////////
diff --git a/src/src/extras/Blinker.cpp b/src/src/extras/Blinker.cpp
index 1bf30d4..ea5cbbe 100644
--- a/src/src/extras/Blinker.cpp
+++ b/src/src/extras/Blinker.cpp
@@ -134,7 +134,7 @@ void Blinker::check(){
if(pauseDuration==0 || isPaused || (millis()-pauseTime)
#include
+[[maybe_unused]] static const char* BLINKER_TAG = "Blinker";
+
////////////////////////////////
// Blinkable Interface //
////////////////////////////////
diff --git a/src/src/extras/Pixel.h b/src/src/extras/Pixel.h
index 6cf4945..929b8a6 100644
--- a/src/src/extras/Pixel.h
+++ b/src/src/extras/Pixel.h
@@ -35,6 +35,8 @@
#include "PwmPin.h"
#include "Blinker.h"
+[[maybe_unused]] static const char* PIXEL_TAG = "Pixel";
+
////////////////////////////////////////////
// Single-Wire RGB/RGBW NeoPixels //
////////////////////////////////////////////
diff --git a/src/src/extras/PwmPin.cpp b/src/src/extras/PwmPin.cpp
index d39b704..d3eaf96 100644
--- a/src/src/extras/PwmPin.cpp
+++ b/src/src/extras/PwmPin.cpp
@@ -52,7 +52,7 @@ LedC::LedC(uint8_t pin, uint16_t freq, boolean invert){
timerList[nTimer][nMode]->duty_resolution=(ledc_timer_bit_t)res;
if(ledc_timer_config(timerList[nTimer][nMode])!=0){
- Serial.printf("\n*** ERROR: Frequency=%d Hz is out of allowed range ---",freq);
+ ESP_LOGE(PWM_TAG,"Frequency=%d Hz is out of allowed range ---",freq);
delete timerList[nTimer][nMode];
timerList[nTimer][nMode]=NULL;
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){
- if(!channel)
- Serial.printf("\n*** ERROR: Can't create LedPin(%d) - no open PWM channels and/or Timers ***\n\n",pin);
+ if(!channel){
+ ESP_LOGE(PWM_TAG,"Can't create LedPin(%d) - no open PWM channels and/or Timers",pin);
+ return;
+ }
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->speed_mode,
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){
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
- 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->speed_mode,
channel->channel,
diff --git a/src/src/extras/PwmPin.h b/src/src/extras/PwmPin.h
index fe5dae5..6528257 100644
--- a/src/src/extras/PwmPin.h
+++ b/src/src/extras/PwmPin.h
@@ -46,6 +46,8 @@
#include
#include "Blinker.h"
+[[maybe_unused]] static const char* PWM_TAG = "PwmPin";
+
#define DEFAULT_PWM_FREQ 5000
/////////////////////////////////////
diff --git a/src/src/extras/RFControl.cpp b/src/src/extras/RFControl.cpp
index f00d4b5..4fe8951 100644
--- a/src/src/extras/RFControl.cpp
+++ b/src/src/extras/RFControl.cpp
@@ -36,7 +36,7 @@ RFControl::RFControl(uint8_t pin, boolean refClock, boolean installDriver){
#else
if(nChannels==RMT_CHANNEL_MAX){
#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;
}
@@ -139,17 +139,17 @@ void RFControl::enableCarrier(uint32_t freq, float duty){
uint32_t lowTime=period*(1.0-duty)+0.5;
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;
}
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;
}
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;
}
diff --git a/src/src/extras/RFControl.h b/src/src/extras/RFControl.h
index 47e5d45..7def768 100644
--- a/src/src/extras/RFControl.h
+++ b/src/src/extras/RFControl.h
@@ -36,6 +36,8 @@
#include "driver/rmt.h"
#include
+[[maybe_unused]] static const char* RFControl_TAG = "RFControl";
+
using std::vector;
class RFControl {
diff --git a/src/src/extras/StepperControl.cpp b/src/src/extras/StepperControl.cpp
new file mode 100644
index 0000000..31d33ea
--- /dev/null
+++ b/src/src/extras/StepperControl.cpp
@@ -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);
+ }
+}
+
+//////////////////////////
diff --git a/src/src/extras/StepperControl.h b/src/src/extras/StepperControl.h
new file mode 100644
index 0000000..05f7bc4
--- /dev/null
+++ b/src/src/extras/StepperControl.h
@@ -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
+
+[[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();
+};
+
+//////////////////////////
diff --git a/src/src/extras/Stepper_A3967.h b/src/src/extras/Stepper_A3967.h
new file mode 100644
index 0000000..e13beca
--- /dev/null
+++ b/src/src/extras/Stepper_A3967.h
@@ -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 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);
+ }
+
+};
+
+//////////////////////////
diff --git a/src/src/extras/Stepper_TB6612.h b/src/src/extras/Stepper_TB6612.h
new file mode 100644
index 0000000..b8bf06a
--- /dev/null
+++ b/src/src/extras/Stepper_TB6612.h
@@ -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 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 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);
+ }
+
+};
+
+//////////////////////////
diff --git a/src/src/extras/extras.ino b/src/src/extras/extras.ino
index 072d8d9..9a8d440 100644
--- a/src/src/extras/extras.ino
+++ b/src/src/extras/extras.ino
@@ -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,
// 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() {
-
- Serial.begin(115200); // start the Serial interface
- Serial.flush();
- delay(1000); // wait for interface to flush
+StepperControl *bigMotor;
+StepperControl *smallMotor;
- 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){
- while(red.fadeStatus()==LedPin::FADING);
- red.fade(i,1000,LedPin::PROPORTIONAL);
- }
+ Serial.begin(115200);
+ delay(1000);
+ 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);
-
- while(1){
- delay(1000);
- if(red.fade(redLevel,5000))
- Serial.printf("Failed\n");
- else{
- Serial.printf("Success\n");
- redLevel=100-redLevel;
- }
-
- }
-
+// smallMotor->setStepType(StepperControl::EIGHTH_STEP);
+
+// bigMotor->setStepType(StepperControl::HALF_STEP);
+// bigMotor->setAccel(10,20);
}
+///////////////////
+
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());
}
|