CMLabs · Psyclone AIOS

5. Messaging & Signals

Everything about moving data: message types and wildcards, signals, dataflow timing parameters, filters, and message tagging.

Message types and wildcards

Every message has a dot-notation type: ASCII parts of any length, grouped semantically with increasing specificity left to right — input.video.raw, input.audio.raw, input.audio.normalised. Types are applied to posts and used by triggers/subscriptions. Messages that match no subscription are discarded, unless the post sets a ttl (time-to-live) to keep them available in shared memory.

Trigger types match exactly or with wildcards:

<trigger name="AnyAudio" type="input.audio.*" />
<trigger name="AnyRaw"   type="input.*.raw" />

Because crank code refers only to trigger/post names, rerouting is spec-only: change a trigger's type from input.audio.raw to input.audio.smoothed and the module's code is untouched.

Signals

Signals are the fast lane: no subscription checking, no complicated routing. Think of a conductor's stick beating time — every subscriber to signal A triggers shortly after it is sent. Signals are independent of contexts and are typical in simulations: do the work for a time step, then wait for the next beat.

<module name="Stepper">
  <signal name="Input"  type="My.Signal.2" />
  <crank name="signal" function="Signal" />
  <signal name="Output" type="My.Signal.1" />
</module>

The built-in Signal crank makes a signal-based ping-pong analogue possible with no user code. Only name and type are parsed on <signal> — it is deliberately minimal.

Dataflow parameters

ParameterWhereEffect
after / delaytriggerDelay delivery: <trigger ... after="5000" /> fires 5 s after the message arrives (after is an alias of delay).
intervaltriggerArtificial trigger at a regular interval: <trigger name="Post" type="Regular.Post" interval="1000" />.
topostDirect copy to a named component regardless of its subscriptions: <post type="Data.Web" to="WB1" />. The message is also still delivered to normal subscribers.
maxagetrigger / retrieveOnly wake the module if the message is younger than this — skip triggers when lagging behind. (Not a <post> attribute.)
Note. An interval timer only starts after the module has first been triggered by a real message. For a whiteboard, to="WB1" on a post and a type subscription on the whiteboard are equivalent ways to store the message.

Filters

Triggers can narrow what they accept with attributes:

<trigger name="input" type="msg.1" maxage="20" />              <!-- skip messages older than 20 ms -->
<trigger name="input" type="msg.1" from="MyOtherModule" />    <!-- only from that component -->
<trigger name="input" type="msg.1" to="SomeModule" />         <!-- only messages addressed with to= -->
<trigger name="input" type="msg.1" tag="SomeTag" />           <!-- only messages carrying the tag -->

Advanced filtering: <filter> children

Every filter must pass for the trigger to fire. A worked example of each of the 8 operations (the last, maxage, is written as a trigger attribute, not a <filter> child):

<trigger name="input" type="msg.1" maxage="5000">                <!-- maxage: message younger than 5000 ms -->
  <filter type="haskey" key="SomeKey" />                        <!-- entry exists (any type; no value=) -->
  <filter type="equals" key="camera" value="front-left" />      <!-- string ==, exact, case-sensitive -->
  <filter type="notequals" key="state" value="disabled" />      <!-- string != -->
  <filter type="equalsnumeric" key="mode" value="3" />          <!-- float == -->
  <filter type="notequalsnumeric" key="channel" value="0" />    <!-- float != -->
  <filter type="greaterthan" key="temp" value="80" />           <!-- float > -->
  <filter type="lessthan" key="pressure" value="2.5" />         <!-- float < -->
</trigger>
Filter typeComparison
haskeyThe entry exists (key only, no value; any entry type — string, number or binary)
equalsString equality — case-sensitive, exact, no wildcards
notequalsString inequality
equalsnumericFloat equality, converting from string/int as needed (epsilon compare)
notequalsnumericNumeric inequality
greaterthanNumeric greater-than (values equal within epsilon also pass, i.e. effectively ≥)
lessthanNumeric less-than (equal values also pass, i.e. effectively ≤)
maxage (trigger attribute)Message created less than maxage milliseconds ago; stored internally as a filter
Warning. Unrecognised filter types are silently ignored — a typo in type= means the filter simply never rejects anything. Double-check spelling against the table above.

The delivery pipeline

messagemsg.1 type matchincl. wildcards context active?see ch. 6 filtersmaxage/from/to/tag crank rejected: discarded (no wake-up)
A message must pass the type match, the active-context check and every filter before the crank is invoked.

Tagging

Two complementary tagging mechanisms group messages of one topic or identity across differing types:

  • String tags. Posts can carry tag="mytag"; triggers can filter with tag="mytag". All downstream messages in the forward chain carry the tag, so a whole processing chain about one entity stays identifiable.
  • Propagating integer tags. A component can set a 32-bit unsigned integer tag on its output messages; the tag is automatically propagated to all consequent output messages of every receiving module — if A tags 10 and posts to B and C, everything B and C (and their descendants) produce carries 10.
<post name="Face" type="vision.face.found" tag="person42" />
<trigger name="ThisPerson" type="vision.*" tag="person42" />
Tip. On triggers, posts and components, tag="*" means "use my own name as the tag" — handy when instancing similar modules.

Guaranteed vs non-guaranteed delivery

By default every posted message is guaranteed: the messaging layer keeps it until each matching subscriber has received it, even under load. Set guaranteed="no" on a <post> to mark it best-effort (internally MESSAGE_NON_GUARANTEED):

<post name="Frame" type="sensor.video.frame" guaranteed="no" />

Best-effort messages may be dropped rather than queued when a consumer is slow or a buffer is full — the sender is never blocked. Use it for high-rate, latest-value-wins streams (camera frames, telemetry ticks) where a stale frame is worthless and you would rather lose one than build a backlog. Keep the default (guaranteed) for commands, state changes and anything a downstream module must not miss.

Note. guaranteed="no" is about buffering/backpressure behaviour, not about ordering or acknowledgement. Combine it with maxage on the consumer's trigger to also skip frames that arrived too late to be useful.
Psyclone AIOS · CMLabs