Skip to content

otaupgrade module documentation

Johny Mattsson edited this page Dec 8, 2015 · 2 revisions

NodeMCU Bootloader

The NodeMCU Bootloader is a DiUS implementation of a minimal yet feature-complete boot loader specifically aimed at NodeMCU. It provides two "slots" of 1meg each, each of which can hold a complete NodeMCU firmware image (optionally including the filesystem). The boot loader itself sits in the first page of the flash, and as such the first 4k in each of the slots is a reserved area.

To facilitate switching between firmware, the ESP8266's flash caching mechanism is used. The boot loader performs the initial unpacking of sections into RAM, and then passes the slot id as an argument to the NodeMCU entrypoint. In NodeMCU, the Cache_Read_Enable() is then overridden to force the correct slot to be mapped. Using the flash cache approach enables the use of a single firmware image which can be installed and run from either slot.

When performing an upgrade, the new image is marked as being "in-test". In this mode, the image only has a limited number of boot attempts to do any/all verification of its functionality, and if satisfied mark itself as being valid. If this is not done, the boot loader will roll back to the previous firmware. Likewise, if an in-test firmware concludes it is not functional, it can explicitly request a rollback. Before booting into a firmware, the boot loader enables the hardware watchdog, and this way catches completely botched upgrades.

The boot loader built as part of the regular NodeMCU build, and it resides in the nodemcu-boot directory. A number of different flavours are built, differing in verbosity and baud-rate used. The "silent" version is only intended for battery-operated deployments where conserving power is a key consideration - it is not intended for normal/development use.

Installing the NodeMCU Bootloader

As mentioned, the boot loader resides in the first page of the flash and therefore must be written to offset 0 during installation. Note that esptool typically erases more than the 4k used by the boot loader, so the boot loader should be the first to be flashed prior to the initial NodeMCU firmware.

Important: when installing the boot loader, the correct flash size MUST be specified using the -fs option, or you will likely end up with cyclic reboots.

$ esptool --port /dev/ttyUSB0 write_flash -fs 32m 0 nodemcu-boot/bin/nodemcu-boot-115200.bin 
Connecting...
Erasing flash...
Writing at 0x00000000... (100 %)

Leaving...

Preparing OTA images

The image format used by the boot loader differs from the ESP8266's format, in order to support the feature-set. A slimmed down version of esptool has been modified to provide the elf2ota command to generate an image in the correct format. It is available as nodemcu-boot/mkespimage.py.

When flashing the very first NodeMCU firmware, it must be pre-marked as valid. Directly flashing a regular OTA image will not yield a bootable firmware. To generate a directly-flashable image, use the --initially_valid argument to the elf2ota command:

nodemcu-boot/mkespimage.py elf2ota --initially_valid -o nodemcu.initial.ota app/.output/eagle/debug/image/eagle.app.v6.out

Normally the command can be run without any switches, and it generates an image usable as an over-the-air upgrade:

nodemcu-boot/mkespimage.py elf2ota -o nodemcu.test.ota app/.output/eagle/debug/image/eagle.app.v6.out

There is also a --max_tests argument which may be passed to control the number of boots allowed before forcing a rollback. The default is 3.

Flashing the initial OTA image

Assuming you have already flashed the boot loader and generated an OTA image with the --initially_valid switch, flashing it can be done to offset 0x1000 in either slot:

$ esptool --port /dev/ttyUSB0 write_flash 0x1000 nodemcu.initial.ota
Connecting...
Erasing flash...
Writing at 0x00078800... (100 %)

Leaving...

or

$ esptool --port /dev/ttyUSB0 write_flash 0x101000 nodemcu.initial.ota
Connecting...
Erasing flash...
Writing at 0x00178800... (100 %)

Leaving...

###otaupgrade module

#otaupgrade module The OTAUpgrade module implements the Over-The-Air (OTA) upgrade capability in NodeMCU. When compiled-in, NodeMCU gains the required support for working with the NodeMCU boot loader, in particular the Cache_Read_Enable() override.

info = otaupgrade.info()

####Description Returns information about the OTA images in the system.

The return value is a table, of the format:

{
  running = X,
  X = { preferred = n, in_test = m, attempts_left = o },
  Y = { preferred = n, in_test = m, attempts_left = o },
}

The running entry specifies which slot is currently booted, either 'A' or 'B'. Information about each slot is also provided, assuming a valid OTA image is found in said slot. The attempts_left key is only present for images which are marked as in-test.

####Example

print("Booted slot:",otaupgrade.info().running)

otaupgrade.commence()

####Description Erases the non-active slot and prepares the internal state for an OTA upgrade.

If the running image is currently in-test, it is not possible to start a new upgrade. The current firmware must be either accepted or rolled back before a new upgrade is attempted.

Note: erasing the slot takes several seconds.

####Example

otaupgrade.commence()

otaupgrade.write(offset, data)

####Description Writes firmware data.

While the common case is expected to write the firmware image in a linear fashion, a torrent-like approach is also possible. The offset parameter is relative to the firmware image - the first byte of the new firmware starts at offset zero. The data parameter is expected to be a Lua string containing raw/binary firmware data, and it will be written as-is.

Writing the same area multiple times is not supported and may yield undefined behaviour.

####Example

offs = 0
-- assuming 'chunk' contains the first part of a firmware downloaded from the upgrade server
otaupgrade.write (offs, chunk)
offs = offs + chunk:len()

otaupgrade.complete(optional_reboot)

####Description Completes the firmware write, and marks the image as ready to boot (until this is done the boot loader will consider the image in the slot to be invalid).

Optionally, the system can be immediately rebooted into the new firmware, if optional_reboot is a non-zero integer.

####Example

otaupgrade.complete() -- complete, but don't restart just yet
-- do whatever final steps are desired...
node.restart() -- then reboot into the new image

otaupgrade.reject(optional_reboot)

####Description If the current firmware is in-test, requests an immediate rollback to the previous firmware. In other circumstances this command has no effect.

####Example

otaupgrade.reject(1) -- roll back to previous firmware, and boot it now

otaupgrade.accept()

####Description If the current firmware is in-test, marks the previous firmware as no-longer-preferred, and the current firmware as valid and preferred.

####Example

otaupgrade.accept()