Nodofi Robot Agent
Run Robot projects from Studio on a real Raspberry Pi, Jetson, or PC with a USB Arduino or ESP32. The agent joins the relay so Studio reaches the robot from anywhere — no public IP required.
Overview
The Robot Agent is a small Node.js process that runs on the robot. Studio talks to it over the relay to start, stop and deploy programs. The program itself (the flow's blocks and the engine that runs them) is sent with each run, so the robot always behaves exactly as it did in the simulator.
| Mode | How it works |
|---|---|
| Tethered run | Studio streams live sensor values back to the canvas while the program runs. The robot stops automatically if the Studio tab is closed or the connection drops (heartbeat timeout, default 3 s). |
| Deploy | The program is saved on the robot and starts on its own — no Studio needed. Survives reboots when the agent runs as a systemd service. |
| STOP | Ends any run at once and sets every motor and output to off, from Studio or from the keyboard. |
Safety: the program runs in a child process; the agent process owns the hardware. Whenever a run ends — for any reason, including a crash or a lost heartbeat — every motor stops and every output goes off.
Before you start
- Node.js 18 or later on the robot. Check with
node -v. - On Raspberry Pi, use 64-bit Raspberry Pi OS. The Pi Zero W and Pi 1 use ARMv6, which has no official Node.js 18 build — use the Zero 2 W or Pi 3+ instead, or build Node.js from source.
- The relay URL — the same one Studio uses (
RELAY_URLin Studio's.env). Both sides must connect to the same relay. - The robot-agent folder on the robot. Copy it over SSH or clone the repo.
- Optional: an Arduino, ESP32, Pico or Teensy on USB for motors and analog sensors. Flash it once with StandardFirmata (Arduino IDE → File → Examples → Firmata → StandardFirmata).
Install & configure
1. Copy the folder to the robot
scp -r robot-agent pi@raspberrypi.local:~/robot-agent
Or, if the full repo is already on the Pi, just cd robot-agent.
2. Install dependencies
cd ~/robot-agent
npm install
This installs required packages and tries the optional hardware libraries. Failed optionals are logged but are not errors — install only what your robot needs (see Hardware libraries).
3. Create the .env file
cp .env.example .env
nano .env
Set at minimum:
# The relay Studio uses — must be the same relay
RELAY_URL=https://relay.nodofi.com
# Unique name for this robot on the relay
ROBOT_NAME=my-robot-1
Pick any name for ROBOT_NAME. It must be unique on the relay — Studio finds the robot
by this name.
First run & pairing key
Start the agent for the first time to generate a pairing key:
npm start
The output looks like:
Nodofi Robot Agent v1.0.0
Robot name : my-robot-1
Relay : https://relay.nodofi.com
Pairing key: abc123XYZdef456 ← copy this now
Connected.
data/agent.json and printed once for convenience. If you lose it, delete data/agent.json and restart — a new key is generated and you re-pair in Studio.Pair with Studio
- Open a Robot project in Studio (create one with New project → Robot).
- Click the Run on dropdown in the toolbar → Add a robot…
- Fill in: Relay URL (same as
RELAY_URL), Robot name (same asROBOT_NAME), and the Pairing key printed on first start. - Select the robot in the Run on dropdown, then press Start. Studio sends the program and streams live sensor values back to the canvas.
Hardware libraries
All hardware libraries are optional — install only what your robot uses. Set
HARDWARE=mock in .env to run any program without touching a pin.
| Library | Boards | What it enables |
|---|---|---|
pigpio |
Raspberry Pi 1–4, Zero 2 W | Full GPIO: digital I/O, hardware PWM, servo pulses, microsecond timing for ultrasonic distance
and wheel encoders. Run the agent with sudo.
|
node-libgpiod |
Raspberry Pi 5, Jetson, BeagleBone, other Linux boards | Digital in/out only (no hardware PWM). Put motors, servos and analog parts on a USB board.
|
i2c-bus |
Any Linux board with I²C | I²C chips: MPU-6050, BNO055, QMC5883L, BME280, VL53L0X, TCS34725, LCD, OLED, 7-segment.
Enable I²C in raspi-config first.
|
firmata + serialport |
Arduino, ESP32, Pico, Teensy over USB | Motors, servos, PWM and analog inputs on the USB board. Flash StandardFirmata once.
|
rpi-ws281x-native |
Raspberry Pi (GPIO18 / 12 / 10 / 21) | NeoPixel / WS2812B strips. Requires pigpio and sudo.
|
rpicam-vid / ffmpeg |
Pi Camera (CSI) / USB webcam | Camera blocks: Find color, Motion, QR code, Take photo, Light level.
|
Start at boot
Create a systemd service so any deployed program starts automatically when the Pi powers on:
[Unit]
Description=Nodofi Robot Agent
After=network-online.target
Wants=network-online.target
[Service]
WorkingDirectory=/home/pi/robot-agent
ExecStart=/usr/bin/node src/agent.js
Restart=always
RestartSec=5
User=root
[Install]
WantedBy=multi-user.target
Save the file to /etc/systemd/system/nodofi-robot.service, then:
sudo systemctl daemon-reload
sudo systemctl enable --now nodofi-robot
sudo systemctl status nodofi-robot
View live logs: journalctl -u nodofi-robot -f
Restart after changing .env: sudo systemctl restart nodofi-robot
Agentic AI drives the robot
Studio's Agentic AI pane can drive a robot from a goal in plain words, such as "have the robot find the red ball". It never sends motor values: it runs your Robot projects as skills, picked by their name and description, on the Simulator or a paired robot. The project opens on the canvas, so you watch the robot move while the agent works.
- Build and test each skill in the simulator first. Give it a clear name and a one-line description ("Obstacle avoider — drives forward and turns away from walls"): that is what the agent reads.
- In the Agentic AI pane choose Run Flow tools in Studio, set the relay and the planner
(a model worker such as
JozieAILLM2, planner modelgemma4). - Under Robots the agent may move, tick the Simulator (on by default) and any paired robot.
Guardrails
| Guardrail | What it does |
|---|---|
| Allowed robots only | Real robots are off until ticked. A goal naming a robot that isn't allowed stops the run; the agent never moves a different robot instead. |
| Your approval | "Ask me before a real robot moves" (on by default) shows the skill, its inputs, the robot and the time limit. Declining ends the whole run. |
| Time limit per skill | Default 20 s, at most 120 s; the model can ask for less, never more. |
| Skills only | No motor values or code from the model; at most 3 skills per goal, one per step; an immediate repeat answers instead of moving again. |
| Stop | The pane's Stop or the canvas STOP ends a skill at once; the robot's heartbeat watchdog still applies if the tab closes. |
Skill inputs (Agent input)
An Agent input block (Events & memory) gives a skill a parameter, so one "Forward" skill
handles "move forward 37 cm". Its settings: a name (cm), number or text, a description the
agent reads ("distance to drive, in cm"), a default, a minimum and maximum, and
The agent must set it (on by default).
- Min and max are enforced inside the program, on the robot, so no planner reply can exceed them; a larger value is clamped and the agent is told.
- No guessed defaults: a required input the agent leaves out (or gives as something that isn't a number) stops that step, and the agent is told what is missing. Start without an agent uses the default.
- Units: the planner converts the goal's units ("half a meter" →
cm = 50; "2 meters" with a 100 cm maximum → two runs of 100). A value written with the input's own unit ("30 cm") is accepted; any other unit is never quietly converted.
Example: Forward N cm
When started → Wheel odometer → Set variable from, and
When started → Drive 40 / 40. Every 50 ms → Wheel odometer,
Get variable from and Agent input cm → Formula
x - y >= z → If → Stop motors. Add a Display of the distance travelled
and the agent reports it. Make Turn left and Turn right the same way with a degrees input and the
Compass. On a real robot, distance needs wheel encoders and turns a compass or IMU.
Missions on the robot
With Mission on a robot (Agentic AI pane), the Agentic AI runs on the robot itself. Studio sends the mission in plain words, the skills it may use (compiled Robot projects, nothing else can run) and limits; the robot plans each step with a language model, runs one skill, reads what happened, and repeats until the mission is done. Studio can be closed meanwhile; reopening shows the log.
| Planner | How |
|---|---|
| On board | The robot's own Ollama (OLLAMA_HOST) — works with no network once the mission is sent. Install Ollama on the robot and ollama pull gemma4; Studio lists the models it finds. |
| Cloud | An AI Worker on the relay (e.g. joined as JozieAILLM2 on a GPU machine). |
Guardrails (nobody approves each move)
- Only the skills sent with the mission run; never motor values or code.
- Before anything moves, the model is asked whether the skills can do the mission at all; a clear "no" ends it with the missing ability named (e.g. "fly").
- A mission time limit (default 10 min, at most 60), a time limit per skill (default 20 s, at most 120) and a maximum number of skill runs (default 20) — set this to what the mission should need.
- Three planner failures in a row end it; STOP ends it at once; every way it ends switches the outputs off; a run or deploy from Studio replaces it; a mission never resumes after the agent restarts.
- Sending one needs your confirmation, which spells out that the robot will move on its own.
Planning uses Ollama's JSON mode with thinking turned off, which is reliable and several times faster with
thinking models such as gemma4.
Persona link
The agent can also join Persona's Drone Operations companion as a ground vehicle.
In Persona: Fleet → + Register Drone, connection mode Remote, vehicle class Ground;
copy the droneUuid and uploadToken from its card into PERSONA_VEHICLE_UUID and
PERSONA_UPLOAD_TOKEN, and set PERSONA_RELAY_URL to the relay Persona uses.
| Robot → Persona | Persona → robot |
|---|---|
| Telemetry (heading, front distance, speed; a fixed position if set) | Pause / abort / emergency stop: stop everything |
| Photos, soil readings | Manual Control: drive with the sticks, capped speed, stops if input pauses for 1 s |
| Resume, return home, waypoint missions: refused with the reason (no GPS navigation) |
Every command from Persona must be signed with the vehicle's token and be under a minute old; anything else is ignored, so a stranger on the relay can't drive or stop the robot. This needs a Persona server that signs its commands. Load the hardware once (run or deploy a program from Studio) before using Manual Control.
Parts reference
Every part type from Studio's Hardware tab and whether it is driven on real hardware or needs a library. Studio's Hardware panel shows the same list when the agent is connected.
| Part | Pins / bus | Notes |
|---|---|---|
| DC motor (3-pin driver) | EN (PWM), IN1, IN2 | EN needs hardware PWM; use a USB board if available. |
| DC motor (2-pin driver) | IN1 (PWM), IN2 (PWM) | Both pins need PWM. |
| Continuous-rotation servo | Signal (servo PWM) | Requires pigpio or a USB board. |
| Servo motor (SG90 / MG996R…) | Signal (servo PWM) | Same. Power from its own 5–6 V supply. |
| Stepper — ULN2003 (28BYJ-48) | IN1–IN4 (digital) | Half-step; works on any GPIO. |
| Stepper — A4988 / DRV8825 | STEP, DIR, ENABLE | Set current limit first. |
| ESC (brushless motor) | Signal (servo PWM) | Sends arming pulse on start. Remove propellers while testing. |
| LED, RGB LED | Digital / 3 × PWM | Any GPIO; RGB needs PWM. |
| NeoPixel strip (WS2812B) | GPIO18 / 12 / 10 / 21 | Needs rpi-ws281x-native and sudo. |
| Buzzer, relay, laser, vibration | Digital or PWM out | Any GPIO. |
| Ultrasonic HC-SR04 / HC-SR04P | TRIG, ECHO | Must be on Pi GPIO with pigpio — Firmata cannot time the echo pulse. |
| Sharp IR distance | Analog out | Needs an analog pin on a USB board (Pi has none). |
| VL53L0X laser / ToF | I²C | Needs i2c-bus. |
| Line sensor (3- or 5-channel) | Digital in (×3 or ×5) | Any GPIO. |
| Color sensor (TCS34725) | I²C | Needs i2c-bus. |
| IMU (MPU-6050) | I²C | Needs i2c-bus. Integrates gyro Z-axis for heading. |
| BNO055 / QMC5883L / HMC5883L | I²C | Needs i2c-bus. Fused absolute heading. |
| Wheel encoder | Channel A (interrupt) | Must be on Pi GPIO with pigpio for edge counting. |
| DHT11 / DHT22 | Digital (pigpio timing) | Must be on Pi GPIO with pigpio. |
| BME280 | I²C | Needs i2c-bus. Temperature, humidity and pressure. |
| Buttons, bumpers, PIR, hall… | Digital in | Any GPIO. |
| Potentiometer, joystick, analog sensors | Analog in | Needs analog pin on a USB board. |
| LCD 16×2, OLED 128×64, 7-segment | I²C or 2-wire | Needs i2c-bus. |
| Raspberry Pi Camera (CSI) | Ribbon cable | Driven by rpicam-vid (Bookworm) or libcamera-vid (Bullseye). |
| USB webcam | USB | Driven by ffmpeg. |
Configuration (.env)
| Setting | Default | Description |
|---|---|---|
RELAY_URL | required | The relay Studio uses. Must match exactly. |
ROBOT_NAME | required | This robot's unique name on the relay. |
DATA_DIR | ./data | Where the pairing key and deployed program are stored. |
HARDWARE | auto | auto: drive real pins. mock: touch nothing (for testing flows on any computer). |
SERIAL_PORT | auto-detect | The USB board's port if auto-detection picks the wrong device (e.g. /dev/ttyACM0). |
PAIRING_KEY | from agent.json | A fixed pairing key instead of the auto-generated one. |
HEARTBEAT_TIMEOUT_MS | 3000 | How long (ms) a tethered run continues without a Studio heartbeat before the robot stops. |
OLLAMA_HOST | http://127.0.0.1:11434 | The on-board model for missions planned on board. |
PLANNER_TIMEOUT_MS | 120000 | How long one planning step may take before it counts as a failure. |
PERSONA_VEHICLE_UUID, PERSONA_UPLOAD_TOKEN | off | Link this robot to Persona (see Persona link). |
PERSONA_RELAY_URL | RELAY_URL | The relay Persona uses, if different. |
PERSONA_TELEMETRY_MS, PERSONA_MAX_MANUAL_SPEED | 2000, 60 | Telemetry interval; speed cap for Persona's Manual Control. |
PERSONA_LATITUDE, PERSONA_LONGITUDE | none | A fixed position for Persona's map (no GPS on Studio robots). |
Troubleshooting
Studio can't find the robot
Confirm the agent is running (npm start or systemctl status nodofi-robot),
that RELAY_URL exactly matches Studio's relay (same protocol, host and port), and that
ROBOT_NAME matches what you entered in Studio's Add a robot… dialog. The agent
logs Connected. when it has successfully joined the relay.
Parts show as "simulator-only"
Install the library the part needs (see Hardware libraries). For pigpio,
run the agent with sudo. For I²C, enable I²C in raspi-config and reboot. For
the USB board, confirm it is flashed with StandardFirmata and connected; the agent logs which serial
ports it tried.
Ultrasonic sensor or encoder always reads null
These require microsecond-level pulse timing. Move those pins to the Raspberry Pi's own GPIO
(pi:GPIOxx in the Hardware tab) and run the agent with sudo (pigpio).
Motors don't move
Motor drivers take power from their own battery, not the Pi's pins — check the battery. Set
HARDWARE=mock to verify the flow reaches the motor block; if it does, the fault is in the
wiring. EN (PWM) on a Pi must be on a hardware-PWM pin (GPIO12, 13, 18 or 19) or on a USB board.
Agent crashes (pigpio error)
pigpio must run as root. Use sudo npm start or set User=root in the
systemd unit. Only one process can open the pigpio device at a time.
Camera: "no picture"
For a Pi Camera, enable it in raspi-config and reboot. Test with
rpicam-still -o test.jpg. For a USB webcam, install ffmpeg and test with
ffmpeg -f v4l2 -i /dev/video0 -frames 1 test.jpg.
Safety
- Keep the robot on a stand (wheels off the ground) the first time you run a new program.
- Keep STOP within reach — it cuts motors immediately.
- Power motors from their own battery, never from the Pi's GPIO pins. Motor current on a GPIO pin damages the board.
- Never leave a deployed program unattended until you trust it handles all edge cases.
- 3.3 V logic — the Pi's GPIO is 3.3 V. A 5 V signal on a Pi pin can destroy it. Use 3.3 V sensors, a voltage divider, or a level shifter.
- Propellers: remove them from brushless motors during all testing.
- Relays and mains voltage are dangerous. Keep projects to low-voltage loads.