EcsControlSchedule
The fourth-round evaluation programs: the LED world under coordinated blink periods with its print-clip observation loop, in five partitions, on one schedule and on two (one or two cores).
Overview
EcsControlSchedule is the fourth-round evaluation program for the Entity Component System (ECS) architecture for control systems, following the third round's EcsControlWatch. It is three programs of one family:
| Program | Schedules | What it demonstrates |
|---|---|---|
EcsControlSchedule-v26 |
one | the partitioned program with the observation loop, the adaptation in a partition of its own |
EcsControlSchedule-v29 |
two | schedule 1 created with the adaptation partition on it; two surfaces crossing |
EcsControlSchedule-v30 |
two | the coordination partition moved to schedule 1 as well; schedule 1 ticking at 10 ms |
What the programs do on the bench is the same in all three, and the same as in the third round: four LEDs under closed-loop control, a heartbeat report to a terminal, and the program watching its own print facility. What changed is where the work runs. The partitions are the same files in all three programs; the three differ in their program-level modules only.
This document describes the programs themselves – their partitions, their Systems, and what they do on the bench. It is deliberately short: the implementation document explains every construct the programs are built from, with their code as its listings; the schedules document explains the two schedules and the two steps from one program to the next; the Observations set covers the observation loop from concepts through verification. The authoritative form of every access fact below is the manifests in the code.
Scope
New at program level this round:
-
five partitions: the adaptation loop's supervisor has left the print partition for a partition of its own, so that the observed facility and the System that adapts to it are two partitions meeting through two transports;
-
two schedules, one per core, in
-v29and-v30: the program's definition declares which partitions run where and what crosses between the schedules, and the kernel runs one loop per core and checks the two plans against each other; -
a definition file per program, the ECS file, with the checker and the extractor over it;
-
the transports under a precondition standard, the print buffer in the framework's ring buffer form, and the adapter as the layer that records the clip observation.
Still at the concept level: the asynchronous seam, programs across several MCUs, and recovery – the motivating horizon, with observations as its detection substrate.
What the Programs Do
Four LEDs under closed-loop control, a heartbeat report to a terminal – and the program watching its own print facility.
Two leader LEDs blink, each at its own period, free-phasing on per-leader tickers. Two follower LEDs each copy their leader's set-point, read as the previous tick's value – a deterministic one-tick delay. SenseSystem reads the actual pin levels back every tick; ActuateSystem writes a pin only on mismatch between set-point and measurement. Every five seconds, PeriodSystem – the "mission control" – publishes the next step of a four-step period table, and both leaders change their blink rate; the table wraps, so the cadence cycles every twenty seconds.
The print side carries the observation loop. HeartbeatSystem writes a fixed text batch into a print ring buffer every 600 ms; DrainSystem feeds the buffered text to a UART at a commanded pace. The buffer is deliberately sized so that the shipped load slightly overfills it: writes that do not fit are clipped, the clips are tallied by the buffer's adapter as it writes, PrintClipSystem distils the tally into a trouble level, and AdaptDwellSystem – in its own partition – reads the level through a sampling port and commands the drain faster or slower through a command port. The loop runs perpetually: fill, clip, trip, faster drain, discharge, clear, slower drain – one cycle about every eleven seconds, visible on both the program terminal and the console.
TickMonitorSystem watches the kernel for missed ticks and deposits a report line only when a slip occurred – in normal operation it stays silent.
In -v29 and -v30 the loop runs across two cores: the observation crosses from the print partition on core 0 to the adaptation partition on core 1, and the command crosses back. In -v30 the mission control runs on core 1 as well, and its period steps cross to the blink partition on core 0. Nothing of this is visible at the LEDs or on the terminal, which is the point.
Hardware
-
Pico2 board; four LEDs with their resistors –
LED0/LED2are the leaders,LED1/LED3the followers, as assigned inRfrom the framework'sLEDext. -
Drain UART:
UART1, TX/RX on pins 4/5, 38400 baud, 8N1 – the "plant" terminal showing the heartbeat text. -
Error console: the framework's console UART (
UART0, pins 0/1, 38400 baud) – the run-time error output, the plan reports, and the verification instrumentation lines, from both cores.
The two-schedule programs need no further hardware: the second core is the RP2350's own.
The Partitions
| Partition | Bench role | Systems |
|---|---|---|
PrintPartition |
the plant: the print facility and its observation | Drain, PrintClip |
AdaptPartition |
the supervisor of the print facility | AdaptDwell |
BlinkPartition |
the demonstration application | Blink, Couple, Actuate, Sense |
CoordPartition |
mission control | Period |
MonPartition |
monitoring and test instrumentation | TickMonitor, Heartbeat |
Each partition owns its complete module family – wiring (Wx), values (Vx), stores (CSx, CLx), tokens (Tx), Systems, the transports it hosts with their adapters, its bindings where it operates a device, and the partition module that creates its world and registers its Systems. The program's token module T allocates each partition's token range; the store carries fifteen tokens, named by their path in the store:
| Partition | Token | Backing | Writer | Readers |
|---|---|---|---|---|
| Blink | ledLeader.setpoints |
leader array | Blink |
Couple (previous value), Actuate |
| Blink | ledLeader.measureds |
leader array | Sense |
Actuate |
| Blink | ledFollower.setpoints |
follower array | Couple |
Actuate |
| Blink | ledFollower.measureds |
follower array | Sense |
Actuate |
| Blink | blinker.tickers |
leader array | Blink (owned) |
– |
| Coord | mission.periodStep |
sequence step | Period (owned) |
– |
| Coord | periodPort.ports |
sampling port | Period |
Blink |
| Mon | heartbeat.status |
test-player state | Heartbeat (owned) |
– |
printBuf.bufs |
ring buffer state + storage | Heartbeat, TickMonitor (deposit) |
Drain (collector) |
|
printBufClip.tallies |
clip tally per buffer | Heartbeat, TickMonitor (deposit) |
PrintClip (collector) |
|
bufDrain.tickers |
drain tickers | Drain (owned) |
– | |
clipsDistiller.accounts |
distiller account | PrintClip (owned) |
– | |
clipsObsPort.ports |
sampling port | PrintClip |
AdaptDwell |
|
| Adapt | clipsAdapter.runs |
run state | AdaptDwell (owned) |
– |
| Adapt | drainCmdPort.ports |
command port | AdaptDwell |
Drain (previous value) |
Four surfaces cross partition lines, every one a transport's surface: Tcoord.I.readPeriods carries the blink periods from Coord to Blink; Tprint.I.print, the print buffer with its tally, is the deposit surface the two monitor Systems name wholesale; Tprint.I.clipsObs carries the trouble level from Print to Adapt; Tadapt.I.drainCmd carries the drain command from Adapt to Print. The clip tally is written on the depositors' access – the buffer adapter records a clip as it writes, and the write belongs to the depositing System.
The Systems
Ten Systems, one module each:
| System | Manifest (compact) | Runs |
|---|---|---|
Blink |
C: readPeriods; O: blinker.tickers; P: ledLeader.setpoints | every tick, per-leader tickers |
Couple |
Cp: ledLeader.setpoints; P: ledFollower.setpoints | every tick |
Actuate |
C: ledLeader.setpoints, ledLeader.measureds, ledFollower.setpoints, ledFollower.measureds | every tick |
Sense |
P: ledLeader.measureds, ledFollower.measureds | every tick |
Period |
O: mission.periodStep; P: periodPort.ports | every 5 s |
Drain |
Cp: drainCmd; O: bufDrain.tickers; Cx: printBuf.bufs | every tick, fires at the commanded period, grid-locked |
PrintClip |
O: clipsDistiller.accounts; P: clipsObsPort.ports; Cx: printBufClip.tallies | every 600 ms |
AdaptDwell |
C: clipsObs; O: clipsAdapter.runs; P: drainCmdPort.ports | every 600 ms |
TickMonitor |
Px: print | every tick, deposits only on slip |
Heartbeat |
O: heartbeat.status; Px: print | every 600 ms |
A foreign surface appears in a manifest by its name, readPeriods, clipsObs, drainCmd, print; the periods are given in time here and counted in ticks of the System's schedule in P.
Two chains flow through the stores. The LED chain, fed by "mission control":
PeriodSystem ---- readPeriods ----> BlinkSystem (sampling port, Coord -> Blink)
BlinkSystem ---- ledLeader.setpoints ----+--> CoupleSystem ---+
(previous value) | | ledFollower.setpoints
+--------------------+--> ActuateSystem
SenseSystem ---- measureds (leader + follower) ---------------+
And the print facility with its observation loop closed around it, across two partitions:
HeartbeatSystem ---+-- printBuf.bufs ---------> DrainSystem --> UART
| (deposited / collected)
TickMonitorSystem -+-- printBufClip.tallies --> PrintClipSystem
(tallied by the adapter) |
| clipsObs (sampling port, Print -> Adapt)
v
DrainSystem <---- drainCmd <---- AdaptDwellSystem
(command port, Adapt -> Print, previous value)
The Systems are registered partition by partition, in an arbitrary order; the run order is derived from the manifests at start-up, per schedule. The derived orders of -v30, both schedules with their crossings, are shown and read in the implementation document.
Wiring and Values
The deployment choices, all in the definition modules:
-
the LED pins, the drain UART's unit, pins and handle, the kernel's timer, and the tick's exception priority:
R; -
the follower coupling:
CLblink.L.ledFollower.follows, wired straight – follower 0 follows leader 0, follower 1 leader 1; -
one print buffer participation (the terminal), paired with its text writer; buffer capacity 64 characters (
Vprint.BufSize) – deliberately below the wiring's 128 so that the shipped load clips; -
the drain period repertoire: 600 / 300 / 150 ms as command values 0 / 1 / 2, held locked in
CLprint.L.bufDrain.cfgs; -
the blink period table: four steps, leader 0 from 300 to 1200 ms, leader 1 from 450 to 1800 ms, in
Vcoord, locked inCLcoord; -
the tick periods: 50 ms in
-v26and on both schedules of-v29; 50 ms on schedule 0 and 10 ms on schedule 1 in-v30; all System periods as tick counts of their schedule, converted once, inP.
The observation calibration is declared in Vadapt: accept 1 clip per 8 operations, trigger at 1 per 6, a burst of 6 clipping operations reaches the trip threshold, clear at 34% of it, cap at 200%. The working thresholds are derived from this declaration at creation, by Observations.Calibrate in CLadapt, which publishes the distiller's half in its interface for the print partition – the calibration document owns the whole scheme.
The heartbeat's load is defined in CLmon as test-case tables – the same tables the verification suite ran; the programs as published select the shipped configuration (sequence 8), in MonPartition.
World Creation
The schedule module's station sequence is the whole bring-up: assert the agreement of the definitions, install the kernel, create the partitions, Kernel.Begin, set the boundary, register the partitions' Systems, then Plan – report – Commit – Run; with two schedules, schedule 0's module asserts the agreement once and then starts the other core, and each schedule module runs the sequence for its own partitions. Each partition creates its world in the four phases build / acquire / value / propagate; concretely:
-
Print builds the UART device and the text writer; values the drain tickers, the distiller accounts, the observation ports, the clip tallies, and the print buffer;
-
Adapt values its run state and its command port;
-
Blink configures the LED pins, acquires the actual pin levels into the measured Components, zeroes set-points and tickers, and propagates the set-points back out to the pins;
-
Coord values the period port from the table's first step;
-
Mon selects the test sequence and values the heartbeat's player state.
The full creation story – the schedule modules of all three programs, listed complete – is in the implementation document and the schedules document.
Two Schedules (Dual Core)
-v29 and -v30 are -v26 with its partitions placed on two schedules. Nothing in a partition changed between the three; the differences are in the program-level modules, and the schedules document gives them step by step, as the two parts of its prescription.
-v29: a second schedule, with the adaptation partition on it. Schedule 0 runs the print, blink, coordination and monitor partitions; schedule 1 runs the adaptation partition. Two surfaces cross: the trouble level from schedule 0 to schedule 1, the drain command back. Both schedules tick at 50 ms. This is the schedules document's Part 1.
-v30: the coordination partition moved as well. Schedule 1 now runs the adaptation and the coordination partition; a third surface crosses, the blink periods from schedule 1 to schedule 0. Schedule 1 ticks at 10 ms, five times faster than schedule 0: the periods of its Systems re-derive to the same milliseconds, and the period values it publishes for the blink partition stay in schedule 0's ticks, since that is where they are counted. This is Part 2 of the schedules document, with its rate test.
What You Observe
The four LEDs: the leaders blink at their two rates, the followers reproduce them one tick behind – and every five seconds the cadence steps, cycling through the four-step table. The three programs are indistinguishable at the LEDs.
On the plant terminal, one heartbeat line per 600 ms: hb, the tick count, the load's name, and a tail of short dash runs. In the clipping phase of the cycle the tail visibly truncates – the designed boundary, readable by eye.
On the console, after the plan report – one in -v26, two in -v29 and -v30, each with its schedule's crossings – the verification record: one line per drain firing (command, period) and one line per distiller pass (operations, events, level, last event tick). The shipped configuration cycles steadily: the level climbs past the trip threshold, the supervisor steps the drain from 600 to 300 ms, the buffer discharges, the level decays below the clear threshold, the drain steps back – one full cycle about every eleven seconds. The dwell policy visible in that record – one command step, held until fresh evidence accumulates – and the decode of both terminals are the verification document's subject; its traces are from EcsControlWatch, and these programs reproduce its cycle. In the two-schedule programs the record reads the same, the supervisor's lines now coming from core 1, and the tick monitor stays silent at the 10 ms tick of -v30.
The kernel's own cost, measured at the 50 ms tick with the run-time instrumentation the kernel keeps: a pass of schedule 0, nine Systems, peaks at 221 µs; a pass of schedule 1, the adaptation partition's one System, at 14 µs.
The Plan Report and the Instrumentation
The programs as published print the plan report: each schedule module calls PlanView.Report between Kernel.Plan and Kernel.Commit, importing PlanView and N; in the two-schedule programs the call is braced by the error console's claim and release, so that the two reports do not interleave. The drain and distiller console lines are deliberate verification instrumentation – their line lengths are tuned to stay within the console UART's FIFO, so they never distort the timing they document.
A program that ships without them drops the report call, the two imports and the instrumentation prints; Plan and Commit appear identically with and without them, and a rejected plan is then a hard fault with its own error code.
The lib Directory
The three programs share the example's own lib directory alongside, which holds, beyond the installed framework:
-
the library modules the programs use – the ECS kernel and the plan report module, the error console, the transport primitives, the observation machinery, and the service modules in their handle-based form;
-
further candidates for the forthcoming
lib/v4.0, not used by the programs.
The directories are self-contained: building an example writes nothing outside its tree, and the installed lib/v3.2 is untouched. The modules the programs do not use are not necessarily up to date; consistency and correctness across the directory are work planned for lib/v4.0, whose delineation is upcoming evaluation work.
Repository
lib/v3.2 – <repo>/examples/v3.2/rpi/pico2/EcsControlSchedule/, holding EcsControlSchedule-v26, -v29 and -v30 and the example's lib directory. Each program directory holds its ECS file, v26-01.ecs and the like. The tools are in ecs-tools/ beside the programs, with the kinds' reference implementations in ecs-tools/templates/; ref/ beside them holds the four transports as generated from those, and the import allow-lists file the review and the tools read the imports against.
See Also
- ECS for Control: Implementation
- ECS for Control: Schedules
- ECS for Control: The ECS File
- ECS for Control: Observations – Concepts
- ECS for Control: Observations – Calibration
- ECS for Control: Observations – Verification
- EcsControlWatch
Last updated: 2 November 2026