Skip to content

Add OTA Update Support

This tutorial extends a running Py ToloMEO service with OTA update capability. By the end you will have a service that accepts OTA commands, manages a download task, and transitions through the OTA state machine.

What you will do

Build a service with an OTAPlugin that responds to NotifyOTAUpdate, StartOTADownload, and InstallOTAUpdate commands, manages a download task, and transitions through the OTA state machine.

Source reference: examples/ota_mock/

Prerequisites

1. Subclass OTAPlugin

OTAPlugin handles the download lifecycle automatically. It is abstract, so a concrete plugin must implement four methods:

Method Purpose
connect Assign self.id; reach whatever performs the flash
disconnect Release that connection
install_update Perform the actual firmware flash
monitor_update Track progress and report success or failure

Leaving connect or disconnect out makes the class uninstantiable, with a TypeError naming the one you missed.

Create my_service/ota_plugin.py:

import asyncio
import logging
from tolomeo.plugins.ota import OTAPlugin


class MyOTAPlugin(OTAPlugin):

    async def connect(self) -> bool:
        self.id = "swupdate"
        self._logger.info("OTA plugin connected")
        return True

    async def disconnect(self) -> bool:
        return True

    async def install_update(self) -> None:
        """Trigger the actual firmware install and start monitoring."""
        self._logger.info("Starting installation from %s", self.fsm.pending_update.download_path)
        self.fsm.install()

        # Start a task to monitor progress
        await self.task_manager.add_task("monitor_update", self.monitor_update, auto_restart=False)

    async def monitor_update(self) -> None:
        """Simulate or perform the installation and report success/failure."""
        self._logger.info("Monitoring update in state: %s", self.state)

        # Replace the sleep + fsm.update_success() with real install logic
        await asyncio.sleep(5)
        self.fsm.update_success()
        self._logger.info("Installation succeeded")

2. Wire the Plugin into a Service

Create my_service/ota_service.py:

from tolomeo.services.ota import OTAService
from .ota_plugin import MyOTAPlugin


class MyOTAService(OTAService):
    class Meta:
        plugin_class = MyOTAPlugin

3. Choose Where Files Land

OTAPlugin declares OTAOptions as its Meta.config_model, so your subclass inherits three keys without writing any parsing. Put them in the service's configuration file, keyed by the plugin id set in connect:

[plugins.swupdate]
download_dir = "/data/firmware"
download_filename = "image.swu"
checkpoint_file = "/data/firmware/ota-state.yml"

All three are optional and default to /data/tolomeo/… (or the system temp directory when /data does not exist), so a deployment shipping no file behaves as before.

If your plugin needs options of its own, extend the inherited model rather than replacing it:

from pathlib import Path
from tolomeo.plugins.ota.models import OTAOptions


class MyOTAOptions(OTAOptions):
    socket_path: Path = Path("/run/swupdate.sock")


class MyOTAPlugin(OTAPlugin):
    class Meta:
        config_model = MyOTAOptions

Replacing it with an unrelated model raises TypeError at import time: OTAPlugin itself reads download_dir, so the fields it relies on have to survive.

4. Wire the Entry Point

Create my_service/ota_main.py:

import asyncio
import logging
from .ota_service import MyOTAService

logging.basicConfig(level=logging.INFO)


async def main() -> None:
    service = MyOTAService("swupdate")
    try:
        await service.run()
    except asyncio.CancelledError:
        pass


if __name__ == "__main__":
    asyncio.run(main())

5. Run the Service

DEVICE_SERIAL_NUMBER=test001 python -m my_service.ota_main

6. Walk Through the Update Flow

Open a NATS subscriber to watch status events:

nats sub events.infos

Step 1 — Notify an update

Send NotifyOTAUpdate with the firmware URL. Status transitions to update_available.

Step 2 — Start the download

Send StartOTADownload. Status transitions: downloadingupdate_ready.

Step 3 — Install

Send InstallOTAUpdate. Status transitions: updatingupdate_successful.

For the exact NATS command syntax and full payload schemas, see Trigger OTA Updates via NATS.

7. Handle a Download Failure

If the download fails (bad URL, network error), the FSM moves to download_failed. Send AbortOTAUpdate to return to update_available and retry. See Recover from Failures for the full command.

Next Steps