7.5 KiB
OTA Upgrade module
Since | Origin / Contributor | Maintainer | Source |
---|---|---|---|
2019-06-24 | DiUS, Johny Mattsson | Johny Mattsson | 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.
Syntax
otaupgrade.info()
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 ofnew
,testing
,valid
,invalid
,aborted
or possiblyundefined
. The valuesinvalid
andaborted
largely mean the same things. See the IDF documentation for specifics. A partition intesting
state needs to callotaupgrade.accept()
if it wishes to becomevalid
.name
the application name, typically "NodeMCU"date
the build datetime
the build timeversion
the build version, as set by the PROJECT_VER variable during buildsecure_version
the secure version number, if secure boot is enabledidf_version
the IDF version
Example
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
-- 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).