233 lines
7.4 KiB
Markdown
233 lines
7.4 KiB
Markdown
|
# OTA Upgrade module
|
||
|
| Since | Origin / Contributor | Maintainer | Source |
|
||
|
| :----- | :-------------------- | :---------- | :------ |
|
||
|
| 2019-06-24 | [DiUS](https://github.com/DiUS), [Johny Mattsson](https://github.com/jmattsson) | [Johny Mattsson](https://github.com/jmattsson) | [otaupgrade.c](../../components/modules/otaupgrade.c)|
|
||
|
|
||
|
The OTA Upgrade module provides access to the IDF Over-The-Air Upgrade
|
||
|
support, enabling new application firmware to be applied and booted into.
|
||
|
|
||
|
This module is not concerned with where the new application comes from.
|
||
|
The choice of download source and method (e.g. https, tftp) is left to
|
||
|
the user, as is the trigger to start an upgrade. A common approach is
|
||
|
to have the device periodically check in with a central server and
|
||
|
compare a provided version number with the currently running version,
|
||
|
and if necessary kick off an upgrade.
|
||
|
|
||
|
In order to use the `otaupgrade` module, there must exist at least two
|
||
|
OTA partitions (type `app`, subtype `ota_0` / `ota_1`), as well as the
|
||
|
"otadata" partition (type `data`, subtype `ota`). The IDF implements
|
||
|
the typical "flip-flop" approach to upgrades, in that one of the
|
||
|
partitions hosts the running application, and the upgrade is downloaded
|
||
|
into the inactive partition and only when fully downloaded and verified
|
||
|
is it marked as bootable. This makes the system resilient to incomplete
|
||
|
upgrades, be it due to power-loss, interrupted downloads, or other such
|
||
|
things.
|
||
|
|
||
|
An example partition table for OTA might look like:
|
||
|
```
|
||
|
# Name, Type, SubType, Offset, Size
|
||
|
nvs, data, nvs, 0x9000, 0x5000
|
||
|
otadata, data, ota, 0xe000, 0x2000
|
||
|
ota_0, app, ota_0, 0x10000,0x130000
|
||
|
ota_1, app, ota_1, 0x140000,0x130000
|
||
|
```
|
||
|
|
||
|
Depending on whether the installed boot loader has been built with or
|
||
|
without rollback support, the upgrade process itself has four or three
|
||
|
steps. Without rollback support, the steps are:
|
||
|
|
||
|
- `otaupgrade.commence()`
|
||
|
- feed the new application image into `otaupgrade.write(data)` in chunks
|
||
|
- `otaupgrade.complete(1)` to finalise and reboot into the new application
|
||
|
|
||
|
If the boot loader is built with rollback support, an extra step is needed
|
||
|
after the new application has booted (and been tested to be "good", by
|
||
|
whatever metric(s) the user chooses):
|
||
|
|
||
|
- `otaupgrade.accept()` to mark this image as valid, and allow it to be
|
||
|
booted into again.
|
||
|
|
||
|
If a new firmware is not `accept()`ed before the device reboots, the
|
||
|
boot loader will switch back to the previous firmware version (provided
|
||
|
said boot loader is built with rollback support). A common test before
|
||
|
marking a new firmware as valid is to ensure the upgrade server can be
|
||
|
reached, on the basis that as long as the firmware can be remotely
|
||
|
upgraded, it's "good enough" to accept.
|
||
|
|
||
|
# otaupgrade.info()
|
||
|
|
||
|
The boot info and application state and version info can be queried with
|
||
|
this function. Typically it will be used to check the version of the
|
||
|
running application, to compare against a "desired" version in order
|
||
|
to decide whether an upgrade is required.
|
||
|
|
||
|
#### Parameters
|
||
|
None.
|
||
|
|
||
|
#### Returns
|
||
|
A list of three values:
|
||
|
- the name of the partition of the running application
|
||
|
- the name of the partition currently marked for boot next (typically the
|
||
|
same as the running application, but after `otaupgrade.complete()` it
|
||
|
may point to a new application partition.
|
||
|
- a table whose keys are the names of OTA partitions and corresponding
|
||
|
values are tables containing:
|
||
|
- `state` one of `new`, `testing`, `valid`, `invalid`, `aborted` or
|
||
|
possibly `undefined`. The values `invalid` and `aborted` largely
|
||
|
mean the same things. See the IDF documentation for specifics.
|
||
|
A partition in `testing` state needs to call `otaupgrade.accept()`
|
||
|
if it wishes to become `valid`.
|
||
|
- `name` the application name, typically "NodeMCU"
|
||
|
- `date` the build date
|
||
|
- `time` the build time
|
||
|
- `version` the build version, as set by the *PROJECT_VER* variable
|
||
|
during build
|
||
|
- `secure_version` the secure version number, if secure boot is enabled
|
||
|
- `idf_version` the IDF version
|
||
|
|
||
|
#### Example
|
||
|
```lua
|
||
|
boot_part, next_part, info = otaupgrade.info()
|
||
|
print("Booted: "..boot_part)
|
||
|
print(" Next: "..next_part)
|
||
|
for p,t in pairs(info) do
|
||
|
print("@ "..p..":")
|
||
|
for k,v in pairs(t) do
|
||
|
print(" "..k..": "..v)
|
||
|
end
|
||
|
end
|
||
|
print("Running version: "..info[boot_part].version)
|
||
|
```
|
||
|
|
||
|
# otaupgrade.commence()
|
||
|
|
||
|
Wipes the spare application partition and prepares to receive the new
|
||
|
application firmware.
|
||
|
|
||
|
If rollback support is enabled, note that the running application must
|
||
|
first be marked valid/accepted before it is possible to commence a
|
||
|
new OTA upgrade.
|
||
|
|
||
|
#### Syntax
|
||
|
`otaupgrade.commence()`
|
||
|
|
||
|
#### Parameters
|
||
|
None.
|
||
|
|
||
|
#### Returns
|
||
|
`nil`
|
||
|
|
||
|
A Lua error may be raised if the OTA upgrade cannot be commenced for some
|
||
|
reason (such as due to incorrect partition setup).
|
||
|
|
||
|
|
||
|
# otaupgrade.write(data)
|
||
|
|
||
|
Write a chunk of application firmware data to the correct partition and
|
||
|
location. Data must be streamed sequentially, the IDF does not support
|
||
|
out-of-order data as would be the case from e.g. bittorrent.
|
||
|
|
||
|
#### Syntax
|
||
|
`otaupgrade.write(data)`
|
||
|
|
||
|
#### Parameters
|
||
|
- `data` a string of binary data
|
||
|
|
||
|
#### Returns
|
||
|
`nil`
|
||
|
|
||
|
A Lua error may be raised if the data can not be written, e.g. due to the
|
||
|
data not being a valid OTA image (the IDF performs some checks in this
|
||
|
regard).
|
||
|
|
||
|
|
||
|
# otaupgrade.complete(reboot)
|
||
|
|
||
|
Finalises the upgrade, and optionally reboots into the new application
|
||
|
firmware right away.
|
||
|
|
||
|
#### Syntax
|
||
|
`otaupgrade.complete(reboot)`
|
||
|
|
||
|
#### Parameters
|
||
|
- `reboot` 1 to reboot into the new firmware immediately, nil to keep running
|
||
|
|
||
|
#### Returns
|
||
|
`nil`
|
||
|
|
||
|
A Lua error may be raised if the image does not pass validation, or no data
|
||
|
has been written to the image at all.
|
||
|
|
||
|
#### Example
|
||
|
```lua
|
||
|
-- Quick, dirty and totally insecure "push upgrade" for development use.
|
||
|
-- Use netcat to push a new firmware to a device:
|
||
|
-- nc -q 1 your-device-ip 9999 < build/NodeMCU.bin
|
||
|
--
|
||
|
osv = net.createServer()
|
||
|
osv:listen(9999, function(conn)
|
||
|
print('Commencing OTA upgrade')
|
||
|
local status, err = pcall(otaupgrade.commence)
|
||
|
if err then
|
||
|
print(err)
|
||
|
conn:send(err)
|
||
|
conn:close()
|
||
|
end
|
||
|
conn:on('receive', function(sck, data)
|
||
|
status, err = pcall(function() otaupgrade.write(data) end)
|
||
|
if err then
|
||
|
print(err)
|
||
|
conn:send(err)
|
||
|
conn:close()
|
||
|
end
|
||
|
end)
|
||
|
conn:on('disconnection', function()
|
||
|
print('EOF, completing OTA')
|
||
|
status, err = pcall(function() otaupgrade.complete(1) end)
|
||
|
if err then
|
||
|
print(err)
|
||
|
end
|
||
|
end)
|
||
|
end)
|
||
|
|
||
|
```
|
||
|
|
||
|
# otaupgrade.accept()
|
||
|
|
||
|
When the installed boot loader is built with rollback support, a new
|
||
|
application image is by default only booted once. During this "test run"
|
||
|
it can perform whatever checks is appropriate (like testing whether it
|
||
|
can still reach the update server), and if satisfied can mark itself
|
||
|
as valid. Without being marked valid, upon the next reboot the system
|
||
|
would "roll back" to the previous version instead.
|
||
|
|
||
|
#### Syntax
|
||
|
`otaupgrade.accept()`
|
||
|
|
||
|
#### Parameters
|
||
|
None.
|
||
|
|
||
|
#### Returns
|
||
|
`nil`
|
||
|
|
||
|
|
||
|
# otaupgrade.rollback()
|
||
|
|
||
|
A new firmware may decide that it is not performing as expected, and
|
||
|
request an explicit rollback to the previous version. If the call to this
|
||
|
function succeeds, the system will reboot without returning from the
|
||
|
call.
|
||
|
|
||
|
Note that it is also possible to roll back to a previous firmware
|
||
|
version even after the new version has called `otaupgrade.accept()`.
|
||
|
|
||
|
#### Syntax
|
||
|
`otaupgrade.rollback()`
|
||
|
|
||
|
#### Parameters
|
||
|
None.
|
||
|
|
||
|
#### Returns
|
||
|
Never. Either the system is rebooted, or a Lua error is raised (e.g. due
|
||
|
to there being no other firmware to roll back to).
|