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
- Establishes the VSI environment itself: it runs the vendor's
env_vsi.ps1in a child PowerShell and harvests the variables, so it works from any shell. It also keeps Git'sshoff 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 undersh(measured). vsiBuildgenerates the twin folder.- The generic glue (
twin_glue/apply_glue_generic.py) writes counters, monitors and trace writers into every component andglue_wiring.jsoninto the twin folder. - 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. mingw32-make compile build(skipped with--skip-build; Python behaviour edits need no rebuild).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.- 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-7passandruns\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)