Host Firmware Update Requests
The Notecard can be used by any host MCU or processor, from an 8-bit Arduino to a Raspberry Pi. It's also possible to communicate with the Notecard from any embedded language, including compiled languages like C and C++, to interpreted languages like Python and JavaScript. Because of this flexibility, you might expect that over the air firmware updates are not possible with the Notecard.
However, while the flexibility of host languages are processors makes a generic end-to-end DFU capability impossible, the Notecard does offer a set of host firmware update APIs that can be utilized by developers. Based on the Notecard's own firmware update protocols, these APIs offload a significant part of the burden of implementing over the air host firmware updates across devices.
At a high-level, the host firmware update process consists of the following steps:
- A developer creates a new firmware revision, including a version number and optional metadata that can be extracted from the binary upon upload.
- The administrator of the Project uploads the firmware binary to Notehub.
- Notehub extracts the version number, metadata, and adds the binary to the Project.
- The administrator of the Project selects one or more Notecards to update and queues a new firmware version for update.
- When target Notecards sync with Notehub, they will identify the new host firmware and download it progressively in the background.
- As the Notecard receives the host firmware, it places it into a special
firmware storage area of flash. Periodically throughout this process, the
host firmware issues
dfu.status
requests to the Notecard to determine the status of a firmware binary download. - Once the download has completed, the host will use
hub.set
to halt network communication and inform the Notecard that it needs to access new firmware. - Next, the host will issue
dfu.get
requests to load chunks of the firmware binary into its own memory. - After the host obtains the full binary, it will re-flash and restart.
- Finally, the host will use
hub.set
to place the Notecard back intoperiodic
orcontinuous
mode.
The remainder of this section covers the Notecard API requests involved in steps 5, 6, and 8 above. Specifically: downloading host firmware, entering DFU mode on the Notecard, and retrieving host firmware from the Notecard to your host device.
Notehub supports the orchestration of the other OTA DFU steps via Notecard Outboard Firmware Update (supported on most modern STM32- and ESP32- based hosts) and IAP Firmware Updates. Consult the Manage Host Firmware section of the Notehub Walkthrough for more information on both methods.
Host firmware updates are not supported on the Notecard LoRa.
Obtaining Host Firmware Download Status
The dfu.status
request is used to determine the status of the background
download of firmware, and locally control whether the Notecard will allow a
background firmware download.
When called with no arguments, a dfu.status
request returns an object
indicating the current mode
, on
or off
to indicate whether local firmware
updates are allowed. The default mode is idle
to indicate that no firmware
download is in progress and no data has been downloaded. In addition, on
is
true
by default to indicate that the Notecard can receive firmware updates.
{
"req": "dfu.status"
}
J *req = NoteNewRequest("dfu.status");
NoteRequest(req);
req = {"req": "dfu.status"}
rsp = card.Transaction(req)
{
"mode": "idle",
"on": true
}
To disable firmware downloads to the Notecard, set the off
argument to true
:
{
"req": "dfu.status",
"off": true
}
J *req = NoteNewRequest("dfu.status");
JAddBoolToObject(req, "off", true);
NoteRequest(req);
req = {"req": "dfu.status"}
req["off"] = True
rsp = card.Transaction(req)
To turn it back on, set the on
argument to true
:
{
"req": "dfu.status",
"on": true
}
J *req = NoteNewRequest("dfu.status");
JAddBoolToObject(req, "on", true);
NoteRequest(req);
req = {"req": "dfu.status"}
req["on"] = True
rsp = card.Transaction(req)
You can also use a voltage-variable value to control whether or not firmware
updates are allowed, based on the battery level of the device, with the
vvalue
argument. This argument expects semicolon-delimited string values of on
or off values that correspond to a battery state. The pre-defined Notecard
battery states are:
usb
high
normal
low
dead
When the Notecard's power source is in a given state, it will adjust whether a firmware download is allowed based on the values in that string. For instance, if you want to allow firmware updates when the battery is full or high, but not when the power is lower, send a request like this:
{
"req": "dfu.status",
"vvalue": "usb:1;high:1;normal:0;low:0;dead:0"
}
J *req = NoteNewRequest("dfu.status");
JAddStringToObject(req, "vvalue", "usb:1;high:1;normal:0;low:0;dead:0");
NoteRequest(req);
req = {"req": "dfu.status"}
req["vvalue"] = "usb:1;high:1;normal:0;low:0;dead:0"
rsp = card.Transaction(req)
In addition to idle
mode, dfu.status
will return one of the following mode
values after a device firmware update has been activated:
downloading
ready
error
completed
The downloading
mode indicates that the Notecard detected the presence of new
firmware on a previous sync and is in the process of downloading it. When in
this mode, a status
string is included in the response with additional details
about download progress.
{
"mode": "downloading",
"status": "downloaded 66% (28672/42892)",
"on": true
}
Once the download is complete, the mode
changes to ready
to indicate that
the firmware binary is fully downloaded and verified. When in this mode, a
status
string is included, as well as a body
JSON object that includes
essential details about the firmware binary, including the length
of the
binary, its md5
hash, and more.
{
"mode": "ready",
"status": "successfully downloaded",
"on": true,
"body": {
"crc32": 2525287425,
"created": 1599163431,
"info": {},
"length": 42892,
"md5": "5a3f73a7f1b4bc8917b12b36c2532969",
"modified": 1599163431,
"name": "stm32-new-firmware$20200903200351.bin",
"notes": "Latest prod firmware",
"source": "stm32-new-firmware.bin",
"type": "firmware"
}
}
If the Notecard encounters an error during the download, the mode
reports as
error
and the status
field will provide a reason for the error.
{
"mode": "error",
"status": "DFU did not complete",
"on": true
}
Entering DFU Mode on the Notecard
Once the firmware binary is available, the Notecard should be put into DFU mode
by setting the hub.set
mode
argument to dfu
. This request halts all
Notecard communications activity and allows the host to access downloaded
firmware from internal storage.
{
"req": "hub.set",
"mode": "dfu"
}
J *req = NoteNewRequest("hub.set");
JAddStringToObject(req, "mode", "dfu");
NoteRequest(req);
req = {"req": "hub.set"}
req["mode"] = "dfu"
rsp = card.Transaction(req)
Ensuring DFU Mode is Active
Depending on in-progress communications on the Notecard, it may take up to 30
seconds after setting the device to dfu
mode before it is ready to retrieve
firmware. To ensure that the Notecard is in DFU mode, issue a dfu.get
request
and set the length
argument to 0
. This will verify that the device is in
DFU mode without attempting to retrieve firmware.
{
"req": "dfu.get",
"length": 0
}
J *req = NoteNewRequest("dfu.get");
JAddNumberToObject(req, "length", 0);
NoteRequest(req);
req = {"req": "dfu.get"}
req["length"] = 0
rsp = card.Transaction(req)
Retrieving Host Firmware from the Notecard
Once the Notecard is in dfu
mode, use the dfu.get
request to retrieve
the downloaded firmware. This is typically done in successive chunks of length
n
until the entire binary has been delivered to the host. Use the length
argument to provide a number of bytes to read for each request, and offset
on
each successive request to skip to the next available chunk.
For instance, if you wanted to read the binary 32 bytes at a time, the first
request to dfu.get
would look like this:
{
"req": "dfu.get",
"length": 32
}
J *req = NoteNewRequest("dfu.get");
JAddNumberToObject(req, "length", 32);
NoteRequest(req);
req = {"req": "dfu.get"}
req["length"] = 32
rsp = card.Transaction(req)
And each subsequent request would add an offset value that increments by the previously-requested length, each time:
{
"req": "dfu.get",
"length": 32,
"offset": 32
}
J *req = NoteNewRequest("dfu.get");
JAddNumberToObject(req, "length", 32);
JAddNumberToObject(req, "offset", 32);
NoteRequest(req);
req = {"req": "dfu.get"}
req["length"] = 32
req["offset"] = 32
rsp = card.Transaction(req)
The first request to dfu.get
returns the same body
returned by dfu.status
after a successful download. The first and subsequent requests also return a
payload
string containing the portion of the binary of the requested
length
and offset
.
{
"payload": "AAAAAAAAAAAAAAAAcy8ACIEvAAgAAAAAjy8ACJ0vAAg="
}
Clearing DFU State
After a firmware update is complete, clear the Notecard's DFU state with a
dfu.status
request and the stop
argument. You can optionally supply a status
message that will be sent to Notehub to indicate the final update status to a
Notehub admin.
{
"req": "dfu.status",
"stop": true,
"status": "firmware update successful"
}
J *req = NoteNewRequest("dfu.status");
JAddBoolToObject(req, "stop", true);
JAddStringToObject(req, "status", "firmware update successful");
NoteRequest(req);
req = {"req": "dfu.status"}
req["stop"] = True
req["status"] = "firmware update successful"
rsp = card.Transaction(req)
This request will set the dfu.status
mode
to completed
and set the
status
field to the string value you provide.
Don't Forget to Exit DFU Mode!
Once the DFU process is completed, be sure to return the Notecard to
periodic
or continuous
mode after the firmware update is complete with
another hub.set
request. If you don't do this, the device will remain in
dfu
mode, unable to sync with Notehub.
{
"req": "hub.set",
"mode": "periodic"
}
J *req = NoteNewRequest("hub.set");
JAddStringToObject(req, "mode", "periodic");
NoteRequest(req);
req = {"req": "hub.set"}
req["mode"] = "periodic"
card.Transaction(req)