# Over-the-Air (OTA) Updates

HomeSpan supports Over-the-Air (OTA) updates, which allows you to *wirelessly* upload sketches directly from the Arduino IDE - no serial connection needed.  To activate this feature for your sketch, simply call the method `homeSpan.enableOTA()` prior to calling `homeSpan.begin()`.

When a HomeSpan sketch is run with OTA enabled, the device shows up as a "network" port that can be selected under the *Tools → Port* menu in the Arduino IDE.  Once selected, the IDE will direct all uploads to the device via WiFi instead of looking for it on a serial port.  Note that you can upload via OTA even if your device is still connected to a serial port, but the Arduino IDE does not presently support multiple port connections at the same time.  If you select a "network" port, the IDE will automatically close the Serial Monitor if it is open.  To re-instate uploads via the "serial" port, simply choose that port from the *Tools → Port* menu in the Arduino IDE.  Uploading via the serial port is always possible regardless of whether you have enabled OTA for a sketch.

By default, HomeSpan requires the use of a password whenever you begin an OTA upload.  The default OTA password is "homespan-ota".  The Arduino will prompt you for this password upon your first attempt to upload a sketch to a newly-connected device.  However, once the password for a specific device is entered, the Arduino IDE retains it in memory as long as the IDE is running, thereby saving you from having to type it again every time you re-upload a sketch via OTA.

You can change the password for a HomeSpan device from the [HomeSpan CLI](CLI.md) with the 'O' command.  Similar to a device's Setup Code, HomeSpan saves a non-recoverable hashed version of the OTA password you specify in non-volatile storage (NVS).  If you forget the password you specified, you'll need to create a new one using the 'O' command, or you can restore the default OTA password by fully erasing the NVS with the 'E' command.

You can also change the password programmatically from within a sketch by calling `homeSpan.enableOTA(const char *pwd)`, where *pwd* is a plain-text password that HomeSpan converts to a hash before saving in NVS.  However, this is not as secure as using the "O" command since it means your sketch includes a copy of your plain-text password.

As an alternative, you can use `homeSpan.enableOTA(const char *pwd)` to instead provide a hash of your plain-text password that you pre-compute yourself instead of specifying the actual plain-text password as follows:

* if *pwd* begins with "0x" followed by *exactly* 64 hexidecimal digits HomeSpan interprets *pwd* as a SHA256 hash
* if *pwd* begins with "0x" followed by *exactly* 32 hexidecimal digits HomeSpan interprets *pwd* as an MD5 hash

When HomeSpan interprets *pwd* as a hash, it saves it directly in NVS.  The same logic above applies to setting your password with the "O" command - if the password you specify is in the form of a SH256 or MD5 hash, it is interpreted as such and saved directly in NVS.  Since SHA256 is more secure than MD5, HomeSpan always uses SHA256 whenever it creates a hash of your plain-text password.  If you set your own hash, SHA256 is recommended.  If you specify an MD5 hash, the Arduino IDE will warn you of your use of a less-secure hash whenever you upload your sketch via OTA.

Note that setting your plain-text password (or a hash of your plain-text password) from within your sketch using `homeSpan.enableOTA(const char *pwd)` causes HomeSpan to use that *pwd* instead of the hash saved in NVS with the "O" command, but it does not alter the one saved. 

> :exclamation: Though not recommended, you can override the requirement for a password when enabling OTA for your sketch by including *false* as a parameter to the enabling method as such: `homeSpan.enableOTA(false)`.  Use with caution!  Anyone who can access the device over your network will now be able to upload a new sketch.

Note that in in order for OTA to properly operate, your sketch must be compiled with a partition scheme that includes OTA partitions.  Partition schemes are found under the *Tools → Partition Scheme* menu of the Arduino IDE.  Select a scheme that indicates it supports OTA.  Note that schemes labeled "default" usually include OTA partitions.  If unsure, try it out.  HomeSpan will let you know if it does or does not.

