Skip to content

Simulator

Note: The simulator supports Node and Python edge apps, screen apps, and web apps (see the note under Running Apps). Any project scaffolded with phy app init is ready to run against the simulator out of the box.

The local device simulator simulates the on-device environment and runtime locally, letting you test application logic, settings behavior, and more. You can also test messaging between multiple apps running on your computer and connected to the same simulator instance. The simulator runs in offline mode and does not fully replicate the real device environment, so always verify your app on a real device before publishing.

The simulator ships as its own package, separate from the PhyStack CLI:

Terminal window
npm i -g @phystack/device-simulator

This provides the phy-simulator binary. App templates already integrate with it out of the box — their dev script runs the app against the simulator for you.

The simulator expects your project to follow a specific structure. Projects scaffolded with phy app init meet these requirements automatically.

  • package.json at the project root with:
    • "application-type": used for auto-detection. You can also pass --type to override this.
    • "name": used to identify the app in the simulator
  • A "start" script in package.json (e.g., "start": "vite"), this is used as the launch command
  • Settings in src/settings/index.json (optional, defaults are generated from schema.ts if the file doesn’t exist)
  • With Dockerfile (recommended): a Dockerfile at the project root. The simulator builds the image and runs the container with environment variables injected. Container configuration is read from settings.json at the project root. An explicit --dev-command skips the Docker build and runs your source directly — this is what the templates do for the fastest feedback loop.
  • Without Dockerfile: falls back to the "start" script in package.json, similar to screen apps
  • Settings in src/settings/index.json (optional, same behavior as screen apps)

If package.json is missing, the simulator exits with an error. If application-type is missing and --type is not passed, it will fail with “Cannot detect app type.”

The simulator is a standalone server that runs in the background. Start it in a separate terminal before running any apps:

Terminal window
phy-simulator start

This can be run from any directory, the simulator listens on port 55000 and serves all apps launched against it.

OptionDescriptionDefault
-p, --port <port>Port to listen on55000

Note: phy-simulator run always connects to port 55000, so keep the default port unless you have a conflict you can resolve another way.

Once the simulator is running, launch an app against it. If you scaffolded from a template, just use the wired-up script:

Terminal window
bun run dev

which invokes the simulator under the hood, e.g. phy-simulator run . --dev-command 'bun src/app.ts'. You can also run it directly:

Terminal window
phy-simulator run <path>

To run the app in the current directory:

Terminal window
phy-simulator run .
OptionDescriptionDefault
--type <type>Override app type (screen, edge, or web)Auto-detected from application-type in package.json
--dev-command <command>Override launch commandThe start script from package.json (edge apps with a Dockerfile build and run the container instead)
--settings-dir <path>Path to the directory containing index.json with local settingssrc/settings

Web apps: run with phy-simulator run . --type web (or set "application-type": "web" in package.json). The simulator writes public/boot.json pointing your app at the local simulator instance, then starts the dev server. Open http://localhost:3000/#code=dev — any #code= value works against the simulator, and 3000 is the template’s Vite port.

The simulator persists a twin identity per app, so your app keeps a stable instance ID across restarts.

The simulator injects the PHYSTACK_SIMULATOR_URL environment variable, which instructs the PhyStack SDK in your app to connect to the local simulator instance instead of the edge runtime on the device. It also creates a local simulated twin and provides the mandatory TWIN_ID environment variable so your app can connect when running locally.

For screen apps, the TWIN_ID must be passed to the browser as a URL hash parameter (/#instanceId=<twin-id>). This mirrors how the production screen bootloader delivers the instance ID to screen apps running inside iframes. The Vite config in the screen app template handles this automatically by opening the browser with the correct hash when the simulator is active.

For this reason, your launch script should ensure these environment variables are propagated to your app on start. Projects scaffolded with phy app init configure this for you out of the box.

When you run phy-simulator run, it performs the following steps:

  1. App type detection, reads application-type from package.json, or uses the --type flag if provided
  2. Twin and settings setup, creates a virtual simulated twin identity for the app (reusing the persisted instance ID on subsequent runs) and uses your local settings from src/settings/index.json (generates defaults from schema.ts if the file doesn’t exist)
  3. Launch:
  • Screen apps runs the resolved launch command (your custom --dev-command, or the start script)
  • Edge apps with a --dev-command runs your source directly — no Docker required
  • Edge apps with Dockerfile builds a Docker image and runs the container with Docker config from settings.json
  • Edge apps without Dockerfile falls back to the start script

“No package.json found” The simulator requires a package.json at the project root with "application-type": "edge" (or "screen"), this is how it identifies and launches the app, regardless of the runtime inside. Both Node and Python edge templates scaffold this file alongside their Dockerfile. You’ll see this error if you ran phy-simulator run from a directory without a package.json or pointed it at the wrong path. Make sure you run it from your project root.

“Cannot detect app type” Your package.json is missing the "application-type" field. Add "application-type": "screen", "edge", or "web", or pass --type screen, --type edge, or --type web.

“Simulator is not running” Start the simulator first with phy-simulator start in a separate terminal. The simulator server must be running before you can launch apps against it.

“No Dockerfile or start script found” Your edge app has no Dockerfile at the project root and no "start" script in package.json. Add one or the other. For Docker-based edge apps, the Dockerfile should be at the project root (not in a subdirectory).

Settings not loading The simulator looks for settings in src/settings/index.json by default. If your settings are elsewhere, pass --settings-dir <path>. The JSON file must contain settings nested under app.gridApp.settings.

An extra “unknown” instance appears in the simulator log A browser tab from a previously run app is still connected to the shared simulator. The simulator only knows the names of apps launched in the current session, so stale connections show as unknown. Close the old tab and restart the simulator.