WendyOS Docs
Device management

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.

--netSetup neededListed by wendy discoverOn your network
user (default)noneYes — Simulator tabNo
sharedone-time socket_vmnet install, macOS onlyYes — Local tab via mDNSHost-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 shared

The 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:

PortCarries
50051the agent
50052the agent again, once the device is provisioned and on mTLS
5000the 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 info

The 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 update

The 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 ended

The 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 disk

wendy 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

FlagDefaultPurpose
--image—Create from a local .wic instead of downloading
--versionlatestVersion to download
--nightlyoffDownload a nightly rather than a release
--disk16Disk size in GiB
--netuseruser or shared — see above
--port50051Host port forwarded to the agent
--memory4096Guest memory in MiB
--cpus4Guest CPU count
--detach / -doffRun in the background rather than on this terminal
--yes / -yoffAccept 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):

HostPort looks likeFind it with
WindowsCOM12Device 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/ttyACM0ls -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-vm

The 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.

On this page