Skip to content
Hochschule für Informatik FHNW P9 — Mars Rover Autonomy

Demo · live · read-only

A virtual rover, driven by n8n

A self-contained showcase for the ROS 2 node package: a simulated rover that offers real ROS 2 topics, services and actions, eleven n8n workflows that drive it, and a read-only live view on the public web. Everything the workflows do shows up on screen.

Read-only at the protocol level, not by convention: x11vnc runs with -viewonly, so the server never applies pointer or keyboard events whatever a client sends. Rosbridge itself is bound to localhost and is not on the internet. Open the viewer on its own.

Telemetry

connecting

State
Battery
Destination
Status LED
Position
Uptime

The watchdog fires below 25 %: it turns the LED red, preempts whatever mission is running and sends the rover to the dock.

Your turn

Drive it, or ask it something

Both of these command the same robot you are watching above. Send it somewhere and the landmark tour is preempted; the trail it draws is the evidence.

Send the rover to a place

open full

Workflow 03. The dropdown is built from /rover/landmarks, a latched topic, so the places come from the robot rather than from the form.

Ask the rover

open full

Workflow 07. The agent is given plain tools and fills in every ROS name, type and payload itself, so nothing about this robot is hardcoded in the workflow. Try “what interfaces does the rover have, and send it to the tree”.

The chat is public and every message spends model tokens, so it starts only when you ask it to.

Drive it, or look inside

How it fits together

The robot is turtlesim wrapped in a rover-shaped interface: a battery that drains while driving, a dock that recharges it, an e-stop, and a map with landmarks drawn on it so a demo can talk about places rather than coordinates. n8n reaches it over a rosbridge WebSocket on the internal Docker network.

Two things are worth pointing at. The custom message types are discovered at runtime — rover_interfaces is a real ROS 2 interface package with nested messages, and nothing about them is hardcoded in n8n. And every interface documents itself: each one publishes a latched <name>/desc topic saying what it is for, which is how an agent that has never seen this robot can still work out what to call.

Three choices keep it safe to leave on the internet: rosbridge is bound to localhost and never exposed, the live view is read-only at the protocol level rather than by convention, and the workflows that only observe use a credential that is refused at the node level if it tries to publish or call a service.

                  ┌─ n8n editor      (owner login)
Internet ─ Caddy ─┤
    (TLS)         └─ live view       (read-only, public)

internal network
  n8n ──ws──▶ ros2 container
                ├─ rosbridge + rosapi   (not exposed)
                ├─ turtlesim on Xvfb
                ├─ x11vnc -viewonly ─▶ noVNC
                ├─ rover_node.py
                └─ camera_node.py

What the robot exposes

Actions

  • /rover/navigate_to Drives to one point. Feedback carries distance remaining; cancellable.
  • /rover/patrol Visits named waypoints in order, feedback per waypoint, optional looping.

Services

  • /rover/set_led Sets the status LED, which is also the trail colour.
  • /rover/dock Drives home and starts charging. Preempts a running mission.
  • /rover/emergency_stop Engages or releases the e-stop.
  • /rover/reset_demo Full reset: battery, e-stop, trail, back to the centre.

Topics

  • /rover/status Rover state at 2 Hz. Embeds the battery and the current waypoint.
  • /rover/battery Charge and charging state at 1 Hz.
  • /rover/events One JSON message per event — this is what the watchdog triggers on.
  • /rover/camera/compressed The screen, published as a camera topic at 2 Hz.

Eleven workflows

The canvases that drive it

These are the running workflows, on a real n8n canvas — pan and zoom them, open a node to see its parameters, or copy the JSON and import it into your own n8n. Click a canvas once before it takes the scroll wheel. The same workflows are live in the editor, behind the owner login.

01

Landmark Tour

Schedule → Service Call → Action, sending a goal and waiting on its feedback.

7 nodes

  • rosTopicNextMessage
  • rosServiceCall
  • rosAction

02

Battery Watchdog

Topic Trigger with a conditions filter, so the workflow only wakes for messages that matter.

4 nodes

  • rosTopicTrigger
  • rosServiceCall
  • rosTopicPublish

03

Send the Rover Somewhere

An n8n Form with a dropdown of places, driving an Action with live feedback.

3 nodes

  • rosAction

04

ROS Calls n8n

Service Trigger — n8n advertises a ROS service that ROS can call.

3 nodes

  • rosServiceTrigger
  • rosTopicNextMessage
  • rosTopicPublish

05

ROS Calls n8n

Action Trigger and Respond — n8n advertises an action server, sends feedback, then succeeds.

6 nodes

  • rosActionTrigger
  • rosActionRespond
  • rosAction
  • rosTopicCaptureImage

06

Camera Snapshot

Webhook → Capture Image, returning a JPEG from a camera topic.

3 nodes

  • rosTopicCaptureImage

07

Ask the Rover

An AI agent with vision: it discovers the graph, drives the rover, and looks through its camera.

15 nodes

  • rosApiTool
  • rosTopicNextMessageTool
  • rosServiceCallTool
  • rosActionTool
  • rosTopicPublishTool
  • rosTopicCaptureImageTool

08

Hourly Demo Reset

Housekeeping — calls /rover/reset_demo every hour so the demo stays clean.

3 nodes

  • rosServiceCall
  • rosTopicPublish

09

Mission Planner

The model plans, n8n executes: plain language in, a JSON plan out, executed step by step.

8 nodes

  • rosServiceCall
  • rosAction