This is because HomeSpan checks that a sketch has been compiled with OTA partitions if OTA has been enabled for that sketch.  If OTA has been enabled but HomeSpan does not find any OTA partitions, it will indicate it cannot start the OTA Server via a warning message sent to the Serial Monitor immediately after WiFi connectivity has been established.  Otherwise it will output a confirmation message indicating the OTA Server has sucessfully started.

### OTA Safe Load

HomeSpan includes two additional safety checks when using OTA to upload a sketch:

1. HomeSpan checks to make sure the new sketch being uploaded is also another HomeSpan sketch. If not, HomeSpan will reject the new sketch and report an OTA error back to the Arduino IDE after the new sketch is uploaded, but before the device reboots.  Instead, HomeSpan will close the OTA connection and resume normal operations based on the existing sketch without rebooting.  The purpose of this safety check is to prevent you from accidentally uploading a non-HomeSpan sketch onto a remote device, making it impossible for you to re-upload the correct sketch without retreiving the remote device and connecting to you computer via the serial port.

1. After a successful upload of a new HomeSpan sketch via OTA, HomeSpan will check that the new HomeSpan sketch just loaded *also* has OTA enabled.  This check occurs after HomeSpan is rebooted with the new sketch.  If HomeSpan does not find OTA enabled, it will mark the current partition as invalid and reboot the device, causing the device to "roll back" to the previous version of the sketch that had OTA enabled.  The purpose of this safety check is to ensure you do not use OTA to upload a new HomeSpan sketch to a remote device, but failed to enable OTA in the new HomeSpan sketch.  If you did this you would be locked out of making any further updated via OTA and would instead need to retreive the remote device and connect it to your computer via the serial port.

Note that these check are *only* applicable when uploading sketches via OTA.  They are ignored whenever sketches are uploaded via the serial port.  Also, though these safety checks are enabled by default, they can be disabled when you first enable OTA by setting the second (optional) argument to *false* as such: `homeSpan.enableOTA(..., false)`.  See the API for details.

### OTA Rollback

The ESP32 operating system marks each OTA APP partition in a partition table with one of the following OTA States: *NEW*, *PENDING_VERIFY*, *VALID*, *INVALID*, *ABORTED*, or *UNDEFINED*.  The OTA State of each OTA App partition determines whether or not that partition is selectable by the bootloader for running when the device is rebooted.  Whenever a new sketch is successfully uploaded via OTA, the operating system marks the partition into which the sketch was uploaded as *NEW*, flags it to be run upon start-up, and then (typically) reboots the device.

Upon rebooting, just before the new sketch starts to run, the operating system re-marks the OTA State of the partition from *NEW* to *PENDING_VERIFY*.  When a sketch is running from a partition marked as *PENDING_VERIFY*, the operating system is expecting that sometime **before the next reboot of the device**, the sketch will itself re-mark the currently-running partition as *VALID*.  If the device is rebooted before the partition is re-marked as *VALID*, then upon restarting the operating system will re-mark the OTA State of the partition to *ABORTED*, the partition will no longer be bootable, and instead the bootloader will "rollback" to running whatever sketch is stored in the last known *VALID* partition.

In this fashion, if you upload a new sketch into your device via OTA and the new sketch crashes immediately upon rebooting, rather than simply repeat an endless cycle of crashing and rebooting, the operating system can instead rollback to the original working sketch when it restarts after the first crash.  Once the original sketch is running again, you will then be able to upload a new (and hopefully corrected) sketch via OTA. This is a terrific safety mechanism and helps ensure you don't accidental render a remote device inoperable by uploading a flawed sketch that crashes immediately and prevents additional OTA uploads.

However, by default the Arduino-ESP32 library automatically re-marks any partition booted with an OTA State of *PENDING_VERIFY* to *VALID* during its initialization phase **before** the Arduino `setup()` function is ever called, and therefore before your sketch (which may have an error that causes the device to crash) even gets a chance to run.  As a result, the safety of the OTA rollback mechanism described above is rendered completely ineffective.  This is because if your sketch crashes, the Arduino-ESP32 library will have already marked the partition *VALID*. It will therefore be selected for running once again when the device reboots, creating the same endless cycle of crashing and rebooting we want to prevent.

