NervesHubLink.Client behaviour (nerves_hub_link v2.9.0)
View SourceThe primary integration point for customizing your applications connection with NervesHub.
The following callbacks are supported:
archive_available/1- an archive is available to download from NervesHubarchive_ready/2- an archive has been downloaded and is available for useconnected/0- a connection to NervesHub has been establishedfirmware_auto_revert_detected?/0- checks if a firmware revert occurredfirmware_validated?/0- checks if the current firmware has been validatedhandle_error/1- a firmware update has failedhandle_fwup_message/1- a message has been received byNervesHubLink.UpdateManageridentify/0- a request received from NervesHub to identify the device (eg. blink leds)reboot/0- a request received from NervesHub to reboot the devicereconnect_backoff/0- how NervesHubLink should handle reconnection backoffsupdate_available/1- should a firmware update be applied
A default Client is included (NervesHubLink.Client.Default) which :applys firmware
updates, :ignores archives, logs firmware update messages, and logs a message when
the identify/0 callback is used.
The recommended way to implement your own Client is to create your own module and add
use NervesHubLink.Client, which will allow you to use the same defaults included in
NervesHubLink.Client.Default, while also being able to customize any of the current
callback implementations.
Otherwise you can add @behaviour NervesHubLink.Client, but you will need to implement
the following required functions:
Example
defmodule MyApp.NervesHubLinkClient do
use NervesHubLink.Client
# override only the functions you want to customize
@impl NervesHubLink.Client
def update_available(data) do
if SomeInternalAPI.is_now_a_good_time_to_update?(data) do
:apply
else
{:reschedule, 60_000}
end
end
endTo have NervesHubLink use your client, add the following to your config.exs:
config :nerves_hub_link, client: MyApp.NervesHubLinkClient
Summary
Types
Archive that comes over a socket.
Supported responses from archive_available/1
Firmware update progress, completion or error report
Update that comes over a socket.
Supported responses from update_available/1
Callbacks
Called when an archive is available for download
Called when an archive has been downloaded and is available for the application to do something
Called when the connection to NervesHub has been established.
Optional callback to check if an auto firmware revert just occurred.
Optional callback to check if the current firmware has been validated.
Called when downloading a firmware update fails.
Called on firmware update reports.
Callback to identify the device from NervesHub.
Optional callback to reboot the device when a firmware update completes
Optional callback when the socket disconnected, before starting to reconnect.
Called to find out what to do when a firmware update is available.
Functions
The common logic to determine if an auto revert occurred is to check if the previous firmware is not validated. This is because, for example, if a device boots into firmware slot A and isn't able to validate the slot within the initialization callback time, the device will reboot into the previous firmware slot, B, and now firmware slot A will be shown as not validated.
A wrapper function which calls firmware_validated?/0 on the configured NervesHubLink.Client.
This function is called internally by NervesHubLink to notify clients of fwup errors.
This function is called internally by NervesHubLink to notify clients of fwup progress.
This function is called internally by NervesHubLink to identify a device.
This function is called internally by NervesHubLink to initiate a reboot.
This function is called internally by NervesHubLink to notify clients of disconnects.
This function is called internally by NervesHubLink to notify clients.
Types
@type archive_data() :: NervesHubLink.Message.ArchiveInfo.t()
Archive that comes over a socket.
@type archive_response() :: :download | :ignore | {:reschedule, pos_integer()}
Supported responses from archive_available/1
@type fwup_message() :: {:ok, non_neg_integer(), String.t()} | {:warning, non_neg_integer(), String.t()} | {:error, non_neg_integer(), String.t()} | {:progress, 0..100}
Firmware update progress, completion or error report
@type update_data() :: NervesHubLink.Message.UpdateInfo.t()
Update that comes over a socket.
@type update_response() :: :apply | :ignore | {:ignore, String.t()} | {:reschedule, pos_integer()} | {:reschedule, pos_integer(), String.t()}
Supported responses from update_available/1
Callbacks
@callback archive_available(archive_data()) :: archive_response()
Called when an archive is available for download
May return one of:
download- Download the archive right nowignore- Don't download this archive.{:reschedule, timeout}- Defer making a decision. Call this function again intimeoutmilliseconds.
@callback archive_ready(archive_data(), Path.t()) :: :ok
Called when an archive has been downloaded and is available for the application to do something
@callback connected() :: any()
Called when the connection to NervesHub has been established.
The return value of this function is not checked.
@callback firmware_auto_revert_detected?() :: boolean()
Optional callback to check if an auto firmware revert just occurred.
The default behavior is to check if the previous firmware slots:
nerves_fw_validatedvalue is0- and
nerves_fw_platformis not empty - and
nerves_fw_architectureis not empty
If there is custom logic built into fwup-ops.conf around prevent-revert, this should be
reflected here.
@callback firmware_validated?() :: boolean()
Optional callback to check if the current firmware has been validated.
The default behavior is to delegate to Nerves.Runtime.firmware_valid?/0.
If there is custom logic built into your fwup.conf and fwup-ops.conf
files, you should implement this callback in your NervesHubLink.Client.
@callback handle_error(any()) :: :ok
Called when downloading a firmware update fails.
The return value of this function is not checked.
@callback handle_fwup_message(fwup_message()) :: :ok
Called on firmware update reports.
The return value of this function is not checked.
@callback identify() :: :ok
Callback to identify the device from NervesHub.
@callback reboot() :: no_return()
Optional callback to reboot the device when a firmware update completes
The default behavior is to call Nerves.Runtime.reboot/0 after a successful update. This
is useful for testing and for doing additional work like notifying users in a UI that a reboot
will happen soon. It is critical that a reboot does happen.
@callback reconnect_backoff() :: [integer()] | nil
Optional callback when the socket disconnected, before starting to reconnect.
The return value is used to reset the next socket's retry timeout. nil asks NervesHubLink
to calculate a set of random backoffs to use.
You may wish to use this to dynamically change the reconnect backoffs. For instance, during a NervesHub deploy you may wish to change the reconnect based on your own logic to not create a thundering herd of reconnections. If you have a particularly flaky connection you can increase how fast the reconnect happens to avoid overloading your server.
@callback update_available(update_data()) :: update_response()
Called to find out what to do when a firmware update is available.
May return one of:
apply- Download and apply the update right now.ignore- Don't download and apply this update.{:reschedule, timeout}- Defer making a decision. Call this function again intimeoutmilliseconds.
Functions
@spec archive_available(archive_data()) :: archive_response()
@spec archive_ready(archive_data(), Path.t()) :: :ok
@spec connected() :: any()
@spec firmware_auto_revert_detected?() :: boolean()
The common logic to determine if an auto revert occurred is to check if the previous firmware is not validated. This is because, for example, if a device boots into firmware slot A and isn't able to validate the slot within the initialization callback time, the device will reboot into the previous firmware slot, B, and now firmware slot A will be shown as not validated.
We also need to account for the logic used by prevent-revert in fwup-ops.conf,
which can be different/custom per Nerves system. The common pattern is to unset
nerves_fw_platform and nerves_fw_architecture.
The default implementation checks if the previous firmware slot is not validated,
and that nerves_fw_platform and nerves_fw_architecture are not empty.
Clears platform and architecture uboot env vars
- https://github.com/nerves-project/nerves_system_rpi4/blob/main/fwup-ops.conf#L51-L52
- https://github.com/nerves-project/nerves_system_rpi5/blob/main/fwup-ops.conf#L51-L52
Clears platform, architecture, and validated uboot env vars
@spec firmware_validated?() :: boolean()
A wrapper function which calls firmware_validated?/0 on the configured NervesHubLink.Client.
If the function isn't implemented, the default logic of delegating to
Nerves.Runtime.firmware_valid?/0 is used.
@spec handle_error(any()) :: :ok
This function is called internally by NervesHubLink to notify clients of fwup errors.
@spec handle_fwup_message(fwup_message()) :: :ok
This function is called internally by NervesHubLink to notify clients of fwup progress.
@spec identify() :: :ok
This function is called internally by NervesHubLink to identify a device.
@spec initiate_reboot() :: :ok
This function is called internally by NervesHubLink to initiate a reboot.
After a successful firmware update, NervesHubLink calls this to start the
reboot process. It calls reboot/0 if supplied or
Nerves.Runtime.reboot/0.
@spec reconnect_backoff() :: [integer()]
This function is called internally by NervesHubLink to notify clients of disconnects.
@spec update_available(update_data()) :: update_response()
This function is called internally by NervesHubLink to notify clients.