Microvisor Private Beta
Microvisor is in a pre-release phase and the information contained in this document is therefore provisional and subject to change. Some features referenced below may not yet be available in or supported by the current version of the Microvisor system calls or REST API.
Application code is delivered to Microvisor-based devices in the form of Bundles. As a developer, you will use the Twilio Microvisor Tools to create a Bundle from your latest application build, upload it to the Twilio cloud, and trigger its deployment it to your development devices. Subsequent code changes follow the same path, and your final production release will also be transferred to devices on the assembly line by way of a Bundle. Later, devices in the field will receive code updates in the form of Bundles.
A Bundle is how application code, provided as a
.elf linkable binary, is packaged for use by Microvisor. Bundles are zip files. The application code these archives contain is sliced up into a series of non-overlapping, discrete blocks called Layers. This allows Bundles to be more readily transferred to devices over low-bandwidth connections. If the download is interrupted, Microvisor needs only reacquire the current Layer, not those that preceded it. It also allows Layers to be installed in any order. The Layer files are generated by the Bundle creation tool.
A Bundle’s Layers are enumerated in its Manifest, a signed file that also includes metadata, such as the application’s debugging policy, or network configuration information. Microvisor downloads the Manifest first and uses it to determine which Layers it needs to retrieve.
A Layer is a block of opaque code that is stored in the cloud for subsequent installation in a device’s flash. The Bundle’s Manifest lists all the Layers that make up the application. Each entry in the list includes the Layer’s SHA-256, which Microvisor uses to request the required Layer from the server.
As the developer, it’s up to you whether a given Layer is encrypted or not. Layers can be encrypted by passing a private key to the Bundle tool when the Bundle is created. All of a Bundle’s Layers are encrypted with the same key. If a Layer is encrypted, then its entry in the Manifest will indicate how the code is to be decrypted by Microvisor after it has been downloaded. This indication is not a cryptography key itself but a reference to a public key that must already be present on the device and which will be used to decrypt the Layer.
Whether the referenced Layer is encrypted or not, the Layer entry in the Manifest also includes the Layer’s SHA. We’ve already seen that this is used to request the Layer from the server. It is also used to confirm that the Layer has not been tampered with in flight and is safe to process. The authenticity of the recorded SHA itself is determined by verifying the Manifest’s own signature; Microvisor does this when it retrieves the Bundle.
Finally, the Layer entry specifies where the Layer’s bytes will eventually be written to in the device once the Layer has been verified as safe to install. This indicates the storage medium of the device — for example, the STM32U585 microcontroller’s internal flash for a code update, or internal or external flash for application data. The address at which the code will be written, and its length within the bounds of the medium, are included too. Layer location is typically set by the Bundle creation tool using data from the build process, but it can be specified manually.
Let’s say you have updated, built and signed a new version of your application. You then use the Microvisor Tools to generate a Bundle. Next, you use the Microvisor REST API, accessed via the Twilio CLI or a tool like curl, to upload the Bundle to Twilio.
You deploy the Bundle using the Microvisor API to instruct Twilio to signal specified devices that an update has been staged for them to download.
On each device, Microvisor downloads the Bundle Manifest and verifies its authenticity by signature. It then confirms that all the decryption keys referenced in the Manifest’s list of Layers are present on the device. It will also check the Bundle metadata to determine whether there are any other factors it needs to consider before proceeding with Layer processing, such as whether remote debugging has been permitted for this build.
Microvisor then parses the Manifest’s Layer list and uses it to request each Layer and store them in the QSPI staging area. Each Layer is encrypted and authenticated using fresh keys generated for this purpose. Layers may be downloaded in any order, so you should not anticipate any specific sequence to Layers’ arrival on the device. You cannot specify the Layer download sequence.
When all of the update’s Layers have been downloaded, Microvisor shuts down the application. It then works through the Layers in the QSPI staging area. Microvisor decrypts each one using the keys generated when the Layer was written to QSPI and then hashes it. It compares this value with the Layer’s hash from the Manifest; if they match, Microvisor moves on to the next Layer. Once it has verified all the Layers, Microvisor writes each Layer to the location specified in the Manifest.
When all the Layers have been processed, and any metadata-specified configuration changes have been applied, Microvisor starts the updated application.
If any of the authenticity checks fail, or required keys are not present on the device, the installation process as whole fails. This ensures that updates are not only partially installed.
We expect to implement limits on the number of Bundles you can have stored in the Twilio cloud at any one time, and the upload facility will also be rate-limited. The details of any such limitations have yet to be determined, but we will publish details as the Microvisor beta program progresses toward GA.
bundler.py utility that is part of the Microvisor Tools located in the public GitHub repo
twilio/twilio-microvisor-tools. Install the tools as follows:
git clone https://github.com/twilio/twilio-microvisor-tools
This tool takes the path of your compiled application
.elf file, and the path and filename at which the resulting Bundle will be written. For example:
python3 twilio-microvisor-tools/bundler-py/bundler.py \ twilio-microvisor-freertos/build/Demo/gpio_toggle_demo.elf \ twilio-microvisor-freertos/build/Demo/gpio_toggle_demo.zip
This assumes that you have installed and compiled the Microvisor FreeRTOS Demo code.
At this time, Bundler does not encrypt the Bundle’s Layers. This functionality will be added shortly.
You can upload the Bundle file to Twilio with this Microvisor API endpoint:
curl -X POST https://microvisor-upload.twilio.com/v1/Apps \ -H 'Content-Type: multipart/form-data' \ -F File=@./twilio-microvisor-freertos/build/Demo/gpio_toggle_demo.zip \ -u $YOUR_ACCOUNT_SID:$YOUR_AUTH_TOKEN
This assumes your Twilio Account SID and Auth Token have been set to the named environment variables.
Once uploaded, the application contained in the Bundle is represented by a Microvisor API App resource, for which a new instance is been created when the app is uploaded. The new App’s details are returned by the upload request; these include the App’s SID, which can be used to deploy the App to devices.
You can also get the App’s SID by listing the Apps currently associated with your account:
curl https://microvisor.twilio.com/v1/Apps \ -u $ACCOUNT_SID:$AUTH_TOKEN
This will return a list in JSON form. You can pipe it through a tool like
jq to pretty-print it.
To avoid using SIDs, you may choose to provide the App with a friendly name to make it easier to identify in future. For example:
curl -X POST https://microvisor.twilio.com/v1/Apps/<APP_SID> \ -d 'UniqueName=<APP_NAME>' \ -u $ACCOUNT_SID:$AUTH_TOKEN
To deploy an App to a Microvisor-empowered device, send a request to the Microvisor API Device resource that represents your hardware:
curl -X POST https://microvisor.twilio.com/v1/Devices/<DEVICE_SID_OR_NAME> \ -d 'AppSid=<APP_SID_OR_NAME>' \ -u $ACCOUNT_SID:$AUTH_TOKEN
You can supply the App’s SID or, if you have set one, its friendly name.
If you don’t have the Device SID handy, make a
GET request to
https://microvisor.twilio.com/v1/Devices to retrieve a list of devices associated with your Twilio Account.
The device will be signalled that an application update is pending, and Microvisor will begin the download process, as described earlier.
The following Python program can be used to bulk delete old versions of apps during development. By default, “stale” apps — those uploaded more than 24 hours earlier — are automatically deleted. If a stale app is installed on a device, it cannot be deleted (error 400) and is listed as such.
The script assumes you have your Twilio login credentials set as environment variables:
#!/usr/bin/env python3 import os import sys import json import requests from requests.auth import HTTPBasicAuth from datetime import datetime, timedelta list_only = False app_list = None to_delete =  url = "https://microvisor.twilio.com/v1/Apps" auth = HTTPBasicAuth(os.environ["TWILIO_ACCOUNT_SID"], os.environ["TWILIO_AUTH_TOKEN"]) if len(sys.argv) > 1: for index, item in enumerate(sys.argv): if item.lower() in ("-h", "--help"): print("Usage: apps [--delete]") print("Options: --delete / -d Delete all stale apps.") print("Stale apps are those more that 24 hours old. Strale apps") print("that are still assigned to a device will not be deleted") sys.exit() if item.lower() in ("-d", "--delete"): list_only = False if item.lower() in ("-l", "--list"): list_only = True print("Listing your Microvisor applications...") date_now = datetime.now() resp = requests.get(url, auth=auth) if resp.status_code == 200: try: app_list = resp.json() if "apps" in app_list: for app in app_list["apps"]: sid = app["sid"] date_then = datetime.strptime(app["date_created"], '%Y-%m-%dT%H:%M:%SZ') if date_now - date_then >= timedelta(days=1): to_delete.append(sid) print(sid,"is STALE") else: print(sid,"is OK") except Exception as e: print("[ERROR] Could not parse response from Twilio", e) else: print("[ERROR] Unable to access your apps") if list_only: sys.exit() if len(to_delete) > 0: print("Deleting stale apps...") for sid in to_delete: resp = requests.delete(url + "/" + sid, auth=auth) if resp.status_code == 204: print(sid, "DELETED") else: print(sid, "NOT DELETED (" + str(resp.status_code) + ")")