Fortunately, the Arduino-ESP32 library also contains a method to disable the library from automatically re-marking the OTA State of a *PENDING_VERIFY* partition to *VALID*, which gives the sketch control of if and when to mark its partition *VALID*.  To implement this method in HomeSpan simply add the following to the top of your sketch:

```C++
#include "SpanRollback.h"
```

By including this header file in your sketch, the Arduino-ESP32 library will NOT automatically validate sketches uploaded via OTA, and the OTA State of the partition will instead remain marked as *PENDING_VERIFY* until the sketch itself decides to re-mark the partition as *VALID* prior to the next reboot (or crash).  To mark the partition *VALID* from within your sketch, call the homeSpan method `markSketchOK()`. 

Note that when this header is included, it is the users responsibility to re-mark the OTA State of the partition as *VALID* somewhere within the sketch by calling `homeSpan.markSketchOK()`.  If not, then upon rebooting (either purposefully or as the result of a panic), the bootloader will rollback to a prior sketch in a previously-validated partition. If the sketch you've just uploaded and has a bug that caused it to panic and crash early on, the resulting rollback is beneficial and will allow you to fix your issue and re-upload a new sketch.  But if you simply forgot to re-mark a working sketch as *VALID* and at some point in the future the device reboots, you will be surprised to find the device has reverted to prior version of your sketch.

When using SpanRollback, one question you must answer is where and when in your sketch to perform the validation and mark the partition as *VALID*.  If you call `homeSpan.markSketchOK()` too early (i.e. near the top of the `setup()` function) than you may have validated your sketch before it had a chance to run through all the setup functionality and you won't be able to protect against any crashes or panics that may occur in those sections.

One possible choice is to wait to until WiFi connectivity has been established and then mark the partition *VALID*.  This is easily done from within a callback you can create using `homeSpan.setConnectionCallback().`  Since this callback does not occur until WiFi connectivity has been established, this necesssarily means all the `setup()` code and HomeSpan initializations have been run, and the HomeSpan `poll()` task has already been called many times --- if the sketch was going to crash, it probably would have done so already.  However, one downside to waiting so late to validate the sketch is that if your device has any problems connecting to your WiFi network after rebooting, and the problem is with the network (not the sketch) such that you have to reboot your device, then it will rollback to a previous version of the sketch when you reboot since the current sketch was not yet validated.

To avoid this issue another possible choice is to validate your sketch earlier, such as immediately after the HomeSpan `poll()` task is *initially* run. This is also easily done from within a callback you can create using `homeSpan.setPollingCallback().` The advantage of marking the sketch *VALID* from within this callback is that it occurs late enough to make sure all the `setup()` functions, HomeSpan initializations, and a first pass through the HomeSPan `poll()` task, have run successfully without crashing, but does not depend on WiFi connectivity to have been established.

Every sketch is different so the choice of where and when to validate, as well as whether to peform any sketch-specific validations, tests are up to your own discretion and preferences.
 
### OTA Tips and Tricks

* The name of the device HomeSpan uses for OTA is the same as the name you assigned in your call to `homeSpan.begin()`.  If you have multiple devices you intend to maintain with OTA, use `homeSpan.begin()` to give them each different names so you can tell them apart when selecting which one to connect to from the Arduino IDE.

* Use the `homeSpan.setSketchVersion()` method to set a version for your sketch (see the [HomeSpan API](Reference.md) for details).  If specified, HomeSpan will include the sketch version as part of its HAP MDNS broadcast.  This allows you determine which version of a sketch is running on a remote HomeSpan device, even if you can't plug it into a serial port for use with the Arduino Serial Monitor.  In addition to the sketch version, HomeSpan also includes other fields in its MDNS broadcast that are useful in identifying the device: the version number of the HomeSpan *library* used to compile the sketch, a field indicating whether or not OTA is enabled for the sketch, the version number of the Arduino-ESP32 library used when compiling, and the type of board (e.g. *feather_esp32*).

