Building a Wireless Networked Display from scratch — an FCM tutorial

This tutorial walks through the construction of the Wireless Networked Display (WND) — a small embedded device that connects to a server over a radio link and displays images the server pushes. We build it from an empty model, one decision at a time, and the diagrams update at each step.

The exercise is also a tour of the Functional Components Method (FCM), the modelling discipline the editor is built around. FCM has opinions about which parts of a system should be a state machine, what the boundary looks like, how components communicate. Those opinions shape the order of the steps below: each phase corresponds to one FCM decision.

The end goal is a complete WND model: three components, three handlers, five interfaces, eleven messages, all wired up and documented. By following along you will have built it yourself, step by step. The intermediate diagrams shown are rendered live from the same PlantUML pipeline the editor uses; they are exactly what you would see on the canvas at each step.

What FCM is

FCM models a device: a piece of software whose responsibilities are clearly bounded. Inside the device, behaviour belongs to Functional Components — each a state machine with one well-defined job. Around the device sits the boundary, populated by handlers — objects with no state machine that mediate every interaction with the outside world (radios, peripherals, displays, databases, OS services). Components communicate exclusively through messages on shared interfaces, never directly.

Two top-level rules make FCM what it is:

This tutorial follows that order: handlers in Phase 1, components in Phase 2, the message vocabulary in Phases 3–4, the state machines themselves in Phases 5–7, and the finishing touches in Phase 8.

Phase 1 — Boundary

Identify the radio

FCM identification order, step 1. Before any component is named, identify the asynchronous edges to the environment. Every input or output that arrives or departs on the device's own schedule — not at the application's request — needs a handler. List them, name each, and that fixes the boundary.

For the WND, the first one is obvious: the radio. The wireless transceiver is a hardware peripheral; packets arrive when they arrive; the device cannot control when. It gets a handler.

The handler exposes a synchronous, non-blocking function API to whatever component eventually decides to use it. We can think of those functions now even without knowing which component will call them:

  • sendAdvertisementPacket() — fire one advertisement.
  • connectServer(serverId) — establish a connection with a known server.
  • rejectServer(serverId) — block further requests from a server.
  • disable() — turn the radio frontend off.
  • sendContentDimensions(displayWidth, displayHeight) — tell the server how big the display is. (Why this lives here will become clear in Phase 7; for now, treat it as a side payload the server needs to know.)

The handler doesn't speak yet — we haven't drawn any interfaces or messages. But it's there on the boundary.

You can ignore the Component Name box on the device diagram below: that's the placeholder component every fresh Stadiæ diagram starts with. We'll replace it with a real component in Stage 4.

Device diagram
Wireless Networked Display (WND)Component NameWirelessTransceiver
JSON snapshot at this stage
{
  "format": "stadiae-v4",
  "interfaces": [],
  "messages": [],
  "components": [
    {
      "name": "Placeholder",
      "states": [
        {
          "name": "Idle"
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "Idle",
          "messages": [],
          "connector": "Down",
          "length": 1
        }
      ]
    }
  ],
  "deviceDisplayName": "Wireless Networked Display (WND)",
  "handlers": [
    {
      "name": "WirelessTransceiver",
      "displayName": "Wireless\\nTransceiver",
      "description": "This handler takes care of receiving and transmitting data packets on the radio frontend by interfacing with the radio hardware peripheral. By looking at the message-type of incoming messages, indications are forwarded either to the `Connection` or `Content` interface.\n\nThe wireless transceiver is one of the device's peripherals: it is brought up by `PeripheralHandler:start()` together with the display, and it signals its readiness and any failure on the `Peripherals` interface so that `DeviceStatusController` can include it in its readiness count. Once readiness has been signalled, the radio is ready to advertise and accept connection requests.",
      "functions": [
        {
          "name": "sendAdvertisementPacket",
          "description": "Send a single advertisement packet to become discoverable."
        },
        {
          "name": "connectServer",
          "description": "Connect to the indicated server.",
          "parameters": [
            {
              "name": "serverId",
              "type": "uint",
              "description": "The ID of the server to connect to."
            }
          ]
        },
        {
          "name": "rejectServer",
          "description": "Block further requests from the indicated server.",
          "parameters": [
            {
              "name": "serverId",
              "type": "uint",
              "description": "The ID of the server that is to be rejected from now on."
            }
          ]
        },
        {
          "name": "disable",
          "description": "Disable the radio frontend."
        },
        {
          "name": "sendContentDimensions",
          "description": "Inform the server of the dimensions of the content the display supports.",
          "parameters": [
            {
              "name": "displayWidth",
              "type": "uint",
              "description": "The width of the display in pixels."
            },
            {
              "name": "displayHeight",
              "type": "uint",
              "description": "The height of the display in pixels."
            }
          ]
        }
      ]
    }
  ]
}

Identify the display driver

The second asynchronous edge is the display panel. Drawing to a display is asynchronous: tell it "show this image" and the panel signals back when the frame has been rendered. That completion signal is what lets the device coordinate its render cycle with the server's push rate — we will come back to it in Phase 7.

The DisplayHandler exposes three functions:

  • initialize() — bring the panel into a usable state.
  • display(image) — push an image to render.
  • displayError() — show the default error screen (used on device-wide failure).

With the radio and the display in place we have both of the device's real asynchronous edges to the outside world. Anything else on the boundary will be a support role — a handler that exists not because something outside is talking to us asynchronously, but because we want to consolidate work that would otherwise be spread across the real handlers. We identify one such handler in the next stage.

Device diagram
Wireless Networked Display (WND)Component NameWirelessTransceiverDisplayHandler

Identify the peripheral manager

Both peripherals — the radio and the display — need bringing up. Power-on initialisation, configuration, signal-line setup. This is asynchronous: the bring-up takes time and signals back when it's done.

We could put the bring-up logic inside each peripheral's own handler. That would work, but the bring-up sequences for the two are similar, and the device as a whole wants a single "everything is ready" signal it can act on. Cleaner to extract a dedicated PeripheralHandler whose one function — start() — kicks off both peripherals' initialisation in one go and reports their readiness uniformly back.

Note the identification order. PeripheralHandler is not on the boundary because something outside is talking to us asynchronously; it is on the boundary because we identified two real handlers that share a bring-up responsibility worth consolidating. Real asynchronous edges come first; consolidators come once there's something to consolidate.

The peripherals will report back via a shared interface that we haven't declared yet. Phase 3 will introduce it.

The boundary is now complete. Every asynchronous interaction the WND can have with the outside world is represented by a handler — two for the real edges, one for shared bring-up. From this point on, no new handlers will be introduced — only components and the messages they exchange.

Device diagram
Wireless Networked Display (WND)Component NameWirelessTransceiverDisplayHandlerPeripheralHandler
JSON snapshot at this stage
{
  "format": "stadiae-v4",
  "interfaces": [],
  "messages": [],
  "components": [
    {
      "name": "Placeholder",
      "states": [
        {
          "name": "Idle"
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "Idle",
          "messages": [],
          "connector": "Down",
          "length": 1
        }
      ]
    }
  ],
  "deviceDisplayName": "Wireless Networked Display (WND)",
  "handlers": [
    {
      "name": "WirelessTransceiver",
      "displayName": "Wireless\\nTransceiver",
      "description": "This handler takes care of receiving and transmitting data packets on the radio frontend by interfacing with the radio hardware peripheral. By looking at the message-type of incoming messages, indications are forwarded either to the `Connection` or `Content` interface.\n\nThe wireless transceiver is one of the device's peripherals: it is brought up by `PeripheralHandler:start()` together with the display, and it signals its readiness and any failure on the `Peripherals` interface so that `DeviceStatusController` can include it in its readiness count. Once readiness has been signalled, the radio is ready to advertise and accept connection requests.",
      "functions": [
        {
          "name": "sendAdvertisementPacket",
          "description": "Send a single advertisement packet to become discoverable."
        },
        {
          "name": "connectServer",
          "description": "Connect to the indicated server.",
          "parameters": [
            {
              "name": "serverId",
              "type": "uint",
              "description": "The ID of the server to connect to."
            }
          ]
        },
        {
          "name": "rejectServer",
          "description": "Block further requests from the indicated server.",
          "parameters": [
            {
              "name": "serverId",
              "type": "uint",
              "description": "The ID of the server that is to be rejected from now on."
            }
          ]
        },
        {
          "name": "disable",
          "description": "Disable the radio frontend."
        },
        {
          "name": "sendContentDimensions",
          "description": "Inform the server of the dimensions of the content the display supports.",
          "parameters": [
            {
              "name": "displayWidth",
              "type": "uint",
              "description": "The width of the display in pixels."
            },
            {
              "name": "displayHeight",
              "type": "uint",
              "description": "The height of the display in pixels."
            }
          ]
        }
      ]
    },
    {
      "name": "PeripheralHandler",
      "displayName": "Peripheral\\nHandler",
      "description": "Takes care of detecting, initializing and configuring hardware peripherals \u2014 the wireless transceiver and the display \u2014 e.g. by accessing low level drivers and serial communication lines. Both are brought up together by `start()` and report readiness and failures on the `Peripherals` interface.",
      "functions": [
        {
          "name": "start",
          "description": "Start initializing and configuring the peripherals."
        }
      ]
    },
    {
      "name": "DisplayHandler",
      "displayName": "Display\\nHandler",
      "description": "Handles the interface to the display to show images.",
      "functions": [
        {
          "name": "initialize",
          "description": "Initialize the display."
        },
        {
          "name": "display",
          "description": "Display the supplied image.",
          "parameters": [
            {
              "name": "image",
              "type": "byteArray",
              "description": "The PNG image to be displayed."
            }
          ]
        },
        {
          "name": "displayError",
          "description": "Display the default error message."
        }
      ]
    }
  ]
}

Phase 2 — Components

Add the Server Connector

FCM identification order, step 2. With the boundary fixed, decompose the device's responsibilities into functional components. Each component should pass three tests: a single responsibility (summarisable in one sentence without "and"), mode-driven behaviour (distinct phases that justify a state machine), and an independent lifecycle (it can start, stop, recover, or fail without dragging the others along).

The first responsibility we identify: managing the connection to the server. Discovery, connection request, connection lost — these are clear modes, and they have nothing to do with what gets displayed once connected. Independent lifecycle, single responsibility.

ServerConnector gets four states:

  • Uninitialized — the device hasn't reported ready yet; ignore everything.
  • Advertising — the device is broadcasting its presence and waiting for a server to find it.
  • Connecting — a server has found us; we're establishing a connection.
  • Connected — the connection is live.

States only; no transitions yet besides the implicit START → Uninitialized. We will fill in the message-driven transitions later; right now we just have a skeleton.

Device diagram
Wireless Networked Display (WND)ServerConnectorWirelessTransceiverDisplayHandlerPeripheralHandler
ServerConnector state machine
Server ConnectorUninitializedAdvertisingConnectingConnected
JSON snapshot at this stage
{
  "format": "stadiae-v4",
  "interfaces": [],
  "messages": [],
  "components": [
    {
      "name": "ServerConnector",
      "displayName": "Server\\nConnector",
      "states": [
        {
          "name": "Uninitialized"
        },
        {
          "name": "Advertising"
        },
        {
          "name": "Connecting"
        },
        {
          "name": "Connected"
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "Uninitialized",
          "messages": [],
          "connector": "Down",
          "length": 1
        }
      ]
    }
  ],
  "deviceDisplayName": "Wireless Networked Display (WND)",
  "handlers": [
    {
      "name": "WirelessTransceiver",
      "displayName": "Wireless\\nTransceiver",
      "description": "This handler takes care of receiving and transmitting data packets on the radio frontend by interfacing with the radio hardware peripheral. By looking at the message-type of incoming messages, indications are forwarded either to the `Connection` or `Content` interface.\n\nThe wireless transceiver is one of the device's peripherals: it is brought up by `PeripheralHandler:start()` together with the display, and it signals its readiness and any failure on the `Peripherals` interface so that `DeviceStatusController` can include it in its readiness count. Once readiness has been signalled, the radio is ready to advertise and accept connection requests.",
      "functions": [
        {
          "name": "sendAdvertisementPacket",
          "description": "Send a single advertisement packet to become discoverable."
        },
        {
          "name": "connectServer",
          "description": "Connect to the indicated server.",
          "parameters": [
            {
              "name": "serverId",
              "type": "uint",
              "description": "The ID of the server to connect to."
            }
          ]
        },
        {
          "name": "rejectServer",
          "description": "Block further requests from the indicated server.",
          "parameters": [
            {
              "name": "serverId",
              "type": "uint",
              "description": "The ID of the server that is to be rejected from now on."
            }
          ]
        },
        {
          "name": "disable",
          "description": "Disable the radio frontend."
        },
        {
          "name": "sendContentDimensions",
          "description": "Inform the server of the dimensions of the content the display supports.",
          "parameters": [
            {
              "name": "displayWidth",
              "type": "uint",
              "description": "The width of the display in pixels."
            },
            {
              "name": "displayHeight",
              "type": "uint",
              "description": "The height of the display in pixels."
            }
          ]
        }
      ]
    },
    {
      "name": "PeripheralHandler",
      "displayName": "Peripheral\\nHandler",
      "description": "Takes care of detecting, initializing and configuring hardware peripherals \u2014 the wireless transceiver and the display \u2014 e.g. by accessing low level drivers and serial communication lines. Both are brought up together by `start()` and report readiness and failures on the `Peripherals` interface.",
      "functions": [
        {
          "name": "start",
          "description": "Start initializing and configuring the peripherals."
        }
      ]
    },
    {
      "name": "DisplayHandler",
      "displayName": "Display\\nHandler",
      "description": "Handles the interface to the display to show images.",
      "functions": [
        {
          "name": "initialize",
          "description": "Initialize the display."
        },
        {
          "name": "display",
          "description": "Display the supplied image.",
          "parameters": [
            {
              "name": "image",
              "type": "byteArray",
              "description": "The PNG image to be displayed."
            }
          ]
        },
        {
          "name": "displayError",
          "description": "Display the default error message."
        }
      ]
    }
  ]
}

Add the Device Status Controller

The second responsibility: determining when the device as a whole is ready. Two peripherals (radio and display) must be operational before any top-level logic can run. Someone has to track that.

DeviceStatusController watches the Peripherals interface, counts readiness reports, and emits a single device-wide Device:ReadyInd once both have signalled. It also handles peripheral failure: any failure report puts the device into a terminal Error state.

Two states for now (a third — Error — will arrive when we add the failure handling in Phase 6):

  • AwaitingPeripherals — counting readiness reports.
  • Operational — the device is up.
Device diagram
Wireless Networked Display (WND)ServerConnectorDevice StatusControllerWirelessTransceiverDisplayHandlerPeripheralHandler
JSON snapshot at this stage
{
  "format": "stadiae-v4",
  "interfaces": [],
  "messages": [],
  "components": [
    {
      "name": "ServerConnector",
      "displayName": "Server\\nConnector",
      "states": [
        {
          "name": "Uninitialized"
        },
        {
          "name": "Advertising"
        },
        {
          "name": "Connecting"
        },
        {
          "name": "Connected"
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "Uninitialized",
          "messages": [],
          "connector": "Down",
          "length": 1
        }
      ]
    },
    {
      "name": "DeviceStatusController",
      "displayName": "Device Status\\nController",
      "states": [
        {
          "name": "AwaitingPeripherals",
          "displayName": "Awaiting\\nPeripherals"
        },
        {
          "name": "Operational"
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "AwaitingPeripherals",
          "messages": [],
          "connector": "Down",
          "length": 1
        }
      ]
    }
  ],
  "deviceDisplayName": "Wireless Networked Display (WND)",
  "handlers": [
    {
      "name": "WirelessTransceiver",
      "displayName": "Wireless\\nTransceiver",
      "description": "This handler takes care of receiving and transmitting data packets on the radio frontend by interfacing with the radio hardware peripheral. By looking at the message-type of incoming messages, indications are forwarded either to the `Connection` or `Content` interface.\n\nThe wireless transceiver is one of the device's peripherals: it is brought up by `PeripheralHandler:start()` together with the display, and it signals its readiness and any failure on the `Peripherals` interface so that `DeviceStatusController` can include it in its readiness count. Once readiness has been signalled, the radio is ready to advertise and accept connection requests.",
      "functions": [
        {
          "name": "sendAdvertisementPacket",
          "description": "Send a single advertisement packet to become discoverable."
        },
        {
          "name": "connectServer",
          "description": "Connect to the indicated server.",
          "parameters": [
            {
              "name": "serverId",
              "type": "uint",
              "description": "The ID of the server to connect to."
            }
          ]
        },
        {
          "name": "rejectServer",
          "description": "Block further requests from the indicated server.",
          "parameters": [
            {
              "name": "serverId",
              "type": "uint",
              "description": "The ID of the server that is to be rejected from now on."
            }
          ]
        },
        {
          "name": "disable",
          "description": "Disable the radio frontend."
        },
        {
          "name": "sendContentDimensions",
          "description": "Inform the server of the dimensions of the content the display supports.",
          "parameters": [
            {
              "name": "displayWidth",
              "type": "uint",
              "description": "The width of the display in pixels."
            },
            {
              "name": "displayHeight",
              "type": "uint",
              "description": "The height of the display in pixels."
            }
          ]
        }
      ]
    },
    {
      "name": "PeripheralHandler",
      "displayName": "Peripheral\\nHandler",
      "description": "Takes care of detecting, initializing and configuring hardware peripherals \u2014 the wireless transceiver and the display \u2014 e.g. by accessing low level drivers and serial communication lines. Both are brought up together by `start()` and report readiness and failures on the `Peripherals` interface.",
      "functions": [
        {
          "name": "start",
          "description": "Start initializing and configuring the peripherals."
        }
      ]
    },
    {
      "name": "DisplayHandler",
      "displayName": "Display\\nHandler",
      "description": "Handles the interface to the display to show images.",
      "functions": [
        {
          "name": "initialize",
          "description": "Initialize the display."
        },
        {
          "name": "display",
          "description": "Display the supplied image.",
          "parameters": [
            {
              "name": "image",
              "type": "byteArray",
              "description": "The PNG image to be displayed."
            }
          ]
        },
        {
          "name": "displayError",
          "description": "Display the default error message."
        }
      ]
    }
  ]
}

Add the UI Controller

The third responsibility: showing content the server pushes. Distinct from the connection lifecycle, distinct from device readiness. A separate component.

UiController initialises the display once the device is ready, then processes images as they arrive. It needs to coordinate with the display peripheral's render cycle — push an image, wait for the panel to finish, push the next. We'll see the mechanism for that in Phase 7.

