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.

What this covers. ESP32-S3 is the reference target. The SDK builds on any board where Zephyr runs MCUboot A/B and has WiFi or Ethernet. If your board is not listed in the SDK's hardware matrix, open a support ticket before you start.

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 and upload it in the console.
  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 signed manifest, 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. The SDK confirms it once it reaches the platform. If it cannot, MCUboot reverts automatically on the next boot — no custom rollback code 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: main           # or pin to a release tag
      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. The console produces a bundle containing the control-plane address, the server CA (so the device can authenticate the platform and detect a man-in-the-middle), and a provisioning-scoped claim credential.
  3. 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 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, gated by a server-side allowlist, and bounded by a use count — it cannot impersonate an enrolled device or reach another account.


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>

WebSerial flashing straight from the console panel is on the way and will remove the USB step for production lines.


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 a rollout targeting your device group, choose your wave strategy, and start it.

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. If the new image reaches the platform it is confirmed; if it does not — crash, failed health check, or no network — MCUboot reverts to the previous image on the next reboot automatically.


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.

  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.