* Use the `homeSpan.setCompileTime()` method without specifying any parameter to have HomeSpan automatically set the compile time using the `__DATE__` and `__TIME__` macros provided by the compiler itself during compilation (see the [HomeSpan API](Reference.md) for details).  This compile time is shown in the Web Log and is thus useful when trying to determine if a new sketch uploaded via OTA is in fact the one running, or if that sketch failed and the system rolled back to a previous version after rebooting.

* It is possible to proactively re-mark the OTA State of the currently-running partition to *INVALID* and force an immedediate reboot and rollback from within your sketch, even if you have not added `#include "SpanRollback.h"`, to the top of your sketch.  However, marking a partition as *INVALID* from within a sketch is usually unecessary since if the sketch has critical failures it will likely crash and reboot before your sketch marks its partition *VALID*, which, as described above, causes a rollback to a prior working sketch during restart.  For this reason HomeSpan does not define its own method for marking a partition as *INVALID*, though you can do so by calling the underlying, and aptly-named, IDF function `esp_ota_mark_app_invalid_rollback_and_reboot()`.

* Sketches uploaded via USB are generally placed into the first OTA APP partition and the operating system automatically marks its OTA State as *UNDEFINED*.  An APP partition marked as *UNDEFINED* is always selectable for booting at start-up.  This means if you upload a sketch via USB and is immediatly crashes, the operating system *will not* roll back to a previous version and an endless cycle of crash and reboot will occur.  This is because, as the name suggests, the OTA Rollback process is only applied if you upload a sketch via OTA.  However, if your sketch crashes upon reboot after being uploaded via USB, that does not cause a problem since you can still correct the sketch and re-upload via USB as many times as needed.  Unlike OTA uploading, USB uploading is always possible since the code supporting USB uploading is built into the bootloader itself.

* It is okay, though unecessary, for your sketch to call `homeSpan.markSketchOK()` when uploading via USB.  This will re-mark the partition from *UNDEFINED* to *VALID* but will otherwise have no effect since, as noted above, an *UNDEFINED* partition is always assumed valid by the operating system and will be selected to run when booting.  Similarly, you can call `esp_ota_mark_app_invalid_rollback_and_reboot()` from within your sketch even if it has been uploaded via USB.  This will re-mark the partition *INVALID*, reboot the decvice, and cause a rollback to a prior *VALID* (or *UNDEFINED*) partition.

* If you are uncertain about the OTA States of your partitions, you can print the full partition table to the Serial Monitor using the the CLI `p` command.  The OTA State of each OTA App partition is shown in the output.

* As discussed above, if the OTA State of a partition is marked as *NEW*, *VALID*, or *UNDEFINED*, it is *selectable* for running by the bootloader.  But that does not mean the partition is guaranteed to contain a viable sketch.  For example, if your device loses power while uploading a valid sketch via OTA into a partition that was previously marked *VALID* or *UNDEFINED*, the partition will be corrupted even though its OTA State will remain unchanged.  If the bootloader then selects that partition to run, it will immediately detect the corruption and try another partition.  If the bootloader finds every selectable partition is corrupted or otherwise does not contain a viable sketch, it will simply reboot in an endless cycle until you upload a new sketch via USB.

* The OTA Rollback mechanism cannot, by itself, rollback a failed sketch if the sketch simply hangs (e.g. freezes in an infinite loop, deadlock, etc.), rather than crashes before it's marked *VALID*, since it typically won't be able to reboot itself.  You'll have to power-cycle the device to force a reboot, at which point if the sketch was not already marked *VALID*, the bootloader will rollback to a previous *VALID* partition as desired.  However, depending on the location of your device, it may be difficult or impossible to remotely cycle the power.  To address this issue you need to provide your sketch with an ability to reboot itself even if it hangs.  This can be done by enabling HomeSpan's Watchdog Timer using the homeSpan `enableWatchdog()` method.  See the [HomeSpan Watchdog Timer](WDT.md) page for details on how to use this additional safety feature.

---

[↩️](../README.md) Back to the Welcome page