Three states to start with (we'll add Updating later when we wire up content reception):

  • Uninitialized — device not ready yet.
  • InitializingDisplay — bringing the panel up.
  • Ready — accepting content.

All three components are now on the canvas. No interfaces, no messages, no transitions yet — those are next.

Device diagram
Wireless Networked Display (WND)ServerConnectorDevice StatusControllerUIControllerWirelessTransceiverDisplayHandlerPeripheralHandler
JSON snapshot at this stage
{
  "format": "stadiae-v4",
  "interfaces": [],
  "messages": [],
  "components": [
    {
      "name": "ServerConnector",
      "displayName": "Server\\nConnector",
      "states": [
        {
          "name": "Uninitialized"
        },
        {
          "name": "Advertising"
        },
        {
          "name": "Connecting"
        },
        {
          "name": "Connected"
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "Uninitialized",
          "messages": [],
          "connector": "Down",
          "length": 1
        }
      ]
    },
    {
      "name": "DeviceStatusController",
      "displayName": "Device Status\\nController",
      "states": [
        {
          "name": "AwaitingPeripherals",
          "displayName": "Awaiting\\nPeripherals"
        },
        {
          "name": "Operational"
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "AwaitingPeripherals",
          "messages": [],
          "connector": "Down",
          "length": 1
        }
      ]
    },
    {
      "name": "UiController",
      "displayName": "UI\\nController",
      "states": [
        {
          "name": "Uninitialized"
        },
        {
          "name": "InitializingDisplay",
          "displayName": "Initializing\\nDisplay"
        },
        {
          "name": "Ready"
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "Uninitialized",
          "messages": [],
          "connector": "Right",
          "length": 1
        }
      ]
    }
  ],
  "deviceDisplayName": "Wireless Networked Display (WND)",
  "handlers": [
    {
      "name": "WirelessTransceiver",
      "displayName": "Wireless\\nTransceiver",
      "description": "This handler takes care of receiving and transmitting data packets on the radio frontend by interfacing with the radio hardware peripheral. By looking at the message-type of incoming messages, indications are forwarded either to the `Connection` or `Content` interface.\n\nThe wireless transceiver is one of the device's peripherals: it is brought up by `PeripheralHandler:start()` together with the display, and it signals its readiness and any failure on the `Peripherals` interface so that `DeviceStatusController` can include it in its readiness count. Once readiness has been signalled, the radio is ready to advertise and accept connection requests.",
      "functions": [
        {
          "name": "sendAdvertisementPacket",
          "description": "Send a single advertisement packet to become discoverable."
        },
        {
          "name": "connectServer",
          "description": "Connect to the indicated server.",
          "parameters": [
            {
              "name": "serverId",
              "type": "uint",
              "description": "The ID of the server to connect to."
            }
          ]
        },
        {
          "name": "rejectServer",
          "description": "Block further requests from the indicated server.",
          "parameters": [
            {
              "name": "serverId",
              "type": "uint",
              "description": "The ID of the server that is to be rejected from now on."
            }
          ]
        },
        {
          "name": "disable",
          "description": "Disable the radio frontend."
        },
        {
          "name": "sendContentDimensions",
          "description": "Inform the server of the dimensions of the content the display supports.",
          "parameters": [
            {
              "name": "displayWidth",
              "type": "uint",
              "description": "The width of the display in pixels."
            },
            {
              "name": "displayHeight",
              "type": "uint",
              "description": "The height of the display in pixels."
            }
          ]
        }
      ]
    },
    {
      "name": "PeripheralHandler",
      "displayName": "Peripheral\\nHandler",
      "description": "Takes care of detecting, initializing and configuring hardware peripherals \u2014 the wireless transceiver and the display \u2014 e.g. by accessing low level drivers and serial communication lines. Both are brought up together by `start()` and report readiness and failures on the `Peripherals` interface.",
      "functions": [
        {
          "name": "start",
          "description": "Start initializing and configuring the peripherals."
        }
      ]
    },
    {
      "name": "DisplayHandler",
      "displayName": "Display\\nHandler",
      "description": "Handles the interface to the display to show images.",
      "functions": [
        {
          "name": "initialize",
          "description": "Initialize the display."
        },
        {
          "name": "display",
          "description": "Display the supplied image.",
          "parameters": [
            {
              "name": "image",
              "type": "byteArray",
              "description": "The PNG image to be displayed."
            }
          ]
        },
        {
          "name": "displayError",
          "description": "Display the default error message."
        }
      ]
    }
  ]
}

Phase 3 — Interfaces

Declare the message families

FCM identification order, step 3. Interfaces are contracts, not bags of unrelated messages. For each pair of components and each component-handler pair on the boundary, ask: what messages flow, and what coherent vocabulary groups them?

For the WND, five interfaces fall out naturally:

  • Device — device-wide status (ReadyInd, ErrorInd). All three components both listen on it and emit on it.
  • Connection — wireless link lifecycle (ConnectReq, ConnectedInd, DisconnectedInd). Spoken between ServerConnector and the radio.
  • Peripherals — readiness signals from the bring-up sequence (ReadyInd, FailureInd). Spoken between PeripheralHandler and DeviceStatusController.
  • Content — image deliveries (ContentInd). Spoken between the radio and UiController.
  • Display — display-panel completion (CompletedInd). Spoken between DisplayHandler and UiController.

In addition to these, every model implicitly has the built-in Logical and Timer interfaces (carrying Yes/No and Timeout respectively), plus the TimerHandler for arming timers. They appear in the sidebar lists in muted italic (so transitions can reference them), but never on the device diagram, and you cannot rename, edit, or delete them; they're conceptually always there, like primitive operations in a programming language.

Device diagram
Wireless Networked Display (WND)ServerConnectorDevice StatusControllerUIControllerWirelessTransceiverDisplayHandlerPeripheralHandler
JSON snapshot at this stage
{
  "format": "stadiae-v4",
  "interfaces": [
    {
      "name": "Device",
      "description": "Device status messages."
    },
    {
      "name": "Connection",
      "description": "Wireless transceiver messages for connection handling."
    },
    {
      "name": "Peripherals",
      "description": "Status indications from the peripherals."
    },
    {
      "name": "Content",
      "description": "Exchange of interaction with the server."
    },
    {
      "name": "Display",
      "description": "Display status and control messages."
    }
  ],
  "messages": [],
  "components": [
    {
      "name": "ServerConnector",
      "displayName": "Server\\nConnector",
      "states": [
        {
          "name": "Uninitialized"
        },
        {
          "name": "Advertising"
        },
        {
          "name": "Connecting"
        },
        {
          "name": "Connected"
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "Uninitialized",
          "messages": [],
          "connector": "Down",
          "length": 1
        }
      ]
    },
    {
      "name": "DeviceStatusController",
      "displayName": "Device Status\\nController",
      "states": [
        {
          "name": "AwaitingPeripherals",
          "displayName": "Awaiting\\nPeripherals"
        },
        {
          "name": "Operational"
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "AwaitingPeripherals",
          "messages": [],
          "connector": "Down",
          "length": 1
        }
      ]
    },
    {
      "name": "UiController",
      "displayName": "UI\\nController",
      "states": [
        {
          "name": "Uninitialized"
        },
        {
          "name": "InitializingDisplay",
          "displayName": "Initializing\\nDisplay"
        },
        {
          "name": "Ready"
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "Uninitialized",
          "messages": [],
          "connector": "Right",
          "length": 1
        }
      ]
    }
  ],
  "deviceDisplayName": "Wireless Networked Display (WND)",
  "handlers": [
    {
      "name": "WirelessTransceiver",
      "displayName": "Wireless\\nTransceiver",
      "description": "This handler takes care of receiving and transmitting data packets on the radio frontend by interfacing with the radio hardware peripheral. By looking at the message-type of incoming messages, indications are forwarded either to the `Connection` or `Content` interface.\n\nThe wireless transceiver is one of the device's peripherals: it is brought up by `PeripheralHandler:start()` together with the display, and it signals its readiness and any failure on the `Peripherals` interface so that `DeviceStatusController` can include it in its readiness count. Once readiness has been signalled, the radio is ready to advertise and accept connection requests.",
      "functions": [
        {
          "name": "sendAdvertisementPacket",
          "description": "Send a single advertisement packet to become discoverable."
        },
        {
          "name": "connectServer",
          "description": "Connect to the indicated server.",
          "parameters": [
            {
              "name": "serverId",
              "type": "uint",
              "description": "The ID of the server to connect to."
            }
          ]
        },
        {
          "name": "rejectServer",
          "description": "Block further requests from the indicated server.",
          "parameters": [
            {
              "name": "serverId",
              "type": "uint",
              "description": "The ID of the server that is to be rejected from now on."
            }
          ]
        },
        {
          "name": "disable",
          "description": "Disable the radio frontend."
        },
        {
          "name": "sendContentDimensions",
          "description": "Inform the server of the dimensions of the content the display supports.",
          "parameters": [
            {
              "name": "displayWidth",
              "type": "uint",
              "description": "The width of the display in pixels."
            },
            {
              "name": "displayHeight",
              "type": "uint",
              "description": "The height of the display in pixels."
            }
          ]
        }
      ]
    },
    {
      "name": "PeripheralHandler",
      "displayName": "Peripheral\\nHandler",
      "description": "Takes care of detecting, initializing and configuring hardware peripherals \u2014 the wireless transceiver and the display \u2014 e.g. by accessing low level drivers and serial communication lines. Both are brought up together by `start()` and report readiness and failures on the `Peripherals` interface.",
      "functions": [
        {
          "name": "start",
          "description": "Start initializing and configuring the peripherals."
        }
      ]
    },
    {
      "name": "DisplayHandler",
      "displayName": "Display\\nHandler",
      "description": "Handles the interface to the display to show images.",
      "functions": [
        {
          "name": "initialize",
          "description": "Initialize the display."
        },
        {
          "name": "display",
          "description": "Display the supplied image.",
          "parameters": [
            {
              "name": "image",
              "type": "byteArray",
              "description": "The PNG image to be displayed."
            }
          ]
        },
        {
          "name": "displayError",
          "description": "Display the default error message."
        }
      ]
    }
  ]
}

Phase 4 — Messages and wiring

Declare the message vocabulary

Now the messages themselves, grouped by interface. Names follow the FCM convention: Req for requests, Ind for indications, Cnf for confirmations.

  • Device:ReadyInd — the device is operational.
  • Device:ErrorInd — an unrecoverable failure occurred.
  • Connection:ConnectReq(serverId) — a server is trying to connect.
  • Connection:ConnectedInd — connection established.
  • Connection:DisconnectedInd — connection lost.
  • Peripherals:ReadyInd — a peripheral has come up.
  • Peripherals:FailureInd — a peripheral has failed.
  • Content:ContentInd(image) — a new image to display.
  • Display:CompletedInd(width, height) — the display has finished its current operation. The two parameters are populated only on the initialisation occurrence (when the panel reports its dimensions); on render-completion they are unset. This dual meaning is a deliberate compactness choice — both events mean "the display is idle and ready for the next instruction", so they share one message.

The device diagram doesn't change visually when messages are added — messages live on interfaces, not on the device picture. The next stage adds types so parameter declarations have real meaning.

Device diagram
Wireless Networked Display (WND)ServerConnectorDevice StatusControllerUIControllerWirelessTransceiverDisplayHandlerPeripheralHandler
JSON snapshot at this stage
{
  "format": "stadiae-v4",
  "interfaces": [
    {
      "name": "Device",
      "description": "Device status messages."
    },
    {
      "name": "Connection",
      "description": "Wireless transceiver messages for connection handling."
    },
    {
      "name": "Peripherals",
      "description": "Status indications from the peripherals."
    },
    {
      "name": "Content",
      "description": "Exchange of interaction with the server."
    },
    {
      "name": "Display",
      "description": "Display status and control messages."
    }
  ],
  "messages": [
    {
      "interface": "Device",
      "name": "ReadyInd",
      "description": "All peripherals are ready and the device can be initialized."
    },
    {
      "interface": "Device",
      "name": "ErrorInd",
      "description": "An error occurred and the device is no longer operational."
    },
    {
      "interface": "Connection",
      "name": "ConnectReq",
      "description": "A server is requesting to connect.",
      "parameters": [
        {
          "name": "serverId",
          "type": "uint",
          "description": "ID of the server that wants to connect."
        }
      ]
    },
    {
      "interface": "Connection",
      "name": "ConnectedInd",
      "description": "Connected to the server."
    },
    {
      "interface": "Connection",
      "name": "DisconnectedInd",
      "description": "The server is no longer connected."
    },
    {
      "interface": "Peripherals",
      "name": "ReadyInd",
      "description": "Indication that a peripheral is ready."
    },
    {
      "interface": "Peripherals",
      "name": "FailureInd",
      "description": "A peripheral has failed."
    },
    {
      "interface": "Content",
      "name": "ContentInd",
      "description": "A new image to display.",
      "parameters": [
        {
          "name": "image",
          "type": "byteMatrix",
          "description": "The PNG image to display."
        }
      ]
    },
    {
      "interface": "Display",
      "name": "CompletedInd",
      "description": "Indication that the display has completed its current operation \u2014 either initialisation (in which case the parameters report the panel's pixel dimensions) or rendering a `Content:ContentInd`. In both cases the display is now idle and ready for the next instruction.",
      "parameters": [
        {
          "name": "width",
          "type": "uint",
          "description": "The width of the display in pixels. Populated on the initialisation occurrence."
        },
        {
          "name": "height",
          "type": "uint",
          "description": "The height of the display in pixels. Populated on the initialisation occurrence."
        }
      ]
    }
  ],
  "components": [
    {
      "name": "ServerConnector",
      "displayName": "Server\\nConnector",
      "states": [
        {
          "name": "Uninitialized"
        },
        {
          "name": "Advertising"
        },
        {
          "name": "Connecting"
        },
        {
          "name": "Connected"
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "Uninitialized",
          "messages": [],
          "connector": "Down",
          "length": 1
        }
      ]
    },
    {
      "name": "DeviceStatusController",
      "displayName": "Device Status\\nController",
      "states": [
        {
          "name": "AwaitingPeripherals",
          "displayName": "Awaiting\\nPeripherals"
        },
        {
          "name": "Operational"
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "AwaitingPeripherals",
          "messages": [],
          "connector": "Down",
          "length": 1
        }
      ]
    },
    {
      "name": "UiController",
      "displayName": "UI\\nController",
      "states": [
        {
          "name": "Uninitialized"
        },
        {
          "name": "InitializingDisplay",
          "displayName": "Initializing\\nDisplay"
        },
        {
          "name": "Ready"
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "Uninitialized",
          "messages": [],
          "connector": "Right",
          "length": 1
        }
      ]
    }
  ],
  "deviceDisplayName": "Wireless Networked Display (WND)",
  "handlers": [
    {
      "name": "WirelessTransceiver",
      "displayName": "Wireless\\nTransceiver",
      "description": "This handler takes care of receiving and transmitting data packets on the radio frontend by interfacing with the radio hardware peripheral. By looking at the message-type of incoming messages, indications are forwarded either to the `Connection` or `Content` interface.\n\nThe wireless transceiver is one of the device's peripherals: it is brought up by `PeripheralHandler:start()` together with the display, and it signals its readiness and any failure on the `Peripherals` interface so that `DeviceStatusController` can include it in its readiness count. Once readiness has been signalled, the radio is ready to advertise and accept connection requests.",
      "functions": [
        {
          "name": "sendAdvertisementPacket",
          "description": "Send a single advertisement packet to become discoverable."
        },
        {
          "name": "connectServer",
          "description": "Connect to the indicated server.",
          "parameters": [
            {
              "name": "serverId",
              "type": "uint",
              "description": "The ID of the server to connect to."
            }
          ]
        },
        {
          "name": "rejectServer",
          "description": "Block further requests from the indicated server.",
          "parameters": [
            {
              "name": "serverId",
              "type": "uint",
              "description": "The ID of the server that is to be rejected from now on."
            }
          ]
        },
        {
          "name": "disable",
          "description": "Disable the radio frontend."
        },
        {
          "name": "sendContentDimensions",
          "description": "Inform the server of the dimensions of the content the display supports.",
          "parameters": [
            {
              "name": "displayWidth",
              "type": "uint",
              "description": "The width of the display in pixels."
            },
            {
              "name": "displayHeight",
              "type": "uint",
              "description": "The height of the display in pixels."
            }
          ]
        }
      ]
    },
    {
      "name": "PeripheralHandler",
      "displayName": "Peripheral\\nHandler",
      "description": "Takes care of detecting, initializing and configuring hardware peripherals \u2014 the wireless transceiver and the display \u2014 e.g. by accessing low level drivers and serial communication lines. Both are brought up together by `start()` and report readiness and failures on the `Peripherals` interface.",
      "functions": [
        {
          "name": "start",
          "description": "Start initializing and configuring the peripherals."
        }
      ]
    },
    {
      "name": "DisplayHandler",
      "displayName": "Display\\nHandler",
      "description": "Handles the interface to the display to show images.",
      "functions": [
        {
          "name": "initialize",
          "description": "Initialize the display."
        },
        {
          "name": "display",
          "description": "Display the supplied image.",
          "parameters": [
            {
              "name": "image",
              "type": "byteArray",
              "description": "The PNG image to be displayed."
            }
          ]
        },
        {
          "name": "displayError",
          "description": "Display the default error message."
        }
      ]
    }
  ]
}

Declare the domain types

Parameters carry types. FCM is intentionally loose here — the type field is free text, so it can be anything from a primitive like int to a domain concept like UserId. But declaring a type explicitly under the device gives the spec export something to link against and signals shared vocabulary.

Four types are enough for the WND:

  • uint — unsigned 32-bit integer. Used for server IDs and pixel dimensions.
  • uintArray — a one-dimensional array of uint. Used for the whitelist of permitted servers.
  • byteArray — a one-dimensional array of bytes. Used for the PNG image the display renders.
  • byteMatrix — a two-dimensional array of bytes. Used for the PNG image as it travels over the Content interface. (Yes, both representations exist — the radio delivers content as a matrix; the display handler accepts a byte array. The conversion is internal to UiController's action text.)

The built-in types Time and TimerID are implicitly available; we'll use TimerID when we get to the timer pattern in Phase 5.

Device diagram
Wireless Networked Display (WND)ServerConnectorDevice StatusControllerUIControllerWirelessTransceiverDisplayHandlerPeripheralHandler
JSON snapshot at this stage
{
  "format": "stadiae-v4",
  "interfaces": [
    {
      "name": "Device",
      "description": "Device status messages."
    },
    {
      "name": "Connection",
      "description": "Wireless transceiver messages for connection handling."
    },
    {
      "name": "Peripherals",
      "description": "Status indications from the peripherals."
    },
    {
      "name": "Content",
      "description": "Exchange of interaction with the server."
    },
    {
      "name": "Display",
      "description": "Display status and control messages."
    }
  ],
  "messages": [
    {
      "interface": "Device",
      "name": "ReadyInd",
      "description": "All peripherals are ready and the device can be initialized."
    },
    {
      "interface": "Device",
      "name": "ErrorInd",
      "description": "An error occurred and the device is no longer operational."
    },
    {
      "interface": "Connection",
      "name": "ConnectReq",
      "description": "A server is requesting to connect.",
      "parameters": [
        {
          "name": "serverId",
          "type": "uint",
          "description": "ID of the server that wants to connect."
        }
      ]
    },
    {
      "interface": "Connection",
      "name": "ConnectedInd",
      "description": "Connected to the server."
    },
    {
      "interface": "Connection",
      "name": "DisconnectedInd",
      "description": "The server is no longer connected."
    },
    {
      "interface": "Peripherals",
      "name": "ReadyInd",
      "description": "Indication that a peripheral is ready."
    },
    {
      "interface": "Peripherals",
      "name": "FailureInd",
      "description": "A peripheral has failed."
    },
    {
      "interface": "Content",
      "name": "ContentInd",
      "description": "A new image to display.",
      "parameters": [
        {
          "name": "image",
          "type": "byteMatrix",
          "description": "The PNG image to display."
        }
      ]
    },
    {
      "interface": "Display",
      "name": "CompletedInd",
      "description": "Indication that the display has completed its current operation \u2014 either initialisation (in which case the parameters report the panel's pixel dimensions) or rendering a `Content:ContentInd`. In both cases the display is now idle and ready for the next instruction.",
      "parameters": [
        {
          "name": "width",
          "type": "uint",
          "description": "The width of the display in pixels. Populated on the initialisation occurrence."
        },
        {
          "name": "height",
          "type": "uint",
          "description": "The height of the display in pixels. Populated on the initialisation occurrence."
        }
      ]
    }
  ],
  "components": [
    {
      "name": "ServerConnector",
      "displayName": "Server\\nConnector",
      "states": [
        {
          "name": "Uninitialized"
        },
        {
          "name": "Advertising"
        },
        {
          "name": "Connecting"
        },
        {
          "name": "Connected"
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "Uninitialized",
          "messages": [],
          "connector": "Down",
          "length": 1
        }
      ]
    },
    {
      "name": "DeviceStatusController",
      "displayName": "Device Status\\nController",
      "states": [
        {
          "name": "AwaitingPeripherals",
          "displayName": "Awaiting\\nPeripherals"
        },
        {
          "name": "Operational"
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "AwaitingPeripherals",
          "messages": [],
          "connector": "Down",
          "length": 1
        }
      ]
    },
    {
      "name": "UiController",
      "displayName": "UI\\nController",
      "states": [
        {
          "name": "Uninitialized"
        },
        {
          "name": "InitializingDisplay",
          "displayName": "Initializing\\nDisplay"
        },
        {
          "name": "Ready"
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "Uninitialized",
          "messages": [],
          "connector": "Right",
          "length": 1
        }
      ]
    }
  ],
  "deviceDisplayName": "Wireless Networked Display (WND)",
  "handlers": [
    {
      "name": "WirelessTransceiver",
      "displayName": "Wireless\\nTransceiver",
      "description": "This handler takes care of receiving and transmitting data packets on the radio frontend by interfacing with the radio hardware peripheral. By looking at the message-type of incoming messages, indications are forwarded either to the `Connection` or `Content` interface.\n\nThe wireless transceiver is one of the device's peripherals: it is brought up by `PeripheralHandler:start()` together with the display, and it signals its readiness and any failure on the `Peripherals` interface so that `DeviceStatusController` can include it in its readiness count. Once readiness has been signalled, the radio is ready to advertise and accept connection requests.",
      "functions": [
        {
          "name": "sendAdvertisementPacket",
          "description": "Send a single advertisement packet to become discoverable."
        },
        {
          "name": "connectServer",
          "description": "Connect to the indicated server.",
          "parameters": [
            {
              "name": "serverId",
              "type": "uint",
              "description": "The ID of the server to connect to."
            }
          ]
        },
        {
          "name": "rejectServer",
          "description": "Block further requests from the indicated server.",
          "parameters": [
            {
              "name": "serverId",
              "type": "uint",
              "description": "The ID of the server that is to be rejected from now on."
            }
          ]
        },
        {
          "name": "disable",
          "description": "Disable the radio frontend."
        },
        {
          "name": "sendContentDimensions",
          "description": "Inform the server of the dimensions of the content the display supports.",
          "parameters": [
            {
              "name": "displayWidth",
              "type": "uint",
              "description": "The width of the display in pixels."
            },
            {
              "name": "displayHeight",
              "type": "uint",
              "description": "The height of the display in pixels."
            }
          ]
        }
      ]
    },
    {
      "name": "PeripheralHandler",
      "displayName": "Peripheral\\nHandler",
      "description": "Takes care of detecting, initializing and configuring hardware peripherals \u2014 the wireless transceiver and the display \u2014 e.g. by accessing low level drivers and serial communication lines. Both are brought up together by `start()` and report readiness and failures on the `Peripherals` interface.",
      "functions": [
        {
          "name": "start",
          "description": "Start initializing and configuring the peripherals."
        }
      ]
    },
    {
      "name": "DisplayHandler",
      "displayName": "Display\\nHandler",
      "description": "Handles the interface to the display to show images.",
      "functions": [
        {
          "name": "initialize",
          "description": "Initialize the display."
        },
        {
          "name": "display",
          "description": "Display the supplied image.",
          "parameters": [
            {
              "name": "image",
              "type": "byteArray",
              "description": "The PNG image to be displayed."
            }
          ]
        },
        {
          "name": "displayError",
          "description": "Display the default error message."
        }
      ]
    }
  ],
  "types": [
    {
      "name": "byteArray",
      "description": "A one dimensional array of bytes",
      "specification": "uint8[]"
    },
    {
      "name": "uint",
      "description": "Unsigned integer",
      "specification": "32-bit numerical value representing an integer value."
    },
    {
      "name": "uintArray",
      "description": "One dimensional array of uint",
      "specification": "uint[]"
    },
    {
      "name": "byteMatrix",
      "description": "2-dimensional array of bytes.",
      "specification": "byte[][]"
    }
  ]
}

Wire components to their interfaces

Now we connect each component to the interfaces it speaks on. Wiring is who-talks-to-what, expressed at the device level — independent of any specific message, independent of the state machines.

Seven connections:

  • ServerConnectorDevice (status) and Connection (the radio's view of the link).
  • DeviceStatusControllerDevice (where it emits readiness) and Peripherals (where it receives bring-up reports).
  • UiControllerDevice (status), Display (panel completion), and Content (incoming images).

The lollipop symbols on the device diagram are the interfaces; the lines say which component sits on which interface. Direction will appear once the handlers are wired in too, because that's when the diagram knows who's sending and who's receiving on each interface.

Device diagram
Wireless Networked Display (WND)DeviceConnectionPeripheralsContentDisplayServerConnectorDevice StatusControllerUIControllerWirelessTransceiverDisplayHandlerPeripheralHandler
JSON snapshot at this stage
{
  "format": "stadiae-v4",
  "interfaces": [
    {
      "name": "Device",
      "description": "Device status messages."
    },
    {
      "name": "Connection",
      "description": "Wireless transceiver messages for connection handling."
    },
    {
      "name": "Peripherals",
      "description": "Status indications from the peripherals."
    },
    {
      "name": "Content",
      "description": "Exchange of interaction with the server."
    },
    {
      "name": "Display",
      "description": "Display status and control messages."
    }
  ],
  "messages": [
    {
      "interface": "Device",
      "name": "ReadyInd",
      "description": "All peripherals are ready and the device can be initialized."
    },
    {
      "interface": "Device",
      "name": "ErrorInd",
      "description": "An error occurred and the device is no longer operational."
    },
    {
      "interface": "Connection",
      "name": "ConnectReq",
      "description": "A server is requesting to connect.",
      "parameters": [
        {
          "name": "serverId",
          "type": "uint",
          "description": "ID of the server that wants to connect."
        }
      ]
    },
    {
      "interface": "Connection",
      "name": "ConnectedInd",
      "description": "Connected to the server."
    },
    {
      "interface": "Connection",
      "name": "DisconnectedInd",
      "description": "The server is no longer connected."
    },
    {
      "interface": "Peripherals",
      "name": "ReadyInd",
      "description": "Indication that a peripheral is ready."
    },
    {
      "interface": "Peripherals",
      "name": "FailureInd",
      "description": "A peripheral has failed."
    },
    {
      "interface": "Content",
      "name": "ContentInd",
      "description": "A new image to display.",
      "parameters": [
        {
          "name": "image",
          "type": "byteMatrix",
          "description": "The PNG image to display."
        }
      ]
    },
    {
      "interface": "Display",
      "name": "CompletedInd",
      "description": "Indication that the display has completed its current operation \u2014 either initialisation (in which case the parameters report the panel's pixel dimensions) or rendering a `Content:ContentInd`. In both cases the display is now idle and ready for the next instruction.",
      "parameters": [
        {
          "name": "width",
          "type": "uint",
          "description": "The width of the display in pixels. Populated on the initialisation occurrence."
        },
        {
          "name": "height",
          "type": "uint",
          "description": "The height of the display in pixels. Populated on the initialisation occurrence."
        }
      ]
    }
  ],
  "components": [
    {
      "name": "ServerConnector",
      "displayName": "Server\\nConnector",
      "states": [
        {
          "name": "Uninitialized"
        },
        {
          "name": "Advertising"
        },
        {
          "name": "Connecting"
        },
        {
          "name": "Connected"
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "Uninitialized",
          "messages": [],
          "connector": "Down",
          "length": 1
        }
      ]
    },
    {
      "name": "DeviceStatusController",
      "displayName": "Device Status\\nController",
      "states": [
        {
          "name": "AwaitingPeripherals",
          "displayName": "Awaiting\\nPeripherals"
        },
        {
          "name": "Operational"
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "AwaitingPeripherals",
          "messages": [],
          "connector": "Down",
          "length": 1
        }
      ]
    },
    {
      "name": "UiController",
      "displayName": "UI\\nController",
      "states": [
        {
          "name": "Uninitialized"
        },
        {
          "name": "InitializingDisplay",
          "displayName": "Initializing\\nDisplay"
        },
        {
          "name": "Ready"
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "Uninitialized",
          "messages": [],
          "connector": "Right",
          "length": 1
        }
      ]
    }
  ],
  "deviceDisplayName": "Wireless Networked Display (WND)",
  "handlers": [
    {
      "name": "WirelessTransceiver",
      "displayName": "Wireless\\nTransceiver",
      "description": "This handler takes care of receiving and transmitting data packets on the radio frontend by interfacing with the radio hardware peripheral. By looking at the message-type of incoming messages, indications are forwarded either to the `Connection` or `Content` interface.\n\nThe wireless transceiver is one of the device's peripherals: it is brought up by `PeripheralHandler:start()` together with the display, and it signals its readiness and any failure on the `Peripherals` interface so that `DeviceStatusController` can include it in its readiness count. Once readiness has been signalled, the radio is ready to advertise and accept connection requests.",
      "functions": [
        {
          "name": "sendAdvertisementPacket",
          "description": "Send a single advertisement packet to become discoverable."
        },
        {
          "name": "connectServer",
          "description": "Connect to the indicated server.",
          "parameters": [
            {
              "name": "serverId",
              "type": "uint",
              "description": "The ID of the server to connect to."
            }
          ]
        },
        {
          "name": "rejectServer",
          "description": "Block further requests from the indicated server.",
          "parameters": [
            {
              "name": "serverId",
              "type": "uint",
              "description": "The ID of the server that is to be rejected from now on."
            }
          ]
        },
        {
          "name": "disable",
          "description": "Disable the radio frontend."
        },
        {
          "name": "sendContentDimensions",
          "description": "Inform the server of the dimensions of the content the display supports.",
          "parameters": [
            {
              "name": "displayWidth",
              "type": "uint",
              "description": "The width of the display in pixels."
            },
            {
              "name": "displayHeight",
              "type": "uint",
              "description": "The height of the display in pixels."
            }
          ]
        }
      ]
    },
    {
      "name": "PeripheralHandler",
      "displayName": "Peripheral\\nHandler",
      "description": "Takes care of detecting, initializing and configuring hardware peripherals \u2014 the wireless transceiver and the display \u2014 e.g. by accessing low level drivers and serial communication lines. Both are brought up together by `start()` and report readiness and failures on the `Peripherals` interface.",
      "functions": [
        {
          "name": "start",
          "description": "Start initializing and configuring the peripherals."
        }
      ]
    },
    {
      "name": "DisplayHandler",
      "displayName": "Display\\nHandler",
      "description": "Handles the interface to the display to show images.",
      "functions": [
        {
          "name": "initialize",
          "description": "Initialize the display."
        },
        {
          "name": "display",
          "description": "Display the supplied image.",
          "parameters": [
            {
              "name": "image",
              "type": "byteArray",
              "description": "The PNG image to be displayed."
            }
          ]
        },
        {
          "name": "displayError",
          "description": "Display the default error message."
        }
      ]
    }
  ],
  "types": [
    {
      "name": "byteArray",
      "description": "A one dimensional array of bytes",
      "specification": "uint8[]"
    },
    {
      "name": "uint",
      "description": "Unsigned integer",
      "specification": "32-bit numerical value representing an integer value."
    },
    {
      "name": "uintArray",
      "description": "One dimensional array of uint",
      "specification": "uint[]"
    },
    {
      "name": "byteMatrix",
      "description": "2-dimensional array of bytes.",
      "specification": "byte[][]"
    }
  ],
  "connections": [
    {
      "component": "ServerConnector",
      "interface": "Device",
      "connector": "Up",
      "length": 1
    },
    {
      "component": "ServerConnector",
      "interface": "Connection",
      "connector": "Left",
      "length": 1
    },
    {
      "component": "DeviceStatusController",
      "interface": "Device",
      "connector": "Up",
      "length": 1
    },
    {
      "component": "DeviceStatusController",
      "interface": "Peripherals",
      "connector": "Down",
      "length": 1
    },
    {
      "component": "UiController",
      "interface": "Device",
      "connector": "Down",
      "length": 1
    },
    {
      "component": "UiController",
      "interface": "Display",
      "connector": "Left",
      "length": 1
    },
    {
      "component": "UiController",
      "interface": "Content",
      "connector": "Down",
      "length": 1
    }
  ]
}

Wire handlers and call dependencies

Two more wiring stages complete the device-level picture:

  1. Handler ↔ interface wiringWirelessTransceiver speaks on Connection (link events) and Content (incoming images); PeripheralHandler speaks on Peripherals (its bring-up reports); DisplayHandler speaks on Display (its completion notifications). When a handler shares an interface with a component, the diagram becomes directional: the handler is implicitly the sender (the asynchronous source), the component is the receiver.
  1. Component → handler call dependencies — dashed arrows showing which components call which handlers' functions. ServerConnector calls into WirelessTransceiver. DeviceStatusController calls into PeripheralHandler. UiController calls into both DisplayHandler (to render) and WirelessTransceiver (to send content dimensions back to the server).

The device diagram is now structurally complete. Every component, every handler, every interface, every wiring is in place. What remains is the behaviour — the state machines that say what each component actually does. The next four phases cover one component each (plus a closing polish phase).

Device diagram
Wireless Networked Display (WND)DeviceConnectionPeripheralsContentDisplayServerConnectorDevice StatusControllerUIControllerWirelessTransceiverDisplayHandlerPeripheralHandler
JSON snapshot at this stage
{
  "format": "stadiae-v4",
  "interfaces": [
    {
      "name": "Device",
      "description": "Device status messages."
    },
    {
      "name": "Connection",
      "description": "Wireless transceiver messages for connection handling."
    },
    {
      "name": "Peripherals",
      "description": "Status indications from the peripherals."
    },
    {
      "name": "Content",
      "description": "Exchange of interaction with the server."
    },
    {
      "name": "Display",
      "description": "Display status and control messages."
    }
  ],
  "messages": [
    {
      "interface": "Device",
      "name": "ReadyInd",
      "description": "All peripherals are ready and the device can be initialized."
    },
    {
      "interface": "Device",
      "name": "ErrorInd",
      "description": "An error occurred and the device is no longer operational."
    },
    {
      "interface": "Connection",
      "name": "ConnectReq",
      "description": "A server is requesting to connect.",
      "parameters": [
        {
          "name": "serverId",
          "type": "uint",
          "description": "ID of the server that wants to connect."
        }
      ]
    },
    {
      "interface": "Connection",
      "name": "ConnectedInd",
      "description": "Connected to the server."
    },
    {
      "interface": "Connection",
      "name": "DisconnectedInd",
      "description": "The server is no longer connected."
    },
    {
      "interface": "Peripherals",
      "name": "ReadyInd",
      "description": "Indication that a peripheral is ready."
    },
    {
      "interface": "Peripherals",
      "name": "FailureInd",
      "description": "A peripheral has failed."
    },
    {
      "interface": "Content",
      "name": "ContentInd",
      "description": "A new image to display.",
      "parameters": [
        {
          "name": "image",
          "type": "byteMatrix",
          "description": "The PNG image to display."
        }
      ]
    },
    {
      "interface": "Display",
      "name": "CompletedInd",
      "description": "Indication that the display has completed its current operation \u2014 either initialisation (in which case the parameters report the panel's pixel dimensions) or rendering a `Content:ContentInd`. In both cases the display is now idle and ready for the next instruction.",
      "parameters": [
        {
          "name": "width",
          "type": "uint",
          "description": "The width of the display in pixels. Populated on the initialisation occurrence."
        },
        {
          "name": "height",
          "type": "uint",
          "description": "The height of the display in pixels. Populated on the initialisation occurrence."
        }
      ]
    }
  ],
  "components": [
    {
      "name": "ServerConnector",
      "displayName": "Server\\nConnector",
      "states": [
        {
          "name": "Uninitialized"
        },
        {
          "name": "Advertising"
        },
        {
          "name": "Connecting"
        },
        {
          "name": "Connected"
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "Uninitialized",
          "messages": [],
          "connector": "Down",
          "length": 1
        }
      ]
    },
    {
      "name": "DeviceStatusController",
      "displayName": "Device Status\\nController",
      "states": [
        {
          "name": "AwaitingPeripherals",
          "displayName": "Awaiting\\nPeripherals"
        },
        {
          "name": "Operational"
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "AwaitingPeripherals",
          "messages": [],
          "connector": "Down",
          "length": 1
        }
      ]
    },
    {
      "name": "UiController",
      "displayName": "UI\\nController",
      "states": [
        {
          "name": "Uninitialized"
        },
        {
          "name": "InitializingDisplay",
          "displayName": "Initializing\\nDisplay"
        },
        {
          "name": "Ready"
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "Uninitialized",
          "messages": [],
          "connector": "Right",
          "length": 1
        }
      ]
    }
  ],
  "deviceDisplayName": "Wireless Networked Display (WND)",
  "handlers": [
    {
      "name": "WirelessTransceiver",
      "displayName": "Wireless\\nTransceiver",
      "description": "This handler takes care of receiving and transmitting data packets on the radio frontend by interfacing with the radio hardware peripheral. By looking at the message-type of incoming messages, indications are forwarded either to the `Connection` or `Content` interface.\n\nThe wireless transceiver is one of the device's peripherals: it is brought up by `PeripheralHandler:start()` together with the display, and it signals its readiness and any failure on the `Peripherals` interface so that `DeviceStatusController` can include it in its readiness count. Once readiness has been signalled, the radio is ready to advertise and accept connection requests.",
      "functions": [
        {
          "name": "sendAdvertisementPacket",
          "description": "Send a single advertisement packet to become discoverable."
        },
        {
          "name": "connectServer",
          "description": "Connect to the indicated server.",
          "parameters": [
            {
              "name": "serverId",
              "type": "uint",
              "description": "The ID of the server to connect to."
            }
          ]
        },
        {
          "name": "rejectServer",
          "description": "Block further requests from the indicated server.",
          "parameters": [
            {
              "name": "serverId",
              "type": "uint",
              "description": "The ID of the server that is to be rejected from now on."
            }
          ]
        },
        {
          "name": "disable",
          "description": "Disable the radio frontend."
        },
        {
          "name": "sendContentDimensions",
          "description": "Inform the server of the dimensions of the content the display supports.",
          "parameters": [
            {
              "name": "displayWidth",
              "type": "uint",
              "description": "The width of the display in pixels."
            },
            {
              "name": "displayHeight",
              "type": "uint",
              "description": "The height of the display in pixels."
            }
          ]
        }
      ]
    },
    {
      "name": "PeripheralHandler",
      "displayName": "Peripheral\\nHandler",
      "description": "Takes care of detecting, initializing and configuring hardware peripherals \u2014 the wireless transceiver and the display \u2014 e.g. by accessing low level drivers and serial communication lines. Both are brought up together by `start()` and report readiness and failures on the `Peripherals` interface.",
      "functions": [
        {
          "name": "start",
          "description": "Start initializing and configuring the peripherals."
        }
      ]
    },
    {
      "name": "DisplayHandler",
      "displayName": "Display\\nHandler",
      "description": "Handles the interface to the display to show images.",
      "functions": [
        {
          "name": "initialize",
          "description": "Initialize the display."
        },
        {
          "name": "display",
          "description": "Display the supplied image.",
          "parameters": [
            {
              "name": "image",
              "type": "byteArray",
              "description": "The PNG image to be displayed."
            }
          ]
        },
        {
          "name": "displayError",
          "description": "Display the default error message."
        }
      ]
    }
  ],
  "types": [
    {
      "name": "byteArray",
      "description": "A one dimensional array of bytes",
      "specification": "uint8[]"
    },
    {
      "name": "uint",
      "description": "Unsigned integer",
      "specification": "32-bit numerical value representing an integer value."
    },
    {
      "name": "uintArray",
      "description": "One dimensional array of uint",
      "specification": "uint[]"
    },
    {
      "name": "byteMatrix",
      "description": "2-dimensional array of bytes.",
      "specification": "byte[][]"
    }
  ],
  "connections": [
    {
      "component": "ServerConnector",
      "interface": "Device",
      "connector": "Up",
      "length": 1
    },
    {
      "component": "ServerConnector",
      "interface": "Connection",
      "connector": "Left",
      "length": 1
    },
    {
      "component": "DeviceStatusController",
      "interface": "Device",
      "connector": "Up",
      "length": 1
    },
    {
      "component": "DeviceStatusController",
      "interface": "Peripherals",
      "connector": "Down",
      "length": 1
    },
    {
      "component": "UiController",
      "interface": "Device",
      "connector": "Down",
      "length": 1
    },
    {
      "component": "UiController",
      "interface": "Display",
      "connector": "Left",
      "length": 1
    },
    {
      "component": "UiController",
      "interface": "Content",
      "connector": "Down",
      "length": 1
    }
  ],
  "handlerConnections": [
    {
      "handler": "WirelessTransceiver",
      "interface": "Connection",
      "connector": "Down",
      "length": 1
    },
    {
      "handler": "PeripheralHandler",
      "interface": "Peripherals",
      "connector": "Left",
      "length": 1
    },
    {
      "handler": "DisplayHandler",
      "interface": "Display",
      "connector": "Right",
      "length": 1
    },
    {
      "handler": "WirelessTransceiver",
      "interface": "Content",
      "connector": "Left",
      "length": 1
    }
  ],
  "handlerCalls": [
    {
      "component": "ServerConnector",
      "handler": "WirelessTransceiver",
      "connector": "Left",
      "length": 1
    },
    {
      "component": "DeviceStatusController",
      "handler": "PeripheralHandler",
      "connector": "Left",
      "length": 1
    },
    {
      "component": "UiController",
      "handler": "DisplayHandler",
      "connector": "Up",
      "length": 1
    },
    {
      "component": "UiController",
      "handler": "WirelessTransceiver",
      "connector": "Right",
      "length": 1
    }
  ]
}

Phase 5 — Server Connector

The happy connection path

Start with the simplest case: a server finds us, we connect, all goes well. Five transitions, no error handling, no timers, no whitelist.

  • START → Uninitialized — the implicit initial transition.
  • Uninitialized → Advertising on Device:ReadyInd — once the device reports ready, start advertising.
  • Advertising → Connecting on Connection:ConnectReq — a server has pinged us; the action stores the server's ID.
  • Connecting → Connected on Connection:ConnectedInd — the radio confirms the link.
  • Connected → Advertising on Connection:DisconnectedInd — link lost, resume advertising for a new server.

The single state variable serverId: uint holds the connected (or connecting) server's ID. The action text Set serverId to ConnectReq:serverId reads a parameter from the triggering message into the variable.

This is the connection lifecycle stripped to its essentials. It works, but it admits any server that asks. Next we'll add a gate.

ServerConnector state machine
Server ConnectorUninitializedAdvertisingConnectingConnectedDevice:ReadyIndConnection:ConnectReqConnection:ConnectedIndConnection:DisconnectedInd
JSON snapshot at this stage
{
  "format": "stadiae-v4",
  "interfaces": [
    {
      "name": "Device",
      "description": "Device status messages."
    },
    {
      "name": "Connection",
      "description": "Wireless transceiver messages for connection handling."
    },
    {
      "name": "Peripherals",
      "description": "Status indications from the peripherals."
    },
    {
      "name": "Content",
      "description": "Exchange of interaction with the server."
    },
    {
      "name": "Display",
      "description": "Display status and control messages."
    }
  ],
  "messages": [
    {
      "interface": "Device",
      "name": "ReadyInd",
      "description": "All peripherals are ready and the device can be initialized."
    },
    {
      "interface": "Device",
      "name": "ErrorInd",
      "description": "An error occurred and the device is no longer operational."
    },
    {
      "interface": "Connection",
      "name": "ConnectReq",
      "description": "A server is requesting to connect.",
      "parameters": [
        {
          "name": "serverId",
          "type": "uint",
          "description": "ID of the server that wants to connect."
        }
      ]
    },
    {
      "interface": "Connection",
      "name": "ConnectedInd",
      "description": "Connected to the server."
    },
    {
      "interface": "Connection",
      "name": "DisconnectedInd",
      "description": "The server is no longer connected."
    },
    {
      "interface": "Peripherals",
      "name": "ReadyInd",
      "description": "Indication that a peripheral is ready."
    },
    {
      "interface": "Peripherals",
      "name": "FailureInd",
      "description": "A peripheral has failed."
    },
    {
      "interface": "Content",
      "name": "ContentInd",
      "description": "A new image to display.",
      "parameters": [
        {
          "name": "image",
          "type": "byteMatrix",
          "description": "The PNG image to display."
        }
      ]
    },
    {
      "interface": "Display",
      "name": "CompletedInd",
      "description": "Indication that the display has completed its current operation \u2014 either initialisation (in which case the parameters report the panel's pixel dimensions) or rendering a `Content:ContentInd`. In both cases the display is now idle and ready for the next instruction.",
      "parameters": [
        {
          "name": "width",
          "type": "uint",
          "description": "The width of the display in pixels. Populated on the initialisation occurrence."
        },
        {
          "name": "height",
          "type": "uint",
          "description": "The height of the display in pixels. Populated on the initialisation occurrence."
        }
      ]
    }
  ],
  "components": [
    {
      "name": "ServerConnector",
      "displayName": "Server\\nConnector",
      "states": [
        {
          "name": "Uninitialized"
        },
        {
          "name": "Advertising"
        },
        {
          "name": "Connecting"
        },
        {
          "name": "Connected"
        }
      ],
      "stateVariables": [
        {
          "name": "serverId",
          "type": "uint",
          "description": "The ID of the connected or connecting server."
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "Uninitialized",
          "messages": [],
          "connector": "Down",
          "length": 1
        },
        {
          "source": "Uninitialized",
          "target": "Advertising",
          "messages": [
            {
              "interface": "Device",
              "name": "ReadyInd"
            }
          ],
          "connector": "Down",
          "length": 1
        },
        {
          "source": "Advertising",
          "target": "Connecting",
          "messages": [
            {
              "interface": "Connection",
              "name": "ConnectReq",
              "action": "Set `serverId` to `ConnectReq:serverId`."
            }
          ],
          "connector": "Down",
          "length": 1
        },
        {
          "source": "Connecting",
          "target": "Connected",
          "messages": [
            {
              "interface": "Connection",
              "name": "ConnectedInd"
            }
          ],
          "connector": "Right",
          "length": 1
        },
        {
          "source": "Connected",
          "target": "Advertising",
          "messages": [
            {
              "interface": "Connection",
              "name": "DisconnectedInd"
            }
          ],
          "connector": "Up",
          "length": 1
        }
      ]
    },
    {
      "name": "DeviceStatusController",
      "displayName": "Device Status\\nController",
      "states": [
        {
          "name": "AwaitingPeripherals",
          "displayName": "Awaiting\\nPeripherals"
        },
        {
          "name": "Operational"
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "AwaitingPeripherals",
          "messages": [],
          "connector": "Down",
          "length": 1
        }
      ]
    },
    {
      "name": "UiController",
      "displayName": "UI\\nController",
      "states": [
        {
          "name": "Uninitialized"
        },
        {
          "name": "InitializingDisplay",
          "displayName": "Initializing\\nDisplay"
        },
        {
          "name": "Ready"
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "Uninitialized",
          "messages": [],
          "connector": "Right",
          "length": 1
        }
      ]
    }
  ],
  "deviceDisplayName": "Wireless Networked Display (WND)",
  "handlers": [
    {
      "name": "WirelessTransceiver",
      "displayName": "Wireless\\nTransceiver",
      "description": "This handler takes care of receiving and transmitting data packets on the radio frontend by interfacing with the radio hardware peripheral. By looking at the message-type of incoming messages, indications are forwarded either to the `Connection` or `Content` interface.\n\nThe wireless transceiver is one of the device's peripherals: it is brought up by `PeripheralHandler:start()` together with the display, and it signals its readiness and any failure on the `Peripherals` interface so that `DeviceStatusController` can include it in its readiness count. Once readiness has been signalled, the radio is ready to advertise and accept connection requests.",
      "functions": [
        {
          "name": "sendAdvertisementPacket",
          "description": "Send a single advertisement packet to become discoverable."
        },
        {
          "name": "connectServer",
          "description": "Connect to the indicated server.",
          "parameters": [
            {
              "name": "serverId",
              "type": "uint",
              "description": "The ID of the server to connect to."
            }
          ]
        },
        {
          "name": "rejectServer",
          "description": "Block further requests from the indicated server.",
          "parameters": [
            {
              "name": "serverId",
              "type": "uint",
              "description": "The ID of the server that is to be rejected from now on."
            }
          ]
        },
        {
          "name": "disable",
          "description": "Disable the radio frontend."
        },
        {
          "name": "sendContentDimensions",
          "description": "Inform the server of the dimensions of the content the display supports.",
          "parameters": [
            {
              "name": "displayWidth",
              "type": "uint",
              "description": "The width of the display in pixels."
            },
            {
              "name": "displayHeight",
              "type": "uint",
              "description": "The height of the display in pixels."
            }
          ]
        }
      ]
    },
    {
      "name": "PeripheralHandler",
      "displayName": "Peripheral\\nHandler",
      "description": "Takes care of detecting, initializing and configuring hardware peripherals \u2014 the wireless transceiver and the display \u2014 e.g. by accessing low level drivers and serial communication lines. Both are brought up together by `start()` and report readiness and failures on the `Peripherals` interface.",
      "functions": [
        {
          "name": "start",
          "description": "Start initializing and configuring the peripherals."
        }
      ]
    },
    {
      "name": "DisplayHandler",
      "displayName": "Display\\nHandler",
      "description": "Handles the interface to the display to show images.",
      "functions": [
        {
          "name": "initialize",
          "description": "Initialize the display."
        },
        {
          "name": "display",
          "description": "Display the supplied image.",
          "parameters": [
            {
              "name": "image",
              "type": "byteArray",
              "description": "The PNG image to be displayed."
            }
          ]
        },
        {
          "name": "displayError",
          "description": "Display the default error message."
        }
      ]
    }
  ],
  "types": [
    {
      "name": "byteArray",
      "description": "A one dimensional array of bytes",
      "specification": "uint8[]"
    },
    {
      "name": "uint",
      "description": "Unsigned integer",
      "specification": "32-bit numerical value representing an integer value."
    },
    {
      "name": "uintArray",
      "description": "One dimensional array of uint",
      "specification": "uint[]"
    },
    {
      "name": "byteMatrix",
      "description": "2-dimensional array of bytes.",
      "specification": "byte[][]"
    }
  ],
  "connections": [
    {
      "component": "ServerConnector",
      "interface": "Device",
      "connector": "Up",
      "length": 1
    },
    {
      "component": "ServerConnector",
      "interface": "Connection",
      "connector": "Left",
      "length": 1
    },
    {
      "component": "DeviceStatusController",
      "interface": "Device",
      "connector": "Up",
      "length": 1
    },
    {
      "component": "DeviceStatusController",
      "interface": "Peripherals",
      "connector": "Down",
      "length": 1
    },
    {
      "component": "UiController",
      "interface": "Device",
      "connector": "Down",
      "length": 1
    },
    {
      "component": "UiController",
      "interface": "Display",
      "connector": "Left",
      "length": 1
    },
    {
      "component": "UiController",
      "interface": "Content",
      "connector": "Down",
      "length": 1
    }
  ],
  "handlerConnections": [
    {
      "handler": "WirelessTransceiver",
      "interface": "Connection",
      "connector": "Down",
      "length": 1
    },
    {
      "handler": "PeripheralHandler",
      "interface": "Peripherals",
      "connector": "Left",
      "length": 1
    },
    {
      "handler": "DisplayHandler",
      "interface": "Display",
      "connector": "Right",
      "length": 1
    },
    {
      "handler": "WirelessTransceiver",
      "interface": "Content",
      "connector": "Left",
      "length": 1
    }
  ],
  "handlerCalls": [
    {
      "component": "ServerConnector",
      "handler": "WirelessTransceiver",
      "connector": "Left",
      "length": 1
    },
    {
      "component": "DeviceStatusController",
      "handler": "PeripheralHandler",
      "connector": "Left",
      "length": 1
    },
    {
      "component": "UiController",
      "handler": "DisplayHandler",
      "connector": "Up",
      "length": 1
    },
    {
      "component": "UiController",
      "handler": "WirelessTransceiver",
      "connector": "Right",
      "length": 1
    }
  ]
}

Gate connections on a whitelist

Not every server should be allowed to connect — only those on a known whitelist. We need to evaluate a condition on receipt of ConnectReq and branch the transition.

FCM's mechanism for this is the choice-point. A choice-point is a pseudostate that evaluates against the component's state variables and emits either Logical:Yes or Logical:No synchronously. The two outgoing transitions handle the two branches. The evaluation itself isn't modelled as a state; it's instantaneous.

We add:

  • A whitelist: uintArray state variable holding the permitted server IDs.
  • A WhiteListed choice-point asking "Is Server Whitelisted?".
  • Two outgoing transitions from CP_WhiteListed:
    • On Logical:No → back to Advertising, calling WirelessTransceiver:rejectServer(serverId) so the radio blocks further requests from that server.
    • On Logical:Yes → on to Connecting, calling WirelessTransceiver:connectServer(serverId) to actually establish the link.

The existing Advertising → Connecting transition is replaced by Advertising → CP_WhiteListed; the choice-point then routes onward.

ServerConnector state machine
Server ConnectorUninitializedAdvertisingConnectingConnectedIs ServerWhitelisted?Device:ReadyIndConnection:ConnectReqNoYesConnection:ConnectedIndConnection:DisconnectedInd
JSON snapshot at this stage
{
  "format": "stadiae-v4",
  "interfaces": [
    {
      "name": "Device",
      "description": "Device status messages."
    },
    {
      "name": "Connection",
      "description": "Wireless transceiver messages for connection handling."
    },
    {
      "name": "Peripherals",
      "description": "Status indications from the peripherals."
    },
    {
      "name": "Content",
      "description": "Exchange of interaction with the server."
    },
    {
      "name": "Display",
      "description": "Display status and control messages."
    }
  ],
  "messages": [
    {
      "interface": "Device",
      "name": "ReadyInd",
      "description": "All peripherals are ready and the device can be initialized."
    },
    {
      "interface": "Device",
      "name": "ErrorInd",
      "description": "An error occurred and the device is no longer operational."
    },
    {
      "interface": "Connection",
      "name": "ConnectReq",
      "description": "A server is requesting to connect.",
      "parameters": [
        {
          "name": "serverId",
          "type": "uint",
          "description": "ID of the server that wants to connect."
        }
      ]
    },
    {
      "interface": "Connection",
      "name": "ConnectedInd",
      "description": "Connected to the server."
    },
    {
      "interface": "Connection",
      "name": "DisconnectedInd",
      "description": "The server is no longer connected."
    },
    {
      "interface": "Peripherals",
      "name": "ReadyInd",
      "description": "Indication that a peripheral is ready."
    },
    {
      "interface": "Peripherals",
      "name": "FailureInd",
      "description": "A peripheral has failed."
    },
    {
      "interface": "Content",
      "name": "ContentInd",
      "description": "A new image to display.",
      "parameters": [
        {
          "name": "image",
          "type": "byteMatrix",
          "description": "The PNG image to display."
        }
      ]
    },
    {
      "interface": "Display",
      "name": "CompletedInd",
      "description": "Indication that the display has completed its current operation \u2014 either initialisation (in which case the parameters report the panel's pixel dimensions) or rendering a `Content:ContentInd`. In both cases the display is now idle and ready for the next instruction.",
      "parameters": [
        {
          "name": "width",
          "type": "uint",
          "description": "The width of the display in pixels. Populated on the initialisation occurrence."
        },
        {
          "name": "height",
          "type": "uint",
          "description": "The height of the display in pixels. Populated on the initialisation occurrence."
        }
      ]
    }
  ],
  "components": [
    {
      "name": "ServerConnector",
      "displayName": "Server\\nConnector",
      "states": [
        {
          "name": "Uninitialized"
        },
        {
          "name": "Advertising"
        },
        {
          "name": "Connecting"
        },
        {
          "name": "Connected"
        }
      ],
      "choicePoints": [
        {
          "name": "WhiteListed",
          "question": "Is Server\\nWhitelisted?"
        }
      ],
      "stateVariables": [
        {
          "name": "serverId",
          "type": "uint",
          "description": "The ID of the connected or connecting server."
        },
        {
          "name": "whitelist",
          "type": "uintArray",
          "description": "The IDs of the servers that are permitted to connect."
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "Uninitialized",
          "messages": [],
          "connector": "Down",
          "length": 1
        },
        {
          "source": "Uninitialized",
          "target": "Advertising",
          "messages": [
            {
              "interface": "Device",
              "name": "ReadyInd"
            }
          ],
          "connector": "Down",
          "length": 1
        },
        {
          "source": "Advertising",
          "target": "CP_WhiteListed",
          "messages": [
            {
              "interface": "Connection",
              "name": "ConnectReq",
              "action": "Set `serverId` to `ConnectReq:serverId`."
            }
          ],
          "connector": "Left",
          "length": 1
        },
        {
          "source": "CP_WhiteListed",
          "target": "Advertising",
          "messages": [
            {
              "interface": "Logical",
              "name": "No",
              "action": "Call `WirelessTransceiver:rejectServer`(`serverId`)."
            }
          ],
          "connector": "Right",
          "length": 1
        },
        {
          "source": "CP_WhiteListed",
          "target": "Connecting",
          "messages": [
            {
              "interface": "Logical",
              "name": "Yes",
              "action": "Call `WirelessTransceiver:connectServer`(`serverId`)."
            }
          ],
          "connector": "Down",
          "length": 1
        },
        {
          "source": "Connecting",
          "target": "Connected",
          "messages": [
            {
              "interface": "Connection",
              "name": "ConnectedInd"
            }
          ],
          "connector": "Right",
          "length": 1
        },
        {
          "source": "Connected",
          "target": "Advertising",
          "messages": [
            {
              "interface": "Connection",
              "name": "DisconnectedInd"
            }
          ],
          "connector": "Up",
          "length": 1
        }
      ]
    },
    {
      "name": "DeviceStatusController",
      "displayName": "Device Status\\nController",
      "states": [
        {
          "name": "AwaitingPeripherals",
          "displayName": "Awaiting\\nPeripherals"
        },
        {
          "name": "Operational"
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "AwaitingPeripherals",
          "messages": [],
          "connector": "Down",
          "length": 1
        }
      ]
    },
    {
      "name": "UiController",
      "displayName": "UI\\nController",
      "states": [
        {
          "name": "Uninitialized"
        },
        {
          "name": "InitializingDisplay",
          "displayName": "Initializing\\nDisplay"
        },
        {
          "name": "Ready"
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "Uninitialized",
          "messages": [],
          "connector": "Right",
          "length": 1
        }
      ]
    }
  ],
  "deviceDisplayName": "Wireless Networked Display (WND)",
  "handlers": [
    {
      "name": "WirelessTransceiver",
      "displayName": "Wireless\\nTransceiver",
      "description": "This handler takes care of receiving and transmitting data packets on the radio frontend by interfacing with the radio hardware peripheral. By looking at the message-type of incoming messages, indications are forwarded either to the `Connection` or `Content` interface.\n\nThe wireless transceiver is one of the device's peripherals: it is brought up by `PeripheralHandler:start()` together with the display, and it signals its readiness and any failure on the `Peripherals` interface so that `DeviceStatusController` can include it in its readiness count. Once readiness has been signalled, the radio is ready to advertise and accept connection requests.",
      "functions": [
        {
          "name": "sendAdvertisementPacket",
          "description": "Send a single advertisement packet to become discoverable."
        },
        {
          "name": "connectServer",
          "description": "Connect to the indicated server.",
          "parameters": [
            {
              "name": "serverId",
              "type": "uint",
              "description": "The ID of the server to connect to."
            }
          ]
        },
        {
          "name": "rejectServer",
          "description": "Block further requests from the indicated server.",
          "parameters": [
            {
              "name": "serverId",
              "type": "uint",
              "description": "The ID of the server that is to be rejected from now on."
            }
          ]
        },
        {
          "name": "disable",
          "description": "Disable the radio frontend."
        },
        {
          "name": "sendContentDimensions",
          "description": "Inform the server of the dimensions of the content the display supports.",
          "parameters": [
            {
              "name": "displayWidth",
              "type": "uint",
              "description": "The width of the display in pixels."
            },
            {
              "name": "displayHeight",
              "type": "uint",
              "description": "The height of the display in pixels."
            }
          ]
        }
      ]
    },
    {
      "name": "PeripheralHandler",
      "displayName": "Peripheral\\nHandler",
      "description": "Takes care of detecting, initializing and configuring hardware peripherals \u2014 the wireless transceiver and the display \u2014 e.g. by accessing low level drivers and serial communication lines. Both are brought up together by `start()` and report readiness and failures on the `Peripherals` interface.",
      "functions": [
        {
          "name": "start",
          "description": "Start initializing and configuring the peripherals."
        }
      ]
    },
    {
      "name": "DisplayHandler",
      "displayName": "Display\\nHandler",
      "description": "Handles the interface to the display to show images.",
      "functions": [
        {
          "name": "initialize",
          "description": "Initialize the display."
        },
        {
          "name": "display",
          "description": "Display the supplied image.",
          "parameters": [
            {
              "name": "image",
              "type": "byteArray",
              "description": "The PNG image to be displayed."
            }
          ]
        },
        {
          "name": "displayError",
          "description": "Display the default error message."
        }
      ]
    }
  ],
  "types": [
    {
      "name": "byteArray",
      "description": "A one dimensional array of bytes",
      "specification": "uint8[]"
    },
    {
      "name": "uint",
      "description": "Unsigned integer",
      "specification": "32-bit numerical value representing an integer value."
    },
    {
      "name": "uintArray",
      "description": "One dimensional array of uint",
      "specification": "uint[]"
    },
    {
      "name": "byteMatrix",
      "description": "2-dimensional array of bytes.",
      "specification": "byte[][]"
    }
  ],
  "connections": [
    {
      "component": "ServerConnector",
      "interface": "Device",
      "connector": "Up",
      "length": 1
    },
    {
      "component": "ServerConnector",
      "interface": "Connection",
      "connector": "Left",
      "length": 1
    },
    {
      "component": "DeviceStatusController",
      "interface": "Device",
      "connector": "Up",
      "length": 1
    },
    {
      "component": "DeviceStatusController",
      "interface": "Peripherals",
      "connector": "Down",
      "length": 1
    },
    {
      "component": "UiController",
      "interface": "Device",
      "connector": "Down",
      "length": 1
    },
    {
      "component": "UiController",
      "interface": "Display",
      "connector": "Left",
      "length": 1
    },
    {
      "component": "UiController",
      "interface": "Content",
      "connector": "Down",
      "length": 1
    }
  ],
  "handlerConnections": [
    {
      "handler": "WirelessTransceiver",
      "interface": "Connection",
      "connector": "Down",
      "length": 1
    },
    {
      "handler": "PeripheralHandler",
      "interface": "Peripherals",
      "connector": "Left",
      "length": 1
    },
    {
      "handler": "DisplayHandler",
      "interface": "Display",
      "connector": "Right",
      "length": 1
    },
    {
      "handler": "WirelessTransceiver",
      "interface": "Content",
      "connector": "Left",
      "length": 1
    }
  ],
  "handlerCalls": [
    {
      "component": "ServerConnector",
      "handler": "WirelessTransceiver",
      "connector": "Left",
      "length": 1
    },
    {
      "component": "DeviceStatusController",
      "handler": "PeripheralHandler",
      "connector": "Left",
      "length": 1
    },
    {
      "component": "UiController",
      "handler": "DisplayHandler",
      "connector": "Up",
      "length": 1
    },
    {
      "component": "UiController",
      "handler": "WirelessTransceiver",
      "connector": "Right",
      "length": 1
    }
  ]
}

The timer pattern

Two timing concerns are missing. First, advertising shouldn't be a single packet — the radio needs to send one every so often to remain discoverable. Second, a connection attempt that hangs shouldn't hang forever — it should time out.

Both are instances of the same FCM timer pattern:

  1. Component wants to wait for time → calls TimerHandler:setTimeout(duration, timerId).
  2. The component stores the timerId in a state variable typed TimerID.
  3. A Timer:Timeout message arrives when the timer expires; the component handles it in a transition out of the waiting state.
  4. If the wait is cancelled (e.g. the connection succeeded before the timeout), the component calls TimerHandler:cancelTimeout(timerId) and moves on.

Two timers, two state variables (advertiseTimerId, connectTimerId), two constants (ADVERTISEMENT = 1000ms, CONNECTION_TIMEOUT = 500ms).

The advertise-cycle is recurring: when Timer:Timeout fires in Advertising, we send another packet and re-arm the timer. The action text for this is the same on three transitions — re-entering Advertising. To avoid duplication, we factor it into a local function: doAdvertise(), which calls sendAdvertisementPacket() and re-arms the timer. The local function lives on the component, takes no parameters, and reads/writes the component's state variables directly.

Three transitions now reference doAdvertise():

  • Uninitialized → Advertising (start the advertise cycle).
  • Connected → Advertising (after a disconnect — back to advertising).
  • Advertising → Advertising on Timer:Timeout (the recurring cycle itself).

And one new transition: Connecting → Advertising on Timer:Timeout — the connection attempt timed out, abandon and resume discovery.

ServerConnector state machine
Server ConnectorUninitializedAdvertisingConnectingConnectedIs ServerWhitelisted?Device:ReadyIndConnection:ConnectReqNoYesConnection:ConnectedIndTimeoutConnection:DisconnectedIndTimeout
JSON snapshot at this stage
{
  "format": "stadiae-v4",
  "interfaces": [
    {
      "name": "Device",
      "description": "Device status messages."
    },
    {
      "name": "Connection",
      "description": "Wireless transceiver messages for connection handling."
    },
    {
      "name": "Peripherals",
      "description": "Status indications from the peripherals."
    },
    {
      "name": "Content",
      "description": "Exchange of interaction with the server."
    },
    {
      "name": "Display",
      "description": "Display status and control messages."
    }
  ],
  "messages": [
    {
      "interface": "Device",
      "name": "ReadyInd",
      "description": "All peripherals are ready and the device can be initialized."
    },
    {
      "interface": "Device",
      "name": "ErrorInd",
      "description": "An error occurred and the device is no longer operational."
    },
    {
      "interface": "Connection",
      "name": "ConnectReq",
      "description": "A server is requesting to connect.",
      "parameters": [
        {
          "name": "serverId",
          "type": "uint",
          "description": "ID of the server that wants to connect."
        }
      ]
    },
    {
      "interface": "Connection",
      "name": "ConnectedInd",
      "description": "Connected to the server."
    },
    {
      "interface": "Connection",
      "name": "DisconnectedInd",
      "description": "The server is no longer connected."
    },
    {
      "interface": "Peripherals",
      "name": "ReadyInd",
      "description": "Indication that a peripheral is ready."
    },
    {
      "interface": "Peripherals",
      "name": "FailureInd",
      "description": "A peripheral has failed."
    },
    {
      "interface": "Content",
      "name": "ContentInd",
      "description": "A new image to display.",
      "parameters": [
        {
          "name": "image",
          "type": "byteMatrix",
          "description": "The PNG image to display."
        }
      ]
    },
    {
      "interface": "Display",
      "name": "CompletedInd",
      "description": "Indication that the display has completed its current operation \u2014 either initialisation (in which case the parameters report the panel's pixel dimensions) or rendering a `Content:ContentInd`. In both cases the display is now idle and ready for the next instruction.",
      "parameters": [
        {
          "name": "width",
          "type": "uint",
          "description": "The width of the display in pixels. Populated on the initialisation occurrence."
        },
        {
          "name": "height",
          "type": "uint",
          "description": "The height of the display in pixels. Populated on the initialisation occurrence."
        }
      ]
    }
  ],
  "components": [
    {
      "name": "ServerConnector",
      "displayName": "Server\\nConnector",
      "states": [
        {
          "name": "Uninitialized"
        },
        {
          "name": "Advertising"
        },
        {
          "name": "Connecting"
        },
        {
          "name": "Connected"
        }
      ],
      "choicePoints": [
        {
          "name": "WhiteListed",
          "question": "Is Server\\nWhitelisted?"
        }
      ],
      "stateVariables": [
        {
          "name": "serverId",
          "type": "uint",
          "description": "The ID of the connected or connecting server."
        },
        {
          "name": "whitelist",
          "type": "uintArray",
          "description": "The IDs of the servers that are permitted to connect."
        },
        {
          "name": "connectTimerId",
          "type": "TimerID",
          "description": "Handle of the connection-attempt timer."
        },
        {
          "name": "advertiseTimerId",
          "type": "TimerID",
          "description": "Handle of the advertisement-interval timer."
        }
      ],
      "constants": [
        {
          "name": "ADVERTISEMENT",
          "value": "1000",
          "description": "Advertisement interval in ms."
        },
        {
          "name": "CONNECTION_TIMEOUT",
          "value": "500",
          "description": "Timeout waiting for server to connect in ms."
        }
      ],
      "localFunctions": [
        {
          "name": "doAdvertise",
          "description": "Send an advertisement packet and (re)start the advertisement-interval timer.",
          "steps": "Call `WirelessTransceiver:sendAdvertisementPacket()`.\nCall `TimerHandler:setTimeout`(`ADVERTISEMENT`, `advertiseTimerId`)."
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "Uninitialized",
          "messages": [],
          "connector": "Down",
          "length": 1
        },
        {
          "source": "Uninitialized",
          "target": "Advertising",
          "messages": [
            {
              "interface": "Device",
              "name": "ReadyInd",
              "action": "Call `doAdvertise()`."
            }
          ],
          "connector": "Down",
          "length": 1
        },
        {
          "source": "Advertising",
          "target": "CP_WhiteListed",
          "messages": [
            {
              "interface": "Connection",
              "name": "ConnectReq",
              "action": "Call `TimerHandler:cancelTimeout`(`advertiseTimerId`).\nSet `serverId` to `ConnectReq:serverId`."
            }
          ],
          "connector": "Left",
          "length": 1
        },
        {
          "source": "CP_WhiteListed",
          "target": "Advertising",
          "messages": [
            {
              "interface": "Logical",
              "name": "No",
              "action": "Call `WirelessTransceiver:rejectServer`(`serverId`).\nCall `doAdvertise()`."
            }
          ],
          "connector": "Right",
          "length": 1
        },
        {
          "source": "CP_WhiteListed",
          "target": "Connecting",
          "messages": [
            {
              "interface": "Logical",
              "name": "Yes",
              "action": "Call `WirelessTransceiver:connectServer`(`serverId`).\nCall `TimerHandler:setTimeout`(`CONNECTION_TIMEOUT`, `connectTimerId`)."
            }
          ],
          "connector": "Down",
          "length": 1
        },
        {
          "source": "Connecting",
          "target": "Connected",
          "messages": [
            {
              "interface": "Connection",
              "name": "ConnectedInd",
              "action": "Call `TimerHandler:cancelTimeout`(`connectTimerId`)."
            }
          ],
          "connector": "Right",
          "length": 1
        },
        {
          "source": "Connecting",
          "target": "Advertising",
          "messages": [
            {
              "interface": "Timer",
              "name": "Timeout",
              "action": "Call `doAdvertise()`."
            }
          ],
          "connector": "Up",
          "length": 1
        },
        {
          "source": "Connected",
          "target": "Advertising",
          "messages": [
            {
              "interface": "Connection",
              "name": "DisconnectedInd",
              "action": "Call `doAdvertise()`."
            }
          ],
          "connector": "Up",
          "length": 1
        },
        {
          "source": "Advertising",
          "target": "Advertising",
          "messages": [
            {
              "interface": "Timer",
              "name": "Timeout",
              "action": "Call `doAdvertise()`."
            }
          ],
          "connector": "Right",
          "length": 1
        }
      ]
    },
    {
      "name": "DeviceStatusController",
      "displayName": "Device Status\\nController",
      "states": [
        {
          "name": "AwaitingPeripherals",
          "displayName": "Awaiting\\nPeripherals"
        },
        {
          "name": "Operational"
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "AwaitingPeripherals",
          "messages": [],
          "connector": "Down",
          "length": 1
        }
      ]
    },
    {
      "name": "UiController",
      "displayName": "UI\\nController",
      "states": [
        {
          "name": "Uninitialized"
        },
        {
          "name": "InitializingDisplay",
          "displayName": "Initializing\\nDisplay"
        },
        {
          "name": "Ready"
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "Uninitialized",
          "messages": [],
          "connector": "Right",
          "length": 1
        }
      ]
    }
  ],
  "deviceDisplayName": "Wireless Networked Display (WND)",
  "handlers": [
    {
      "name": "WirelessTransceiver",
      "displayName": "Wireless\\nTransceiver",
      "description": "This handler takes care of receiving and transmitting data packets on the radio frontend by interfacing with the radio hardware peripheral. By looking at the message-type of incoming messages, indications are forwarded either to the `Connection` or `Content` interface.\n\nThe wireless transceiver is one of the device's peripherals: it is brought up by `PeripheralHandler:start()` together with the display, and it signals its readiness and any failure on the `Peripherals` interface so that `DeviceStatusController` can include it in its readiness count. Once readiness has been signalled, the radio is ready to advertise and accept connection requests.",
      "functions": [
        {
          "name": "sendAdvertisementPacket",
          "description": "Send a single advertisement packet to become discoverable."
        },
        {
          "name": "connectServer",
          "description": "Connect to the indicated server.",
          "parameters": [
            {
              "name": "serverId",
              "type": "uint",
              "description": "The ID of the server to connect to."
            }
          ]
        },
        {
          "name": "rejectServer",
          "description": "Block further requests from the indicated server.",
          "parameters": [
            {
              "name": "serverId",
              "type": "uint",
              "description": "The ID of the server that is to be rejected from now on."
            }
          ]
        },
        {
          "name": "disable",
          "description": "Disable the radio frontend."
        },
        {
          "name": "sendContentDimensions",
          "description": "Inform the server of the dimensions of the content the display supports.",
          "parameters": [
            {
              "name": "displayWidth",
              "type": "uint",
              "description": "The width of the display in pixels."
            },
            {
              "name": "displayHeight",
              "type": "uint",
              "description": "The height of the display in pixels."
            }
          ]
        }
      ]
    },
    {
      "name": "PeripheralHandler",
      "displayName": "Peripheral\\nHandler",
      "description": "Takes care of detecting, initializing and configuring hardware peripherals \u2014 the wireless transceiver and the display \u2014 e.g. by accessing low level drivers and serial communication lines. Both are brought up together by `start()` and report readiness and failures on the `Peripherals` interface.",
      "functions": [
        {
          "name": "start",
          "description": "Start initializing and configuring the peripherals."
        }
      ]
    },
    {
      "name": "DisplayHandler",
      "displayName": "Display\\nHandler",
      "description": "Handles the interface to the display to show images.",
      "functions": [
        {
          "name": "initialize",
          "description": "Initialize the display."
        },
        {
          "name": "display",
          "description": "Display the supplied image.",
          "parameters": [
            {
              "name": "image",
              "type": "byteArray",
              "description": "The PNG image to be displayed."
            }
          ]
        },
        {
          "name": "displayError",
          "description": "Display the default error message."
        }
      ]
    }
  ],
  "types": [
    {
      "name": "byteArray",
      "description": "A one dimensional array of bytes",
      "specification": "uint8[]"
    },
    {
      "name": "uint",
      "description": "Unsigned integer",
      "specification": "32-bit numerical value representing an integer value."
    },
    {
      "name": "uintArray",
      "description": "One dimensional array of uint",
      "specification": "uint[]"
    },
    {
      "name": "byteMatrix",
      "description": "2-dimensional array of bytes.",
      "specification": "byte[][]"
    }
  ],
  "connections": [
    {
      "component": "ServerConnector",
      "interface": "Device",
      "connector": "Up",
      "length": 1
    },
    {
      "component": "ServerConnector",
      "interface": "Connection",
      "connector": "Left",
      "length": 1
    },
    {
      "component": "DeviceStatusController",
      "interface": "Device",
      "connector": "Up",
      "length": 1
    },
    {
      "component": "DeviceStatusController",
      "interface": "Peripherals",
      "connector": "Down",
      "length": 1
    },
    {
      "component": "UiController",
      "interface": "Device",
      "connector": "Down",
      "length": 1
    },
    {
      "component": "UiController",
      "interface": "Display",
      "connector": "Left",
      "length": 1
    },
    {
      "component": "UiController",
      "interface": "Content",
      "connector": "Down",
      "length": 1
    }
  ],
  "handlerConnections": [
    {
      "handler": "WirelessTransceiver",
      "interface": "Connection",
      "connector": "Down",
      "length": 1
    },
    {
      "handler": "PeripheralHandler",
      "interface": "Peripherals",
      "connector": "Left",
      "length": 1
    },
    {
      "handler": "DisplayHandler",
      "interface": "Display",
      "connector": "Right",
      "length": 1
    },
    {
      "handler": "WirelessTransceiver",
      "interface": "Content",
      "connector": "Left",
      "length": 1
    }
  ],
  "handlerCalls": [
    {
      "component": "ServerConnector",
      "handler": "WirelessTransceiver",
      "connector": "Left",
      "length": 1
    },
    {
      "component": "DeviceStatusController",
      "handler": "PeripheralHandler",
      "connector": "Left",
      "length": 1
    },
    {
      "component": "UiController",
      "handler": "DisplayHandler",
      "connector": "Up",
      "length": 1
    },
    {
      "component": "UiController",
      "handler": "WirelessTransceiver",
      "connector": "Right",
      "length": 1
    }
  ]
}

Error handling — and the finished state machine

One more thing: a device-wide error should halt everything. Whenever Device:ErrorInd arrives, regardless of current state, we cancel both timers, disable the radio, and return to Uninitialized.

This is the *-source pattern: a transition with source: "*" fires from any state. It's the FCM mechanism for global error handling without having to draw an arrow from every state to the error state.

We also add an ANY-message wildcard self-transition on Uninitialized: {interface: "*", name: "*"} matches any otherwise- unhandled message in that state and silently absorbs it. This protects the component from messages that arrive before it's ready (early radio chatter, peripheral activity from the previous run, etc.) — without an explicit handler each such message would be dropped by the dispatcher with a runtime warning, which is exactly the noise the wildcard suppresses. (We come back to this dispatch model in detail in Stage 19.)

That completes ServerConnector. Three more transitions added, eleven in total. Every state has an outgoing path for every message it could plausibly receive, and the error path catches anything that slips through.

ServerConnector state machine
Server ConnectorUninitializedAdvertisingConnectingConnected*Is ServerWhitelisted?Device:ReadyIndConnection:ConnectReqNoYesConnection:ConnectedIndTimeoutConnection:DisconnectedIndTimeoutDevice:ErrorInd*
JSON snapshot at this stage
{
  "format": "stadiae-v4",
  "interfaces": [
    {
      "name": "Device",
      "description": "Device status messages."
    },
    {
      "name": "Connection",
      "description": "Wireless transceiver messages for connection handling."
    },
    {
      "name": "Peripherals",
      "description": "Status indications from the peripherals."
    },
    {
      "name": "Content",
      "description": "Exchange of interaction with the server."
    },
    {
      "name": "Display",
      "description": "Display status and control messages."
    }
  ],
  "messages": [
    {
      "interface": "Device",
      "name": "ReadyInd",
      "description": "All peripherals are ready and the device can be initialized."
    },
    {
      "interface": "Device",
      "name": "ErrorInd",
      "description": "An error occurred and the device is no longer operational."
    },
    {
      "interface": "Connection",
      "name": "ConnectReq",
      "description": "A server is requesting to connect.",
      "parameters": [
        {
          "name": "serverId",
          "type": "uint",
          "description": "ID of the server that wants to connect."
        }
      ]
    },
    {
      "interface": "Connection",
      "name": "ConnectedInd",
      "description": "Connected to the server."
    },
    {
      "interface": "Connection",
      "name": "DisconnectedInd",
      "description": "The server is no longer connected."
    },
    {
      "interface": "Peripherals",
      "name": "ReadyInd",
      "description": "Indication that a peripheral is ready."
    },
    {
      "interface": "Peripherals",
      "name": "FailureInd",
      "description": "A peripheral has failed."
    },
    {
      "interface": "Content",
      "name": "ContentInd",
      "description": "A new image to display.",
      "parameters": [
        {
          "name": "image",
          "type": "byteMatrix",
          "description": "The PNG image to display."
        }
      ]
    },
    {
      "interface": "Display",
      "name": "CompletedInd",
      "description": "Indication that the display has completed its current operation \u2014 either initialisation (in which case the parameters report the panel's pixel dimensions) or rendering a `Content:ContentInd`. In both cases the display is now idle and ready for the next instruction.",
      "parameters": [
        {
          "name": "width",
          "type": "uint",
          "description": "The width of the display in pixels. Populated on the initialisation occurrence."
        },
        {
          "name": "height",
          "type": "uint",
          "description": "The height of the display in pixels. Populated on the initialisation occurrence."
        }
      ]
    }
  ],
  "components": [
    {
      "name": "ServerConnector",
      "displayName": "Server\\nConnector",
      "description": "Controls and establishes the connection with the server. It waits for the device to become ready and then starts advertising its presence on the network to be discoverable by a server.\n\n# Behavior\n\nThe component is responsible for managing the lifecycle of the server connection over the wireless link.\n\n## Start-up\n\nThe component must not begin advertising until the device as a whole signals readiness. Activity received before that point is silently discarded.\n\n## Discovery\n\nWhen ready, the component makes the device discoverable by broadcasting an advertisement packet every `ADVERTISEMENT` milliseconds. This continues until a server attempts to connect or the link is lost.\n\n## Connection request\n\nA server may at any time during discovery request to connect, identifying itself with a `serverId`. The component must:\n\n- Stop advertising while the request is being evaluated.\n- If the server is not in the `whitelist` of permitted servers, reject the request via the radio so further requests from the same server are blocked, then resume discovery.\n- Otherwise, instruct the radio to establish the connection and wait at most `CONNECTION_TIMEOUT` milliseconds for confirmation.\n\nIf confirmation arrives in time, the device is considered connected to the identified server. If the timeout elapses first, the connection attempt is abandoned and discovery resumes.\n\n## Disconnection\n\nIf the radio reports that an established connection has been lost, the component must return to discovery so that a new server can find the device.\n\n## Error\n\nIf the device as a whole reports an unrecoverable error, the component must cancel any pending timers, disable the radio frontend, and return to its uninitialized condition. It must accept no further activity from the radio until the device signals readiness again.",
      "states": [
        {
          "name": "Uninitialized",
          "description": "Waiting for the device to become ready."
        },
        {
          "name": "Advertising",
          "description": "Broadcasting advertisement packets on the network to be discoverable by a server."
        },
        {
          "name": "Connecting",
          "description": "Establishing a connection to the server."
        },
        {
          "name": "Connected",
          "description": "Connected to the server."
        }
      ],
      "choicePoints": [
        {
          "name": "WhiteListed",
          "question": "Is Server\\nWhitelisted?",
          "description": "Check whether the server that tries to connect is in the `whitelist` of permitted servers."
        }
      ],
      "stateVariables": [
        {
          "name": "serverId",
          "type": "uint",
          "description": "The ID of the connected or connecting server."
        },
        {
          "name": "whitelist",
          "type": "uintArray",
          "description": "The IDs of the servers that are permitted to connect."
        },
        {
          "name": "connectTimerId",
          "type": "TimerID",
          "description": "Handle of the connection-attempt timer."
        },
        {
          "name": "advertiseTimerId",
          "type": "TimerID",
          "description": "Handle of the advertisement-interval timer."
        }
      ],
      "constants": [
        {
          "name": "ADVERTISEMENT",
          "value": "1000",
          "description": "Advertisement interval in ms."
        },
        {
          "name": "CONNECTION_TIMEOUT",
          "value": "500",
          "description": "Timeout waiting for server to connect in ms."
        }
      ],
      "localFunctions": [
        {
          "name": "doAdvertise",
          "description": "Send an advertisement packet and (re)start the advertisement-interval timer.",
          "steps": "Call `WirelessTransceiver:sendAdvertisementPacket()`.\nCall `TimerHandler:setTimeout`(`ADVERTISEMENT`, `advertiseTimerId`)."
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "Uninitialized",
          "messages": [],
          "connector": "Down",
          "length": 1
        },
        {
          "source": "Uninitialized",
          "target": "Advertising",
          "messages": [
            {
              "interface": "Device",
              "name": "ReadyInd",
              "action": "Call `doAdvertise()`."
            }
          ],
          "connector": "Down",
          "length": 1
        },
        {
          "source": "Advertising",
          "target": "CP_WhiteListed",
          "messages": [
            {
              "interface": "Connection",
              "name": "ConnectReq",
              "action": "Call `TimerHandler:cancelTimeout`(`advertiseTimerId`).\nSet `serverId` to `ConnectReq:serverId`."
            }
          ],
          "connector": "Left",
          "length": 1
        },
        {
          "source": "CP_WhiteListed",
          "target": "Advertising",
          "messages": [
            {
              "interface": "Logical",
              "name": "No",
              "action": "Call `WirelessTransceiver:rejectServer`(`serverId`).\nCall `doAdvertise()`."
            }
          ],
          "connector": "Right",
          "length": 1
        },
        {
          "source": "CP_WhiteListed",
          "target": "Connecting",
          "messages": [
            {
              "interface": "Logical",
              "name": "Yes",
              "action": "Call `WirelessTransceiver:connectServer`(`serverId`).\nCall `TimerHandler:setTimeout`(`CONNECTION_TIMEOUT`, `connectTimerId`)."
            }
          ],
          "connector": "Down",
          "length": 1
        },
        {
          "source": "Connecting",
          "target": "Connected",
          "messages": [
            {
              "interface": "Connection",
              "name": "ConnectedInd",
              "action": "Call `TimerHandler:cancelTimeout`(`connectTimerId`)."
            }
          ],
          "connector": "Right",
          "length": 1
        },
        {
          "source": "Connecting",
          "target": "Advertising",
          "messages": [
            {
              "interface": "Timer",
              "name": "Timeout",
              "action": "Call `doAdvertise()`."
            }
          ],
          "connector": "Up",
          "length": 1
        },
        {
          "source": "Connected",
          "target": "Advertising",
          "messages": [
            {
              "interface": "Connection",
              "name": "DisconnectedInd",
              "action": "Call `doAdvertise()`."
            }
          ],
          "connector": "Up",
          "length": 1
        },
        {
          "source": "Advertising",
          "target": "Advertising",
          "messages": [
            {
              "interface": "Timer",
              "name": "Timeout",
              "action": "Call `doAdvertise()`."
            }
          ],
          "connector": "Right",
          "length": 1
        },
        {
          "source": "*",
          "target": "Uninitialized",
          "messages": [
            {
              "interface": "Device",
              "name": "ErrorInd",
              "action": "Call `TimerHandler:cancelTimeout`(`advertiseTimerId`).\nCall `TimerHandler:cancelTimeout`(`connectTimerId`).\nCall `WirelessTransceiver:disable()`."
            }
          ],
          "connector": "Right",
          "length": 1
        },
        {
          "source": "Uninitialized",
          "target": "Uninitialized",
          "messages": [
            {
              "interface": "*",
              "name": "*"
            }
          ],
          "connector": "Right",
          "length": 1
        }
      ]
    },
    {
      "name": "DeviceStatusController",
      "displayName": "Device Status\\nController",
      "states": [
        {
          "name": "AwaitingPeripherals",
          "displayName": "Awaiting\\nPeripherals"
        },
        {
          "name": "Operational"
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "AwaitingPeripherals",
          "messages": [],
          "connector": "Down",
          "length": 1
        }
      ]
    },
    {
      "name": "UiController",
      "displayName": "UI\\nController",
      "states": [
        {
          "name": "Uninitialized"
        },
        {
          "name": "InitializingDisplay",
          "displayName": "Initializing\\nDisplay"
        },
        {
          "name": "Ready"
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "Uninitialized",
          "messages": [],
          "connector": "Right",
          "length": 1
        }
      ]
    }
  ],
  "deviceDisplayName": "Wireless Networked Display (WND)",
  "handlers": [
    {
      "name": "WirelessTransceiver",
      "displayName": "Wireless\\nTransceiver",
      "description": "This handler takes care of receiving and transmitting data packets on the radio frontend by interfacing with the radio hardware peripheral. By looking at the message-type of incoming messages, indications are forwarded either to the `Connection` or `Content` interface.\n\nThe wireless transceiver is one of the device's peripherals: it is brought up by `PeripheralHandler:start()` together with the display, and it signals its readiness and any failure on the `Peripherals` interface so that `DeviceStatusController` can include it in its readiness count. Once readiness has been signalled, the radio is ready to advertise and accept connection requests.",
      "functions": [
        {
          "name": "sendAdvertisementPacket",
          "description": "Send a single advertisement packet to become discoverable."
        },
        {
          "name": "connectServer",
          "description": "Connect to the indicated server.",
          "parameters": [
            {
              "name": "serverId",
              "type": "uint",
              "description": "The ID of the server to connect to."
            }
          ]
        },
        {
          "name": "rejectServer",
          "description": "Block further requests from the indicated server.",
          "parameters": [
            {
              "name": "serverId",
              "type": "uint",
              "description": "The ID of the server that is to be rejected from now on."
            }
          ]
        },
        {
          "name": "disable",
          "description": "Disable the radio frontend."
        },
        {
          "name": "sendContentDimensions",
          "description": "Inform the server of the dimensions of the content the display supports.",
          "parameters": [
            {
              "name": "displayWidth",
              "type": "uint",
              "description": "The width of the display in pixels."
            },
            {
              "name": "displayHeight",
              "type": "uint",
              "description": "The height of the display in pixels."
            }
          ]
        }
      ]
    },
    {
      "name": "PeripheralHandler",
      "displayName": "Peripheral\\nHandler",
      "description": "Takes care of detecting, initializing and configuring hardware peripherals \u2014 the wireless transceiver and the display \u2014 e.g. by accessing low level drivers and serial communication lines. Both are brought up together by `start()` and report readiness and failures on the `Peripherals` interface.",
      "functions": [
        {
          "name": "start",
          "description": "Start initializing and configuring the peripherals."
        }
      ]
    },
    {
      "name": "DisplayHandler",
      "displayName": "Display\\nHandler",
      "description": "Handles the interface to the display to show images.",
      "functions": [
        {
          "name": "initialize",
          "description": "Initialize the display."
        },
        {
          "name": "display",
          "description": "Display the supplied image.",
          "parameters": [
            {
              "name": "image",
              "type": "byteArray",
              "description": "The PNG image to be displayed."
            }
          ]
        },
        {
          "name": "displayError",
          "description": "Display the default error message."
        }
      ]
    }
  ],
  "types": [
    {
      "name": "byteArray",
      "description": "A one dimensional array of bytes",
      "specification": "uint8[]"
    },
    {
      "name": "uint",
      "description": "Unsigned integer",
      "specification": "32-bit numerical value representing an integer value."
    },
    {
      "name": "uintArray",
      "description": "One dimensional array of uint",
      "specification": "uint[]"
    },
    {
      "name": "byteMatrix",
      "description": "2-dimensional array of bytes.",
      "specification": "byte[][]"
    }
  ],
  "connections": [
    {
      "component": "ServerConnector",
      "interface": "Device",
      "connector": "Up",
      "length": 1
    },
    {
      "component": "ServerConnector",
      "interface": "Connection",
      "connector": "Left",
      "length": 1
    },
    {
      "component": "DeviceStatusController",
      "interface": "Device",
      "connector": "Up",
      "length": 1
    },
    {
      "component": "DeviceStatusController",
      "interface": "Peripherals",
      "connector": "Down",
      "length": 1
    },
    {
      "component": "UiController",
      "interface": "Device",
      "connector": "Down",
      "length": 1
    },
    {
      "component": "UiController",
      "interface": "Display",
      "connector": "Left",
      "length": 1
    },
    {
      "component": "UiController",
      "interface": "Content",
      "connector": "Down",
      "length": 1
    }
  ],
  "handlerConnections": [
    {
      "handler": "WirelessTransceiver",
      "interface": "Connection",
      "connector": "Down",
      "length": 1
    },
    {
      "handler": "PeripheralHandler",
      "interface": "Peripherals",
      "connector": "Left",
      "length": 1
    },
    {
      "handler": "DisplayHandler",
      "interface": "Display",
      "connector": "Right",
      "length": 1
    },
    {
      "handler": "WirelessTransceiver",
      "interface": "Content",
      "connector": "Left",
      "length": 1
    }
  ],
  "handlerCalls": [
    {
      "component": "ServerConnector",
      "handler": "WirelessTransceiver",
      "connector": "Left",
      "length": 1
    },
    {
      "component": "DeviceStatusController",
      "handler": "PeripheralHandler",
      "connector": "Left",
      "length": 1
    },
    {
      "component": "UiController",
      "handler": "DisplayHandler",
      "connector": "Up",
      "length": 1
    },
    {
      "component": "UiController",
      "handler": "WirelessTransceiver",
      "connector": "Right",
      "length": 1
    }
  ]
}

Phase 6 — Device Status Controller

Counting peripherals to readiness

DeviceStatusController's job is to wait for NUM_PERIPHERALS = 2 ready signals on the Peripherals interface and then emit one Device:ReadyInd. The mechanism is count-and-compare.

  • A state variable numReady: uint counts the readiness reports.
  • Every Peripherals:ReadyInd increments the count and routes through a choice-point CP_AllReady asking "All Peripherals Operational?".
  • The choice-point evaluates numReady == NUM_PERIPHERALS:
    • No → back to AwaitingPeripherals (keep waiting).
    • Yes → on to Operational, sending Device:ReadyInd.

Note the initialize local function: when the component is created it calls PeripheralHandler:start() to kick off the bring-up sequence whose results it's now waiting for. initialize is one of two built-in local functions that every component has (the other is resendLastReceivedMessage). It exists as a slot for startup logic; the user fills it in if needed.

Notice the constant: NUM_PERIPHERALS = 2. Hard-coded to the number of peripherals on this particular device. Changing the device's hardware (adding a third peripheral, say) is a constant update — no state-machine surgery needed.

DeviceStatusController state machine
Device Status ControllerAwaitingPeripheralsOperationalAll PeripheralsOperational?Peripherals:ReadyIndNoYes
JSON snapshot at this stage
{
  "format": "stadiae-v4",
  "interfaces": [
    {
      "name": "Device",
      "description": "Device status messages."
    },
    {
      "name": "Connection",
      "description": "Wireless transceiver messages for connection handling."
    },
    {
      "name": "Peripherals",
      "description": "Status indications from the peripherals."
    },
    {
      "name": "Content",
      "description": "Exchange of interaction with the server."
    },
    {
      "name": "Display",
      "description": "Display status and control messages."
    }
  ],
  "messages": [
    {
      "interface": "Device",
      "name": "ReadyInd",
      "description": "All peripherals are ready and the device can be initialized."
    },
    {
      "interface": "Device",
      "name": "ErrorInd",
      "description": "An error occurred and the device is no longer operational."
    },
    {
      "interface": "Connection",
      "name": "ConnectReq",
      "description": "A server is requesting to connect.",
      "parameters": [
        {
          "name": "serverId",
          "type": "uint",
          "description": "ID of the server that wants to connect."
        }
      ]
    },
    {
      "interface": "Connection",
      "name": "ConnectedInd",
      "description": "Connected to the server."
    },
    {
      "interface": "Connection",
      "name": "DisconnectedInd",
      "description": "The server is no longer connected."
    },
    {
      "interface": "Peripherals",
      "name": "ReadyInd",
      "description": "Indication that a peripheral is ready."
    },
    {
      "interface": "Peripherals",
      "name": "FailureInd",
      "description": "A peripheral has failed."
    },
    {
      "interface": "Content",
      "name": "ContentInd",
      "description": "A new image to display.",
      "parameters": [
        {
          "name": "image",
          "type": "byteMatrix",
          "description": "The PNG image to display."
        }
      ]
    },
    {
      "interface": "Display",
      "name": "CompletedInd",
      "description": "Indication that the display has completed its current operation \u2014 either initialisation (in which case the parameters report the panel's pixel dimensions) or rendering a `Content:ContentInd`. In both cases the display is now idle and ready for the next instruction.",
      "parameters": [
        {
          "name": "width",
          "type": "uint",
          "description": "The width of the display in pixels. Populated on the initialisation occurrence."
        },
        {
          "name": "height",
          "type": "uint",
          "description": "The height of the display in pixels. Populated on the initialisation occurrence."
        }
      ]
    }
  ],
  "components": [
    {
      "name": "ServerConnector",
      "displayName": "Server\\nConnector",
      "description": "Controls and establishes the connection with the server. It waits for the device to become ready and then starts advertising its presence on the network to be discoverable by a server.\n\n# Behavior\n\nThe component is responsible for managing the lifecycle of the server connection over the wireless link.\n\n## Start-up\n\nThe component must not begin advertising until the device as a whole signals readiness. Activity received before that point is silently discarded.\n\n## Discovery\n\nWhen ready, the component makes the device discoverable by broadcasting an advertisement packet every `ADVERTISEMENT` milliseconds. This continues until a server attempts to connect or the link is lost.\n\n## Connection request\n\nA server may at any time during discovery request to connect, identifying itself with a `serverId`. The component must:\n\n- Stop advertising while the request is being evaluated.\n- If the server is not in the `whitelist` of permitted servers, reject the request via the radio so further requests from the same server are blocked, then resume discovery.\n- Otherwise, instruct the radio to establish the connection and wait at most `CONNECTION_TIMEOUT` milliseconds for confirmation.\n\nIf confirmation arrives in time, the device is considered connected to the identified server. If the timeout elapses first, the connection attempt is abandoned and discovery resumes.\n\n## Disconnection\n\nIf the radio reports that an established connection has been lost, the component must return to discovery so that a new server can find the device.\n\n## Error\n\nIf the device as a whole reports an unrecoverable error, the component must cancel any pending timers, disable the radio frontend, and return to its uninitialized condition. It must accept no further activity from the radio until the device signals readiness again.",
      "states": [
        {
          "name": "Uninitialized",
          "description": "Waiting for the device to become ready."
        },
        {
          "name": "Advertising",
          "description": "Broadcasting advertisement packets on the network to be discoverable by a server."
        },
        {
          "name": "Connecting",
          "description": "Establishing a connection to the server."
        },
        {
          "name": "Connected",
          "description": "Connected to the server."
        }
      ],
      "choicePoints": [
        {
          "name": "WhiteListed",
          "question": "Is Server\\nWhitelisted?",
          "description": "Check whether the server that tries to connect is in the `whitelist` of permitted servers."
        }
      ],
      "stateVariables": [
        {
          "name": "serverId",
          "type": "uint",
          "description": "The ID of the connected or connecting server."
        },
        {
          "name": "whitelist",
          "type": "uintArray",
          "description": "The IDs of the servers that are permitted to connect."
        },
        {
          "name": "connectTimerId",
          "type": "TimerID",
          "description": "Handle of the connection-attempt timer."
        },
        {
          "name": "advertiseTimerId",
          "type": "TimerID",
          "description": "Handle of the advertisement-interval timer."
        }
      ],
      "constants": [
        {
          "name": "ADVERTISEMENT",
          "value": "1000",
          "description": "Advertisement interval in ms."
        },
        {
          "name": "CONNECTION_TIMEOUT",
          "value": "500",
          "description": "Timeout waiting for server to connect in ms."
        }
      ],
      "localFunctions": [
        {
          "name": "doAdvertise",
          "description": "Send an advertisement packet and (re)start the advertisement-interval timer.",
          "steps": "Call `WirelessTransceiver:sendAdvertisementPacket()`.\nCall `TimerHandler:setTimeout`(`ADVERTISEMENT`, `advertiseTimerId`)."
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "Uninitialized",
          "messages": [],
          "connector": "Down",
          "length": 1
        },
        {
          "source": "Uninitialized",
          "target": "Advertising",
          "messages": [
            {
              "interface": "Device",
              "name": "ReadyInd",
              "action": "Call `doAdvertise()`."
            }
          ],
          "connector": "Down",
          "length": 1
        },
        {
          "source": "Advertising",
          "target": "CP_WhiteListed",
          "messages": [
            {
              "interface": "Connection",
              "name": "ConnectReq",
              "action": "Call `TimerHandler:cancelTimeout`(`advertiseTimerId`).\nSet `serverId` to `ConnectReq:serverId`."
            }
          ],
          "connector": "Left",
          "length": 1
        },
        {
          "source": "CP_WhiteListed",
          "target": "Advertising",
          "messages": [
            {
              "interface": "Logical",
              "name": "No",
              "action": "Call `WirelessTransceiver:rejectServer`(`serverId`).\nCall `doAdvertise()`."
            }
          ],
          "connector": "Right",
          "length": 1
        },
        {
          "source": "CP_WhiteListed",
          "target": "Connecting",
          "messages": [
            {
              "interface": "Logical",
              "name": "Yes",
              "action": "Call `WirelessTransceiver:connectServer`(`serverId`).\nCall `TimerHandler:setTimeout`(`CONNECTION_TIMEOUT`, `connectTimerId`)."
            }
          ],
          "connector": "Down",
          "length": 1
        },
        {
          "source": "Connecting",
          "target": "Connected",
          "messages": [
            {
              "interface": "Connection",
              "name": "ConnectedInd",
              "action": "Call `TimerHandler:cancelTimeout`(`connectTimerId`)."
            }
          ],
          "connector": "Right",
          "length": 1
        },
        {
          "source": "Connecting",
          "target": "Advertising",
          "messages": [
            {
              "interface": "Timer",
              "name": "Timeout",
              "action": "Call `doAdvertise()`."
            }
          ],
          "connector": "Up",
          "length": 1
        },
        {
          "source": "Connected",
          "target": "Advertising",
          "messages": [
            {
              "interface": "Connection",
              "name": "DisconnectedInd",
              "action": "Call `doAdvertise()`."
            }
          ],
          "connector": "Up",
          "length": 1
        },
        {
          "source": "Advertising",
          "target": "Advertising",
          "messages": [
            {
              "interface": "Timer",
              "name": "Timeout",
              "action": "Call `doAdvertise()`."
            }
          ],
          "connector": "Right",
          "length": 1
        },
        {
          "source": "*",
          "target": "Uninitialized",
          "messages": [
            {
              "interface": "Device",
              "name": "ErrorInd",
              "action": "Call `TimerHandler:cancelTimeout`(`advertiseTimerId`).\nCall `TimerHandler:cancelTimeout`(`connectTimerId`).\nCall `WirelessTransceiver:disable()`."
            }
          ],
          "connector": "Right",
          "length": 1
        },
        {
          "source": "Uninitialized",
          "target": "Uninitialized",
          "messages": [
            {
              "interface": "*",
              "name": "*"
            }
          ],
          "connector": "Right",
          "length": 1
        }
      ]
    },
    {
      "name": "DeviceStatusController",
      "displayName": "Device Status\\nController",
      "states": [
        {
          "name": "AwaitingPeripherals",
          "displayName": "Awaiting\\nPeripherals"
        },
        {
          "name": "Operational"
        }
      ],
      "choicePoints": [
        {
          "name": "AllReady",
          "question": "All Peripherals\\nOperational?"
        }
      ],
      "stateVariables": [
        {
          "name": "numReady",
          "type": "uint",
          "description": "Count of peripherals that have signalled they are operational."
        }
      ],
      "constants": [
        {
          "name": "NUM_PERIPHERALS",
          "value": "2",
          "description": "Number of expected peripherals: wireless transceiver and display."
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "AwaitingPeripherals",
          "messages": [],
          "connector": "Down",
          "length": 1
        },
        {
          "source": "AwaitingPeripherals",
          "target": "CP_AllReady",
          "messages": [
            {
              "interface": "Peripherals",
              "name": "ReadyInd",
              "action": "Increment `numReady`."
            }
          ],
          "connector": "Down",
          "length": 1
        },
        {
          "source": "CP_AllReady",
          "target": "AwaitingPeripherals",
          "messages": [
            {
              "interface": "Logical",
              "name": "No"
            }
          ],
          "connector": "Up",
          "length": 1
        },
        {
          "source": "CP_AllReady",
          "target": "Operational",
          "messages": [
            {
              "interface": "Logical",
              "name": "Yes",
              "action": "Send the `Device:ReadyInd` message."
            }
          ],
          "connector": "Right",
          "length": 1
        }
      ]
    },
    {
      "name": "UiController",
      "displayName": "UI\\nController",
      "states": [
        {
          "name": "Uninitialized"
        },
        {
          "name": "InitializingDisplay",
          "displayName": "Initializing\\nDisplay"
        },
        {
          "name": "Ready"
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "Uninitialized",
          "messages": [],
          "connector": "Right",
          "length": 1
        }
      ]
    }
  ],
  "deviceDisplayName": "Wireless Networked Display (WND)",
  "handlers": [
    {
      "name": "WirelessTransceiver",
      "displayName": "Wireless\\nTransceiver",
      "description": "This handler takes care of receiving and transmitting data packets on the radio frontend by interfacing with the radio hardware peripheral. By looking at the message-type of incoming messages, indications are forwarded either to the `Connection` or `Content` interface.\n\nThe wireless transceiver is one of the device's peripherals: it is brought up by `PeripheralHandler:start()` together with the display, and it signals its readiness and any failure on the `Peripherals` interface so that `DeviceStatusController` can include it in its readiness count. Once readiness has been signalled, the radio is ready to advertise and accept connection requests.",
      "functions": [
        {
          "name": "sendAdvertisementPacket",
          "description": "Send a single advertisement packet to become discoverable."
        },
        {
          "name": "connectServer",
          "description": "Connect to the indicated server.",
          "parameters": [
            {
              "name": "serverId",
              "type": "uint",
              "description": "The ID of the server to connect to."
            }
          ]
        },
        {
          "name": "rejectServer",
          "description": "Block further requests from the indicated server.",
          "parameters": [
            {
              "name": "serverId",
              "type": "uint",
              "description": "The ID of the server that is to be rejected from now on."
            }
          ]
        },
        {
          "name": "disable",
          "description": "Disable the radio frontend."
        },
        {
          "name": "sendContentDimensions",
          "description": "Inform the server of the dimensions of the content the display supports.",
          "parameters": [
            {
              "name": "displayWidth",
              "type": "uint",
              "description": "The width of the display in pixels."
            },
            {
              "name": "displayHeight",
              "type": "uint",
              "description": "The height of the display in pixels."
            }
          ]
        }
      ]
    },
    {
      "name": "PeripheralHandler",
      "displayName": "Peripheral\\nHandler",
      "description": "Takes care of detecting, initializing and configuring hardware peripherals \u2014 the wireless transceiver and the display \u2014 e.g. by accessing low level drivers and serial communication lines. Both are brought up together by `start()` and report readiness and failures on the `Peripherals` interface.",
      "functions": [
        {
          "name": "start",
          "description": "Start initializing and configuring the peripherals."
        }
      ]
    },
    {
      "name": "DisplayHandler",
      "displayName": "Display\\nHandler",
      "description": "Handles the interface to the display to show images.",
      "functions": [
        {
          "name": "initialize",
          "description": "Initialize the display."
        },
        {
          "name": "display",
          "description": "Display the supplied image.",
          "parameters": [
            {
              "name": "image",
              "type": "byteArray",
              "description": "The PNG image to be displayed."
            }
          ]
        },
        {
          "name": "displayError",
          "description": "Display the default error message."
        }
      ]
    }
  ],
  "types": [
    {
      "name": "byteArray",
      "description": "A one dimensional array of bytes",
      "specification": "uint8[]"
    },
    {
      "name": "uint",
      "description": "Unsigned integer",
      "specification": "32-bit numerical value representing an integer value."
    },
    {
      "name": "uintArray",
      "description": "One dimensional array of uint",
      "specification": "uint[]"
    },
    {
      "name": "byteMatrix",
      "description": "2-dimensional array of bytes.",
      "specification": "byte[][]"
    }
  ],
  "connections": [
    {
      "component": "ServerConnector",
      "interface": "Device",
      "connector": "Up",
      "length": 1
    },
    {
      "component": "ServerConnector",
      "interface": "Connection",
      "connector": "Left",
      "length": 1
    },
    {
      "component": "DeviceStatusController",
      "interface": "Device",
      "connector": "Up",
      "length": 1
    },
    {
      "component": "DeviceStatusController",
      "interface": "Peripherals",
      "connector": "Down",
      "length": 1
    },
    {
      "component": "UiController",
      "interface": "Device",
      "connector": "Down",
      "length": 1
    },
    {
      "component": "UiController",
      "interface": "Display",
      "connector": "Left",
      "length": 1
    },
    {
      "component": "UiController",
      "interface": "Content",
      "connector": "Down",
      "length": 1
    }
  ],
  "handlerConnections": [
    {
      "handler": "WirelessTransceiver",
      "interface": "Connection",
      "connector": "Down",
      "length": 1
    },
    {
      "handler": "PeripheralHandler",
      "interface": "Peripherals",
      "connector": "Left",
      "length": 1
    },
    {
      "handler": "DisplayHandler",
      "interface": "Display",
      "connector": "Right",
      "length": 1
    },
    {
      "handler": "WirelessTransceiver",
      "interface": "Content",
      "connector": "Left",
      "length": 1
    }
  ],
  "handlerCalls": [
    {
      "component": "ServerConnector",
      "handler": "WirelessTransceiver",
      "connector": "Left",
      "length": 1
    },
    {
      "component": "DeviceStatusController",
      "handler": "PeripheralHandler",
      "connector": "Left",
      "length": 1
    },
    {
      "component": "UiController",
      "handler": "DisplayHandler",
      "connector": "Up",
      "length": 1
    },
    {
      "component": "UiController",
      "handler": "WirelessTransceiver",
      "connector": "Right",
      "length": 1
    }
  ]
}

Failure handling

Adding the third state — Error — and the failure transitions.

A peripheral can fail at any time: during bring-up (in AwaitingPeripherals) or after the device is operational (in Operational). Both transition to Error, both emit Device:ErrorInd to inform the rest of the device.

Once in Error, the component is inert: an ANY-message self-transition silently absorbs every further message. No recovery; recovery requires device restart. This is the error pattern — terminal failure handling.

There's no *-source transition for the failure here, because the two specific source states (AwaitingPeripherals and Operational) are the only states where a peripheral failure can be received. The choice- point doesn't accept arbitrary messages — it only receives Logical:Yes or Logical:No. Drawing a *Error transition would over-claim; two explicit transitions communicate the real state structure.

DeviceStatusController is now complete.

DeviceStatusController state machine
Device Status ControllerAwaitingPeripheralsOperationalErrorAll PeripheralsOperational?Peripherals:ReadyIndNoYesPeripherals:FailureIndPeripherals:FailureInd*
JSON snapshot at this stage
{
  "format": "stadiae-v4",
  "interfaces": [
    {
      "name": "Device",
      "description": "Device status messages."
    },
    {
      "name": "Connection",
      "description": "Wireless transceiver messages for connection handling."
    },
    {
      "name": "Peripherals",
      "description": "Status indications from the peripherals."
    },
    {
      "name": "Content",
      "description": "Exchange of interaction with the server."
    },
    {
      "name": "Display",
      "description": "Display status and control messages."
    }
  ],
  "messages": [
    {
      "interface": "Device",
      "name": "ReadyInd",
      "description": "All peripherals are ready and the device can be initialized."
    },
    {
      "interface": "Device",
      "name": "ErrorInd",
      "description": "An error occurred and the device is no longer operational."
    },
    {
      "interface": "Connection",
      "name": "ConnectReq",
      "description": "A server is requesting to connect.",
      "parameters": [
        {
          "name": "serverId",
          "type": "uint",
          "description": "ID of the server that wants to connect."
        }
      ]
    },
    {
      "interface": "Connection",
      "name": "ConnectedInd",
      "description": "Connected to the server."
    },
    {
      "interface": "Connection",
      "name": "DisconnectedInd",
      "description": "The server is no longer connected."
    },
    {
      "interface": "Peripherals",
      "name": "ReadyInd",
      "description": "Indication that a peripheral is ready."
    },
    {
      "interface": "Peripherals",
      "name": "FailureInd",
      "description": "A peripheral has failed."
    },
    {
      "interface": "Content",
      "name": "ContentInd",
      "description": "A new image to display.",
      "parameters": [
        {
          "name": "image",
          "type": "byteMatrix",
          "description": "The PNG image to display."
        }
      ]
    },
    {
      "interface": "Display",
      "name": "CompletedInd",
      "description": "Indication that the display has completed its current operation \u2014 either initialisation (in which case the parameters report the panel's pixel dimensions) or rendering a `Content:ContentInd`. In both cases the display is now idle and ready for the next instruction.",
      "parameters": [
        {
          "name": "width",
          "type": "uint",
          "description": "The width of the display in pixels. Populated on the initialisation occurrence."
        },
        {
          "name": "height",
          "type": "uint",
          "description": "The height of the display in pixels. Populated on the initialisation occurrence."
        }
      ]
    }
  ],
  "components": [
    {
      "name": "ServerConnector",
      "displayName": "Server\\nConnector",
      "description": "Controls and establishes the connection with the server. It waits for the device to become ready and then starts advertising its presence on the network to be discoverable by a server.\n\n# Behavior\n\nThe component is responsible for managing the lifecycle of the server connection over the wireless link.\n\n## Start-up\n\nThe component must not begin advertising until the device as a whole signals readiness. Activity received before that point is silently discarded.\n\n## Discovery\n\nWhen ready, the component makes the device discoverable by broadcasting an advertisement packet every `ADVERTISEMENT` milliseconds. This continues until a server attempts to connect or the link is lost.\n\n## Connection request\n\nA server may at any time during discovery request to connect, identifying itself with a `serverId`. The component must:\n\n- Stop advertising while the request is being evaluated.\n- If the server is not in the `whitelist` of permitted servers, reject the request via the radio so further requests from the same server are blocked, then resume discovery.\n- Otherwise, instruct the radio to establish the connection and wait at most `CONNECTION_TIMEOUT` milliseconds for confirmation.\n\nIf confirmation arrives in time, the device is considered connected to the identified server. If the timeout elapses first, the connection attempt is abandoned and discovery resumes.\n\n## Disconnection\n\nIf the radio reports that an established connection has been lost, the component must return to discovery so that a new server can find the device.\n\n## Error\n\nIf the device as a whole reports an unrecoverable error, the component must cancel any pending timers, disable the radio frontend, and return to its uninitialized condition. It must accept no further activity from the radio until the device signals readiness again.",
      "states": [
        {
          "name": "Uninitialized",
          "description": "Waiting for the device to become ready."
        },
        {
          "name": "Advertising",
          "description": "Broadcasting advertisement packets on the network to be discoverable by a server."
        },
        {
          "name": "Connecting",
          "description": "Establishing a connection to the server."
        },
        {
          "name": "Connected",
          "description": "Connected to the server."
        }
      ],
      "choicePoints": [
        {
          "name": "WhiteListed",
          "question": "Is Server\\nWhitelisted?",
          "description": "Check whether the server that tries to connect is in the `whitelist` of permitted servers."
        }
      ],
      "stateVariables": [
        {
          "name": "serverId",
          "type": "uint",
          "description": "The ID of the connected or connecting server."
        },
        {
          "name": "whitelist",
          "type": "uintArray",
          "description": "The IDs of the servers that are permitted to connect."
        },
        {
          "name": "connectTimerId",
          "type": "TimerID",
          "description": "Handle of the connection-attempt timer."
        },
        {
          "name": "advertiseTimerId",
          "type": "TimerID",
          "description": "Handle of the advertisement-interval timer."
        }
      ],
      "constants": [
        {
          "name": "ADVERTISEMENT",
          "value": "1000",
          "description": "Advertisement interval in ms."
        },
        {
          "name": "CONNECTION_TIMEOUT",
          "value": "500",
          "description": "Timeout waiting for server to connect in ms."
        }
      ],
      "localFunctions": [
        {
          "name": "doAdvertise",
          "description": "Send an advertisement packet and (re)start the advertisement-interval timer.",
          "steps": "Call `WirelessTransceiver:sendAdvertisementPacket()`.\nCall `TimerHandler:setTimeout`(`ADVERTISEMENT`, `advertiseTimerId`)."
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "Uninitialized",
          "messages": [],
          "connector": "Down",
          "length": 1
        },
        {
          "source": "Uninitialized",
          "target": "Advertising",
          "messages": [
            {
              "interface": "Device",
              "name": "ReadyInd",
              "action": "Call `doAdvertise()`."
            }
          ],
          "connector": "Down",
          "length": 1
        },
        {
          "source": "Advertising",
          "target": "CP_WhiteListed",
          "messages": [
            {
              "interface": "Connection",
              "name": "ConnectReq",
              "action": "Call `TimerHandler:cancelTimeout`(`advertiseTimerId`).\nSet `serverId` to `ConnectReq:serverId`."
            }
          ],
          "connector": "Left",
          "length": 1
        },
        {
          "source": "CP_WhiteListed",
          "target": "Advertising",
          "messages": [
            {
              "interface": "Logical",
              "name": "No",
              "action": "Call `WirelessTransceiver:rejectServer`(`serverId`).\nCall `doAdvertise()`."
            }
          ],
          "connector": "Right",
          "length": 1
        },
        {
          "source": "CP_WhiteListed",
          "target": "Connecting",
          "messages": [
            {
              "interface": "Logical",
              "name": "Yes",
              "action": "Call `WirelessTransceiver:connectServer`(`serverId`).\nCall `TimerHandler:setTimeout`(`CONNECTION_TIMEOUT`, `connectTimerId`)."
            }
          ],
          "connector": "Down",
          "length": 1
        },
        {
          "source": "Connecting",
          "target": "Connected",
          "messages": [
            {
              "interface": "Connection",
              "name": "ConnectedInd",
              "action": "Call `TimerHandler:cancelTimeout`(`connectTimerId`)."
            }
          ],
          "connector": "Right",
          "length": 1
        },
        {
          "source": "Connecting",
          "target": "Advertising",
          "messages": [
            {
              "interface": "Timer",
              "name": "Timeout",
              "action": "Call `doAdvertise()`."
            }
          ],
          "connector": "Up",
          "length": 1
        },
        {
          "source": "Connected",
          "target": "Advertising",
          "messages": [
            {
              "interface": "Connection",
              "name": "DisconnectedInd",
              "action": "Call `doAdvertise()`."
            }
          ],
          "connector": "Up",
          "length": 1
        },
        {
          "source": "Advertising",
          "target": "Advertising",
          "messages": [
            {
              "interface": "Timer",
              "name": "Timeout",
              "action": "Call `doAdvertise()`."
            }
          ],
          "connector": "Right",
          "length": 1
        },
        {
          "source": "*",
          "target": "Uninitialized",
          "messages": [
            {
              "interface": "Device",
              "name": "ErrorInd",
              "action": "Call `TimerHandler:cancelTimeout`(`advertiseTimerId`).\nCall `TimerHandler:cancelTimeout`(`connectTimerId`).\nCall `WirelessTransceiver:disable()`."
            }
          ],
          "connector": "Right",
          "length": 1
        },
        {
          "source": "Uninitialized",
          "target": "Uninitialized",
          "messages": [
            {
              "interface": "*",
              "name": "*"
            }
          ],
          "connector": "Right",
          "length": 1
        }
      ]
    },
    {
      "name": "DeviceStatusController",
      "displayName": "Device Status\\nController",
      "description": "Takes care of initializing the device by waiting for all the peripherals to be operational.\n\n# Behavior\n\nThe component is responsible for determining when the device as a whole is ready and for detecting peripheral failures.\n\n## Peripherals\n\nThe device has two peripherals: the wireless transceiver and the display. Both are brought up uniformly by `PeripheralHandler:start()` and signal readiness and failure on the `Peripherals` interface.\n\n## Start-up\n\nOn construction, the component starts the peripherals and begins counting how many of them have come up. The device must not be reported as ready until `NUM_PERIPHERALS` peripherals have signalled they are operational.\n\nEach peripheral that becomes operational signals so; the component counts the signals and re-evaluates whether the full set has been received. As soon as the count reaches `NUM_PERIPHERALS`, the component announces device-wide readiness by emitting `Device:ReadyInd`. From that point on the device is considered operational.\n\n## Failure\n\nIf any peripheral reports failure, whether before or after the device has reached the operational condition, the component announces device-wide failure by emitting `Device:ErrorInd` and enters a terminal failed condition. While failed, the component is inert: it makes no further announcements, and recovery requires reconstruction of the component.",
      "states": [
        {
          "name": "AwaitingPeripherals",
          "displayName": "Awaiting\\nPeripherals",
          "description": "Waiting for all the peripherals to become ready."
        },
        {
          "name": "Operational",
          "description": "All peripherals are operational."
        },
        {
          "name": "Error",
          "description": "A peripheral failed, either during start-up or after the device became operational."
        }
      ],
      "choicePoints": [
        {
          "name": "AllReady",
          "question": "All Peripherals\\nOperational?",
          "description": "Check whether `numReady` equals `NUM_PERIPHERALS`."
        }
      ],
      "stateVariables": [
        {
          "name": "numReady",
          "type": "uint",
          "description": "Count of peripherals that have signalled they are operational."
        }
      ],
      "constants": [
        {
          "name": "NUM_PERIPHERALS",
          "value": "2",
          "description": "Number of expected peripherals: wireless transceiver and display."
        }
      ],
      "localFunctions": [
        {
          "name": "initialize",
          "steps": "Call `PeripheralHandler:start()`."
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "AwaitingPeripherals",
          "messages": [],
          "connector": "Down",
          "length": 1
        },
        {
          "source": "AwaitingPeripherals",
          "target": "CP_AllReady",
          "messages": [
            {
              "interface": "Peripherals",
              "name": "ReadyInd",
              "action": "Increment `numReady`."
            }
          ],
          "connector": "Down",
          "length": 1
        },
        {
          "source": "CP_AllReady",
          "target": "AwaitingPeripherals",
          "messages": [
            {
              "interface": "Logical",
              "name": "No"
            }
          ],
          "connector": "Up",
          "length": 1
        },
        {
          "source": "CP_AllReady",
          "target": "Operational",
          "messages": [
            {
              "interface": "Logical",
              "name": "Yes",
              "action": "Send the `Device:ReadyInd` message."
            }
          ],
          "connector": "Right",
          "length": 1
        },
        {
          "source": "AwaitingPeripherals",
          "target": "Error",
          "messages": [
            {
              "interface": "Peripherals",
              "name": "FailureInd",
              "action": "Send the `Device:ErrorInd` message."
            }
          ],
          "connector": "Right",
          "length": 1
        },
        {
          "source": "Operational",
          "target": "Error",
          "messages": [
            {
              "interface": "Peripherals",
              "name": "FailureInd",
              "action": "Send the `Device:ErrorInd` message."
            }
          ],
          "connector": "Up",
          "length": 1
        },
        {
          "source": "Error",
          "target": "Error",
          "messages": [
            {
              "interface": "*",
              "name": "*"
            }
          ],
          "connector": "Right",
          "length": 1
        }
      ]
    },
    {
      "name": "UiController",
      "displayName": "UI\\nController",
      "states": [
        {
          "name": "Uninitialized"
        },
        {
          "name": "InitializingDisplay",
          "displayName": "Initializing\\nDisplay"
        },
        {
          "name": "Ready"
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "Uninitialized",
          "messages": [],
          "connector": "Right",
          "length": 1
        }
      ]
    }
  ],
  "deviceDisplayName": "Wireless Networked Display (WND)",
  "handlers": [
    {
      "name": "WirelessTransceiver",
      "displayName": "Wireless\\nTransceiver",
      "description": "This handler takes care of receiving and transmitting data packets on the radio frontend by interfacing with the radio hardware peripheral. By looking at the message-type of incoming messages, indications are forwarded either to the `Connection` or `Content` interface.\n\nThe wireless transceiver is one of the device's peripherals: it is brought up by `PeripheralHandler:start()` together with the display, and it signals its readiness and any failure on the `Peripherals` interface so that `DeviceStatusController` can include it in its readiness count. Once readiness has been signalled, the radio is ready to advertise and accept connection requests.",
      "functions": [
        {
          "name": "sendAdvertisementPacket",
          "description": "Send a single advertisement packet to become discoverable."
        },
        {
          "name": "connectServer",
          "description": "Connect to the indicated server.",
          "parameters": [
            {
              "name": "serverId",
              "type": "uint",
              "description": "The ID of the server to connect to."
            }
          ]
        },
        {
          "name": "rejectServer",
          "description": "Block further requests from the indicated server.",
          "parameters": [
            {
              "name": "serverId",
              "type": "uint",
              "description": "The ID of the server that is to be rejected from now on."
            }
          ]
        },
        {
          "name": "disable",
          "description": "Disable the radio frontend."
        },
        {
          "name": "sendContentDimensions",
          "description": "Inform the server of the dimensions of the content the display supports.",
          "parameters": [
            {
              "name": "displayWidth",
              "type": "uint",
              "description": "The width of the display in pixels."
            },
            {
              "name": "displayHeight",
              "type": "uint",
              "description": "The height of the display in pixels."
            }
          ]
        }
      ]
    },
    {
      "name": "PeripheralHandler",
      "displayName": "Peripheral\\nHandler",
      "description": "Takes care of detecting, initializing and configuring hardware peripherals \u2014 the wireless transceiver and the display \u2014 e.g. by accessing low level drivers and serial communication lines. Both are brought up together by `start()` and report readiness and failures on the `Peripherals` interface.",
      "functions": [
        {
          "name": "start",
          "description": "Start initializing and configuring the peripherals."
        }
      ]
    },
    {
      "name": "DisplayHandler",
      "displayName": "Display\\nHandler",
      "description": "Handles the interface to the display to show images.",
      "functions": [
        {
          "name": "initialize",
          "description": "Initialize the display."
        },
        {
          "name": "display",
          "description": "Display the supplied image.",
          "parameters": [
            {
              "name": "image",
              "type": "byteArray",
              "description": "The PNG image to be displayed."
            }
          ]
        },
        {
          "name": "displayError",
          "description": "Display the default error message."
        }
      ]
    }
  ],
  "types": [
    {
      "name": "byteArray",
      "description": "A one dimensional array of bytes",
      "specification": "uint8[]"
    },
    {
      "name": "uint",
      "description": "Unsigned integer",
      "specification": "32-bit numerical value representing an integer value."
    },
    {
      "name": "uintArray",
      "description": "One dimensional array of uint",
      "specification": "uint[]"
    },
    {
      "name": "byteMatrix",
      "description": "2-dimensional array of bytes.",
      "specification": "byte[][]"
    }
  ],
  "connections": [
    {
      "component": "ServerConnector",
      "interface": "Device",
      "connector": "Up",
      "length": 1
    },
    {
      "component": "ServerConnector",
      "interface": "Connection",
      "connector": "Left",
      "length": 1
    },
    {
      "component": "DeviceStatusController",
      "interface": "Device",
      "connector": "Up",
      "length": 1
    },
    {
      "component": "DeviceStatusController",
      "interface": "Peripherals",
      "connector": "Down",
      "length": 1
    },
    {
      "component": "UiController",
      "interface": "Device",
      "connector": "Down",
      "length": 1
    },
    {
      "component": "UiController",
      "interface": "Display",
      "connector": "Left",
      "length": 1
    },
    {
      "component": "UiController",
      "interface": "Content",
      "connector": "Down",
      "length": 1
    }
  ],
  "handlerConnections": [
    {
      "handler": "WirelessTransceiver",
      "interface": "Connection",
      "connector": "Down",
      "length": 1
    },
    {
      "handler": "PeripheralHandler",
      "interface": "Peripherals",
      "connector": "Left",
      "length": 1
    },
    {
      "handler": "DisplayHandler",
      "interface": "Display",
      "connector": "Right",
      "length": 1
    },
    {
      "handler": "WirelessTransceiver",
      "interface": "Content",
      "connector": "Left",
      "length": 1
    }
  ],
  "handlerCalls": [
    {
      "component": "ServerConnector",
      "handler": "WirelessTransceiver",
      "connector": "Left",
      "length": 1
    },
    {
      "component": "DeviceStatusController",
      "handler": "PeripheralHandler",
      "connector": "Left",
      "length": 1
    },
    {
      "component": "UiController",
      "handler": "DisplayHandler",
      "connector": "Up",
      "length": 1
    },
    {
      "component": "UiController",
      "handler": "WirelessTransceiver",
      "connector": "Right",
      "length": 1
    }
  ]
}

Phase 7 — UI Controller

Initialise the display, report dimensions

UiController's startup sequence has three phases:

  1. Wait for Device:ReadyInd (the device is up).
  2. Initialise the display via DisplayHandler:initialize() and wait for the panel to report back.
  3. Forward the display's pixel dimensions to the server via WirelessTransceiver:sendContentDimensions(...).

The interesting bit is step 2. The display reports completion via Display:CompletedInd, which on this initialisation occurrence carries the panel's width and height as parameters. The transition's action reads those parameters directly: sendContentDimensions(CompletedInd:width, CompletedInd:height).

The same Display:CompletedInd message will be used later for a different purpose — render completion — but with width and height ignored. This dual use is what was mentioned in Phase 4. The display handler emits the same message on both occasions; the component routes it differently based on the current state.

For now, three states and three transitions: START → Uninitialized → InitializingDisplay → Ready.

UiController state machine
UI ControllerUninitializedInitializingDisplayReadyDevice:ReadyIndDisplay:CompletedInd
JSON snapshot at this stage
{
  "format": "stadiae-v4",
  "interfaces": [
    {
      "name": "Device",
      "description": "Device status messages."
    },
    {
      "name": "Connection",
      "description": "Wireless transceiver messages for connection handling."
    },
    {
      "name": "Peripherals",
      "description": "Status indications from the peripherals."
    },
    {
      "name": "Content",
      "description": "Exchange of interaction with the server."
    },
    {
      "name": "Display",
      "description": "Display status and control messages."
    }
  ],
  "messages": [
    {
      "interface": "Device",
      "name": "ReadyInd",
      "description": "All peripherals are ready and the device can be initialized."
    },
    {
      "interface": "Device",
      "name": "ErrorInd",
      "description": "An error occurred and the device is no longer operational."
    },
    {
      "interface": "Connection",
      "name": "ConnectReq",
      "description": "A server is requesting to connect.",
      "parameters": [
        {
          "name": "serverId",
          "type": "uint",
          "description": "ID of the server that wants to connect."
        }
      ]
    },
    {
      "interface": "Connection",
      "name": "ConnectedInd",
      "description": "Connected to the server."
    },
    {
      "interface": "Connection",
      "name": "DisconnectedInd",
      "description": "The server is no longer connected."
    },
    {
      "interface": "Peripherals",
      "name": "ReadyInd",
      "description": "Indication that a peripheral is ready."
    },
    {
      "interface": "Peripherals",
      "name": "FailureInd",
      "description": "A peripheral has failed."
    },
    {
      "interface": "Content",
      "name": "ContentInd",
      "description": "A new image to display.",
      "parameters": [
        {
          "name": "image",
          "type": "byteMatrix",
          "description": "The PNG image to display."
        }
      ]
    },
    {
      "interface": "Display",
      "name": "CompletedInd",
      "description": "Indication that the display has completed its current operation \u2014 either initialisation (in which case the parameters report the panel's pixel dimensions) or rendering a `Content:ContentInd`. In both cases the display is now idle and ready for the next instruction.",
      "parameters": [
        {
          "name": "width",
          "type": "uint",
          "description": "The width of the display in pixels. Populated on the initialisation occurrence."
        },
        {
          "name": "height",
          "type": "uint",
          "description": "The height of the display in pixels. Populated on the initialisation occurrence."
        }
      ]
    }
  ],
  "components": [
    {
      "name": "ServerConnector",
      "displayName": "Server\\nConnector",
      "description": "Controls and establishes the connection with the server. It waits for the device to become ready and then starts advertising its presence on the network to be discoverable by a server.\n\n# Behavior\n\nThe component is responsible for managing the lifecycle of the server connection over the wireless link.\n\n## Start-up\n\nThe component must not begin advertising until the device as a whole signals readiness. Activity received before that point is silently discarded.\n\n## Discovery\n\nWhen ready, the component makes the device discoverable by broadcasting an advertisement packet every `ADVERTISEMENT` milliseconds. This continues until a server attempts to connect or the link is lost.\n\n## Connection request\n\nA server may at any time during discovery request to connect, identifying itself with a `serverId`. The component must:\n\n- Stop advertising while the request is being evaluated.\n- If the server is not in the `whitelist` of permitted servers, reject the request via the radio so further requests from the same server are blocked, then resume discovery.\n- Otherwise, instruct the radio to establish the connection and wait at most `CONNECTION_TIMEOUT` milliseconds for confirmation.\n\nIf confirmation arrives in time, the device is considered connected to the identified server. If the timeout elapses first, the connection attempt is abandoned and discovery resumes.\n\n## Disconnection\n\nIf the radio reports that an established connection has been lost, the component must return to discovery so that a new server can find the device.\n\n## Error\n\nIf the device as a whole reports an unrecoverable error, the component must cancel any pending timers, disable the radio frontend, and return to its uninitialized condition. It must accept no further activity from the radio until the device signals readiness again.",
      "states": [
        {
          "name": "Uninitialized",
          "description": "Waiting for the device to become ready."
        },
        {
          "name": "Advertising",
          "description": "Broadcasting advertisement packets on the network to be discoverable by a server."
        },
        {
          "name": "Connecting",
          "description": "Establishing a connection to the server."
        },
        {
          "name": "Connected",
          "description": "Connected to the server."
        }
      ],
      "choicePoints": [
        {
          "name": "WhiteListed",
          "question": "Is Server\\nWhitelisted?",
          "description": "Check whether the server that tries to connect is in the `whitelist` of permitted servers."
        }
      ],
      "stateVariables": [
        {
          "name": "serverId",
          "type": "uint",
          "description": "The ID of the connected or connecting server."
        },
        {
          "name": "whitelist",
          "type": "uintArray",
          "description": "The IDs of the servers that are permitted to connect."
        },
        {
          "name": "connectTimerId",
          "type": "TimerID",
          "description": "Handle of the connection-attempt timer."
        },
        {
          "name": "advertiseTimerId",
          "type": "TimerID",
          "description": "Handle of the advertisement-interval timer."
        }
      ],
      "constants": [
        {
          "name": "ADVERTISEMENT",
          "value": "1000",
          "description": "Advertisement interval in ms."
        },
        {
          "name": "CONNECTION_TIMEOUT",
          "value": "500",
          "description": "Timeout waiting for server to connect in ms."
        }
      ],
      "localFunctions": [
        {
          "name": "doAdvertise",
          "description": "Send an advertisement packet and (re)start the advertisement-interval timer.",
          "steps": "Call `WirelessTransceiver:sendAdvertisementPacket()`.\nCall `TimerHandler:setTimeout`(`ADVERTISEMENT`, `advertiseTimerId`)."
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "Uninitialized",
          "messages": [],
          "connector": "Down",
          "length": 1
        },
        {
          "source": "Uninitialized",
          "target": "Advertising",
          "messages": [
            {
              "interface": "Device",
              "name": "ReadyInd",
              "action": "Call `doAdvertise()`."
            }
          ],
          "connector": "Down",
          "length": 1
        },
        {
          "source": "Advertising",
          "target": "CP_WhiteListed",
          "messages": [
            {
              "interface": "Connection",
              "name": "ConnectReq",
              "action": "Call `TimerHandler:cancelTimeout`(`advertiseTimerId`).\nSet `serverId` to `ConnectReq:serverId`."
            }
          ],
          "connector": "Left",
          "length": 1
        },
        {
          "source": "CP_WhiteListed",
          "target": "Advertising",
          "messages": [
            {
              "interface": "Logical",
              "name": "No",
              "action": "Call `WirelessTransceiver:rejectServer`(`serverId`).\nCall `doAdvertise()`."
            }
          ],
          "connector": "Right",
          "length": 1
        },
        {
          "source": "CP_WhiteListed",
          "target": "Connecting",
          "messages": [
            {
              "interface": "Logical",
              "name": "Yes",
              "action": "Call `WirelessTransceiver:connectServer`(`serverId`).\nCall `TimerHandler:setTimeout`(`CONNECTION_TIMEOUT`, `connectTimerId`)."
            }
          ],
          "connector": "Down",
          "length": 1
        },
        {
          "source": "Connecting",
          "target": "Connected",
          "messages": [
            {
              "interface": "Connection",
              "name": "ConnectedInd",
              "action": "Call `TimerHandler:cancelTimeout`(`connectTimerId`)."
            }
          ],
          "connector": "Right",
          "length": 1
        },
        {
          "source": "Connecting",
          "target": "Advertising",
          "messages": [
            {
              "interface": "Timer",
              "name": "Timeout",
              "action": "Call `doAdvertise()`."
            }
          ],
          "connector": "Up",
          "length": 1
        },
        {
          "source": "Connected",
          "target": "Advertising",
          "messages": [
            {
              "interface": "Connection",
              "name": "DisconnectedInd",
              "action": "Call `doAdvertise()`."
            }
          ],
          "connector": "Up",
          "length": 1
        },
        {
          "source": "Advertising",
          "target": "Advertising",
          "messages": [
            {
              "interface": "Timer",
              "name": "Timeout",
              "action": "Call `doAdvertise()`."
            }
          ],
          "connector": "Right",
          "length": 1
        },
        {
          "source": "*",
          "target": "Uninitialized",
          "messages": [
            {
              "interface": "Device",
              "name": "ErrorInd",
              "action": "Call `TimerHandler:cancelTimeout`(`advertiseTimerId`).\nCall `TimerHandler:cancelTimeout`(`connectTimerId`).\nCall `WirelessTransceiver:disable()`."
            }
          ],
          "connector": "Right",
          "length": 1
        },
        {
          "source": "Uninitialized",
          "target": "Uninitialized",
          "messages": [
            {
              "interface": "*",
              "name": "*"
            }
          ],
          "connector": "Right",
          "length": 1
        }
      ]
    },
    {
      "name": "DeviceStatusController",
      "displayName": "Device Status\\nController",
      "description": "Takes care of initializing the device by waiting for all the peripherals to be operational.\n\n# Behavior\n\nThe component is responsible for determining when the device as a whole is ready and for detecting peripheral failures.\n\n## Peripherals\n\nThe device has two peripherals: the wireless transceiver and the display. Both are brought up uniformly by `PeripheralHandler:start()` and signal readiness and failure on the `Peripherals` interface.\n\n## Start-up\n\nOn construction, the component starts the peripherals and begins counting how many of them have come up. The device must not be reported as ready until `NUM_PERIPHERALS` peripherals have signalled they are operational.\n\nEach peripheral that becomes operational signals so; the component counts the signals and re-evaluates whether the full set has been received. As soon as the count reaches `NUM_PERIPHERALS`, the component announces device-wide readiness by emitting `Device:ReadyInd`. From that point on the device is considered operational.\n\n## Failure\n\nIf any peripheral reports failure, whether before or after the device has reached the operational condition, the component announces device-wide failure by emitting `Device:ErrorInd` and enters a terminal failed condition. While failed, the component is inert: it makes no further announcements, and recovery requires reconstruction of the component.",
      "states": [
        {
          "name": "AwaitingPeripherals",
          "displayName": "Awaiting\\nPeripherals",
          "description": "Waiting for all the peripherals to become ready."
        },
        {
          "name": "Operational",
          "description": "All peripherals are operational."
        },
        {
          "name": "Error",
          "description": "A peripheral failed, either during start-up or after the device became operational."
        }
      ],
      "choicePoints": [
        {
          "name": "AllReady",
          "question": "All Peripherals\\nOperational?",
          "description": "Check whether `numReady` equals `NUM_PERIPHERALS`."
        }
      ],
      "stateVariables": [
        {
          "name": "numReady",
          "type": "uint",
          "description": "Count of peripherals that have signalled they are operational."
        }
      ],
      "constants": [
        {
          "name": "NUM_PERIPHERALS",
          "value": "2",
          "description": "Number of expected peripherals: wireless transceiver and display."
        }
      ],
      "localFunctions": [
        {
          "name": "initialize",
          "steps": "Call `PeripheralHandler:start()`."
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "AwaitingPeripherals",
          "messages": [],
          "connector": "Down",
          "length": 1
        },
        {
          "source": "AwaitingPeripherals",
          "target": "CP_AllReady",
          "messages": [
            {
              "interface": "Peripherals",
              "name": "ReadyInd",
              "action": "Increment `numReady`."
            }
          ],
          "connector": "Down",
          "length": 1
        },
        {
          "source": "CP_AllReady",
          "target": "AwaitingPeripherals",
          "messages": [
            {
              "interface": "Logical",
              "name": "No"
            }
          ],
          "connector": "Up",
          "length": 1
        },
        {
          "source": "CP_AllReady",
          "target": "Operational",
          "messages": [
            {
              "interface": "Logical",
              "name": "Yes",
              "action": "Send the `Device:ReadyInd` message."
            }
          ],
          "connector": "Right",
          "length": 1
        },
        {
          "source": "AwaitingPeripherals",
          "target": "Error",
          "messages": [
            {
              "interface": "Peripherals",
              "name": "FailureInd",
              "action": "Send the `Device:ErrorInd` message."
            }
          ],
          "connector": "Right",
          "length": 1
        },
        {
          "source": "Operational",
          "target": "Error",
          "messages": [
            {
              "interface": "Peripherals",
              "name": "FailureInd",
              "action": "Send the `Device:ErrorInd` message."
            }
          ],
          "connector": "Up",
          "length": 1
        },
        {
          "source": "Error",
          "target": "Error",
          "messages": [
            {
              "interface": "*",
              "name": "*"
            }
          ],
          "connector": "Right",
          "length": 1
        }
      ]
    },
    {
      "name": "UiController",
      "displayName": "UI\\nController",
      "states": [
        {
          "name": "Uninitialized"
        },
        {
          "name": "InitializingDisplay",
          "displayName": "Initializing\\nDisplay"
        },
        {
          "name": "Ready"
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "Uninitialized",
          "messages": [],
          "connector": "Right",
          "length": 1
        },
        {
          "source": "Uninitialized",
          "target": "InitializingDisplay",
          "messages": [
            {
              "interface": "Device",
              "name": "ReadyInd",
              "action": "Call `DisplayHandler:initialize()`."
            }
          ],
          "connector": "Down",
          "length": 1
        },
        {
          "source": "InitializingDisplay",
          "target": "Ready",
          "messages": [
            {
              "interface": "Display",
              "name": "CompletedInd",
              "action": "Call `WirelessTransceiver:sendContentDimensions`(`CompletedInd:width`, `CompletedInd:height`)."
            }
          ],
          "connector": "Down",
          "length": 1
        }
      ]
    }
  ],
  "deviceDisplayName": "Wireless Networked Display (WND)",
  "handlers": [
    {
      "name": "WirelessTransceiver",
      "displayName": "Wireless\\nTransceiver",
      "description": "This handler takes care of receiving and transmitting data packets on the radio frontend by interfacing with the radio hardware peripheral. By looking at the message-type of incoming messages, indications are forwarded either to the `Connection` or `Content` interface.\n\nThe wireless transceiver is one of the device's peripherals: it is brought up by `PeripheralHandler:start()` together with the display, and it signals its readiness and any failure on the `Peripherals` interface so that `DeviceStatusController` can include it in its readiness count. Once readiness has been signalled, the radio is ready to advertise and accept connection requests.",
      "functions": [
        {
          "name": "sendAdvertisementPacket",
          "description": "Send a single advertisement packet to become discoverable."
        },
        {
          "name": "connectServer",
          "description": "Connect to the indicated server.",
          "parameters": [
            {
              "name": "serverId",
              "type": "uint",
              "description": "The ID of the server to connect to."
            }
          ]
        },
        {
          "name": "rejectServer",
          "description": "Block further requests from the indicated server.",
          "parameters": [
            {
              "name": "serverId",
              "type": "uint",
              "description": "The ID of the server that is to be rejected from now on."
            }
          ]
        },
        {
          "name": "disable",
          "description": "Disable the radio frontend."
        },
        {
          "name": "sendContentDimensions",
          "description": "Inform the server of the dimensions of the content the display supports.",
          "parameters": [
            {
              "name": "displayWidth",
              "type": "uint",
              "description": "The width of the display in pixels."
            },
            {
              "name": "displayHeight",
              "type": "uint",
              "description": "The height of the display in pixels."
            }
          ]
        }
      ]
    },
    {
      "name": "PeripheralHandler",
      "displayName": "Peripheral\\nHandler",
      "description": "Takes care of detecting, initializing and configuring hardware peripherals \u2014 the wireless transceiver and the display \u2014 e.g. by accessing low level drivers and serial communication lines. Both are brought up together by `start()` and report readiness and failures on the `Peripherals` interface.",
      "functions": [
        {
          "name": "start",
          "description": "Start initializing and configuring the peripherals."
        }
      ]
    },
    {
      "name": "DisplayHandler",
      "displayName": "Display\\nHandler",
      "description": "Handles the interface to the display to show images.",
      "functions": [
        {
          "name": "initialize",
          "description": "Initialize the display."
        },
        {
          "name": "display",
          "description": "Display the supplied image.",
          "parameters": [
            {
              "name": "image",
              "type": "byteArray",
              "description": "The PNG image to be displayed."
            }
          ]
        },
        {
          "name": "displayError",
          "description": "Display the default error message."
        }
      ]
    }
  ],
  "types": [
    {
      "name": "byteArray",
      "description": "A one dimensional array of bytes",
      "specification": "uint8[]"
    },
    {
      "name": "uint",
      "description": "Unsigned integer",
      "specification": "32-bit numerical value representing an integer value."
    },
    {
      "name": "uintArray",
      "description": "One dimensional array of uint",
      "specification": "uint[]"
    },
    {
      "name": "byteMatrix",
      "description": "2-dimensional array of bytes.",
      "specification": "byte[][]"
    }
  ],
  "connections": [
    {
      "component": "ServerConnector",
      "interface": "Device",
      "connector": "Up",
      "length": 1
    },
    {
      "component": "ServerConnector",
      "interface": "Connection",
      "connector": "Left",
      "length": 1
    },
    {
      "component": "DeviceStatusController",
      "interface": "Device",
      "connector": "Up",
      "length": 1
    },
    {
      "component": "DeviceStatusController",
      "interface": "Peripherals",
      "connector": "Down",
      "length": 1
    },
    {
      "component": "UiController",
      "interface": "Device",
      "connector": "Down",
      "length": 1
    },
    {
      "component": "UiController",
      "interface": "Display",
      "connector": "Left",
      "length": 1
    },
    {
      "component": "UiController",
      "interface": "Content",
      "connector": "Down",
      "length": 1
    }
  ],
  "handlerConnections": [
    {
      "handler": "WirelessTransceiver",
      "interface": "Connection",
      "connector": "Down",
      "length": 1
    },
    {
      "handler": "PeripheralHandler",
      "interface": "Peripherals",
      "connector": "Left",
      "length": 1
    },
    {
      "handler": "DisplayHandler",
      "interface": "Display",
      "connector": "Right",
      "length": 1
    },
    {
      "handler": "WirelessTransceiver",
      "interface": "Content",
      "connector": "Left",
      "length": 1
    }
  ],
  "handlerCalls": [
    {
      "component": "ServerConnector",
      "handler": "WirelessTransceiver",
      "connector": "Left",
      "length": 1
    },
    {
      "component": "DeviceStatusController",
      "handler": "PeripheralHandler",
      "connector": "Left",
      "length": 1
    },
    {
      "component": "UiController",
      "handler": "DisplayHandler",
      "connector": "Up",
      "length": 1
    },
    {
      "component": "UiController",
      "handler": "WirelessTransceiver",
      "connector": "Right",
      "length": 1
    }
  ]
}

The back-pressured render loop

The update mechanism is two transitions and one state. The server pushes images one at a time as Content:ContentInd messages; the device renders each one and emits Display:CompletedInd when the display reports it's done.

The transitions:

  • Ready → Updating on Content:ContentInd. Stores the new image in the image state variable, then calls DisplayHandler:display(image).
  • Updating → Ready on Display:CompletedInd. The display has finished rendering and is idle again; we are ready for the next image.

This is the second use of Display:CompletedInd — same message, different meaning. Both meanings reduce to "the display is now ready".

A note on dispatch — and on what this back-pressure actually is

It is tempting to read the diagram as "while UiController is in Updating it queues incoming ContentInd messages and processes them when it returns to Ready." That would be wrong, and it matters.

In FCM, when a message arrives at a component, the run-to-completion dispatcher pulls it out of the queue first and offers it to the active state. If the active state has no matching transition (and no *-source transition catches it, and no *:* wildcard absorbs it), the message is dropped on the floor, and the runtime emits a state-machine warning. The queue does not hold messages "until the component is ready for them"; that is not how the dispatch model works.

So what happens here if a second ContentInd arrives while we are still in Updating? It is dropped, and a warning is logged. Nothing in the diagram catches it.

That is cooperative back-pressure. The contract we are designing to is: "The server pushes one image, waits for some signal of completion, then pushes the next." The state machine itself does not enforce this contract; it relies on the server to honour it. If the server misbehaves and floods us with ContentInds, frames are silently dropped — though the runtime warnings would surface the problem during testing.

This is a perfectly reasonable design when the server is in scope and trusted, which it is here. A production-grade variant facing untrusted or flakier producers would defend against this — for example by adding an explicit Updating → Updating self-transition on Content:ContentInd that buffers the latest image (so the most recent one renders next, with older ones discarded), or by signalling overflow to the server via a new indication. Both are valid. The model as drawn says "server, please don't spam us."

The cross-cutting transitions

We also add two transitions that don't belong to the render loop itself:

  • *Uninitialized on Device:ErrorInd, calling DisplayHandler:displayError() to show the default error screen.
  • Uninitialized → Uninitialized on {*:*} to absorb early chatter (messages that arrive before the device reports ready — same pattern as in ServerConnector).

UiController is complete. Four states, seven transitions, one state variable.

UiController state machine
UI ControllerUninitializedInitializingDisplayReadyUpdating*Device:ReadyIndDisplay:CompletedIndDevice:ErrorInd*Content:ContentIndDisplay:CompletedInd
JSON snapshot at this stage
{
  "format": "stadiae-v4",
  "interfaces": [
    {
      "name": "Device",
      "description": "Device status messages."
    },
    {
      "name": "Connection",
      "description": "Wireless transceiver messages for connection handling."
    },
    {
      "name": "Peripherals",
      "description": "Status indications from the peripherals."
    },
    {
      "name": "Content",
      "description": "Exchange of interaction with the server."
    },
    {
      "name": "Display",
      "description": "Display status and control messages."
    }
  ],
  "messages": [
    {
      "interface": "Device",
      "name": "ReadyInd",
      "description": "All peripherals are ready and the device can be initialized."
    },
    {
      "interface": "Device",
      "name": "ErrorInd",
      "description": "An error occurred and the device is no longer operational."
    },
    {
      "interface": "Connection",
      "name": "ConnectReq",
      "description": "A server is requesting to connect.",
      "parameters": [
        {
          "name": "serverId",
          "type": "uint",
          "description": "ID of the server that wants to connect."
        }
      ]
    },
    {
      "interface": "Connection",
      "name": "ConnectedInd",
      "description": "Connected to the server."
    },
    {
      "interface": "Connection",
      "name": "DisconnectedInd",
      "description": "The server is no longer connected."
    },
    {
      "interface": "Peripherals",
      "name": "ReadyInd",
      "description": "Indication that a peripheral is ready."
    },
    {
      "interface": "Peripherals",
      "name": "FailureInd",
      "description": "A peripheral has failed."
    },
    {
      "interface": "Content",
      "name": "ContentInd",
      "description": "A new image to display.",
      "parameters": [
        {
          "name": "image",
          "type": "byteMatrix",
          "description": "The PNG image to display."
        }
      ]
    },
    {
      "interface": "Display",
      "name": "CompletedInd",
      "description": "Indication that the display has completed its current operation \u2014 either initialisation (in which case the parameters report the panel's pixel dimensions) or rendering a `Content:ContentInd`. In both cases the display is now idle and ready for the next instruction.",
      "parameters": [
        {
          "name": "width",
          "type": "uint",
          "description": "The width of the display in pixels. Populated on the initialisation occurrence."
        },
        {
          "name": "height",
          "type": "uint",
          "description": "The height of the display in pixels. Populated on the initialisation occurrence."
        }
      ]
    }
  ],
  "components": [
    {
      "name": "ServerConnector",
      "displayName": "Server\\nConnector",
      "description": "Controls and establishes the connection with the server. It waits for the device to become ready and then starts advertising its presence on the network to be discoverable by a server.\n\n# Behavior\n\nThe component is responsible for managing the lifecycle of the server connection over the wireless link.\n\n## Start-up\n\nThe component must not begin advertising until the device as a whole signals readiness. Activity received before that point is silently discarded.\n\n## Discovery\n\nWhen ready, the component makes the device discoverable by broadcasting an advertisement packet every `ADVERTISEMENT` milliseconds. This continues until a server attempts to connect or the link is lost.\n\n## Connection request\n\nA server may at any time during discovery request to connect, identifying itself with a `serverId`. The component must:\n\n- Stop advertising while the request is being evaluated.\n- If the server is not in the `whitelist` of permitted servers, reject the request via the radio so further requests from the same server are blocked, then resume discovery.\n- Otherwise, instruct the radio to establish the connection and wait at most `CONNECTION_TIMEOUT` milliseconds for confirmation.\n\nIf confirmation arrives in time, the device is considered connected to the identified server. If the timeout elapses first, the connection attempt is abandoned and discovery resumes.\n\n## Disconnection\n\nIf the radio reports that an established connection has been lost, the component must return to discovery so that a new server can find the device.\n\n## Error\n\nIf the device as a whole reports an unrecoverable error, the component must cancel any pending timers, disable the radio frontend, and return to its uninitialized condition. It must accept no further activity from the radio until the device signals readiness again.",
      "states": [
        {
          "name": "Uninitialized",
          "description": "Waiting for the device to become ready."
        },
        {
          "name": "Advertising",
          "description": "Broadcasting advertisement packets on the network to be discoverable by a server."
        },
        {
          "name": "Connecting",
          "description": "Establishing a connection to the server."
        },
        {
          "name": "Connected",
          "description": "Connected to the server."
        }
      ],
      "choicePoints": [
        {
          "name": "WhiteListed",
          "question": "Is Server\\nWhitelisted?",
          "description": "Check whether the server that tries to connect is in the `whitelist` of permitted servers."
        }
      ],
      "stateVariables": [
        {
          "name": "serverId",
          "type": "uint",
          "description": "The ID of the connected or connecting server."
        },
        {
          "name": "whitelist",
          "type": "uintArray",
          "description": "The IDs of the servers that are permitted to connect."
        },
        {
          "name": "connectTimerId",
          "type": "TimerID",
          "description": "Handle of the connection-attempt timer."
        },
        {
          "name": "advertiseTimerId",
          "type": "TimerID",
          "description": "Handle of the advertisement-interval timer."
        }
      ],
      "constants": [
        {
          "name": "ADVERTISEMENT",
          "value": "1000",
          "description": "Advertisement interval in ms."
        },
        {
          "name": "CONNECTION_TIMEOUT",
          "value": "500",
          "description": "Timeout waiting for server to connect in ms."
        }
      ],
      "localFunctions": [
        {
          "name": "doAdvertise",
          "description": "Send an advertisement packet and (re)start the advertisement-interval timer.",
          "steps": "Call `WirelessTransceiver:sendAdvertisementPacket()`.\nCall `TimerHandler:setTimeout`(`ADVERTISEMENT`, `advertiseTimerId`)."
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "Uninitialized",
          "messages": [],
          "connector": "Down",
          "length": 1
        },
        {
          "source": "Uninitialized",
          "target": "Advertising",
          "messages": [
            {
              "interface": "Device",
              "name": "ReadyInd",
              "action": "Call `doAdvertise()`."
            }
          ],
          "connector": "Down",
          "length": 1
        },
        {
          "source": "Advertising",
          "target": "CP_WhiteListed",
          "messages": [
            {
              "interface": "Connection",
              "name": "ConnectReq",
              "action": "Call `TimerHandler:cancelTimeout`(`advertiseTimerId`).\nSet `serverId` to `ConnectReq:serverId`."
            }
          ],
          "connector": "Left",
          "length": 1
        },
        {
          "source": "CP_WhiteListed",
          "target": "Advertising",
          "messages": [
            {
              "interface": "Logical",
              "name": "No",
              "action": "Call `WirelessTransceiver:rejectServer`(`serverId`).\nCall `doAdvertise()`."
            }
          ],
          "connector": "Right",
          "length": 1
        },
        {
          "source": "CP_WhiteListed",
          "target": "Connecting",
          "messages": [
            {
              "interface": "Logical",
              "name": "Yes",
              "action": "Call `WirelessTransceiver:connectServer`(`serverId`).\nCall `TimerHandler:setTimeout`(`CONNECTION_TIMEOUT`, `connectTimerId`)."
            }
          ],
          "connector": "Down",
          "length": 1
        },
        {
          "source": "Connecting",
          "target": "Connected",
          "messages": [
            {
              "interface": "Connection",
              "name": "ConnectedInd",
              "action": "Call `TimerHandler:cancelTimeout`(`connectTimerId`)."
            }
          ],
          "connector": "Right",
          "length": 1
        },
        {
          "source": "Connecting",
          "target": "Advertising",
          "messages": [
            {
              "interface": "Timer",
              "name": "Timeout",
              "action": "Call `doAdvertise()`."
            }
          ],
          "connector": "Up",
          "length": 1
        },
        {
          "source": "Connected",
          "target": "Advertising",
          "messages": [
            {
              "interface": "Connection",
              "name": "DisconnectedInd",
              "action": "Call `doAdvertise()`."
            }
          ],
          "connector": "Up",
          "length": 1
        },
        {
          "source": "Advertising",
          "target": "Advertising",
          "messages": [
            {
              "interface": "Timer",
              "name": "Timeout",
              "action": "Call `doAdvertise()`."
            }
          ],
          "connector": "Right",
          "length": 1
        },
        {
          "source": "*",
          "target": "Uninitialized",
          "messages": [
            {
              "interface": "Device",
              "name": "ErrorInd",
              "action": "Call `TimerHandler:cancelTimeout`(`advertiseTimerId`).\nCall `TimerHandler:cancelTimeout`(`connectTimerId`).\nCall `WirelessTransceiver:disable()`."
            }
          ],
          "connector": "Right",
          "length": 1
        },
        {
          "source": "Uninitialized",
          "target": "Uninitialized",
          "messages": [
            {
              "interface": "*",
              "name": "*"
            }
          ],
          "connector": "Right",
          "length": 1
        }
      ]
    },
    {
      "name": "DeviceStatusController",
      "displayName": "Device Status\\nController",
      "description": "Takes care of initializing the device by waiting for all the peripherals to be operational.\n\n# Behavior\n\nThe component is responsible for determining when the device as a whole is ready and for detecting peripheral failures.\n\n## Peripherals\n\nThe device has two peripherals: the wireless transceiver and the display. Both are brought up uniformly by `PeripheralHandler:start()` and signal readiness and failure on the `Peripherals` interface.\n\n## Start-up\n\nOn construction, the component starts the peripherals and begins counting how many of them have come up. The device must not be reported as ready until `NUM_PERIPHERALS` peripherals have signalled they are operational.\n\nEach peripheral that becomes operational signals so; the component counts the signals and re-evaluates whether the full set has been received. As soon as the count reaches `NUM_PERIPHERALS`, the component announces device-wide readiness by emitting `Device:ReadyInd`. From that point on the device is considered operational.\n\n## Failure\n\nIf any peripheral reports failure, whether before or after the device has reached the operational condition, the component announces device-wide failure by emitting `Device:ErrorInd` and enters a terminal failed condition. While failed, the component is inert: it makes no further announcements, and recovery requires reconstruction of the component.",
      "states": [
        {
          "name": "AwaitingPeripherals",
          "displayName": "Awaiting\\nPeripherals",
          "description": "Waiting for all the peripherals to become ready."
        },
        {
          "name": "Operational",
          "description": "All peripherals are operational."
        },
        {
          "name": "Error",
          "description": "A peripheral failed, either during start-up or after the device became operational."
        }
      ],
      "choicePoints": [
        {
          "name": "AllReady",
          "question": "All Peripherals\\nOperational?",
          "description": "Check whether `numReady` equals `NUM_PERIPHERALS`."
        }
      ],
      "stateVariables": [
        {
          "name": "numReady",
          "type": "uint",
          "description": "Count of peripherals that have signalled they are operational."
        }
      ],
      "constants": [
        {
          "name": "NUM_PERIPHERALS",
          "value": "2",
          "description": "Number of expected peripherals: wireless transceiver and display."
        }
      ],
      "localFunctions": [
        {
          "name": "initialize",
          "steps": "Call `PeripheralHandler:start()`."
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "AwaitingPeripherals",
          "messages": [],
          "connector": "Down",
          "length": 1
        },
        {
          "source": "AwaitingPeripherals",
          "target": "CP_AllReady",
          "messages": [
            {
              "interface": "Peripherals",
              "name": "ReadyInd",
              "action": "Increment `numReady`."
            }
          ],
          "connector": "Down",
          "length": 1
        },
        {
          "source": "CP_AllReady",
          "target": "AwaitingPeripherals",
          "messages": [
            {
              "interface": "Logical",
              "name": "No"
            }
          ],
          "connector": "Up",
          "length": 1
        },
        {
          "source": "CP_AllReady",
          "target": "Operational",
          "messages": [
            {
              "interface": "Logical",
              "name": "Yes",
              "action": "Send the `Device:ReadyInd` message."
            }
          ],
          "connector": "Right",
          "length": 1
        },
        {
          "source": "AwaitingPeripherals",
          "target": "Error",
          "messages": [
            {
              "interface": "Peripherals",
              "name": "FailureInd",
              "action": "Send the `Device:ErrorInd` message."
            }
          ],
          "connector": "Right",
          "length": 1
        },
        {
          "source": "Operational",
          "target": "Error",
          "messages": [
            {
              "interface": "Peripherals",
              "name": "FailureInd",
              "action": "Send the `Device:ErrorInd` message."
            }
          ],
          "connector": "Up",
          "length": 1
        },
        {
          "source": "Error",
          "target": "Error",
          "messages": [
            {
              "interface": "*",
              "name": "*"
            }
          ],
          "connector": "Right",
          "length": 1
        }
      ]
    },
    {
      "name": "UiController",
      "displayName": "UI\\nController",
      "description": "Displays content received from the server.\n\n# Behavior\n\nThe component is responsible for initializing the display and showing content pushed by the server.\n\n## Start-up\n\nThe component must not present any content until the device as a whole signals readiness. Once ready, the component initializes the display. When the display is ready, it reports its pixel dimensions. The component forwards these dimensions to the server so that the server knows the canvas size for any content it pushes. The component is then ready to receive content.\n\n## Updates\n\nThe server pushes images individually as `Content:ContentInd` messages. On each `ContentInd`, the component stores the new image and instructs the display to render it. The display reports completion via `Display:CompletedInd` when it is finished and ready to render the next image; until then the component holds in the `Updating` condition and does not accept further content. This back-pressure prevents a new render from being started while a previous one is in flight.\n\n## Error\n\nIf the device as a whole reports an unrecoverable error, the component shows the default error screen and returns to its uninitialized condition.",
      "states": [
        {
          "name": "Uninitialized",
          "description": "Waiting for the device to become ready."
        },
        {
          "name": "InitializingDisplay",
          "displayName": "Initializing\\nDisplay",
          "description": "Initializing the display."
        },
        {
          "name": "Ready",
          "description": "The display is initialized and content can be received and displayed."
        },
        {
          "name": "Updating",
          "description": "A new image is being rendered on the display."
        }
      ],
      "stateVariables": [
        {
          "name": "image",
          "type": "byteArray",
          "description": "The PNG image currently displayed."
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "Uninitialized",
          "messages": [],
          "connector": "Right",
          "length": 1
        },
        {
          "source": "Uninitialized",
          "target": "InitializingDisplay",
          "messages": [
            {
              "interface": "Device",
              "name": "ReadyInd",
              "action": "Call `DisplayHandler:initialize()`."
            }
          ],
          "connector": "Down",
          "length": 1
        },
        {
          "source": "InitializingDisplay",
          "target": "Ready",
          "messages": [
            {
              "interface": "Display",
              "name": "CompletedInd",
              "action": "Call `WirelessTransceiver:sendContentDimensions`(`CompletedInd:width`, `CompletedInd:height`)."
            }
          ],
          "connector": "Down",
          "length": 1
        },
        {
          "source": "*",
          "target": "Uninitialized",
          "messages": [
            {
              "interface": "Device",
              "name": "ErrorInd",
              "action": "Call `DisplayHandler:displayError()`."
            }
          ],
          "connector": "Down",
          "length": 1
        },
        {
          "source": "Uninitialized",
          "target": "Uninitialized",
          "messages": [
            {
              "interface": "*",
              "name": "*"
            }
          ],
          "connector": "Right",
          "length": 1
        },
        {
          "source": "Ready",
          "target": "Updating",
          "messages": [
            {
              "interface": "Content",
              "name": "ContentInd",
              "action": "Set `image` to `ContentInd:image`.\nCall `DisplayHandler:display`(`image`)."
            }
          ],
          "connector": "Left",
          "length": 1
        },
        {
          "source": "Updating",
          "target": "Ready",
          "messages": [
            {
              "interface": "Display",
              "name": "CompletedInd"
            }
          ],
          "connector": "Left",
          "length": 1
        }
      ]
    }
  ],
  "deviceDisplayName": "Wireless Networked Display (WND)",
  "handlers": [
    {
      "name": "WirelessTransceiver",
      "displayName": "Wireless\\nTransceiver",
      "description": "This handler takes care of receiving and transmitting data packets on the radio frontend by interfacing with the radio hardware peripheral. By looking at the message-type of incoming messages, indications are forwarded either to the `Connection` or `Content` interface.\n\nThe wireless transceiver is one of the device's peripherals: it is brought up by `PeripheralHandler:start()` together with the display, and it signals its readiness and any failure on the `Peripherals` interface so that `DeviceStatusController` can include it in its readiness count. Once readiness has been signalled, the radio is ready to advertise and accept connection requests.",
      "functions": [
        {
          "name": "sendAdvertisementPacket",
          "description": "Send a single advertisement packet to become discoverable."
        },
        {
          "name": "connectServer",
          "description": "Connect to the indicated server.",
          "parameters": [
            {
              "name": "serverId",
              "type": "uint",
              "description": "The ID of the server to connect to."
            }
          ]
        },
        {
          "name": "rejectServer",
          "description": "Block further requests from the indicated server.",
          "parameters": [
            {
              "name": "serverId",
              "type": "uint",
              "description": "The ID of the server that is to be rejected from now on."
            }
          ]
        },
        {
          "name": "disable",
          "description": "Disable the radio frontend."
        },
        {
          "name": "sendContentDimensions",
          "description": "Inform the server of the dimensions of the content the display supports.",
          "parameters": [
            {
              "name": "displayWidth",
              "type": "uint",
              "description": "The width of the display in pixels."
            },
            {
              "name": "displayHeight",
              "type": "uint",
              "description": "The height of the display in pixels."
            }
          ]
        }
      ]
    },
    {
      "name": "PeripheralHandler",
      "displayName": "Peripheral\\nHandler",
      "description": "Takes care of detecting, initializing and configuring hardware peripherals \u2014 the wireless transceiver and the display \u2014 e.g. by accessing low level drivers and serial communication lines. Both are brought up together by `start()` and report readiness and failures on the `Peripherals` interface.",
      "functions": [
        {
          "name": "start",
          "description": "Start initializing and configuring the peripherals."
        }
      ]
    },
    {
      "name": "DisplayHandler",
      "displayName": "Display\\nHandler",
      "description": "Handles the interface to the display to show images.",
      "functions": [
        {
          "name": "initialize",
          "description": "Initialize the display."
        },
        {
          "name": "display",
          "description": "Display the supplied image.",
          "parameters": [
            {
              "name": "image",
              "type": "byteArray",
              "description": "The PNG image to be displayed."
            }
          ]
        },
        {
          "name": "displayError",
          "description": "Display the default error message."
        }
      ]
    }
  ],
  "types": [
    {
      "name": "byteArray",
      "description": "A one dimensional array of bytes",
      "specification": "uint8[]"
    },
    {
      "name": "uint",
      "description": "Unsigned integer",
      "specification": "32-bit numerical value representing an integer value."
    },
    {
      "name": "uintArray",
      "description": "One dimensional array of uint",
      "specification": "uint[]"
    },
    {
      "name": "byteMatrix",
      "description": "2-dimensional array of bytes.",
      "specification": "byte[][]"
    }
  ],
  "connections": [
    {
      "component": "ServerConnector",
      "interface": "Device",
      "connector": "Up",
      "length": 1
    },
    {
      "component": "ServerConnector",
      "interface": "Connection",
      "connector": "Left",
      "length": 1
    },
    {
      "component": "DeviceStatusController",
      "interface": "Device",
      "connector": "Up",
      "length": 1
    },
    {
      "component": "DeviceStatusController",
      "interface": "Peripherals",
      "connector": "Down",
      "length": 1
    },
    {
      "component": "UiController",
      "interface": "Device",
      "connector": "Down",
      "length": 1
    },
    {
      "component": "UiController",
      "interface": "Display",
      "connector": "Left",
      "length": 1
    },
    {
      "component": "UiController",
      "interface": "Content",
      "connector": "Down",
      "length": 1
    }
  ],
  "handlerConnections": [
    {
      "handler": "WirelessTransceiver",
      "interface": "Connection",
      "connector": "Down",
      "length": 1
    },
    {
      "handler": "PeripheralHandler",
      "interface": "Peripherals",
      "connector": "Left",
      "length": 1
    },
    {
      "handler": "DisplayHandler",
      "interface": "Display",
      "connector": "Right",
      "length": 1
    },
    {
      "handler": "WirelessTransceiver",
      "interface": "Content",
      "connector": "Left",
      "length": 1
    }
  ],
  "handlerCalls": [
    {
      "component": "ServerConnector",
      "handler": "WirelessTransceiver",
      "connector": "Left",
      "length": 1
    },
    {
      "component": "DeviceStatusController",
      "handler": "PeripheralHandler",
      "connector": "Left",
      "length": 1
    },
    {
      "component": "UiController",
      "handler": "DisplayHandler",
      "connector": "Up",
      "length": 1
    },
    {
      "component": "UiController",
      "handler": "WirelessTransceiver",
      "connector": "Right",
      "length": 1
    }
  ]
}

Phase 8 — Polish

The finished WND — and its specification

Everything that remains is documentation: the device-wide specification text, full component and handler descriptions, parameter and state variable explanations. None of it changes the diagrams; all of it appears in the spec export.

The most important addition is deviceSpecification — the prose at the top of the file that says what this device is, what it does, and what guarantees it makes to anyone who reads its interfaces. Written in the external register: it describes the contract, not the implementation. A different state-machine decomposition could satisfy the same specification.

Two views of the same model

Up to this point the editor has been a diagram tool. But the same model also has a second face: a navigable specification document. Stadiæ generates it on demand, on the same conceptual content — components, handlers, interfaces, messages, transitions, types — but laid out for reading instead of editing.

The export includes:

  • A title page and introduction from your device's name and deviceSpecification text.
  • A device-level diagram at the top — the same picture you've been looking at in Device view.
  • One chapter per component, each containing its filtered context diagram (just that component and what it touches), description, constants, state variables, the full state-machine diagram, the transitions table, and the local functions.
  • One section per handler with its function signatures and descriptions.
  • One section per non-default interface with its messages and parameters.
  • A type definitions chapter at the end.
  • A sticky sidebar table of contents that turns the whole document into a navigable reference. Backtick references in the model become hyperlinks; clicking one jumps you to the definition.

It is available in two formats:

  • HTML — a single self-contained file. CSS inlined, diagrams as inline SVG, the stadiae-v4 JSON source embedded at the end as a recovery aid. Opens in any browser, works offline forever, can be shared as one attachment.
  • Word (.docx) — the same content as an editable Word document, for workflows where the spec needs to be marked up, reviewed, or merged with surrounding deliverables.

The two formats are produced from the same model in the same export flow; the choice is purely a deployment one.

Why this matters

The spec export is the FCM model's payoff at the documentation layer. The diagram is one view of the model; the document is another. They are derived from the same source. Edits to either one of them happen in one place — the model — and both regenerate. There is no parallel specification to keep in sync, no drift between picture and prose, no copy-paste of state lists between a slide deck and a confluence page.

This is the structural payoff of treating the model as the source of truth. Build the model once, well; everything else falls out of it.

Below are all four diagrams side by side: the device-level wiring and each component's state machine. The editor steps cover the final documentation pass plus the export itself.

Device diagram
Wireless Networked Display (WND)DeviceConnectionPeripheralsContentDisplayServerConnectorDevice StatusControllerUIControllerWirelessTransceiverDisplayHandlerPeripheralHandler
ServerConnector state machine
Server ConnectorUninitializedAdvertisingConnectingConnected*Is ServerWhitelisted?Device:ReadyIndConnection:ConnectReqNoYesConnection:ConnectedIndTimeoutConnection:DisconnectedIndTimeoutDevice:ErrorInd*
DeviceStatusController state machine
Device Status ControllerAwaitingPeripheralsOperationalErrorAll PeripheralsOperational?Peripherals:ReadyIndNoYesPeripherals:FailureIndPeripherals:FailureInd*
UiController state machine
UI ControllerUninitializedInitializingDisplayReadyUpdating*Device:ReadyIndDisplay:CompletedIndDevice:ErrorInd*Content:ContentIndDisplay:CompletedInd
JSON snapshot at this stage
{
  "format": "stadiae-v4",
  "interfaces": [
    {
      "name": "Device",
      "description": "Device status messages."
    },
    {
      "name": "Connection",
      "description": "Wireless transceiver messages for connection handling."
    },
    {
      "name": "Peripherals",
      "description": "Status indications from the peripherals."
    },
    {
      "name": "Content",
      "description": "Exchange of interaction with the server."
    },
    {
      "name": "Display",
      "description": "Display status and control messages."
    }
  ],
  "messages": [
    {
      "interface": "Device",
      "name": "ReadyInd",
      "description": "All peripherals are ready and the device can be initialized."
    },
    {
      "interface": "Device",
      "name": "ErrorInd",
      "description": "An error occurred and the device is no longer operational."
    },
    {
      "interface": "Connection",
      "name": "ConnectReq",
      "description": "A server is requesting to connect.",
      "parameters": [
        {
          "name": "serverId",
          "type": "uint",
          "description": "ID of the server that wants to connect."
        }
      ]
    },
    {
      "interface": "Connection",
      "name": "ConnectedInd",
      "description": "Connected to the server."
    },
    {
      "interface": "Connection",
      "name": "DisconnectedInd",
      "description": "The server is no longer connected."
    },
    {
      "interface": "Peripherals",
      "name": "ReadyInd",
      "description": "Indication that a peripheral is ready."
    },
    {
      "interface": "Peripherals",
      "name": "FailureInd",
      "description": "A peripheral has failed."
    },
    {
      "interface": "Content",
      "name": "ContentInd",
      "description": "A new image to display.",
      "parameters": [
        {
          "name": "image",
          "type": "byteMatrix",
          "description": "The PNG image to display."
        }
      ]
    },
    {
      "interface": "Display",
      "name": "CompletedInd",
      "description": "Indication that the display has completed its current operation \u2014 either initialisation (in which case the parameters report the panel's pixel dimensions) or rendering a `Content:ContentInd`. In both cases the display is now idle and ready for the next instruction.",
      "parameters": [
        {
          "name": "width",
          "type": "uint",
          "description": "The width of the display in pixels. Populated on the initialisation occurrence."
        },
        {
          "name": "height",
          "type": "uint",
          "description": "The height of the display in pixels. Populated on the initialisation occurrence."
        }
      ]
    }
  ],
  "components": [
    {
      "name": "ServerConnector",
      "displayName": "Server\\nConnector",
      "description": "Controls and establishes the connection with the server. It waits for the device to become ready and then starts advertising its presence on the network to be discoverable by a server.\n\n# Behavior\n\nThe component is responsible for managing the lifecycle of the server connection over the wireless link.\n\n## Start-up\n\nThe component must not begin advertising until the device as a whole signals readiness. Activity received before that point is silently discarded.\n\n## Discovery\n\nWhen ready, the component makes the device discoverable by broadcasting an advertisement packet every `ADVERTISEMENT` milliseconds. This continues until a server attempts to connect or the link is lost.\n\n## Connection request\n\nA server may at any time during discovery request to connect, identifying itself with a `serverId`. The component must:\n\n- Stop advertising while the request is being evaluated.\n- If the server is not in the `whitelist` of permitted servers, reject the request via the radio so further requests from the same server are blocked, then resume discovery.\n- Otherwise, instruct the radio to establish the connection and wait at most `CONNECTION_TIMEOUT` milliseconds for confirmation.\n\nIf confirmation arrives in time, the device is considered connected to the identified server. If the timeout elapses first, the connection attempt is abandoned and discovery resumes.\n\n## Disconnection\n\nIf the radio reports that an established connection has been lost, the component must return to discovery so that a new server can find the device.\n\n## Error\n\nIf the device as a whole reports an unrecoverable error, the component must cancel any pending timers, disable the radio frontend, and return to its uninitialized condition. It must accept no further activity from the radio until the device signals readiness again.",
      "states": [
        {
          "name": "Uninitialized",
          "description": "Waiting for the device to become ready."
        },
        {
          "name": "Advertising",
          "description": "Broadcasting advertisement packets on the network to be discoverable by a server."
        },
        {
          "name": "Connecting",
          "description": "Establishing a connection to the server."
        },
        {
          "name": "Connected",
          "description": "Connected to the server."
        }
      ],
      "choicePoints": [
        {
          "name": "WhiteListed",
          "question": "Is Server\\nWhitelisted?",
          "description": "Check whether the server that tries to connect is in the `whitelist` of permitted servers."
        }
      ],
      "stateVariables": [
        {
          "name": "serverId",
          "type": "uint",
          "description": "The ID of the connected or connecting server."
        },
        {
          "name": "whitelist",
          "type": "uintArray",
          "description": "The IDs of the servers that are permitted to connect."
        },
        {
          "name": "connectTimerId",
          "type": "TimerID",
          "description": "Handle of the connection-attempt timer."
        },
        {
          "name": "advertiseTimerId",
          "type": "TimerID",
          "description": "Handle of the advertisement-interval timer."
        }
      ],
      "constants": [
        {
          "name": "ADVERTISEMENT",
          "value": "1000",
          "description": "Advertisement interval in ms."
        },
        {
          "name": "CONNECTION_TIMEOUT",
          "value": "500",
          "description": "Timeout waiting for server to connect in ms."
        }
      ],
      "localFunctions": [
        {
          "name": "doAdvertise",
          "description": "Send an advertisement packet and (re)start the advertisement-interval timer.",
          "steps": "Call `WirelessTransceiver:sendAdvertisementPacket()`.\nCall `TimerHandler:setTimeout`(`ADVERTISEMENT`, `advertiseTimerId`)."
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "Uninitialized",
          "messages": [],
          "connector": "Down",
          "length": 1
        },
        {
          "source": "Uninitialized",
          "target": "Advertising",
          "messages": [
            {
              "interface": "Device",
              "name": "ReadyInd",
              "action": "Call `doAdvertise()`."
            }
          ],
          "connector": "Down",
          "length": 1
        },
        {
          "source": "Advertising",
          "target": "CP_WhiteListed",
          "messages": [
            {
              "interface": "Connection",
              "name": "ConnectReq",
              "action": "Call `TimerHandler:cancelTimeout`(`advertiseTimerId`).\nSet `serverId` to `ConnectReq:serverId`."
            }
          ],
          "connector": "Left",
          "length": 1
        },
        {
          "source": "CP_WhiteListed",
          "target": "Advertising",
          "messages": [
            {
              "interface": "Logical",
              "name": "No",
              "action": "Call `WirelessTransceiver:rejectServer`(`serverId`).\nCall `doAdvertise()`."
            }
          ],
          "connector": "Right",
          "length": 1
        },
        {
          "source": "CP_WhiteListed",
          "target": "Connecting",
          "messages": [
            {
              "interface": "Logical",
              "name": "Yes",
              "action": "Call `WirelessTransceiver:connectServer`(`serverId`).\nCall `TimerHandler:setTimeout`(`CONNECTION_TIMEOUT`, `connectTimerId`)."
            }
          ],
          "connector": "Down",
          "length": 1
        },
        {
          "source": "Connecting",
          "target": "Connected",
          "messages": [
            {
              "interface": "Connection",
              "name": "ConnectedInd",
              "action": "Call `TimerHandler:cancelTimeout`(`connectTimerId`)."
            }
          ],
          "connector": "Right",
          "length": 1
        },
        {
          "source": "Connecting",
          "target": "Advertising",
          "messages": [
            {
              "interface": "Timer",
              "name": "Timeout",
              "action": "Call `doAdvertise()`."
            }
          ],
          "connector": "Up",
          "length": 1
        },
        {
          "source": "Connected",
          "target": "Advertising",
          "messages": [
            {
              "interface": "Connection",
              "name": "DisconnectedInd",
              "action": "Call `doAdvertise()`."
            }
          ],
          "connector": "Up",
          "length": 1
        },
        {
          "source": "Advertising",
          "target": "Advertising",
          "messages": [
            {
              "interface": "Timer",
              "name": "Timeout",
              "action": "Call `doAdvertise()`."
            }
          ],
          "connector": "Right",
          "length": 1
        },
        {
          "source": "*",
          "target": "Uninitialized",
          "messages": [
            {
              "interface": "Device",
              "name": "ErrorInd",
              "action": "Call `TimerHandler:cancelTimeout`(`advertiseTimerId`).\nCall `TimerHandler:cancelTimeout`(`connectTimerId`).\nCall `WirelessTransceiver:disable()`."
            }
          ],
          "connector": "Right",
          "length": 1
        },
        {
          "source": "Uninitialized",
          "target": "Uninitialized",
          "messages": [
            {
              "interface": "*",
              "name": "*"
            }
          ],
          "connector": "Right",
          "length": 1
        }
      ]
    },
    {
      "name": "DeviceStatusController",
      "displayName": "Device Status\\nController",
      "description": "Takes care of initializing the device by waiting for all the peripherals to be operational.\n\n# Behavior\n\nThe component is responsible for determining when the device as a whole is ready and for detecting peripheral failures.\n\n## Peripherals\n\nThe device has two peripherals: the wireless transceiver and the display. Both are brought up uniformly by `PeripheralHandler:start()` and signal readiness and failure on the `Peripherals` interface.\n\n## Start-up\n\nOn construction, the component starts the peripherals and begins counting how many of them have come up. The device must not be reported as ready until `NUM_PERIPHERALS` peripherals have signalled they are operational.\n\nEach peripheral that becomes operational signals so; the component counts the signals and re-evaluates whether the full set has been received. As soon as the count reaches `NUM_PERIPHERALS`, the component announces device-wide readiness by emitting `Device:ReadyInd`. From that point on the device is considered operational.\n\n## Failure\n\nIf any peripheral reports failure, whether before or after the device has reached the operational condition, the component announces device-wide failure by emitting `Device:ErrorInd` and enters a terminal failed condition. While failed, the component is inert: it makes no further announcements, and recovery requires reconstruction of the component.",
      "states": [
        {
          "name": "AwaitingPeripherals",
          "displayName": "Awaiting\\nPeripherals",
          "description": "Waiting for all the peripherals to become ready."
        },
        {
          "name": "Operational",
          "description": "All peripherals are operational."
        },
        {
          "name": "Error",
          "description": "A peripheral failed, either during start-up or after the device became operational."
        }
      ],
      "choicePoints": [
        {
          "name": "AllReady",
          "question": "All Peripherals\\nOperational?",
          "description": "Check whether `numReady` equals `NUM_PERIPHERALS`."
        }
      ],
      "stateVariables": [
        {
          "name": "numReady",
          "type": "uint",
          "description": "Count of peripherals that have signalled they are operational."
        }
      ],
      "constants": [
        {
          "name": "NUM_PERIPHERALS",
          "value": "2",
          "description": "Number of expected peripherals: wireless transceiver and display."
        }
      ],
      "localFunctions": [
        {
          "name": "initialize",
          "steps": "Call `PeripheralHandler:start()`."
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "AwaitingPeripherals",
          "messages": [],
          "connector": "Down",
          "length": 1
        },
        {
          "source": "AwaitingPeripherals",
          "target": "CP_AllReady",
          "messages": [
            {
              "interface": "Peripherals",
              "name": "ReadyInd",
              "action": "Increment `numReady`."
            }
          ],
          "connector": "Down",
          "length": 1
        },
        {
          "source": "CP_AllReady",
          "target": "AwaitingPeripherals",
          "messages": [
            {
              "interface": "Logical",
              "name": "No"
            }
          ],
          "connector": "Up",
          "length": 1
        },
        {
          "source": "CP_AllReady",
          "target": "Operational",
          "messages": [
            {
              "interface": "Logical",
              "name": "Yes",
              "action": "Send the `Device:ReadyInd` message."
            }
          ],
          "connector": "Right",
          "length": 1
        },
        {
          "source": "AwaitingPeripherals",
          "target": "Error",
          "messages": [
            {
              "interface": "Peripherals",
              "name": "FailureInd",
              "action": "Send the `Device:ErrorInd` message."
            }
          ],
          "connector": "Right",
          "length": 1
        },
        {
          "source": "Operational",
          "target": "Error",
          "messages": [
            {
              "interface": "Peripherals",
              "name": "FailureInd",
              "action": "Send the `Device:ErrorInd` message."
            }
          ],
          "connector": "Up",
          "length": 1
        },
        {
          "source": "Error",
          "target": "Error",
          "messages": [
            {
              "interface": "*",
              "name": "*"
            }
          ],
          "connector": "Right",
          "length": 1
        }
      ]
    },
    {
      "name": "UiController",
      "displayName": "UI\\nController",
      "description": "Displays content received from the server.\n\n# Behavior\n\nThe component is responsible for initializing the display and showing content pushed by the server.\n\n## Start-up\n\nThe component must not present any content until the device as a whole signals readiness. Once ready, the component initializes the display. When the display is ready, it reports its pixel dimensions. The component forwards these dimensions to the server so that the server knows the canvas size for any content it pushes. The component is then ready to receive content.\n\n## Updates\n\nThe server pushes images individually as `Content:ContentInd` messages. On each `ContentInd`, the component stores the new image and instructs the display to render it. The display reports completion via `Display:CompletedInd` when it is finished and ready to render the next image; until then the component holds in the `Updating` condition and does not accept further content. This back-pressure prevents a new render from being started while a previous one is in flight.\n\n## Error\n\nIf the device as a whole reports an unrecoverable error, the component shows the default error screen and returns to its uninitialized condition.",
      "states": [
        {
          "name": "Uninitialized",
          "description": "Waiting for the device to become ready."
        },
        {
          "name": "InitializingDisplay",
          "displayName": "Initializing\\nDisplay",
          "description": "Initializing the display."
        },
        {
          "name": "Ready",
          "description": "The display is initialized and content can be received and displayed."
        },
        {
          "name": "Updating",
          "description": "A new image is being rendered on the display."
        }
      ],
      "stateVariables": [
        {
          "name": "image",
          "type": "byteArray",
          "description": "The PNG image currently displayed."
        }
      ],
      "transitions": [
        {
          "source": "START",
          "target": "Uninitialized",
          "messages": [],
          "connector": "Right",
          "length": 1
        },
        {
          "source": "Uninitialized",
          "target": "InitializingDisplay",
          "messages": [
            {
              "interface": "Device",
              "name": "ReadyInd",
              "action": "Call `DisplayHandler:initialize()`."
            }
          ],
          "connector": "Down",
          "length": 1
        },
        {
          "source": "InitializingDisplay",
          "target": "Ready",
          "messages": [
            {
              "interface": "Display",
              "name": "CompletedInd",
              "action": "Call `WirelessTransceiver:sendContentDimensions`(`CompletedInd:width`, `CompletedInd:height`)."
            }
          ],
          "connector": "Down",
          "length": 1
        },
        {
          "source": "*",
          "target": "Uninitialized",
          "messages": [
            {
              "interface": "Device",
              "name": "ErrorInd",
              "action": "Call `DisplayHandler:displayError()`."
            }
          ],
          "connector": "Down",
          "length": 1
        },
        {
          "source": "Uninitialized",
          "target": "Uninitialized",
          "messages": [
            {
              "interface": "*",
              "name": "*"
            }
          ],
          "connector": "Right",
          "length": 1
        },
        {
          "source": "Ready",
          "target": "Updating",
          "messages": [
            {
              "interface": "Content",
              "name": "ContentInd",
              "action": "Set `image` to `ContentInd:image`.\nCall `DisplayHandler:display`(`image`)."
            }
          ],
          "connector": "Left",
          "length": 1
        },
        {
          "source": "Updating",
          "target": "Ready",
          "messages": [
            {
              "interface": "Display",
              "name": "CompletedInd"
            }
          ],
          "connector": "Left",
          "length": 1
        }
      ]
    }
  ],
  "deviceDisplayName": "Wireless Networked Display (WND)",
  "handlers": [
    {
      "name": "WirelessTransceiver",
      "displayName": "Wireless\\nTransceiver",
      "description": "This handler takes care of receiving and transmitting data packets on the radio frontend by interfacing with the radio hardware peripheral. By looking at the message-type of incoming messages, indications are forwarded either to the `Connection` or `Content` interface.\n\nThe wireless transceiver is one of the device's peripherals: it is brought up by `PeripheralHandler:start()` together with the display, and it signals its readiness and any failure on the `Peripherals` interface so that `DeviceStatusController` can include it in its readiness count. Once readiness has been signalled, the radio is ready to advertise and accept connection requests.",
      "functions": [
        {
          "name": "sendAdvertisementPacket",
          "description": "Send a single advertisement packet to become discoverable."
        },
        {
          "name": "connectServer",
          "description": "Connect to the indicated server.",
          "parameters": [
            {
              "name": "serverId",
              "type": "uint",
              "description": "The ID of the server to connect to."
            }
          ]
        },
        {
          "name": "rejectServer",
          "description": "Block further requests from the indicated server.",
          "parameters": [
            {
              "name": "serverId",
              "type": "uint",
              "description": "The ID of the server that is to be rejected from now on."
            }
          ]
        },
        {
          "name": "disable",
          "description": "Disable the radio frontend."
        },
        {
          "name": "sendContentDimensions",
          "description": "Inform the server of the dimensions of the content the display supports.",
          "parameters": [
            {
              "name": "displayWidth",
              "type": "uint",
              "description": "The width of the display in pixels."
            },
            {
              "name": "displayHeight",
              "type": "uint",
              "description": "The height of the display in pixels."
            }
          ]
        }
      ]
    },
    {
      "name": "PeripheralHandler",
      "displayName": "Peripheral\\nHandler",
      "description": "Takes care of detecting, initializing and configuring hardware peripherals \u2014 the wireless transceiver and the display \u2014 e.g. by accessing low level drivers and serial communication lines. Both are brought up together by `start()` and report readiness and failures on the `Peripherals` interface.",
      "functions": [
        {
          "name": "start",
          "description": "Start initializing and configuring the peripherals."
        }
      ]
    },
    {
      "name": "DisplayHandler",
      "displayName": "Display\\nHandler",
      "description": "Handles the interface to the display to show images.",
      "functions": [
        {
          "name": "initialize",
          "description": "Initialize the display."
        },
        {
          "name": "display",
          "description": "Display the supplied image.",
          "parameters": [
            {
              "name": "image",
              "type": "byteArray",
              "description": "The PNG image to be displayed."
            }
          ]
        },
        {
          "name": "displayError",
          "description": "Display the default error message."
        }
      ]
    }
  ],
  "types": [
    {
      "name": "byteArray",
      "description": "A one dimensional array of bytes",
      "specification": "uint8[]"
    },
    {
      "name": "uint",
      "description": "Unsigned integer",
      "specification": "32-bit numerical value representing an integer value."
    },
    {
      "name": "uintArray",
      "description": "One dimensional array of uint",
      "specification": "uint[]"
    },
    {
      "name": "byteMatrix",
      "description": "2-dimensional array of bytes.",
      "specification": "byte[][]"
    }
  ],
  "connections": [
    {
      "component": "ServerConnector",
      "interface": "Device",
      "connector": "Up",
      "length": 1
    },
    {
      "component": "ServerConnector",
      "interface": "Connection",
      "connector": "Left",
      "length": 1
    },
    {
      "component": "DeviceStatusController",
      "interface": "Device",
      "connector": "Up",
      "length": 1
    },
    {
      "component": "DeviceStatusController",
      "interface": "Peripherals",
      "connector": "Down",
      "length": 1
    },
    {
      "component": "UiController",
      "interface": "Device",
      "connector": "Down",
      "length": 1
    },
    {
      "component": "UiController",
      "interface": "Display",
      "connector": "Left",
      "length": 1
    },
    {
      "component": "UiController",
      "interface": "Content",
      "connector": "Down",
      "length": 1
    }
  ],
  "handlerConnections": [
    {
      "handler": "WirelessTransceiver",
      "interface": "Connection",
      "connector": "Down",
      "length": 1
    },
    {
      "handler": "PeripheralHandler",
      "interface": "Peripherals",
      "connector": "Left",
      "length": 1
    },
    {
      "handler": "DisplayHandler",
      "interface": "Display",
      "connector": "Right",
      "length": 1
    },
    {
      "handler": "WirelessTransceiver",
      "interface": "Content",
      "connector": "Left",
      "length": 1
    }
  ],
  "handlerCalls": [
    {
      "component": "ServerConnector",
      "handler": "WirelessTransceiver",
      "connector": "Left",
      "length": 1
    },
    {
      "component": "DeviceStatusController",
      "handler": "PeripheralHandler",
      "connector": "Left",
      "length": 1
    },
    {
      "component": "UiController",
      "handler": "DisplayHandler",
      "connector": "Up",
      "length": 1
    },
    {
      "component": "UiController",
      "handler": "WirelessTransceiver",
      "connector": "Right",
      "length": 1
    }
  ],
  "deviceSpecification": "The device is a Wireless Networked Display (WND) which is controlled by a central server (not in scope). It operates by creating a connection to the server and then handling requests to display content formatted as a PNG image.\n\nThe device must be extremely low cost. The display itself is a simple non-interactive panel: the server pushes images, the device shows them.\n\nThe device has two peripherals: the wireless transceiver and the display. Both are managed uniformly by the peripheral handler and report their readiness and failures on the `Peripherals` interface.\n\nThe server pushes images individually; the device renders each one in turn and signals back-pressure via the display peripheral's completion indication, so that a new image is never accepted while a previous render is still in flight."
}

What you have built

A complete WND device:

  • Three functional components, each with a single responsibility and an independent lifecycle.
  • Three handlers, each owning one asynchronous edge to the outside world.
  • Five interfaces carrying eleven messages in total — a small, focused vocabulary that every component reads from a single shared schema.
  • A device specification that describes the system as a whole in the external register: what it does, not how it does it.

The state machines fit on a single screen each. They are flat, they are named in domain vocabulary, and any one of them can be replaced with a different implementation that respects the same interface contract without the others noticing. This is the FCM payoff — low coupling, modelled explicitly, visible at a glance.

What to try next

  • Add a Worker Handler. Insert one alongside WirelessTransceiver for a long-running computation (e.g. image decoding, encryption). Note where it fits in the boundary-first identification order.
  • Add a new component. Suppose the device needs to log every server interaction to non-volatile storage. What is its single responsibility? What interface does it speak? What new handler does it need?
  • Refactor UiController. The dual meaning of Display:CompletedInd (initialisation completion and render completion) is a deliberate compactness choice. What would splitting it into two distinct messages cost in vocabulary, and gain in clarity? When would that trade-off favour splitting?
  • Refactor UiController's back-pressure. The current model relies on the server to send one image at a time. What would change if you had to defend against an overlapping Content:ContentInd? Try adding a transition that absorbs or buffers it. What does the diagram tell you about which design you chose?
  • Try the canvas-first workflow. Most of this tutorial drove the editor via the side lists and the Add Transition / Add Connection buttons. Stadiæ is much faster once you select entities directly on the canvas and edit them with the keyboard (see the use-cases catalogue's "Click an element on the canvas to select it" and "Edit any selected entity via the keyboard"). For modelling at speed, list-driven editing is the long way round.