Notecard API Requests for DFU
Notecard provides a set of APIs to help you monitor and manage both host MCU and Notecard firmware updates. Based on the Notecard's own firmware update protocols, these APIs offload a significant part of the burden of implementing over-the-air firmware updates.
Certain API requests below are only available for IAP host MCU firmware updates, Notecard Outboard Firmware Update, and/or Notecard firmware updates and are identified on a per-API basis.
NOFU Only: Enabling Notecard Outboard Firmware Update
The
card.dfuAPI is only relevant for Notecard Outboard Firmware Update.
The card.dfu API is used to configure a Notecard to enable or disable Notecard Outboard Firmware Update.
For example, to allow the Notecard to flash the host MCU with a downloaded
binary on an STM32-based host, set "name" to "stm32" and "on" to true:
{
"req": "card.dfu",
"name": "stm32",
"on": true
}J *req = NoteNewRequest("card.dfu");
JAddStringToObject(req, "name", "stm32");
JAddBoolToObject(req, "on", true);
NoteRequest(req);req = {"req": "card.dfu"}
req["name"] = "stm32"
req["on"] = True
card.Transaction(req)Other host architectures use a different name, such as esp32 or mcuboot. See
Notecard to Host MCU
for the supported host names and pin settings.
The setting persists across restarts. To pause outboard updates later, send
card.dfu with "off": true.
Obtaining Firmware Download Status
The
dfu.statusAPI works with all types of firmware updates.
The dfu.status API 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 with two
fields that answer two different questions:
modereports where the firmware download currently stands. On a Notecard that has never downloaded firmware this isidle, meaning no download is in progress and no data has been downloaded. The other possible values are covered in DFU modes below.onreports whether the Notecard is allowed to download firmware at all. This istrueby default.
Use the "name" argument to specify the type of firmware update you want to
check:
"user"(the default) for IAP or Notecard Outboard Firmware Update host MCU updates."card"for Notecard firmware updates.
{
"req": "dfu.status",
"name": "user"
}J *req = NoteNewRequest("dfu.status");
JAddStringToObject(req, "name", "user");
NoteRequest(req);req = {"req": "dfu.status"}
req["name"] = "user"
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, by using the
vvalue argument. This argument expects a semicolon-delimited string of
<state>:<1|0> pairs, where 1 allows firmware downloads in that state and 0
disallows them. The pre-defined Notecard battery states are:
usbhighnormallowdead
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 voltage 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)DFU Modes
In addition to idle mode, these are the mode values you will typically see
after a device firmware update has been activated:
downloadingreadyoutboard-readyerrorcompleted
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 Notecard is configured for
Notecard Outboard Firmware Update
with card.dfu, a host firmware (user) request reports outboard-ready
instead of ready. This means Notecard will flash the host itself.
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": "firmware update superseded {dfu-superseded}",
"on": true
}IAP Only: Entering Host DFU Mode on the Notecard
The
hub.setAPI's"mode":"dfu"argument is only relevant for IAP host MCU firmware updates.
Once the firmware binary is available, Notecard can be put into DFU mode by
setting the
hub.set API's
mode argument to dfu. This request halts all Notecard communications
activity and allows the host to access downloaded host MCU firmware from
internal storage.
This step is not required on modern Notecards that store the downloaded image in
onboard flash and run firmware v7.3.1 or later. They can serve dfu.get requests
while connected and syncing normally. It is required on green Notecard Cellular
(legacy) devices though. See
When DFU Mode Is Required
for more information.
{
"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)IAP Only: Ensuring Host DFU Mode is Active
The
dfu.getAPI is only relevant for IAP host MCU firmware updates and only applies if you entered DFU mode with ahub.set, mode:dfurequest.
Setting the device to "dfu" mode does not make it ready to retrieve host MCU
firmware immediately. The Notecard first has to finish any in-progress
communications and close its network connection, and how long that takes depends
on what it was doing at the time. To check whether the Notecard is ready, use the
dfu.get API to 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)Until Notecard has closed its network connection and is ready to serve the
image, this request returns an error containing {dfu-not-ready}. Wait a few
seconds and retry.
{
"err": "disconnecting so that we can enter DFU mode {dfu-not-ready}"
}Once DFU mode is active, the request returns the same body that dfu.status
reports for the downloaded image, and you can start
retrieving the firmware.
If you instead get a not currently in the DFU operating mode error, DFU mode
was never entered or has timed out, so send the hub.set request again.
IAP Only: Retrieving Host Firmware from the Notecard
The
dfu.getAPI is only relevant for IAP host MCU firmware updates.
Once the Notecard is in dfu mode, use the
dfu.get API to
retrieve the downloaded host MCU 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
base64-encoded payload string containing the portion of the binary of the
requested length and offset, and a status string holding the MD5 hash of
that chunk, which you can use to verify it.
{
"payload": "AAAAAAAAAAAAAAAAcy8ACIEvAAgAAAAAjy8ACJ0vAAg=",
"status": "4bfa4cb4ef66b8cc24ad65ff2c33ace0"
}A single dfu.get request can return at most 8192 bytes, and a larger length
returns a the maximum length of any single request must be <= 8192 error.
Requests also cannot read past the end of the image, so size the final request
to body.length minus the current offset. Once you have the whole image,
check it against body.md5 before applying it.
Faster Transfers with the Binary Storage Area
The payload field is base64-encoded, which inflates every chunk by roughly a
third and makes the transfer of a large host binary slow (especially over I2C or
a low-baud serial connection). On Notecard firmware v7.3.1 and later, the
binary argument to dfu.get avoids the encoding entirely. Instead of
returning payload, Notecard places the chunk in its
binary storage area,
where your host reads it out with
card.binary.get.
The binary storage area holds one object at a time, and the whole application
shares it. If yours also uses it for web.post uploads or note.add payloads,
make sure those operations have finished before starting a firmware transfer.
-
Clear anything left in the binary storage area from a previous operation.
>{"req":"card.binary", "delete":true}{"max":130554} -
Request a chunk of firmware with
binaryset totrue. The response describes what currently sits in the binary storage area:lengthis the number of firmware bytes,cobsis the COBS-encoded length you will read off the wire, andstatusis the MD5 hash of that chunk.>{"req":"dfu.get", "binary":true, "offset":0, "length":8192}{ "cobs": 8225, "length": 8192, "status": "5d41402abc4b2a76b9719d911017c592" } -
Read the chunk out of the binary storage area.
note-c'sNoteBinaryStoreReceive()handles the COBS decoding and MD5 verification for you, and is available to any host that linksnote-c, including Arduino sketches usingnote-arduino.uint32_t chunk_len = 0; NoteBinaryStoreDecodedLength(&chunk_len); // NoteBinaryStoreReceive() reads the COBS-encoded bytes off the wire and // decodes them in place, so the buffer must be sized for the encoded form. uint32_t buf_len = NoteBinaryCodecMaxEncodedLength(chunk_len); uint8_t *chunk = (uint8_t *)malloc(buf_len); const char *err = NoteBinaryStoreReceive(chunk, buf_len, 0, chunk_len); if (err != NULL) { // Transfer or MD5 check failed; read the chunk again }Or issue the
card.binary.getrequest directly and COBS-decode the result yourself. See Reading Binary Data from Notecard for both paths in full.>{"req":"card.binary.get"}{"status":"5d41402abc4b2a76b9719d911017c592"} -
Write the chunk to your host's update region, then repeat steps 2 and 3, incrementing
offsetby the previouslengtheach time, until you have retrievedbody.lengthbytes.>{"req":"dfu.get", "binary":true, "offset":8192, "length":8192}{ "cobs": 8225, "length": 8192, "status": "0cc175b9c0f1b6a831c399e269772661" }Each
dfu.getoverwrites the binary storage area, so there is no need to clear it between chunks. Reading the area does not empty it either, which means you can re-read a chunk withcard.binary.getif a transfer to your host's flash fails, without asking the Notecard for it again. -
Release the binary storage area once the entire image has been retrieved, so the rest of your application can use it.
>{"req":"card.binary", "delete":true}{"max":130554}
Pair this with Faster Binary Transfers over AUX UART to move the read off I2C and onto a high-baud serial connection.
While Notecard is using Starnote for non-terrestrial connectivity, the binary
storage area is repurposed as the satellite queue, and dfu.get cannot return
firmware data at all. To work around this you would have to switch Notecard back
to a terrestrial transport before running an IAP update.
Clearing DFU State
The
dfu.statusAPI works with all types of firmware updates.
When your host finishes applying an
IAP host MCU firmware update,
let Notecard know with a
dfu.status request
that sets stop to true. This changes the DFU mode to completed, and the
optional status string is sent to Notehub as the result of the update.
Notecard Outboard Firmware Update and Notecard firmware updates don't need this step, because Notecard marks them completed on its own.
{
"req": "dfu.status",
"stop": true,
"status": "firmware update successful",
"name": "user"
}J *req = NoteNewRequest("dfu.status");
JAddBoolToObject(req, "stop", true);
JAddStringToObject(req, "status", "firmware update successful");
JAddStringToObject(req, "name", "user");
NoteRequest(req);req = {"req": "dfu.status"}
req["stop"] = True
req["status"] = "firmware update successful"
req["name"] = "user"
rsp = card.Transaction(req)name selects which update to clear: "user" (the default) for host firmware,
or "card" for Notecard firmware.
Reporting a Failed Update
If the update failed, send err instead of status. This sets the mode to
error and records your string as the reason. If you send both, err wins.
{
"req": "dfu.status",
"stop": true,
"err": "CRC check failed",
"name": "user"
}J *req = NoteNewRequest("dfu.status");
JAddBoolToObject(req, "stop", true);
JAddStringToObject(req, "err", "CRC check failed");
JAddStringToObject(req, "name", "user");
NoteRequest(req);req = {"req": "dfu.status"}
req["stop"] = True
req["err"] = "CRC check failed"
req["name"] = "user"
rsp = card.Transaction(req)Exiting DFU Mode
If you put Notecard into DFU mode with hub.set, finish by sending hub.set
with mode set to dfu-completed. Notecard leaves DFU mode and resumes syncing
with Notehub in whatever mode it was using before.
{
"req": "hub.set",
"mode": "dfu-completed"
}J *req = NoteNewRequest("hub.set");
JAddStringToObject(req, "mode", "dfu-completed");
NoteRequest(req);req = {"req": "hub.set"}
req["mode"] = "dfu-completed"
card.Transaction(req)