MeshanicsDocs
Device agent

MCU install (Zephyr SDK)

The Meshanics MCU SDK (meshanics-com/mcu-sdk) is a Zephyr module you link into your firmware to get secure over-the-air updates, fleet identity, and automatic rollback - without writing the update machinery. One function call starts the agent; the rest is the platform's job. Using ESP-IDF and FreeRTOS instead? See the ESP-IDF / FreeRTOS guide.

Supported profile. This guide covers ESP32-S3 with Zephyr, WiFi and MCUboot. Qualify the exact board, partition layout, transport and recovery behavior used by your product before a production rollout. Other boards and transports require a separately supported profile.

How it works

The device generates its own key on-chip during first boot, enrolls with the platform, and appears in your fleet - no per-device firmware, no key spreadsheets, no hand-edited credential headers. The application image is generic: one build serves every device. After that:

  1. You build a new firmware version, upload it, and include it in a signed release for the MCU product and hardware profile.
  2. The platform queues the update according to your rollout strategy (waves, canary, halt rules - the same engine every other device class uses).
  3. On the device, the SDK pulls a compact signed authorization, verifies it against the key it was provisioned with at enrollment, downloads the firmware block-by-block over mutual TLS, stages it into the MCUboot secondary slot, and triggers a revertable swap.
  4. After reboot, MCUboot loads the new image. Registration, the local status listener and the application's bounded health callback must all succeed before the SDK confirms it. If they do not, MCUboot reverts automatically on the next boot. No custom rollback code is required.

Two independent signatures protect every update: your own MCUboot image signing key (only firmware you built can boot your devices) and the Meshanics update key (visible and verifiable on the Trust Center). There is no path that skips either check.


Prerequisites

  • Zephyr SDK and west installed (Zephyr getting started guide)
  • MCUboot in your west workspace (bootloader/mcuboot)
  • A Meshanics account with at least one device slot available

1. Add the SDK as a west module

In your application's west.yml (or your workspace manifest), add the SDK under projects:

manifest:
  projects:
    - name: meshanics-mcu-sdk
      url: https://github.com/meshanics-com/mcu-sdk
      revision: v0.2.0
      path: modules/meshanics

Then fetch it:

west update

The SDK registers itself as a Zephyr module; Kconfig and CMake pick it up automatically after west update.


2. Write your application

Kconfig (prj.conf)

Enable the agent and set your firmware version. The version is what the platform matches against rollout targets.

# Meshanics MCU agent - selects the networking, mutual-TLS, MCUboot and NVS
# subsystems it needs.
CONFIG_MESHANICS_AGENT=y
CONFIG_MESHANICS_FW_VERSION="1.0.0"

# MCUboot A/B (required for the OTA swap + auto-rollback)
CONFIG_BOOTLOADER_MCUBOOT=y
CONFIG_MCUBOOT_IMG_MANAGER=y

The SDK selects the subsystems, but the mbedTLS cipher/curve profile (TLS 1.3, P-256/P-384, SNI, heap sizing) is sized per device - copy the proven block from the SDK's samples/sensor-node/prj.conf. It is the configuration the enrollment handshake needs; building the sample first is the fastest way to confirm your board.

Application entry point (src/main.c)

#include <zephyr/kernel.h>
#include <meshanics/agent.h>

int main(void)
{
    /* Starts the agent in a background thread (connectivity, first-boot
     * enrollment, heartbeat, signed OTA, rollback) and RETURNS immediately -
     * your control loop is yours. */
    meshanics_agent_start(NULL);

    while (1) {
        /* your application logic */
        k_sleep(K_SECONDS(5));
    }
    return 0;
}

meshanics_agent_start() is non-blocking: it spawns the agent thread and returns. Pass a const meshanics_config * to override defaults (firmware version, a lifecycle callback, an app-specific health check) - NULL uses the Kconfig values.


3. Create an MCU product and provisioning bundle

Before you build, generate the provisioning bundle - the minimal, out-of-band root of trust the device needs to authenticate the platform and enroll itself.

  1. In the console, go to Devices → + Microcontroller batch.
  2. Select the Zephyr runtime and enter the firmware stream and hardware profile the device is allowed to follow.
  3. Download meshanics-mcu-provisioning.json. It contains the complete claim, artifact and fleet endpoints, the server CA, the bounded claim credential and the exact release-stream bindings. It does not contain a private key.
  4. For development, copy the SDK's meshanics_provisioning.h.example to meshanics_provisioning.h on your application's include path and fill in those values plus your WiFi. In production this content is written to a data partition at flash time (WebSerial / USB), not compiled in - so one generic image serves every device.

The SDK resolves control_plane_host with DNS. A private deployment can instead set the optional claim and server IP fields while still using the hostname for SNI and certificate verification. Do not replace the hostname with an IP unless that IP is present in the server certificate.

