Add rpi-connect/rpi-connect-ota-demo example - #789
kilograham wants to merge 2 commits into
Conversation
Move the device-side Pi Connect OTA application from the SDK's test/pico_rpi_connect_test into pico-examples. The host (POSIX) registration/debug tool and unit tests remain in the SDK tree. No SDK changes required - the app only uses public pico/ headers.
| ### Step 2 — Register the device identity with your organisation | ||
|
|
||
| Registration runs on the **host** (Linux/macOS) using the host build of this | ||
| example (`main.c` in this directory), which links the `pico_rpi_connect` | ||
| library against host OpenSSL and curl. | ||
|
|
||
| Build the host tool with the SDK host platform. Use a separate build | ||
| directory and select the `none` board explicitly: a device `PICO_BOARD` | ||
| exported in your environment (e.g. `pico2_w`) would otherwise be picked up and | ||
| conflict with the host platform. `rpi-connect-ota-host-build.sh` in this | ||
| directory runs exactly these two commands: | ||
|
|
||
| ```sh | ||
| cmake -S $PICO_EXAMPLES_PATH -B $PICO_EXAMPLES_PATH/build-host -DPICO_PLATFORM=host -DPICO_BOARD=none | ||
| cmake --build $PICO_EXAMPLES_PATH/build-host --target rpi_connect_ota_demo | ||
| alias rpi-connect-test=$PICO_EXAMPLES_PATH/build-host/rpi-connect/rpi-connect-ota-demo/rpi_connect_ota_demo | ||
| ``` | ||
|
|
||
| The host build needs the libcurl and OpenSSL development packages installed; | ||
| without them the SDK skips `pico_rpi_connect` and the target does not exist. | ||
|
|
||
| Register the public key against your org. The request is itself signed with the | ||
| private key (`--device-privkey`) to prove ownership of the pair: | ||
|
|
||
| ```sh | ||
| export RPI_CONNECT_ORG_TOKEN="<your-organisation-token>" | ||
|
|
||
| rpi-connect-test --create-device-identity \ | ||
| --device-privkey device-priv-key.pem \ | ||
| --device-pubkey device-pub-key.pem \ | ||
| --description "Pico 2 W OTA demo" \ | ||
| --device-name "pico-ota-01" | ||
| ``` | ||
|
|
||
| The device now exists in your organisation. (You can sanity-check the whole | ||
| auth chain before touching OTP by running | ||
| `rpi-connect-test --device-identity-exchange --serial <serial> | ||
| --device-privkey device-priv-key.pem --device-pubkey device-pub-key.pem`, which | ||
| prints the `RPI_CONNECT_TOKEN` the device would obtain.) |
There was a problem hiding this comment.
I don't think this is going to work in VS Code, because we don't have host compilers (especially on Windows) - would it be possible to do this setup using a Python script instead?
There was a problem hiding this comment.
Or alternatively a separate binary that runs on the device to perform first registration with the RPI_CONNECT_ORG_TOKEN compiled in, maybe reading the private key from OTP directly and calculating the public key?
There was a problem hiding this comment.
This key should be programmed as ECC, not RAW
Also, this functionality should probably be added into picotool otp load, instead of requiring this script - maybe picotool otp load -s <ROW> <FILE>.pem (it can detect the file type). picotool already has the functionality to convert PEM files to 32-byte scalars in read_keys, so that also removes the dependency on OpenSSL here.
|
|
||
| # Workaround lack of E10 errata handling in picotool: | ||
| # Need to request an `info` before requesting the erase! | ||
| picotool info | ||
| picotool erase --range 0x10000000 0x10400000 |
There was a problem hiding this comment.
This workaround is in picotool now
| # Workaround lack of E10 errata handling in picotool: | |
| # Need to request an `info` before requesting the erase! | |
| picotool info | |
| picotool erase --range 0x10000000 0x10400000 | |
| picotool erase --range 0x10000000 0x10400000 |
| static char *ffs_get_string(uint8_t file_id) { | ||
| const char *data; | ||
| int rc = ffs_read(file_id, &data); | ||
| if (rc >= 0 && data) { | ||
| return strdup(data); | ||
| } | ||
| return NULL; | ||
| } |
There was a problem hiding this comment.
Given ffs_read returns the file size, could this be modified to read non-NUL-terminated strings instead? Those would make it possible to create these files using CMake, as CMake cannot write NUL bytes to a file.
Maybe just use strndup, which should provide support for both NUL-terminated and non-NUL-terminated strings?
| static char *ffs_get_string(uint8_t file_id) { | |
| const char *data; | |
| int rc = ffs_read(file_id, &data); | |
| if (rc >= 0 && data) { | |
| return strdup(data); | |
| } | |
| return NULL; | |
| } | |
| static char *ffs_get_string(uint8_t file_id) { | |
| const char *data; | |
| int rc = ffs_read(file_id, &data); | |
| if (rc >= 0 && data) { | |
| return strndup(data, rc); | |
| } | |
| return NULL; | |
| } |
| deployment targeting this device with a new `rpi_connect_ota_demo.uf2`. The | ||
| image must carry a strictly higher picobin version than the one running or the | ||
| bootrom reverts to the old image at the next power cycle: the minor version |
There was a problem hiding this comment.
Is this statement true? Presumably the update reboots with a flash_update_boot, in which case it will work fine if you update with an older version, it'll just erase the newer version when you buy it
There was a problem hiding this comment.
Can confirm that it will update fine to an older version, using the normal version downgrade functionality (bootrom erases the newer version, so it always picks the older version)
| Success is **not** reported to the server before the reboot — only the | ||
| `APPLYING` status is persisted to FFS. After the reboot the new image signs in | ||
| and registers its OTA capability, and only then is success reported and the | ||
| bootrom *buy* issued to commit it (both from `rpi_connect_ota_boot_sync()`). So the deployment is | ||
| reported successful only once the new firmware has actually booted and reached | ||
| the API. If the new image can't sign in or reach the server (or crashes | ||
| earlier), neither the success report nor the buy happens, and the bootrom rolls | ||
| back to the previous image on the next reset. The old image then finds the | ||
| leftover `APPLYING` status on a normal (non-flash-update) boot and reports the | ||
| deployment **failed**, so the server knows to re-deploy. If a download is | ||
| interrupted by a reboot mid-way, the resume path picks it up and re-downloads | ||
| automatically. |
There was a problem hiding this comment.
This describes the TBYB behaviour of the bootrom, but I don't see PICO_CRT0_IMAGE_TYPE_TBYB being set anywhere - if that isn't set, then the bootrom buys the image before booting it, and therefore will not roll back to the old image on failure, instead it'll just boot the new image again.
Is PICO_CRT0_IMAGE_TYPE_TBYB actually being set on the update UF2?
There was a problem hiding this comment.
Can confirm that it does not behave well without PICO_CRT0_IMAGE_TYPE_TBYB set - if I update from major version 1 to 2, and yank power during the first boot of version 2, when I plug back in it boots version 2 but reports that the deployment failed:
Update was not applied for deployment ID=xxx; reporting failure
Failing deployment ID=xxx reason=update not applied
|
Running this overnight seems to eventually lead to this error - could something be added to recover from this? This was with the following debug options: |
* Reorganise CMakeLists.txt with rpi_connect_ota_demo_libs Makes it easier to add other variants of rpi_connect_ota_demo (e.g. no_flash signed/encrypted) * ffs_get_string moved into ffs library * Further refactoring, combined UF2s, and TBYB/no_flash variants * More fixups Add on-device registration binary, for use if no host build available Update README.md with new instructions for provisioning using picotool, on-device registration, and combined UF2s Delete partitioning and OTP writing scripts, now those are handled by combined UF2s and picotool * Add update UF2 copying into source tree Should make it easier to use the update UF2s, can specify directory with RPI_CONNECT_OTA_UPDATE_DIR Also sign the packaged binary with the example signing key * Add optional CONNECT_TOKEN CMake variable Also remove leftover references to shell scripts, and emphasize that you should only upload updates to Pi Connect, never combined UF2s * Only add CONNECT_TOKEN to FFS blob if set
This requires the rpi-connect-preview branch of pico-sdk