Oberon RTK

EcsControlStore

The second-round evaluation program: a closed-loop LED world under the derived schedule, with a deposited-and-collected reporting facility.

Overview

EcsControlStore is the second-round evaluation program for the Entity Component System (ECS) architecture for control systems, following the first round's EcsControlBase variants. Where the first round explored the store, the layering, and the adaptation modes across eight variants, this round built the mechanisms: declared access roles, the schedule derived from them, checked construction, the value store split into its variable and locked halves.

This document describes the program itself – its world, its Systems, its wiring, and what it does on the bench. The implementation document explains the mechanisms the program is built from; the authoritative form of every access fact below is the manifests in the code.

Scope

What the first round's programs performed by discipline and review is now mechanised: the producer and consumer roles are declarations (the manifests), the scheduling order is derived at start-up, and the construction checks run before the first tick – no check is relaxed, no exemption exists. The deposit classes and the tick monitoring are new with this round.

Still at the concept level: partitioning and second schedules, the asynchronous seam (this program drives all hardware synchronously; the first round's variants explore and demonstrate that seam), and recovery – the motivating horizon, with snapshot work ahead. The outlook document starts from these.

What the Program Does

Four LEDs under closed-loop control, and a report to a terminal.

Two leader LEDs blink, each at its own period. Two follower LEDs each track a leader – crosswise: follower 0 follows leader 1, follower 1 follows leader 0. The coupling is measurement-based: SenseSystem reads the actual pin levels back each tick, and the followers are set to what their leaders measurably do, not to what the leaders were told to do. Actuation writes a pin only on mismatch between set-point and measurement. The loop closes through the hardware, tick by tick.

The report side is the round's shared facility: HeartbeatSystem deposits a status line once per second, TickMonitorSystem deposits a report whenever the kernel recorded missed ticks – and stays silent otherwise. PrintSystem collects both and hands the text to DrainSystem, which feeds the UART.

Hardware

  • Pico2 board; four LEDs, LED0LED3 as assigned in LEDbinding – the external hardware is four LEDs and their resistors, nothing more.

  • Report UART: UART1, TX/RX on pins 4/5, 38400 baud, 8N1.

  • The console UART stays reserved for the run-time error output and the development-build construction report.

The World

The populations are two leaders, two followers, and two printer participations – all W constants. The store carries eight tokens:

Token Type backing Writer Readers
LedMeasured leader + follower arrays Sense Couple, Actuate
LedSetpointLeader leader array Blink Actuate
LedSetpointFollower follower array Couple Actuate
BlinkConfig leader array Blink (owned)
PrintCursor printer array Print (owned)
PrintBuf printer array Heartbeat, TickMonitor (deposit) Print (collector)
HeartbeatStatus single record Heartbeat (owned)
DrainBuf none – edge token Print Drain

LedMeasured and the two set-point tokens share one measurement story but split at the write-partition boundaries: one token per writer, one array per token where the writers differ. DrainBuf is the pure-id edge token – its payload lives in a service module's buffer.

The locked half (CL) holds the pin bindings, the crosswise follows references, the printer wiring (writer and buffer per participant), and the device handles for UART, timer, and drain buffer.

The Systems

Eight Systems, one module each:

System Manifest (compact) Runs
Sense P: LedMeasured every tick
Blink O: BlinkConfig; P: LedSetpointLeader every tick
Couple C: LedMeasured; P: LedSetpointFollower every tick
Actuate C: LedMeasured, LedSetpointLeader, LedSetpointFollower every tick
Heartbeat O: HeartbeatStatus; Px: PrintBuf every 100 ticks
TickMonitor Px: PrintBuf every tick, deposits only on slip
Print O: PrintCursor; P: DrainBuf; Cx: PrintBuf every tick
Drain C: DrainBuf every tick

Two chains flow through the store:

SenseSystem ---- LedMeasured ---------+--> CoupleSystem ---+
                                      |                    | LedSetpointFollower
                                      +--------------------+--> ActuateSystem
                                                           |
BlinkSystem ---- LedSetpointLeader ------------------------+

HeartbeatSystem ---+
                   +-- PrintBuf --> PrintSystem -- DrainBuf --> DrainSystem --> UART
TickMonitorSystem -+   (deposited / collected)

The Systems are registered in an arbitrary order; the schedule is derived from the manifests at start-up. The development build prints the whole derivation – manifests, dependencies, order, check results – at start-up: the listing is shown under Development and Deployment Build, below.

Wiring and Values

The deployment choices, all in W, I, and the CL wiring lines:

  • identities (I): Leader_0, Leader_1; Printer_Heartbeat, Printer_TickMonitor;

  • the crosswise coupling: follows wired I.Leader_1 and I.Leader_0 respectively;

  • each printer participation paired with its own text writer and print buffer;

  • one UART, one timer, one drain buffer, each behind a W handle.

The tuning values (V): tick period 10 ms; blink periods 400 ms and 650 ms, the heartbeat 1000 ms – all held as tick counts, converted once, in V. Report baud rate 38,400.

The full pin and handle assignments read directly off W.mod and CL.mod – a screenful together; this document does not duplicate them.

World Creation, Concretely

The main program module's body is the whole sequence – build, attest, acquire, value, propagate, then the kernel calls. In this program:

  • build – allocates the store and seven device objects (the UART, the drain buffer, two print buffers, two text writers, the timer), binds each to its W handle, and configures the hardware through the binding modules;

  • attest – verifies every device reference wired in CL against a bound device: seven checks, one per wiring;

  • acquire – samples the four LED pins into the measured Components and takes the heartbeat's first timer reading;

  • value – sets the blink periods and tickers, zeroes the set-points, sequence counters, and the heartbeat count;

  • propagate – writes the set-points out to the four pins, so the hardware starts in agreement with the store.

What You Observe

The four LEDs: the leaders blink at their two rates; the followers reproduce their leaders' measured state, crosswise, one tick behind.

On the terminal, one heartbeat line per second – the System's name, its sequence count, the timer reading, and the delta to the previous beat:

Heartbeat          13    13225786   1000000
Heartbeat          14    14225786   1000000
Heartbeat          15    15225785    999999
Heartbeat          16    16225786   1000001
Heartbeat          17    17225785    999999

TickMonitor stays silent: in normal operation no tick is missed. To see it report, the program includes a deliberate test aid: in PrintSystem.mod, the blocks marked "uncomment for TickMonitor trigger" add a periodic busy-loop that overruns the tick on purpose. Rebuilt with the trigger enabled, the monitor deposits its report – elapsed ticks of the pass, cumulative missed ticks, the tick count – whenever the kernel records a slip:

Heartbeat          13    13236490   1000001
TickMonitor           9         416        1301
TickMonitor           9         424        1326
TickMonitor           9         432        1351
TickMonitor           9         440        1376
Heartbeat          14    14236490   1000000
TickMonitor           9         448        1401
TickMonitor           9         456        1426
TickMonitor           9         464        1451
TickMonitor           9         472        1476
Heartbeat          15    15236490   1000000
TickMonitor           9         480        1501
TickMonitor           9         488        1526
TickMonitor           9         496        1551
TickMonitor           9         504        1576
Heartbeat          16    16236490   1000000
TickMonitor           9         512        1601
TickMonitor           9         520        1626
TickMonitor           9         528        1651
TickMonitor           9         536        1676
Heartbeat          17    17236489    999999
TickMonitor           9         544        1701
TickMonitor           9         552        1726
TickMonitor           9         560        1751
TickMonitor           9         568        1776

The slip-and-count policy behind those numbers is the kernel's, described in the implementation document.

Development and Deployment Build

The program as published is the development build: main calls PlanView.Report between Kernel.Plan and Kernel.Commit, and the development-only modules PlanView and TokenNames are imported. A commented Kernel.Relax line documents the coverage-check waiver used while a producer's consumer does not exist yet. A deployed build drops the report call, the Relax line, and the two imports – Plan and Commit appear identically in both builds, and a rejected plan is then a hard fault with its own error code.

The report at start-up, program as published – the manifests in registration order, the dependency matrix, the derived order, the verdict:

Kernel.Plan
manifests (registration order):
 0 Blink
   O:
     BlinkConfig
   P:
     LedSetpoint Leader
 1 Couple
   C:
     LedMeasured
   P:
     LedSetpoint Follower
 2 Actuate
   C:
     LedMeasured
     LedSetpoint Leader
     LedSetpoint Follower
 3 Drain
   C:
     DrainBuf
 4 TickMonitor
   Px:
     PrintBuf
 5 Print
   O:
     PrintCursor
   P:
     DrainBuf
   Cx:
     PrintBuf
 6 Sense
   P:
     LedMeasured
 7 Heartbeat
   O:
     HeartbeatStatus
   Px:
     PrintBuf
dependencies (registration order):
 0 Blink
 1 Couple
     Sense
 2 Actuate
     Blink
     Couple
     Sense
 3 Drain
     Print
 4 TickMonitor
 5 Print
     TickMonitor
     Heartbeat
 6 Sense
 7 Heartbeat
derived scheduling order:
 0 Blink
 1 TickMonitor
 2 Sense
 3 Couple
 4 Actuate
 5 Heartbeat
 6 Print
 7 Drain
plan checks: all pass

Three things read off directly. The registration order is visibly not the schedule: Sense registers sixth and runs second, Heartbeat registers last and runs fifth. The collector's place is derived, not stated: Print follows both of its depositors, and Drain follows Print. And the last line is the gate's evidence – plan checks: all pass is what Kernel.Commit then silently confirms.

To show the checks failing, the same program is rebuilt with two violations induced in BlinkSystem's module body – two edited manifest lines, no code changed: LedMeasured added to P, a second writer beside Sense; LedSetpoint Leader added to C, the System now consuming its own product. The report then reads:

Kernel.Plan
manifests (registration order):
 0 Blink
   C:
     LedSetpoint Leader
   O:
     BlinkConfig
   P:
     LedMeasured
     LedSetpoint Leader
 1 Couple
   C:
     LedMeasured
   P:
     LedSetpoint Follower
 2 Actuate
   C:
     LedMeasured
     LedSetpoint Leader
     LedSetpoint Follower
 3 Drain
   C:
     DrainBuf
 4 TickMonitor
   Px:
     PrintBuf
 5 Print
   O:
     PrintCursor
   P:
     DrainBuf
   Cx:
     PrintBuf
 6 Sense
   P:
     LedMeasured
 7 Heartbeat
   O:
     HeartbeatStatus
   Px:
     PrintBuf
dependencies (registration order):
 0 Blink
 1 Couple
     Blink
     Sense
 2 Actuate
     Blink
     Couple
     Sense
 3 Drain
     Print
 4 TickMonitor
 5 Print
     TickMonitor
     Heartbeat
 6 Sense
 7 Heartbeat
derived scheduling order:
 0 Blink
 1 TickMonitor
 2 Sense
 3 Couple
 4 Actuate
 5 Heartbeat
 6 Print
 7 Drain
plan check violations:
 multiple writers
     LedMeasured
 consumes own written token
     LedSetpoint Leader
run-time error in thread mode: 54 core: 0
kernel plan check violation
Kernel.Commit  addr: 100063CEH  line: 196
stacked registers:
 xpsr: 29000000H
   pc: 100063CEH
   lr: 100087C3H
  r12: 96B5E400H
   r3: 00000002H
   r2: 00000080H
   r1: 00000000H
   r0: 00000009H
   sp: 2003F0A8H
trace:                       code addr    ln   frame addr  fsz
  Kernel.Commit              100063CEH   196
  EcsControlStore..init      100087BEH   164   2003F0CCH     4

Everything still happens, in order. Plan gathers and derives – note the dependency matrix now showing Couple depending on Blink, an edge created by the false producer claim – and the derived order still prints, since neither violation is a cycle. Both violations are reported by name, with their offending tokens, in the one run. Then Commit faults with the plan-check error code, and the run-time error machinery takes over, as it would for any construction fault.

The perturbation never touched executable code: two declaration lines were false, and the construction checks caught exactly them, completely, before the first tick.

The lib Directory

The example directory holds the program: the store modules, the Systems, the bindings, the main program module. Alongside it, the example's own lib directory holds everything the program needs beyond the installed framework: the kernel and its development-build modules, the service modules – buffers, writers, drivers – in their handle-based form, and the framework modules the build requires alongside them. Together the two directories are self-contained: the installed framework library (lib/v3.2) is left untouched – building the example writes nothing outside the example's tree, and every deviation from v3.2 lives visibly in the example's lib.

The division is not packaging convenience; it is the evaluation's seam. What sits in the example's lib is candidate material for the framework's next major version: the ECS kernel, and – reaching further – changes to the framework's own idioms, the handle-based device access first among them, which alters the API of modules as basic as the UART driver. Form and scope are still being worked out across the evaluation rounds; the destination is settled: changes of this kind will result in lib/v4.0 – API and conceptual changes, not additions to v3.2. As of now, programs written against v3.2 are unaffected by the evaluation, and what form v4.0 takes – including how it will affect existing programs – is itself still evaluation work.

Repository

lib/v3.2<repo>/examples/v3.2/rpi/pico2/EcsControlStore/EcsControlStore-v12, with the example's lib directory alongside.

See Also

Last updated: 30 July 2026