Coupled Modules - Understanding SECoP Inter-Module Interactions#
Note
This tutorial focuses on a single concept: how SECoP describes coupling between
two modules that compete for, or hand off, control of a piece of
hardware, expressed through the controlled_by and
control_active parameters. This tutorial assumes familiarity with
the SECoP busy sequence (change/do, update, and the status
parameter), as described in the companion tutorial.
Introduction#
So far we have treated SECoP modules as if each one independently owned
its own little piece of hardware: a module has a value, maybe a
target, and a status that tells the ECS whether it is busy. In
practice, sample environment hardware is rarely that tidy. A single
physical device often exposes several SECoP modules that are not
independent at all, because underneath they share the same actuator.
Two classic examples:
A temperature controller drives a heater. The ECS may see two modules: a temperature module
temperature(a Drivable, with a target temperature) and a heater-power moduleheater_power(a Writable, with a target power in Watts). Normallytemperaturedecides how much power to apply, but a user doing instrument commissioning might want to bypass the control loop and set the heater power directly.A power supply for a magnet can be operated in constant-current mode or constant-voltage mode, but never both at once. The ECS sees two modules,
currentandvoltage, each independently writable, yet writing to one must implicitly disable the other.
SECoP needs a standard way to describe “these two modules share an
actuator, and right now, this one is the one actually steering it.”
That is exactly what controlled_by and control_active are for.
Why is this needed at all?#
Without an explicit coupling mechanism, the situations above would be invisible to the ECS, with consequences that range from confusing to dangerous:
Silent loss of control. If the ECS sets
heater_power:targetto some fixed value to do a manual measurement, and the temperature moduletemperatureis still internally running its control loop,temperaturemay overwrite that value moments later. The ECS would have no way of knowing why its setting “didn’t stick.”Two writers, one wire. With the
current/voltagepower supply, nothing in a plain SECoP module description prevents an ECS (or a confused user) from writing to bothcurrent:targetandvoltage:targetin the same session. Without a coupling indicator, the ECS cannot tell that doing so is contradictory, nor which of the two settings is actually in effect.No way to ask “who is in charge right now?” Even if the firmware internally handles the conflict sensibly, the ECS (and the human using it) still need a parameter to read, to decide whether it is safe or sensible to issue a particular command at this moment.
No standard way to hand over control. Switching from automatic to manual heater control (and back) needs a defined trigger and a defined, observable outcome, in the same self-describing spirit as the rest of SECoP. It should not require a custom, undocumented parameter every single instrument re-invents.
SECoP’s answer is two read-only parameters, present on the modules
that participate in the coupling, plus an ordinary target/go
write to switch which module is in control, following the very same
busy-sequence rules already familiar from change and do.
The two parameters involved#
controlled_by: “who is driving me?”A read-only parameter of datatype enum: Enumerated type, whose possible values are the names of other modules, plus the special value
"self"(which must be enum value0). A module that has this parameter is declaring: “my behaviour may currently be dictated by one of these other named modules, instead of by my owntarget.”control_active: “am I the one actually in control?”A read-only boolean on a
DrivableorWritablemodule. Whentrue, the module’s own control mechanism is trying to bring itsvalueto itstarget; this is the normal, “business as usual” case, and is also the implicit default for a module that does not even have this parameter. Whenfalse, that control mechanism is switched off and the module’s owntargetis not considered any more; the module behaves like a plainReadableuntil control is handed back to it.
These two parameters are normally used together, on a pair of coupled modules, to describe the two sides of the same coin.
Case 1: a controller and a controlled actuator#
This is the temperature/heater example. Module temperature (temperature) can
control module heater_power (heater power). They are never both
“in charge” of the heater at the same time.
State |
Module |
Module |
|---|---|---|
temperature is controlling |
|
|
heater_power is self-controlled |
|
|
How control is taken#
The rule given by the specification is simple and symmetric: whichever
module receives a target change (or a go command, if present)
takes over control.
Writing
change temperature:target ...(or sendinggototemperature) makestemperaturetake over control. Before the SEC node sends back thechanged/donereply for that request, it must already have setheater_power:controlled_byto"temperature"and updated both modules’control_activeparameters correctly.Writing
change heater_power:target ...makesheater_powertake back control for itself. The SEC node must setheater_power:controlled_byto"self"and again update bothcontrol_activeparameters before replying.
This “update side effects before replying” rule should look familiar:
it is exactly the same rule used in the busy-sequence tutorial for
status changes. Here, controlled_by and control_active are
simply two more parameters that count as “side effects” of a target
change, and must be communicated via update events to all
activated clients, before the direct reply is sent.
Explicitly releasing control: control_off#
Setting a module’s target always turns its own control back on.
But what if there is no controlling module to hand off to, for
instance a temperature loop with no heater module exposed at all, or
one where the heater happens not to be a Writable? For this
case, a module may offer the optional control_off command, whose
only job is to set control_active to false directly. More
generally, control_off can be thought of as putting the module into
an “energy saving” state: switching off active heating/cooling for a
temperature loop, or cutting drive current for a motor.
A worked example#
Both connections below have already sent activate; qualifiers are
abbreviated as {...}:
> read temperature:control_active
< reply temperature:control_active [true,{...}]
> read heater_power:controlled_by
< reply heater_power:controlled_by ["temperature",{...}]
> change heater_power:target 5.5
< update heater_power:controlled_by ["self",{...}]
< update temperature:control_active [false,{...}]
< update heater_power:control_active [true,{...}]
< update heater_power:target [5.5,{...}]
< changed heater_power:target [5.5,{...}]
A user has just taken manual control of the heater to run it at a fixed
5.5 W, bypassing temperature’s control loop. Notice the order: the coupling
parameters (controlled_by, both control_active flags) are
announced as update events first; only then does the target
update and the final changed reply follow, precisely the
side-effects-before-reply ordering from the busy sequence.
Handing control back is the mirror image:
> change temperature:target 300
< update heater_power:controlled_by ["temperature",{...}]
< update temperature:control_active [true,{...}]
< update heater_power:control_active [false,{...}]
< update temperature:target [300,{...}]
< update temperature:status [[300,"ramping"],{...}]
< changed temperature:target [300,{...}]
Setting a new target on temperature reclaims control of the heater for the
control loop, and exactly as in an ordinary single-module
busy sequence, temperature may now also go BUSY while it ramps towards 300 K.
The two concepts compose cleanly: coupling decides who is allowed to
drive the hardware right now, while status (as covered in the
busy-sequence tutorial) describes whether that module is currently busy
doing so.
Case 2: two mutually exclusive writable modules#
The second pattern in the specification has no single “controller”
module; instead, two peer modules, current (current) and voltage (voltage)
on a power supply, are mutually exclusive alternatives. Exactly one of
them is ever controlled_by="self" at a time.
State |
Module |
Module |
|---|---|---|
constant current |
|
|
constant voltage |
|
|
The module with control_active=false behaves like a plain
Readable: its own target is ignored entirely, even though
the parameter is still present and (for the inactive module) simply
not taken into account. Writing to the other module’s target is
what flips the supply from one mode to the other, toggling both
modules’ control_active flags as a side effect:
> read current:control_active
< reply current:control_active [true,{...}]
> read voltage:control_active
< reply voltage:control_active [false,{...}]
> change voltage:target 12
< update current:controlled_by ["voltage",{...}]
< update current:control_active [false,{...}]
< update voltage:controlled_by ["self",{...}]
< update voltage:control_active [true,{...}]
< update voltage:target [12,{...}]
< changed voltage:target [12,{...}]
After this exchange, the power supply is now in constant-voltage mode
at 12 V; any value still present in current:target is simply not acted
upon until current is given a new target of its own.
Why this fits the rest of SECoP’s design#
It is worth noting what SECoP does not introduce here: there is no
new message type, no new busy-sequence step, and no special “take control”
command in the general case (only the optional control_off adds a
new command, and only where there is no module to hand control back
to). Coupling is expressed entirely through:
two ordinary, read-only parameters (
controlled_by,control_active) that any generic SECoP client already knows how to read, poll, or subscribe to viaactivate, andthe existing
change/gobusy sequence, whose “side effects before reply” rule is simply asked to also cover these two parameters whenever a target change causes a hand-over of control.
This is consistent with SECoP’s general design philosophy: rather than inventing protocol-level machinery for every new situation, new semantics are expressed as additional, self-describing parameters layered on top of the same small set of messages introduced for the basic busy sequence.
Summary#
Two (or more) SECoP modules can share a single physical actuator. SECoP makes this explicit via the read-only parameters
controlled_by(which module currently has control, or"self") andcontrol_active(whether this module’s own control loop is currently engaged).Control is taken implicitly: whichever module receives a
targetchange (orgocommand) becomes the one in control, and the SEC node updatescontrolled_by/control_activeon both modules accordingly.A module without an alternative to hand control to may offer the optional
control_offcommand, simply switching its owncontrol_activetofalse.These coupling updates must be communicated to all activated clients before the direct reply to the triggering
change/dorequest, exactly the same side-effects-before-reply rule already used forstatustransitions in the basic busy sequence.For more complex arbitration between several controlled modules, instruments may add further custom, device-specific parameters on top of this basic mechanism.
Further reading#
Parameters and commands — parameters and commands, including coupled modules