VSIKnowledge

Building a Twin

The wizard: make a twin with Python blocks by answering questions

vsi_wizard.py asks what a twin needs, writes the command file, and can carry the twin all the way through generate, glue, build, run and check. For Python blocks it also writes one plain behaviour file per component, so a person writes what a block does without learning the generated skeleton.

Ask, or replay

python vsi_wizard.py
python vsi_wizard.py --answers wizard\Wiz3DT.answers.json --run
python vsi_wizard.py --answers wizard\Wiz3DT.answers.json --run --stimulus behaviour --skip-build

Interactive mode asks, with defaults: twin name and workspace root (short, because Python gateways break the Windows path limit from a deep folder), step and total time, components (name, C++ or Python), ports (name, protocol), signals per port, then links: signal to signal over GenericPayload ports, a CAN frame shared by named ports (the first named port owns it), or an Ethernet server and client pair. Every run writes TWIN.answers.json beside TWIN.vsi.cmd; edit the JSON and replay with --answers rather than answering again.

What it refuses and what it folds

The wizard applies the rules that vsiBuild would otherwise report only after a long generate or, worse, not at all: two ports of one protocol on a component; a port with no signals; a signal that is both a source and a destination; a link over a port that is not GenericPayload; a CAN member that is not a CAN port. It folds UART and GPIO onto GenericPayload (no peer gateway exists between generated components), keeps a Python block that carries an Ethernet port as C++ (vsiBuild generates invalid Python for a socket with no connected signals) and prints a note for every fold, every unlinked signal declared as an output, and every CAN layout it assumed at 8 bits per signal.

What --run does

  1. Establishes the VSI environment itself: it runs the vendor's env_vsi.ps1 in a child PowerShell and harvests the variables, so it works from any shell. It also keeps Git's sh off the child PATH and names cmd.exe as make's shell, because the generated Makefiles use cmd.exe syntax and fail on the first rule under sh (measured).
  2. vsiBuild generates the twin folder.
  3. The generic glue (twin_glue/apply_glue_generic.py) writes counters, monitors and trace writers into every component and glue_wiring.json into the twin folder.
  4. In behaviour mode, each Python component additionally gets src/COMPONENT/COMPONENT_behaviour.py (only if absent, so edits survive re-runs) and its skeleton regions are patched to call it; both files are compiled before anything launches, because a client that dies at start leaves the fabric waiting forever.
  5. mingw32-make compile build (skipped with --skip-build; Python behaviour edits need no rebuild).
  6. vsiSim TWIN.dt --batch --run, bounded at 300 s, followed by a cleanup of any process still carrying the twin's folder on its command line.
  7. The verdict: the fabric log's stop line plus one trace row per step from every component, never the exit code. In generic mode the checker runs and its RESULT line is printed; in both modes the first and last row of every trace is printed.

The behaviour file

def on_step(step, time_ns, inputs):
    outs = {}
    outs['sense_temperature'] = (7 * step + 0) % 256
    outs['sense_pressure'] = (7 * step + 16) % 256
    return outs

inputs is a dict of the component's input signals as last received; the return value is a dict of its output signals for this step. The wizard decides which signals are inputs and which are outputs from the links (a CAN frame's fields are outputs on the owner and inputs everywhere else). Values are ints; CAN fields must stay within 8 bits. The default stub is a saw-tooth, 7 times step, deliberately unlike the generic counters so a trace shows which glue produced a value. Consumers of a frame they do not own are muted the same way the generic glue mutes them.

Proof (EXERCISED 2026-09-09)

wizard\Wiz3DT.answers.json: a Python Sensor (two GenericPayload outputs and a CAN port), a C++ Controller (the two inputs, one output, a CAN port) and a Python Actuator (a UART input folded onto GenericPayload, a CAN port), with the sensor owning frame 0x100.

  • Generic mode: generate 1 s, glue, build 65 s (one C++ client, two Python gateway extensions), run 22 s, checker 7 pass, 0 fail, 0 missing, 5 of 5 producer rates.
  • Behaviour mode, no rebuild: the controller's trace shows the sensor's saw-tooth arriving (7, 14, 21 and so on, one step behind) and the frame fields at 32 plus 7 times step; the actuator's trace shows the C++ counter arriving. Evidence in runs\20260909-0625-wiz3-generic-7pass and runs\20260909-0623-wiz3-behaviour, the latter with the generated stubs and one patched Python skeleton.

Two things found and fixed on the way, both now guarded in the wizard: a doubled percent sign in the first stub template made the actuator exit with a syntax error at start and the fabric waited for it past ten minutes; and launching from Git Bash handed make a sh that cannot run the generated Makefiles.

What it does not do

No virtual platforms, VSI Route, FMU, ROS or RTL components; no Teamcenter or SysML v2 input (those have their own generators and produce the same command file, so the same --run steps apply); no graphical front end. The questions are the same ones a form in Agentcenter would ask, if that is ever wanted.

Source: innexis-vsi/vsi_wizard.py, wizard/Wiz3DT.answers.json, runs/20260909-0625-wiz3-generic-7pass and runs/20260909-0623-wiz3-behaviour (EXERCISED 2026-09-09) · retrieved Wed Sep 09 2026 00:00:00 GMT+0000 (Coordinated Universal Time)