Module nh_ota

Applying a firmware update.

Description

Applying a firmware update.

NervesHub sends an update message naming a URL, a size and a SHA-256. This downloads that archive into the slot the device is not running, checks it, and points the boot path at it. Nothing reboots here — see nh_slots for the slot model and commit/0 and revert/0 for what happens on the way back up.

Nothing is held in memory

A packbeam is far larger than the heap an ESP32 has to spare, so the download is streamed: ahttp_client hands back {data, Ref, Bin} as the socket delivers it, each chunk is buffered only to a flash block, written, hashed and dropped. Peak memory is one block, whatever the archive weighs.

What is checked, and when

The digest is computed while writing rather than by reading the partition back, so a download that was corrupted in flight is caught. The archive is then read back from flash and walked, which catches a write that did not land. Only after both does the boot path move.

Order matters: until the boot path is written the device still boots what it was running, so a failure at any earlier point costs nothing but the download.

Data Types

update_result()

update_result() = {ok, binary()} | {error, term()}

Function Index

apply_update/1Equivalent to apply_update(Payload, #{}).
apply_update/2Download and install an update, returning the slot it was written to.
available/0Whether this platform can write flash at all.
available/1As available/0, against a given esp module.
commit/0Accept the running firmware, so it is no longer on trial.
commit/1As commit/0, against a given esp module.
digest_matches/2Compare a computed digest against the one NervesHub sent.
parse_url/1Split a firmware URL into what ahttp_client:connect/4 takes.
pending/0The slot a reboot is on trial for, if any.
pending/1As pending/0, against a given esp module.
revert/0Point the boot path back at the firmware that was running before.
revert/1As revert/0, against a given esp module.
start_update/2Run an update in its own process, reporting back to the caller.
start_update/3As start_update/2, with options for apply_update/2.

Function Details

apply_update/1

apply_update(Payload::map()) -> update_result()

Equivalent to apply_update(Payload, #{}).

apply_update/2

apply_update(Payload::map(), Opts::map()) -> update_result()

Download and install an update, returning the slot it was written to.

Payload is what NervesHub sends: firmware_url, size and checksum. Opts may carry a progress function of one argument, called with a percentage as the download proceeds, and a slot to override the target.

available/0

available() -> boolean()

Whether this platform can write flash at all.

available/1

available(Opts::map()) -> boolean()

As available/0, against a given esp module.

commit/0

commit() -> ok | {error, term()}

Accept the running firmware, so it is no longer on trial.

Called once the device has done something that proves the update worked — joining NervesHub is the evidence this library uses, because an update that cannot reach the server is one nothing could recover from remotely.

commit/1

commit(Opts::map()) -> ok | {error, term()}

As commit/0, against a given esp module.

digest_matches/2

digest_matches(Digest::binary(), Checksum::binary() | undefined) -> boolean()

Compare a computed digest against the one NervesHub sent.

Case insensitive: NervesHub stores a firmware checksum upper case and this library works in lower case, and a comparison that failed on that alone would reject every good download.

parse_url/1

parse_url(Url::binary() | string() | undefined) -> {ok, map()} | {error, term()}

Split a firmware URL into what ahttp_client:connect/4 takes.

Deliberately small: NervesHub hands out ordinary absolute URLs, and a full URI parser is not something to carry onto a device for that.

pending/0

pending() -> {ok, binary()} | none

The slot a reboot is on trial for, if any.

Set by an update and cleared by commit/0. A device that finds one here is running firmware that has not yet proved itself.

pending/1

pending(Opts::map()) -> {ok, binary()} | none

As pending/0, against a given esp module.

revert/0

revert() -> {ok, binary()} | {error, term()}

Point the boot path back at the firmware that was running before.

Does not reboot. The caller decides when, because reverting mid-flight and rebooting immediately would cut off whatever it was trying to report.

revert/1

revert(Opts::map()) -> {ok, binary()} | {error, term()}

As revert/0, against a given esp module.

start_update/2

start_update(Payload::map(), Owner::pid()) -> pid()

Run an update in its own process, reporting back to the caller.

The download takes as long as it takes, and the agent has heartbeats to send while it runs — so it does not run on the agent's process. The caller receives {nh_ota, self(), {progress, Percent}} as it goes and {nh_ota, self(), Result} at the end.

start_update/3

start_update(Payload::map(), Owner::pid(), Opts::map()) -> pid()

As start_update/2, with options for apply_update/2.


Generated by EDoc