The provisioning bundle holds no per-device identity and no update key. The device generates its own key pair on first boot and receives its unique certificate and the update-verification key from the platform over the authenticated channel. The claim credential is provisioning-scoped, bounded by expiry and use count, and revocable. It authorizes the provisioned batch and hardware profile; it is not a serial-number allowlist for individual boards.


4. Sign and build your firmware

You sign the firmware with your own MCUboot image key. If you do not have one yet, generate it once and keep the private half offline:

imgtool keygen -k my-signing-key.pem -t ecdsa-p256

Build with --sysbuild (MCUboot requires it for the A/B slot layout):

west build --sysbuild \
  -b esp32s3_devkitc/esp32s3/procpu \
  -- \
  -DSB_CONFIG_BOOT_SIGNATURE_KEY_FILE=\"$(pwd)/my-signing-key.pem\"

Required for rollback: a revertable swap mode. The bundled sysbuild.conf sets SB_CONFIG_MCUBOOT_MODE_SWAP_USING_MOVE=y. Keep it. On ESP32 the MCUboot default is overwrite-only, which makes every update permanent - a firmware that fails its health check would then be unable to roll back. The swap-using-move mode (no scratch partition needed) is what arms the automatic rollback; without it, the agent stages and swaps but the bootloader can never revert.

The image is generic - it carries no per-device identity and no update key, so the same build flashes onto every device.


5. Flash the first image over USB

The first flash is over USB by design: it installs MCUboot and the signed application image, which makes the device OTA-capable. After this, every subsequent update arrives over the air.

west flash --esp-device /dev/cu.<your-port>

The first image must be installed through your controlled USB or manufacturing flashing process.


6. Watch the device self-enroll

Open a serial monitor at 115200 baud. On first boot you will see the device mint its own identity and enroll:

meshanics agent v1.0.0 starting
WiFi connected
first boot: self-enrolling with the control plane
enrolling device esp32-<id>
claimed: received device certificate
enrolled: pinned manifest key_id=<key id>
registered with control plane - online in console
meshanics mcu agent v1.0.0 up; status on http://<ip>:8080/

Within a few seconds the device appears in Devices, live, with its detected hardware profile, and enrollment is recorded in the audit trail. The agent serves a small read-only JSON status page on port 8080 (version, uptime, the pinned key_id) for local diagnostics.

If the device does not appear after 60 seconds, check:

  • WiFi credentials in your provisioning config are correct
  • The claim credential has uses remaining (generate a new bundle in the console)
  • The control-plane ports are reachable from the device's network

7. Push an update

When you are ready to ship a new firmware version:

  1. Bump CONFIG_MESHANICS_FW_VERSION in prj.conf (and your MCUboot image version).
  2. Rebuild with the same signing key.
  3. In the console, go to Artifacts → Publish and upload the built zephyr.signed.bin as kind firmware, with the version string matching your Kconfig value.
  4. Create and authorize a signed release for the MCU product and profile, then create its rollout for your device group and wave strategy.

The device picks up the update on its next manifest poll (default: every 30 seconds, CONFIG_MESHANICS_POLL_INTERVAL_SEC). It downloads, verifies the digest, stages, and reboots into the new image. Registration, the local status listener and the configured application health callback must pass within the bounded startup window before the image is confirmed. A crash, failed health check or missing network leaves the image unconfirmed, so MCUboot returns to the previous image on the next boot.


Trust model

Every update is protected by two independent checks, in order:

  1. Manifest signature. The device verifies the update manifest against the Ed25519 public key it was provisioned with at enrollment (stored in NVS, not compiled into the image, and rotatable without reflashing). A manifest from another account cannot pass this check. The platform signs manifests with a key whose public counterpart is published on the Trust Center, so you can cross-check the key_id your device reports against the genuine key. Compact manifest v2 also covers the tenant, product, hardware profile, artifact stream, customer Release Manifest ID and digest, and exact rollout ID. The device refuses a different locally provisioned product, tenant, stream, or profile. This compact object is signed by Meshanics after the customer release is verified; the MCU does not independently parse the customer's DSSE envelope.

  2. Image integrity. The manifest carries a SHA-256 digest of the firmware image. The device computes this digest over the downloaded bytes and refuses to stage an image that does not match - so a corrupted download or a modified binary is caught before MCUboot ever sees it. MCUboot then verifies the image signature (your key) before it boots.

The device verifies before it stages; MCUboot verifies before it boots. Neither step is skippable. There is no --insecure or --skip-verify mode, in any build.

The anti-rollback counter (stored in non-volatile flash) prevents an older manifest from being replayed after a power cycle. The counter is the freshness authority, not the clock, so it works correctly on devices without a real-time clock.

The default counter is stored in ordinary NVS. It resists stale network responses, replay, reboot, and normal power loss, but it is not a hardware monotonic counter and does not claim resistance to an attacker who can rewrite flash. Hardware-backed anti-rollback and attested receipts require a supported secure element, eFuse, or measured-boot provider.