VSIKnowledge

Running and Controlling

The stimulus glue: counters, monitors, traces and per-bus rates

A generated twin does nothing by itself: every signal stays at zero and every component sends its zeros each step. The glue in innexis-vsi\twin_glue derives a stimulus from the twin's own command file so that any twin, however large, can be exercised and checked without writing behaviour by hand.

Roles, decided from the command file

  • A signal that is the source of a connect signals line PRODUCES: it carries an 8-bit counter, distinct per signal, advancing every period steps.
  • The destination of a connect signals line CONSUMES: it is monitored (last value, change count) and traced.
  • On a CAN or LIN frame id defined by several components, the FIRST component defining it produces its frame signals and every other node consumes the same-position signal.
  • Everything else (Ethernet sockets carry no signals) is traced only, and the checker reports such components as "nothing to check", never as passes.

The produced value is one expression, (k times 16 plus step divided by period) and 0xFF, in generic_stim.h for C++ and generic_stim.py for Python, and the checker's self-test proves the two agree with its own copy.

Per-bus periods (EXERCISED 2026-09-08)

Each producing bus, the generator's # bus NAME [PROTOCOL] group that a connection belongs to, gets its own period from the list 1, 2, 3, 4, 5, 6, 8, 10, 12 in file order, baked into the generated call as twin::produce(g_step, k, period). On Wildfire that gives nine distinct rates, from the rangefinder serial link changing every step to the CAN control bus changing every twelfth. The table lives in glue_wiring.json under buses, and every produce entry carries its period as a third element. These are a stimulus schedule chosen to make rates distinguishable in a visualisation; Teamcenter records no signal timing, so they are not the real system's rates and must not be presented as such.

The bus name in that comment is read as one token. A name with a space folds every flow into one unnamed bus at period 1, silently: the twin still builds, runs and passes the link check with the wrong rates. Generators in this workspace write names without spaces.

Seeing the rates

Those nine periods are nine visible densities of traffic on an animated page. One symbol leaves its sender for every producer value change, travels to its bus, and fans out to the components that subscribe to it:

Append #t=36.8 to any of them to open paused at that step, which is how to catch the CAN broadcast mid fan-out.

The pages carry the same three caveats this one does, stated on the page rather than left to a reader: a message is derived from a value change because VSI logs no packets, the drawn transit time is a visual convention rather than the 0-or-1-step delivery lag, and the periods are a stimulus schedule rather than the real system's timing. They are generated by vsi_flow.py in the vsi-message-flow repository, which ships a checker with negative controls that fails the build if those sentences are edited out.

CAN consumers are muted

vsiBuild emits the send call in every node that has output or in-out signals on a frame, and it sends unconditionally every step. The generator declares every bus signal in-out, so all 22 nodes on Wildfire's frame 0x100 transmit, and 21 of them put a zero-payload frame with the producer's id on the bus. Measured: every consumer read 0 for all 100 steps while the owner sent 96 plus step. The glue mutes each non-owner: in C++ a macro in the globals region rewrites the one sendCanPacket() call site into a harmless call, in Python the gateway alias is rebound to a wrapper that swallows that one method. Neither wraps the send in a block across regions (that also silenced the same component's other sends, measured) nor returns early from a region (that skips the end-of-step handshake and deadlocks the main thread). The muted list is silent_can in glue_wiring.json. The threshold at which drowning starts has not been measured; a valid small control needs consumers that carry a send call, which a three-node twin with input-only consumers does not.

Where the glue goes

apply_glue_generic.py TWIN.vsi.cmd TWIN_DIR patches three user regions of every component, recognising both the C++ // and the Python # region markers:

  • "Global Variables and Definitions": the include or import, the trace object, the consumer monitors, and for a muted CAN consumer the mute.
  • "Before sending the packet": one produce assignment per produced signal.
  • "After sending the packet": one observe per consumed signal, the trace row, and the step increment.

It copies both stimulus files into the twin's src, writes glue_wiring.json (roles, frame owners, muted nodes, the bus table, the language per component) and, for every Python component, compiles the file to catch a skeleton that is not valid Python. Re-run it after every generate; it rewrites all three regions each time.

A Python client runs from the twin root rather than from vsi.sim\_logs, so the Python trace writer redirects into vsi.sim\_logs when it can see that folder, and one traces directory serves the checker for a mixed twin.

The scripted alternative: msp2_glue

For a small twin whose behaviour matters more than coverage, msp2_glue is the model: a header with a scenario (card insertion at step 3, boot to healthy at 20, a fault at 60 that clears at 65, a heartbeat stall from 70 that trips a watchdog at 75), a host-side test program that proves the scenario with negative controls before any twin is built, and an apply script that writes the calls into the seven skeletons by region name. The run is judged by reading the traces against the scenario, and it matched exactly with the designed one-step lag on every reaction.

Source: innexis-vsi/twin_glue/ (generic_stim.h, generic_stim.py, apply_glue_generic.py, README.md), msp2_glue/ (EXERCISED 2026-09-06 to 2026-09-08) · retrieved Tue Sep 08 2026 00:00:00 GMT+0000 (Coordinated Universal Time)