Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Runtime ownership

Rule. Stateful drivers and services have one mutable runtime owner; other tasks interact with them through bounded handles, snapshots or commands.

There are no global mutable stores and no shared locks around drivers. The owner is an Embassy task (or, for the radio, one runtime object); everything else holds a small copyable handle into a bounded, statically allocated channel.

ServiceOwnerHandleReached through
Measurementsstore task owns MeasurementStoreMeasurementHandlebounded snapshot request/response
Configurationconfiguration task owns ConfigStoreConfigHandlebounded, numbered request/response
Wi-FiWifiManager task owns the Wi-Fi backendWifiHandledesired-state cell, bounded commands, published status
IP stackthe platform stack (EspIp) behind the stack runner taskNetworkHandleDNS and TCP through standard traits
ESP wireless resourcesEspWireless (radio peripherals, wireless heap)the drivers it hands out oncewifi(), ble()
BLE peripheralBleRunner task owns the controller and host stackEspBle (BleTransport)bounded RX/TX queues
MeasurementStore  ─ one owner task ─►  MeasurementHandle
ConfigStore       ─ one owner task ─►  ConfigHandle
WifiManager       ─ one owner      ─►  WifiHandle
ESP wireless resources ─ one owner / runtime ─► Wi-Fi + BLE

Why: a single owner makes ordering explicit (requests are served one at a time), keeps flash and radio access out of timing-critical tasks, makes cancellation analysable (see Async and cancellation) and lets the owner be tested on the host with a mock backend.

Consequences

  • Nothing outside altruist_urban::config touches the configuration flash; nothing outside the Wi-Fi manager task calls the Wi-Fi driver; sensor tasks never touch the MeasurementStore.
  • The owner never blocks on a caller that gave up: responses to abandoned requests are discarded.
  • Handles are cheap (Copy) and are the only thing passed to other crates, which keeps portable crates independent of the owner’s concrete types.
  • Failure of one owner (for example Wi-Fi) is contained: the others keep running.

State versus command

Two kinds of interaction exist; mixing them in one queue causes subtle bugs:

desired persistent state  ──►  latest-state publication / reconciliation
transient requested action ──►  bounded command

Guideline. Prefer state publication over queued commands for persistent desired state. Use commands for transient actions.

InteractionKindMechanism
Desired WifiConfigstateWifiHandle::set_desired_config (latest value wins, synchronous)
Latest system / Wi-Fi statusstatepublished WifiStatus (Watch)
Scan, reconnect, disconnectcommandbounded queue, Busy when full
One-shot request (snapshot, config read)commandbounded request/response

Wi-Fi configuration is the canonical example. The persistent configuration is state: the manager continuously reconciles the actual radio state towards the latest desired value. Rapid updates A, B, C converge on C, an identical update does nothing, and a new value invalidates the retry state of the old one. Had it been a command in the same FIFO as Scan, a full queue would have forced the publisher to wait (a cancellable gap after the durable commit), updates could not be coalesced and ordering against transient commands would matter. The reconciliation rules are in Networking.