Skip to content
| Marketplace
Sign in
Visual Studio Code>Education>NorrDog Studio ConnectorNew to Visual Studio Code? Get it now.
NorrDog Studio Connector

NorrDog Studio Connector

NorrSpect AB

|
4 installs
| (0) | Free
Write Python for the NorrBot Studio robot dog in VS Code — connect to the desktop Studio or a real Go2, drive motion, and pull camera frames.
Installation
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.
Copied to clipboard
More Info

NorrDog Studio Connector

Write Python for the NorrBot Studio robot dog in VS Code instead of the browser.

Press Connect to Desktop, run a .py file, and the same norrdog SDK students already use in the Code Studio drives either the simulator running in their browser or a real Unitree Go2 over rosbridge — except that now the code runs in their own Python, so pip, breakpoints, NumPy, OpenCV and frame.save("shot.jpg") all work.

Currently supports the Robot Dog (Unitree Go2). The robot picker is where future platforms will appear.


How it fits together

        VS Code                                   the desktop
┌────────────────────────┐
│  NorrDog panel         │
│   Robot   Robot Dog ▾  │
│   Target  Simulator ▾  │      ws://127.0.0.1:8010    ┌──────────────────┐
│  [ Connect to Desktop ]│◄───────────────────────────►│  Studio page     │
│                        │                             │  window.Norrdog  │
│   link hub ────────────┤                             └──────────────────┘
└────────┬───────────────┘      ws://<ros-laptop>:9090 ┌──────────────────┐
         │                 ◄───────────────────────────►│ rosbridge → Go2 │
         │ 127.0.0.1:8011                               └──────────────────┘
         ▼
┌────────────────────────┐
│  your program.py       │
│  from norrdog import * │
└────────────────────────┘

The extension hosts a small hub. The Studio page dials it (a browser can open a socket but not accept one); your Python dials it too, over plain TCP so the SDK needs nothing but the standard library. Op names are the ones in go2_simulator/web/code.js's BRIDGE table, which is what norrdog.py calls inside Pyodide — so a program written for the Code Studio runs here unchanged.

Getting started

  1. Install the extension.
  2. Open the NorrDog Studio view in the activity bar and press Connect to Desktop, then Open Studio. Sign in as usual — it is the live site at academy.norrspect.com, just served from your own machine.
  3. The status dot turns green once the page attaches.
  4. Run NorrDog: Create Example Project, then press ▶ on 01_motion.py.

Nothing else to install. No simulate.py, no local copy of the Studio.

from norrdog import dog, wait

dog.stand()
for _ in range(4):
    dog.move(1.0)
    dog.turn(90)

frame = dog.camera("front")
frame.save("shot.jpg")
print(frame.dominant_color())

Why the Studio is served from 127.0.0.1

The extension mirrors the hosted Studio at http://127.0.0.1:8009 and opens that, rather than sending you to https://academy.norrspect.com directly. The content is identical and live — it is fetched from the site on every request.

This is not a preference. A page served from a public https origin is not allowed to open ws://127.0.0.1: Chrome refuses it with

net::ERR_BLOCKED_BY_LOCAL_NETWORK_ACCESS_CHECKS