10

Self-Documenting Robot

Runtime discovery turned into an operator manual, written from the live ROS graph.

9 nodes

  • rosApi

11

Incident Responder

The same trigger as 02, but an agent decides whether to dock, finish, or e-stop.

9 nodes · inactive

  • rosTopicTrigger
  • rosTopicNextMessageTool
  • rosApiTool
  • rosServiceCallTool
  • rosTopicPublishTool
  • rosActionTool
  • rosTopicCaptureImageTool

Ships inactive on purpose: it listens to the same battery_low event as workflow 02, so running both means two things racing to command the rover. Turn 02 off before turning this on — the pair is meant to be shown as a before/after, fixed logic against a reasoned decision.

From the real rover

Five workflows doing an actual job

The eleven above were written to show what a node does. These five came off the n8n instance that drives the physical rover, and were written to get work done — swapping an end effector, running a sampling campaign, walking a maintenance checklist. They are longer, they retry, and they call each other. Shown here read-only: identifiers have been pseudonymised, and the hardware they name is not on the simulated rover.

Deep Sampling

Drill a core, weigh it, file the result

The full sampling run as one operator-driven sequence: drive to the sampling site, photograph it, drill out a core, drop it in the onboard container, weigh it and photograph the result. The drill is driven entirely through its own ROS service surface — man_lower_drill, man_up, man_unload, target speed up and down — while max_depth and the load cell are read back from topics between steps. Depth and mass end up on an Asana task, so the science record writes itself.

52 nodes · 24 ROS

  • Reads a load cell and a depth encoder straight off ROS topics
  • Forms between steps: a human confirms before the drill moves
  • Captures the front camera into an Asana attachment
  • srv /wecant/TURBO/targ_container/SetValue
  • srv /wecant/LoadCell_A_1/Tare/SetValue
  • srv /wecant/TURBO/man_drill_off/SetValue
  • srv /wecant/TURBO/man_unload/SetValue
  • srv /wecant/TURBO/auto_start/SetValue
  • srv /wecant/TURBO/targ_speed_up/SetValue
  • srv /wecant/TURBO/targ_speed_down/SetValue
  • srv /wecant/TURBO/initialize/SetValue
  • +4 more
  • top /wecant/TURBO/max_depth/Value
  • top /wecant/LoadCell_A_1/Weight/Value
  • top /wecant/TURBO/act_container/Value
  • top /wecant/TURBO/state/Value
  • top /cam_front/image_raw/compressed

Drilling · Unload · Tare · Weigh Sample · Set Parameters · Take Photo · Put drilling values into Asana task · Initialize

Maintenance Task

A manipulator working a switch panel

The largest workflow here, and the one that looks least like a script: an operator walks a panel of switches, sockets and rods, and at each step n8n moves the arm, hands control to a learned policy, and checks whether it worked. Failures fall through to a retry or a manual skip that re-enables the spacemouse, so a stuck switch stalls one step instead of the run. Vision comes from /detect_board_objects; the arm is posed through ROS parameters and actions.

80 nodes · 24 ROS

  • Action goals for the policy controller, with retry and manual override
  • Sub-workflows per panel element, called 22 times over
  • Operator dropdowns generated from what the vision service found
  • srv /detect_board_objects

Set the main Switch to On · Set four switches to on position · Three rotary switches · Insert plug into socket · Turn rotary power switches · Place plate on electromagnet · Change Tool

Toolchanger

Pick up and drop off end effectors

Swapping the arm's end effector, which is the kind of job that is all edge cases. The coupler is locked and unlocked by publishing to /wecant/TCC/Lock/Set, the gripper is homed over a service, and /joint_states plus the gripper's own absolute-position topic are read back to confirm the tool actually seated. /shake_gripper is exactly what it sounds like — a deliberate wiggle to prove the tool is latched before the arm trusts it.

25 nodes · 8 ROS

  • Topic publish, service call and topic read in one control loop
  • Pickup and dropoff share a slot-selection sub-workflow
  • Verifies the latch physically instead of assuming it took
  • srv /shake_gripper
  • srv /wecant/GRAB/Grip_Home/SetValue
  • top /wecant/TCC/Lock/Set
  • top /joint_states
  • top /wecant/GRAB/Grip_Abs/Value

Position anfahren

Named joint positions, three ways in

The small one, and the one most worth copying. A named position goes in, /move_to_joint_positions gets called, and that is the whole workflow — but it exposes itself three ways at once: a form for a human, a webhook for a script, and an execute-workflow trigger so every other workflow here can reuse it. The position names come from an n8n data table rather than being hardcoded, so the vocabulary is editable without touching the graph.

7 nodes · 1 ROS

  • One body, three triggers: form, webhook, sub-workflow
  • Position names looked up from a data table
  • The building block the bigger workflows call into
  • srv /move_to_joint_positions

ROS2 Agent

A chat agent with the robot as its tool belt

The production sibling of the demo's workflow 07. Nothing about the robot is hardcoded: the agent lists nodes, expands services and topics into concrete field definitions, reads messages, calls services and looks through the camera, filling in every ROS name and payload itself. It can also search, create and update Asana tasks, so "check the gripper and open a ticket if it is sticking" is one sentence rather than two systems.

18 nodes · 10 ROS

  • Seven ROS2 API tools for runtime discovery, plus service call and camera
  • Asana tools alongside the robot tools in one agent
  • Reachable as chat or as a plain webhook