Simulator networking and management
Reach apps on a VM, update its OS, manage VMs, and pass a serial device through.
This page continues from Simulator, which covers what the simulator is and how to create and start a VM.
Trying a change before it merges
Every pull request to WendyOS-Builder builds a VM image, so a change can be run without hardware and without waiting for a release:
wendy vm create review --pr 1234
wendy vm start review--pr takes the pull request number and stands in for the version: a PR
publishes exactly one build, so it cannot be combined with --version,
--nightly or --image.
A pull request's image is built from an unmerged branch, so booting one runs
code that has not been reviewed — on your machine, with a real filesystem and a
real network stack. Treat --pr the way you would treat running that branch's
build script: only for branches you trust. wendy vm create --pr says the same
thing when it starts.
Networking
wendy vm start offers two networks. Both are discoverable from your own
machine; the difference is whether anything else can reach the VM.
--net | Setup needed | Listed by wendy discover | On your network |
|---|---|---|---|
user (default) | none | Yes — Simulator tab | No |
shared | one-time socket_vmnet install, macOS only | Yes — Local tab via mDNS | Host-shared segment |
The default forwards the agent's port to your machine and needs no privileges.
Nothing on your network can reach the guest. Its mDNS announcement does leak
out (QEMU passes the guest's multicast through one way), advertising an address
nobody can dial — wendy discover recognises that sighting as your VM and
keeps it off the Local tab. A user-mode VM is listed only on the
Simulator tab, which shows stopped ones too; in JSON output it is under
simulators as vm:<name>.
Choose shared to test mDNS or reach the guest directly by IP on the host's
vmnet segment. Its reachable entry stays on Local (and in JSON's
lanDevices); the Simulator tab still lists its local lifecycle state.
This is not a bridge onto the physical LAN; reachability from another machine
depends on routing and the helper's configuration:
wendy vm start dev --net sharedThe helper it needs is socket_vmnet; if it is missing, wendy offers to run
brew install socket_vmnet and sudo brew services start socket_vmnet for you.
Both are also fine to run by hand.
--net shared is macOS only — it builds on Apple's vmnet framework. On Linux
use the default --net user and reach the VM by address.
On macOS 15 and later the first wendy discover may ask for Local Network
permission. Discovery cannot see anything until you allow it.
Reaching an app you deployed
--net user puts the guest on a private NAT. The address the guest sees itself
at — something like 10.0.2.15 — does not exist on your machine, and nothing
routes into that network. Only forwarded ports are reachable, and the launcher
forwards three:
| Port | Carries |
|---|---|
50051 | the agent |
50052 | the agent again, once the device is provisioned and on mTLS |
5000 | the device's container registry, which wendy run pushes to |
On macOS and Linux, wendy run also forwards each declared http entitlement
and TCP readiness port to the same port on 127.0.0.1. The app URL uses that
loopback address, and readiness checks use it too. This works with background
VMs and detached applications; forwards last until the VM stops and are
recreated on the next run.
For an app with a declared http endpoint, capture
the detached result and fetch the reported host URL:
wendy --json --device vm:dev run --yes --detach > deploy.json
url=$(jq -er '.url' deploy.json)
curl --fail --retry 10 --retry-connrefused --retry-delay 1 --max-time 5 "$url"vm:dev selects the device; it is not a DNS hostname. The result's
readiness: "not_checked" means start was acknowledged, so check the response
against your app's expected behavior. If url is absent, inspect the declared
HTTP endpoints, forwarding and logs. See the detached result reference
for fields and multi-service output.
If another application or VM owns the host port, deployment reports a forwarding
error. Free that port or choose a different application port. Other ports are
not automatically exposed. VMs started with an older launcher must be restarted
once to enable this feature. On --net shared the guest is on your network
directly and none of this applies.
The launcher manages these forwards through a Unix control socket in the VM's
directory, whose permissions are enforced as 0700 on each launch. There is no
TCP control listener. Other user accounts cannot traverse the directory, but
processes running as your user (and root) can connect and have full QEMU control.
If port 50051 is taken
Prefer --device vm:<name> (or save it with wendy device set-default vm:<name>) over a literal loopback address. The alias follows the VM's current
port and keeps enrolled-device trust pins separate for each VM, so two VMs on
different ports never share one. While a VM is running, a literal
127.0.0.1:<port> on its forwarded agent port, or on the mTLS port after it,
is that VM too: it is pinned as vm:<name>, and reconnects to the VM by name
after an agent update.
Every other loopback address (another port, localhost, ::1) shares the pin
of its bare host (127.0.0.1, localhost or ::1), as it always has, whatever
the port. That includes the VM's own addresses once the VM is stopped. A
connection to a running VM through its typed 127.0.0.1 address also records
the VM's identity in the shared 127.0.0.1 pin when that pin is not set yet,
or names only the VM's organization, so the address stays protected while the
VM is stopped; it never replaces a 127.0.0.1 pin that names another device.
To replace an enrolled VM intentionally, run wendy device unpin vm:<name>
before connecting to the replacement. It clears only the VM's own pin, never
the shared 127.0.0.1 pin that other loopback addresses use. That pin is
cleared by wendy device unpin 127.0.0.1.
The agent's port is often already in use on a development machine. wendy vm start and --device vm:<name> then move the host side of the forward to the
next free port on their own, and the VM keeps that port for later starts. To
choose it yourself, pass --port; a port given that way that is in use is an
error rather than a hint to move:
wendy vm start dev --port 50151
wendy --device vm:dev device infoThe alias follows the VM to its new port.
Updating the OS
The VM is partitioned exactly like a device — two rootfs slots plus a growable
/data, with GRUB choosing between them through grubenv — so A/B OTA works
here the same way it works on hardware:
wendy --device vm:dev os updateThe update is written to the slot that is not running, the VM reboots into it,
and the new slot is committed once the agent comes back. /data is untouched,
so containers and app data survive.
device info reports both slots under osUpdate.engine — currentSlot is the
one running, and each entry names its partition, so you can watch A on
/dev/vda3 hand over to B on /dev/vda4:
wendy --device vm:dev device info
wendy --device vm:dev os update-status # how the last update endedThe agent returns on the same forwarded port after the reboot, so nothing needs
restarting on your side. wendy vm logs <name> shows the reboot and the slot
GRUB picked, which is the quickest way to watch an update land.
This makes the simulator the cheapest way to rehearse an OS update — including a rollback — without tying up a Jetson or a Raspberry Pi.
Managing VMs
wendy vm is hidden from wendy --help on purpose — the simulator is meant to
be reached through wendy run or --device sim. The subcommands below are all
still available for when you need to look under the hood.
wendy vm create # prompt for a VM name and simulator profile
wendy vm list # every VM, with its state, address and version
wendy vm start dev -d # run it in the background instead of taking the terminal
wendy vm logs dev -f # the console of a backgrounded VM
wendy vm stop dev # request graceful guest shutdown
wendy vm rm dev # delete a VM and its diskwendy vm create prompts for a name and a profile: Generic WendyOS, Unitree Go2,
Unitree G1, LeRobot SO-101, Yahboom ROSMASTER R2, or Wendy Lite (WASM or native).
Supply the name or --profile generic|go2|g1|so101|rosmaster-r2|wendy-lite|wendy-lite-native
to skip that prompt.
For scripts, pass the name explicitly; the profile defaults to generic without
an interactive terminal, with --json, or with --yes.
The VM name is optional for start, stop, logs, and rm in an interactive
terminal. Leave it out to choose a VM from a list; scripts should continue to
pass the name explicitly.
vm stop waits up to five minutes for the guest to stop its apps, shut down and
flush its filesystems. If QMP is unavailable
or the guest is still shutting down after that, it reports an error without
killing the VM. Shut down inside an older guest without QMP, or
use wendy vm stop dev --force only when an immediate power cut is intended.
A backgrounded VM outlives the command that started it, so closing the terminal
or pressing Ctrl-C does not power it off. wendy vm rm refuses to delete a
running VM unless you pass --force.
VMs live in ~/.wendy/vms/<name>/. Removing one deletes its disk, which cannot
be undone — but the image you created it from is untouched, so you can always
make a fresh one.
Published VM images are cached by their manifest SHA-256, not by the version label. Cached and newly downloaded bytes are verified before use, so rebuilding a PR under the same label cannot silently reuse the old image. If no checksum is published, each create downloads afresh and removes that temporary download after provisioning. Existing VM disks are never refreshed implicitly.
New VMs receive a random locally-administered MAC stored in meta.json, stable
across restarts and distinct even when another host creates a VM with the same
name. Older VMs without a stored MAC retain their legacy name-derived address
to avoid changing their network identity on upgrade. If two such legacy VMs
share a name, do not place both on the same shared network; recreate one to
allocate a fresh address. Copying an entire VM directory also copies its MAC.
A freshly created VM's agent is unauthenticated until the device is provisioned,
and it is reachable on 127.0.0.1. On a machine you share with other people,
that means any local account can talk to it — including enrolling it — in the
window before you do. Provision it promptly, or use a machine you do not share.
Options
| Flag | Default | Purpose |
|---|---|---|
--image | — | Create from a local .wic instead of downloading |
--version | latest | Version to download |
--nightly | off | Download a nightly rather than a release |
--disk | 16 | Disk size in GiB |
--net | user | user or shared — see above |
--port | 50051 | Host port forwarded to the agent |
--memory | 4096 | Guest memory in MiB |
--cpus | 4 | Guest CPU count |
--detach / -d | off | Run in the background rather than on this terminal |
--yes / -y | off | Accept prompts, including installing QEMU on macOS and Windows |
Serial passthrough
A Windows, macOS or Linux host can forward one serial port to a WendyOS ARM64 VM as a QEMU USB serial adapter. Install the adapter's driver if your host needs one, find its host name and the exact baud required by your device, then save the mapping. For example (replace the name, port and baud):
| Host | Port looks like | Find it with |
|---|---|---|
| Windows | COM12 | Device Manager → Ports (COM & LPT) |
| macOS | /dev/cu.usbmodem1101 or /dev/cu.usbserial-… | ls /dev/cu.* before and after plugging in |
| Linux | /dev/serial/by-id/usb-… (stable) or /dev/ttyACM0 | ls -l /dev/serial/by-id/ |
wendy vm serial my-vm --port /dev/cu.usbmodem1101 --baud 1000000
wendy vm serial my-vm
wendy vm start my-vmThe first command saves serial.json inside this VM's directory under the Wendy
configuration directory. The second prints the mapping as JSON (null if none).
Each foreground start loads the same mapping. Stop the VM before changing it.
To disable passthrough, run wendy vm serial my-vm --clear while it is stopped.
Configuration does not open the port; availability and driver support are checked
at the next start. A mapping saved on one host type is rejected on another.
On macOS use the cu. node rather than tty.. On Linux, prefer the
/dev/serial/by-id/ name: ttyACM0 can change when devices are replugged. If
the start reports permission denied, add your user to the device's group
(usually dialout) and log in again.
The host line is fixed at the requested baud, 8 data bits, no parity, one stop bit, no handshake, DTR and RTS disabled. Wendy submits the exact baud to the host driver and never selects a nearby baud: a rate the driver refuses fails the start. Windows also checks the driver's reported settings. A driver can still round the rate internally, so no host check proves the adapter's actual electrical baud. Guest termios baud changes and modem-control commands do not change the host line. The bridge forwards binary bytes in both directions without line processing.
The guest's existing ftdi_sio driver normally exposes /dev/ttyUSB0. Check the
actual guest path, then grant your app its serial entitlement, for example:
{"type":"serial","device":"ttyUSB0"}No guest image modification is required. Keep wendy vm start open in its
terminal and use a second terminal for ordinary app commands targeting
--device vm:my-vm. The bridge runs inside that launcher, so background starts
(--detach) and automatic starts through workflows that require detaching are
refused for a VM with a mapping; start it explicitly first. On macOS and Linux,
wendy vm stop my-vm shuts it down as usual. Windows does not yet have the
managed QMP monitor: shut down inside the guest, use Ctrl-A then X, or
explicitly cut power with wendy vm stop my-vm --force.
Each VM gets a dynamically allocated listener bound only to 127.0.0.1; the
listener closes after QEMU connects. There is no LAN listener or fixed bridge
port. Local processes are trusted: this TCP connection has no authentication.
The port is held exclusively while the VM runs, so two VMs cannot share it. On
macOS and Linux Wendy also takes the advisory lock that pyserial-based tools
(esptool, idf.py monitor) use, so they see the port as busy. Different VMs
may use different ports (and must also have distinct forwarded agent ports,
using vm start --port where necessary).
Normal stop, QEMU exit and startup failures release the port and listener. A
serial I/O error or cable disconnect that the host driver reports stops QEMU
and prints an actionable error; reconnect the device and restart the VM. There
is no silent reconnect. If the launcher is forcibly terminated, check for a
remaining QEMU process before starting again. On macOS and Linux, use
wendy vm stop my-vm; on Windows, check Task Manager.
Check your serial connection
After starting the VM, confirm that your app can open the guest serial device and exchange the data it expects. Saving a mapping does not verify the adapter, its baud rate, or the attached hardware. Start with your device's documented read-only checks before enabling any physical controls.
If the cable disconnects or a serial I/O error stops the VM, reconnect the device and start the VM again. If another program reports the port busy, stop the VM before using the port elsewhere. One serial device can be mapped per VM; this does not provide general USB, camera, or GPU passthrough.