and granting the localNetworkAccess permission does not lift it. (Mixed Content is not the rule involved — ws://127.0.0.1 is a potentially trustworthy URL, so that check passes. Local Network Access is a separate, newer rule about public sites reaching your machine.) Serving the page from loopback makes the whole exchange local-to-local, which no browser objects to.

Two details make the mirror work rather than merely load: absolute references to the live origin are rewritten so Supabase at /sb stays same-origin and passes the site's own connect-src 'self' CSP, and binary assets (the ~25 MB of robot meshes) stream through with their compression intact.

To point at a different Studio, set norrdog.studioUrl. If you are running simulate.py yourself, set it to http://127.0.0.1:8000 — the extension detects a loopback address and skips the mirror.

Keep the Studio window beside VS Code, not behind it

The simulator advances motion inside the browser's requestAnimationFrame loop, and browsers stop that loop for a window that is hidden, minimised or completely covered. So with the Studio buried behind VS Code, dog.move() has nothing to run in.

Arrange the two side by side. The panel says "window hidden, motion paused" when the loop has stopped, and a motion command fails straight away with an explanation instead of hanging until it times out. Sensor reads and dog.camera() are plain function calls and keep working either way.

Only one Studio page can hold the link at a time — the most recent one to connect wins. If several tabs are open, close the extras so commands go to the window you are actually watching.

What you get

Connect to Desktop One button, at the top of the panel. Simulator or real robot.
Run File on Robot ▶ in the editor title bar for any .py. Runs in your interpreter, in a real terminal.
Stop Kills the program and halts the dog. Ctrl+C in the terminal does the same.
Camera Live preview in the panel at 4 fps, or NorrDog: Open Camera Preview for a full-size frame.
Activity log Link state, MQTT traffic, program start/stop.
Snippets ndimport, ndsquare, ndcapture, ndframes, nduntil, ndfind, ndvelocity, ndsubscribe.

The SDK

Identical to the browser's norrdog, method for method:

motion    move  turn  strafe  velocity  jump  sit  stand  stop  wait
sensors   imu  pose  twist  ultrasonic  battery  state
camera    camera() -> Image
          Image: save  to_bytes  array  show  dominant_color  brightness  find  format
mqtt      publish  subscribe  get_message
world     set_terrain  reset

Four differences, all of them because your code is no longer inside a browser:

  • No await, and no AST rewriting. The browser version rewrites your program so synchronous-looking code can drive an async simulator living in the render loop. Here the calls really are synchronous.
  • time.sleep() is just time.sleep(). There is no render loop to freeze, so nothing is rewritten. wait() still exists and is interruptible by Stop.
  • Image.save() writes a real file, and Image.array() gives you a NumPy RGB array (needs pillow and numpy).
  • dog.frames(seconds, fps) is new — a generator that yields frames, which is the short way to pull a clip off the camera.

Stop raises RobotStopped in your program, exactly as the Stop button does in the Code Studio.

MQTT reaches across both

The hub carries the studio's virtual broker, with the same +/# wildcards. A Blockly script in the Studio page and a Python file in your editor share the same topics in both directions, and everything published from VS Code shows up in the Studio's MQTT panel.

Installing the SDK into your own Python

Run File on Robot needs no install — it injects the bundled copy on PYTHONPATH for that one run. Install it properly when you want import norrdog to work everywhere else: a plain terminal, a notebook, Code Runner, pytest, or another editor.

pip install norrdog                 # https://pypi.org/project/norrdog/
pip install "norrdog[vision]"       # + Pillow/NumPy for Image.array()

Install it into the same interpreter VS Code has selected, so the editor and your terminal agree. Inside a virtualenv, activate it first.

Once installed, nothing else is needed — the SDK dials 127.0.0.1:8011 by default, which is where the extension listens:

python3 my_program.py

Set NORRDOG_LINK=host:port only if you changed norrdog.clientPort. The link still has to be connected either way — the extension owns the hub.

NorrDog: Show Python SDK Path prints the bundled copy's path and copies it to the clipboard, for the PYTHONPATH route if you would rather not install.

Settings

Setting Default
norrdog.robot dog Which robot. Only the dog today.
norrdog.target simulator simulator or robot.
norrdog.studioUrl https://academy.norrspect.com The Studio to mirror and open.
norrdog.serveStudioLocally true Mirror it on loopback. Required for any https Studio.
norrdog.proxyPort 8009 Port the mirrored Studio is served on.
norrdog.rosbridgeUrl ws://127.0.0.1:9090 Used when the target is robot.
norrdog.linkPort 8010 Loopback port the Studio page dials.
norrdog.clientPort 8011 Loopback port the Python SDK dials.
norrdog.pythonPath (empty) Interpreter for Run. Empty uses the Python extension's.
norrdog.autoConnect true Start the link when the workspace imports norrdog.
norrdog.openStudioOnConnect true Offer to open a Studio page if none attaches.

Both listeners bind to 127.0.0.1 only — nothing on your network can reach them.

Real robot

Selecting Real robot talks to rosbridge_websocket with the topology LIVE_MODE_PLAN.md §4.1 specifies:

VS Code --/cmd_vel_teleop--> [cmd_vel_watchdog_node] --/cmd_vel--> driver --> Go2
        <--camera/image_raw------------------------------------------------

It never publishes to /cmd_vel directly, so the dead-man's switch always stays in the path. A move streams its twist at 10 Hz for the whole travel time — publishing once and sleeping (as live-link.js does) lets the 1.0 s watchdog stop the dog early on anything longer than a second.

The driver exposes /cmd_vel_teleop and camera/image_raw only, so move, turn, strafe, velocity, stop, wait and camera work, and pose/twist/imu work if something is publishing /odom or /imu. Everything else — jump, sit, stand, ultrasonic, battery, state, label, set_terrain, reset — raises an error saying so rather than pretending. Closing the link publishes a zero twist first.

Turn rate is still TURN_RATE_DEG_S = 45 in src/link/backends/rosbridge.ts, carried over from live-link.js and not yet measured on hardware. Tune it once the Go2 is in the room.

Building

cd vscode-extension
npm install
npm run compile          # bundles to dist/extension.js
npm test                 # typecheck + both end-to-end suites
npx vsce package         # -> norrdog-studio-connector-0.1.0.vsix

Press F5 in VS Code to launch an Extension Development Host.

Tests

Neither suite mocks the parts that matter. test/e2e.mjs runs the real hub and the real Python SDK in a real interpreter against a stand-in Studio page; test/rosbridge.mjs does the same against a stand-in rosbridge, and checks the twist stream, the raw-pixels-to-PNG path and the colour classifier against runtime.js's exact thresholds.

The browser half

go2_simulator/web/desktop-link.js is the other end of the link, loaded by both index.html and code.html. It retries the loopback port quietly and is a complete no-op when the extension is not running — the same pattern main.js already uses for the camera bridge on port 8001.

  • Contact us
  • Jobs
  • Privacy
  • Manage cookies
  • Terms of use
  • Trademarks
  • Your Privacy Choices
  • Consumer Health Privacy
© 2026 Microsoft