Skip to content

Firmware APIs

Firmware Updates

To perform a firmware update the <BASE-URL>/api/v1/firmware-update/ endpoint can be used. According to the request parameters, the configured delivery procedure and the permission scheme, the Device Hub will provide firmware image via a download link.

A firmware update contains mulitiple stages. Each firmware update session is identified by an identifer. The identifier is returned on the initial request (API response entry id) and must be provided on subsequent request endpoints. The state string (up_to_date, ready, queued, processing, created, unconfirmed, success, fail) is returned in the API response state entry.

The state diagram shows the stages involved for a complete firmware update.

  stateDiagram
    state request <<fork>>
    state "ready" as Ready
    state "up_to_date" as UpToDate
    state "queued" as Queued
    state "processing" as Processing: Run procedure pipeline
    state "created" as Created
    state "fail" as Fail
    state "success" as Success
    state "unconfirmed" as Unconfirmed

    [*] --> request: /request
    request --> Ready: Update available
    request --> UpToDate: No update available
    UpToDate --> [*]: Timeout
    Ready --> [*]: Timeout
    Ready --> Queued: /{id}/start
    Queued --> Processing
    Queued --> Fail : Queing Error
    Fail --> [*] : Timeout
    Processing --> Created : With confirm step
    Processing --> Fail: Processing Error
    Created --> Unconfirmed : Timeout
    Created --> Success : /{id}/confirm success=True
    Processing --> Success : Without confirm step
    Created --> Fail : /{id}/confirm success=False
    Unconfirmed --> [*] : Timeout
    Success --> [*] : Timeout

Example

Pre-requisites

  • Created a Product and Model
  • Created a Delivery Method and Delivery Procedure
  • Created a Channel
  • Created a User with a Token
  • Configured access rights
  • (optional) Uploaded a Firmware
  • (optional) Created a Device

The following API call initiates a firmware update request. It specifies the target image by specifying the Model, Delivery Method, Delivery Procedure, Channel, and the current firmware version of the requester. If the Device ID is provided, the Device Hub will update the device twin information. These parameters would usually be provided by the bootloader of the device. Other combinations of parameters can be used to specify a target image.

Python

Requires a client machine with Python 3 and the request package installed.

Request a firmware update session by providing your specification as a JSON body:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import json
import requests
import time

from private_settings import token

response = requests.post(
    "https://device.leitwert.ch/api/v1/firmware-update/request",
    json={
        "model": "CAFEBABE",
        "device_id": "my-device-id",
        "delivery_method": "DEADBEEF",
        "delivery_procedure_tid": "dp-handle",
        "current_channel": "BAADFOOD,
        "current_firmware_version": "0.1.0",
        "allow_draft_firmware": False,
    },
    headers={
        "Authorization": f"Token {token}",
        "Content-Type": "application/json",
    },
    )
print(json.dumps(response.json(), indent=2))
response.raise_for_status()

Example response:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
{
  "id": "6e429224-e20a-4c4a-93f6-3c20d5e2594b",
  "create_time": "2023-04-18T13:12:50.365610+02:00",
  "creator": "my-machine-user",
  "state": "ready",
  "finished": false,
  "expiration_time": "2023-04-18T13:27:50.347376+02:00",
  "delivery_procedure": "9ad0fe1b-282d-4e38-8c73-cc3a5f13e3a7",
  "current_firmware": null,
  "target_firmware": "742d63d1-a83b-4db3-9b8d-56f048df3a97",
  "device": "899a14d8-ecef-4605-ad3a-e54406f29e36",
...
}

Start the procedure pipline:

1
2
3
4
5
id = response.json()["id"]
response = requests.post(
    f"https://next-dms.leitwert.ch/api/v1/firmware-update/{id}/start",
    headers=header,
)

Wait until the download link is available. Note: Usually you would add some timeout in the loop:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
while response.json()["state"] not in ["fail", "created", "success"]:
    if response.json()["state"] == "fail":
        print("fail")
        exit(1)
    response = requests.get(
        f"https://next-dms.leitwert.ch/api/v1/firmware-update/{id}/",
        headers=header,
    )
    time.sleep(5)

download_url = None
if response.json()["state"] in ["created", "success"]:
    download_url = response.json()['payload']['download_link']

Download the firmware image:

1
2
3
if download_url:
    output = requests.get(download_url, headers=header).content
    print(f"Downloaded {len(output)} bytes.")

Confirm that the update is completed:

1
2
3
4
5
6
if output and response.json()["state"] == "created":
    response = requests.post(
        f"https://next-dms.leitwert.ch/api/v1/firmware-update/{id}/confirm",
        json={"success": True},
        headers=header,
    )

To check if the firmware request session completed successfully, you can check if the firwmare update state reached success:

1
2
3
4
5
response = requests.get(
    f"https://next-dms.leitwert.ch/api/v1/firmware-update/{id}/",
    headers=header,
)
print(response.json()["state"])

Chunked firmware download

To download a firmware image from the DMS server, you have two options:

  1. download the whole file in one request
  2. use chunked download using HTTP range requests

In most cases, option 1. will be good enough. However, if you have large firmware images and the connection to the server is unreliable (e.g. LTE connection with weak signal strength), option 2. can be a good alternative.

To download a single chunk, add a HTTP range header to the request, e.g.

1
{ "Range": "bytes=0-99" }
With this header, the response will only contain the first 100 bytes of the firmware image. You can then iterate the Range to download all chunks. The logic to merge the chunks and retry failed requests has to be implemented in the client.

Note that multipart range headers are not supported.

The following is an example in Python to download a firmware image in chunks:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import requests
import math

download_url = # todo: fill in your download URL
from private_settings import token

headers = {
    "Authorization": f"Token {token}",
    "Range": "bytes=0-1"
}
response = requests.get(url=download_url, headers=headers)
total_size = int(response.headers["Content-Range"].split("/")[1])
print(f"Expected total size: {total_size} bytes")

# download in chunks
number_of_chunks = 10
chunk_size = math.ceil(total_size / number_of_chunks)
full_content = b""
for index in range(0, number_of_chunks):
    start = index * chunk_size
    end = min(start + chunk_size - 1, total_size - 1)
    headers = {
        "Authorization": f"Token {token}",
        "Range": f"bytes={start}-{end}",
    }
    print(f"Get bytes {start} to {end}.")
    response = requests.get(url=download_url, headers=headers)
    full_content += response.content

    # write to file
    with open("firmware_image.bin", "wb") as f:
        f.write(full_content)
print(f"Downloaded {len(full_content)} bytes in chunks of {chunk_size} bytes")


Last update: 2023-04-28