Repository navigation
Web UI Layout
The reference map of the Linorobot2 Cockpit web interface: header, banners, every tab, the tools and the output dock.
Open it athttp://<robot-computer>:8000. For a guided first run, see the beginner walkthrough.
flowchart TD
subgraph AppLayout["Linorobot2 Cockpit Web Interface Layout"]
Header["1. Application Header (Brand, Version, Robot Selector, Git Branch, 1-Click Button, Status Chips, Settings)"]
Banners["2. Global Banners (Board Mismatch Warning & No-Board Notice)"]
NavTabs["3. Workflow & Tool Navigation Bar (Tabs 1–11 + 4 Tool Tabs)"]
subgraph MainContent["4. Main Workspace (Active Tab Pane)"]
direction LR
W1["1. MCU & Sim<br/>(Controller & Presets)"]
W2["2. Base<br/>(Chassis & Physics HUD)"]
W3["3. Drive & Motors<br/>(H-Bridge & Inversion)"]
W4["4. Sensors<br/>(I2C Probe, LiDAR, Battery)"]
W5["5. Pin Matrix<br/>(GPIO Validation)"]
W6["6. Hardware Tests<br/>(the tool)"]
W7["7. LiDAR / Scan<br/>(Laser Driver & Viewer)"]
W8["8. Bringup<br/>(ROS 2 & Health Check)"]
W9["9. Teleop<br/>(Virtual Gamepad)"]
W10["10. SLAM & Nav<br/>(RViz, SLAM, Nav2, Tuning)"]
W11["11. Operations<br/>(1-Click Pipeline)"]
T1["Map Viewer<br/>(Rosbridge 2D)"]
T2["Secrets<br/>(Wi-Fi Credentials)"]
T3["Config Studio<br/>(YAML & Runtime)"]
T4["Install<br/>(Native / Docker / Udev)"]
end
Dock["5. Output Dock (Collapsible Live Console)"]
Header --> Banners --> NavTabs --> MainContent --> Dock
end
classDef hdr fill:#1e293b,stroke:#38bdf8,stroke-width:2px,color:#f8fafc;
classDef main fill:#0f172a,stroke:#64748b,stroke-width:1px,color:#f8fafc;
classDef tab fill:#1e1b4b,stroke:#818cf8,stroke-width:1px,color:#ffffff;
classDef tool fill:#14532d,stroke:#4ade80,stroke-width:1px,color:#ffffff;
classDef dock fill:#18181b,stroke:#f59e0b,stroke-width:2px,color:#ffffff;
class Header hdr;
class W1,W2,W3,W4,W5,W6,W7,W8,W9,W10,W11 tab;
class T1,T2,T3,T4 tool;
class Dock dock;
The header is fixed above all tabs and holds the global status and controls.

+---------------------------------------------------------------------------------------------------------------------------------------+
| [LOGO] Linorobot2 Cockpit [v<ver> ] [Commit] [About] | Robot: [linorobot2 v] Branch: [main] [= name] [Start 1-Click] |
| | MCU: [RP2350] Agent: [running] Bringup: [down] WS: [ready] [Settings v] |
+---------------------------------------------------------------------------------------------------------------------------------------+
- Logo & title: the Cockpit insignia (telemetry reticle, LiDAR sweeps, attitude horizon, heading vector).
-
Build version badge (
#header-build-version):vand the build'sVERSION, orvand the commit when there is noVERSION. -
Git version badge (
#git-version-badge): the short commit hash and dirty state. Click it for a popover with the branch, remotes and recent commits. -
About (
#btn-about-cockpit): version, supported MCUs and ROS 2 distros, licence and links.
-
🤖 Robot (
#hdr-robot-name): your robot, one<name>_config.yamlin the config directory (~/linorobot2-config/by default). Type a new name and press Enter to create it from what is on screen (a name is letters, digits,_,-and., starting with a letter or a digit, at most 64 characters: the one rule the page and the backend both hold); from then on every selection and every action saves to it (values are edited where they stand, so the file's comments stay). Nothing waits for a Save (the 💾 Save MCU settings buttons on tab 1 only write the pending changes at once): the indicator beside the name says where it stands, ✓ Saved, … saving, ⚠ Not saved (click to retry, for example after the pin check refused a pin), or ✎ Name your robot. Beside it, a badge says what the robot is: 🧪 Simulation for a bare module (no pin assigned but the LED: every device simulated), 🔧 Real for a robot built from a design or with any pin assigned. It is not a switch: 1-Click and Bringup run the robot in the mode it implies, and a design or a pin is how it becomes real. The caret (#btn-robot-menu) lists your robots and the Sim MCU robot (bare_sim). Reference designs are not in this list: they are applied to your robot from MCU & Sim (tab 1). When a board is detected and you have not named a robot, the cockpit creates one for it,lino_<mcu>(lino_esp32,lino_unoq): a bare module of that board, every device simulated, yours to rename. With no board and no robot of yours selected, it runs the Sim MCU robot (bare_sim); a robot of yours, real or a bare module, stays selected. -
⎇ Branch (
#hdr-git-branch): the git branch of the config directory, which is its own git repository. Enter switches branch; the caret (#btn-branch-menu) lists local branches. -
= name(#btn-branch-to-name): sets the branch to the robot name and checks it out. -
🚀 Start 1-Click (
#btn-header-start-pipeline): runs the One-Click Pipeline from any tab. When it does not start, it says why wherever you are: a toast, the action banner under the header ("1-Click held: bring-up not complete" with the missing steps, or "1-Click not started" with the reason, such as a Run option out of range or a real robot's options to review on Operations) and a Not started badge.
-
🔌 Detected MCU (
#hdr-detected-mcu-badge): what the USB scan found, e.g.Raspberry Pi Pico 2 (RP2350)orESP32. A second pill (#hdr-board-id-badge) shows the chip's ID. -
⚡ Agent (
#hdr-agent-pill): state ofmicro_ros_agent. The 🔍 button (#hdr-btn-agent-check) checks whether another process holds the agent's port. -
🚀 Bringup (
#hdr-bringup-pill): state of the ROS 2 robot stack. -
📁 WS (
#hdr-workspace-pill): workspace status. -
⚙ Settings ▾ (
#btn-hdr-settings): opens the settings drawer.
- 🌐 ROS 2: Jazzy (24.04) or Lyrical (26.04), the two supported distros. Other entries in the list are not supported.
- 🛠️ Build: Auto, Native (Host), Docker or Podman.
- 🤖 Agent: Auto (match workflow), Docker (rootless), Podman + systemd, Podman (container) or Native. The ❔ button opens the rootless setup guide; see Rootless vs rootful.
-
📦 Registry: Auto, Private registry, Docker Hub, or Custom
host:port.
Full-width, directly under the header:
-
Board mismatch (
#hdr-mcu-mismatch, red): the robot's config builds for different silicon than the board on the bus (e.g. the config is apico2and the bus has anesp32). Flash and 1-Click are refused with a warning and nothing is sent;flash_mcu.pyrefuses on its own as well. A plugged board never rewrites a real robot's base controller: the banner is all it raises, and the robot takes a serial port only from a board of its own silicon (a renumbered tty). Only a bare module or the Sim MCU adopts the board that is plugged in. -
No board (
#hdr-no-board, amber): no microcontroller is on the bus. A robot on the Wi-Fi transport is never absent: it is neither switched nor told it was. Unless you picked a controller yourself, a generatedbare_<board>robot gives way to thebare_simrobot (the Sim MCU); a robot of yours, real or a bare module, stays selected. When a board is plugged in the cockpit switches back to the previous robot if that is the same silicon, otherwise to the plugged board'slino_<mcu>robot.
[ 1 MCU & Sim ] [ 2 Base ] [ 3 Drive & Motors ] [ 4 Sensors ] [ 5 Pin Matrix ] [ 6 Hardware Tests ] [ 7 LiDAR / Scan ] [ 8 Bringup ] [ 9 Teleop ] [ 10 SLAM & Nav ] [ 11 Operations ] | [ Map Viewer ] [ Secrets ] [ Config Studio ] [ Install ]
The numbered tabs follow the order you build a robot in. The four tool tabs on the right are not numbered.

-
Microcontroller discovery: the detected silicon (
RP2350,RP2040,ESP32,ESP32-S3), its device node (e.g./dev/ttyACM0), and 🔄 Re-scan Hardware. On an Arduino UNO Q the cockpit reads the board it runs on and reports its on-boardSTM32U585on/dev/ttyHS1; that MCU is never on USB, so the board is not taken for one with nothing attached. A board that is not on USB is described as what it is, never as plugged in or missing: the UNO Q's STM32 is On board (UART / SWD); a UNO Q robot opened on another computer says to open the cockpit on the UNO Q itself; a robot on the Wi-Fi transport is Over Wi-Fi, with nothing on the bus expected and the controller taken from its config; the Sim MCU has no board to probe. -
Reference Designs (
#preset-select): No design (a bare module of the board, every device simulated), or one of the shipped reference designs, grouped by MCU with the current MCU's group first. Every design this release ships is offered, whatever board is plugged in and whatever the config directory holds. Picking one applies it to your named robot: the design's controller side (MCU, board, pins, motor driver, sensors, link) becomes the robot's, and the robot's own chassis and Nav2 tuning stay. A robot still on the default chassis takes the design's, and a kit (the XRP, the Maker's Pet) brings its own motors, wheels and body, which you can change afterwards. The robot is then real: Sim mode and the Sim MCU are disabled for it. The list comes from the cockpit'sconfig/reference/, so a new design appears here the day it ships. -
Sim Mode preview card: simulated
/odom(50 Hz),/imu/data(50 Hz) and/scan(10 Hz), with ⚡ Make it a real robot: pick a reference design, or assign its pins and 🚀 Jump to SLAM / Nav2 Preview. On a real robot the first button is replaced by ↩ Back to a bare module: unassign every pin (Back to a bare module). See Simulation Mode & the Bare Module. -
Microcontroller Target & Serial Connection:
- Base Controller (
#cfg-mcu):pico2,pico2w,pico,picow,xrp(SparkFun XRP Controller, RP2350B),esp32,esp32s3,unoq(Arduino UNO Q: its on-board STM32U585, flashed over SWD),sim(Sim MCU: the firmware runs on the robot computer, nothing to flash). - micro-ROS serial port, e.g.
/dev/ttyACM0. - micro-ROS baud rate: 921,600 (default), 1,500,000, 4,000,000 (the UNO Q's on-board UART), 115,200 or 57,600. A rate a robot's config names that is not in the list is added to it, so loading and saving a robot never changes its rate.
- ESP32-S3 console port: Native USB (
/dev/ttyACM*, default) or UART0 through a USB bridge (/dev/ttyUSB*, e.g. the Yahboom YB-EET01). -
Network Name (Wi-Fi robot, mDNS): what an ESP32 / ESP32-S3 robot on the Wi-Fi transport answers to as
<name>.localonce it is off the USB cable. Blank: the robot's name, made DNS-safe (telemetry.hostname). -
ROS Domain ID: the DDS domain this robot's board and stack share (
base_controller.domain_id, 0–232). The board is flashed for it, and bringup, SLAM, Nav2 and the Web UI's own view join it; two robots on one network stay apart on different domains. Blank uses the container'sROS_DOMAIN_ID. After a change, reflash (or rewrite the env) and restart the stack. -
Robot IP Address (Wi-Fi robot): where OTA, ping and Monitor reach it. It shows the address detected at the robot's first boot after its USB flash and kept current by its answers; left as it is it stays automatic, edited it is pinned (
base_controller.robot_ip). See A Wi-Fi robot leaves the cable.
- Base Controller (
-
Simulated World Environment (Sim MCU and simulated sensors only):
- World:
wall(one room, one obstacle wall: the Nav2 goal test),rooms(four spaces joined by doors: exploration),map(a map you saved, raycast by the simulated LiDAR and camera). - Saved map path for
map. A change takes effect at the next flash or the next Sim MCU start.
- World:

-
⚡ Live Kinematics Physics HUD:
- Headroom badge and the basic numbers: max linear and angular velocity, wheel circumference, ticks per metre.
- Motor performance & Nav2 budget: achievable top speed, acceleration at the first kick and once the pack has sagged, time to 0.9× max, rotation radius, command budget. It comes from the simulated drivetrain model (gearbox efficiency, gear drag, battery sag, driver losses), so a chassis can be judged before anything is ordered.
-
Simulated
test_accrun: what the tool would print (max velocity, max acceleration, time to 0.9× max, distance to stop), with no board. - Nav2 limits vs this drivetrain.
- Kinematics base type: 2WD Differential, 4WD Skid Steer or 4WD Mecanum.
- Robot physical geometry: wheel diameter, wheel separation (track width), wheelbase.
- Body & sensor placement (URDF): body length, width, height and mass, tyre width, axle height, LiDAR X/Y/Z/yaw, IMU X/Y/Z, casters (2WD). The TF tree is generated from these at every bringup.
- Motor speed & closed-loop PID: rated RPM, encoder CPR, PID headroom ratio (default 0.85), K_P / K_I / K_D.
- Motor operating voltages: operating voltage and max power voltage.
- Load & drivetrain losses: total robot mass, gearbox efficiency, gear drag (constant, rpm/s), battery sag at stall, sag time constant, driver fixed drop, driver loss at stall, motor stall current, driver current limit. Blank keeps the firmware's default.
-
Nav2 velocity limits: Auto-tune Nav2 limits from this robot's motors when I save (
kinematics.auto_nav2_limits). See the motor model.

-
Motor driver type:
Option Pins per motor Notes AT82362 (dual PWM, IN_A + IN_B) Default; recommended BTS79602 (dual PWM, RPWM + LPWM) BTS7960 modules; a DRV8833 has its own option below DRV8411A2 (dual PWM) SparkFun XRP BDC30P2 (dual PWM, IN1 + IN2) Maker's Pet BDC-30P TB67H4502 (dual PWM, IN1 + IN2) — RZ78892 (dual PWM, BI + FI) NULLLAB Maker-ESP32 PRO AT88332 (dual PWM, IN1 + IN2) — DRV88332 (dual PWM, IN1 + IN2) Pimoroni Motor 2040 MX15082 (dual PWM, IN1 + IN2) Cytron Maker Pi RP2040 GENERIC_2_IN3 (PWM enable + IN_A + IN_B) L298N, TB6612FNG with STBY, VNH5019 GENERIC_1_IN2 (PWM + one direction pin) Cytron MD10C / MD30C ESC1 (RC servo pulse) Which to choose, and how a TB6612 can be driven the two-PWM way: Motor driver schemes.
-
PWM: frequency (default 20,000 Hz, no audible whine) and resolution (default 10 bits).
-
Motor inversion: Motor 1 (front left), 2 (front right), 3 (rear left), 4 (rear right).
-
Encoder inversion: Encoder 1 to 4.

The firmware detects the IMU, magnetometer and barometer at boot, so swapping one needs no change here. What it cannot discover is your wiring: battery monitor, divider resistors, sonar pins.
-
I2C bus scan (Scan I2C bus): opens the tool, runs its
i2c(the same WHO_AM_I scan the firmware runs at every boot), shows what answered as a table, and leaves the tool again. It is a read-out, not a setting: whatever is on the bus is what the robot firmware drives. The Sim MCU has no bus to probe. - LiDAR & depth camera: primary LiDAR model and optional depth camera (Intel RealSense D400, Luxonis OAK-D / Lite / Pro, Stereolabs ZED / Mini / 2 / 2i). Supported models: Which LiDAR, Depth camera.
- IMU & magnetometer: IMU and magnetometer selects, hard-iron bias (X, Y, Z in µT) and ⚡ Run Calibration. See Supported sensors and Calibrating the magnetometer.
- Battery: mode (None, ADC resistor divider, INA219), capacity, nominal / cutoff / full voltage, sag dip threshold, and R1, R2 and filter capacitor for a divider.
-
Environmental & range sensors: barometer (Auto, BMP280, BME280, Off), depth camera, HC-SR04 sonar on
/sonar(disabled by default). - Simulation toggles: simulated IMU, magnetometer, wheels, LD19, depth camera, barometer, sonar and battery, on a bare module. The first pin assigned turns them all off (a real robot simulates nothing), and they come back with ↩ Back to a bare module.

- Pin fields: status LED, motor driver pins, encoder pins, I2C bus, battery and sensor pins.
- Hardware Safety Inspector: live error and warning count against the selected controller's limits (strapping pins, input-only pins, SPI flash pins, duplicates). See Per-MCU validation.
- ⚡ Smart Auto-Assign: fills in valid pins for the selected board.
- 🔌 Wiring Table: the wiring chart from the saved config, with 📋 Copy Markdown and ⬇ Download .md. See The wiring chart.
- ↩ Back to a bare module (a real robot only): unassigns every pin but the LED and simulates every device again, keeping the robot's chassis and tuning. See Back to a bare module.

The tab drives the tool, the firmware's own command shell for testing sensors and motors. Nothing is flashed to use it, and entering or using the tool writes nothing to the env. Two things on this tab do write the env (never a reflash): a bring-up step's Save, and Back to robot when the env names a test.
-
Real-robot bring-up (the card at the top): the bring-up checklist, one panel per step -- Sensors (① hold still, ② turn and shake: a live table of the accelerometer, gyro, magnetometer and roll/pitch/yaw with each axis's span, and a 10 s plot), Battery (the board's reading against a multimeter; an ADC divider's new R1 is offered), Motors (each wheel: Spin forward, then say which way it turned; the invert flags it needs are offered, or set them with the boxes), Wheel speed (each wheel's encoder RPM against a laser tachometer:
counts_per_revandmax_rpm; or count turns by hand), LiDAR, Speeds and test_acc (the robot started; the last two drive it). Each step shows passed, failed, to do, config changed, skipped (recorded withrobot_bringup.py mark <step> skip) or not needed (a part the robot does not have); the pill at the top says whether the 1-Click may move the robot. Save writes a calibration into the config and the board's env. -
Header: the robot, its link (USB serial or Wi-Fi), the state (
closed,in tool,running motor…) and its wheels (2, real,4, simulated…, read from the tool'sstatus). -
Enter tool: the cockpit restarts the board and catches its boot window (
tool+ Enter over USB; on a Wi-Fi robotlino-tooland the terminal's login). A Wi-Fi robot is opened over the air unless the port selected on the page exists (the robot on its USB cable). The robot leaves micro-ROS while it is in the tool. Back to robot (base) sendsstopandbase: the board restarts into the robot firmware. When the boot banner shows an older firmware whose env names a test (app=test_motors…), Back to robot also writesapp=base(the env only, never a reflash), or that firmware would run the test again at every power-on. -
Checks: Scan I2C (
i2c; the chips found show as a table; the button is hidden when the config names no I2C bus), Sensors (sensors 10: readings ten times a second), Encoder counts (enc, motors off), All motors with a power % (motors), Accel profile with its run phase in ms and a power cap % (acc, on the floor), and ADC sweep (adc, only on a board with a DAC; elsewhere the reason shows in its place). -
One wheel: a top-down sketch of the robot (a 2WD shows wheels 1 and 2 only) and all, forward / reverse, the power slider (sent as
set tm_pctwhen you let go, so a turning wheel changes speed at once), seconds (0= until Stop), Run wheel (motor) and Find deadzone (ramp) (ramp, with the robot lifted). -
Live readouts: each wheel's RPM and encoder edges, the pack voltage, the IMU (roll/pitch/yaw or acceleration), the gyro, the magnetometer and the range, from the tool's
[TOOL_JSON]line. -
Terminal: the tool's output, and a Command line for anything the buttons do not cover. Every button types its command into the terminal, so what a button does is what you would type. The cockpit checks each line against the tool's commands before sending it, and says why it refuses one. It takes the tool's short forms (
m 1 30,st) and sends the full command. Of the env keys, it sets the test settings and the inverts (set m1_inv 1, saved on the board and in use at once); other keys are changed in the robot config and flashed. The board answers every command in words --ok: motor 1 40 3 (running),fail: motor 3 40 -- this robot has 2 wheels …, thendone: …,fail: … -- whyorstopped: … (why)when a running one ends -- shown in the terminal and the status line; no answer in 2 s is said too. -
■ STOP MOTORS (Esc): shows whenever a command runs, on every tab, and Esc does the same anywhere on the page. It sends
stopto the robot whose tool is open (not whichever robot the header shows), and on a Wi-Fi robot thelino-stopdatagram as well; when the stop did not reach the board it says so in red (STOP FAILED -- press Esc / unplug the motors' power). While the tool is open the header will not switch robots. On a phone it is fixed full width at the bottom of the screen, and the page leaves room under it. -
A drive needs the page. The cockpit keeps the board's link alive only while at least one page with the tab is open: close the last one while a wheel turns and the cockpit sends
stop. The same robot's tool is shared by every open page: a page that finds it already open says so (Back to robot ends it). -
The login stays in the cockpit. A Wi-Fi robot's OTA password is read on the server for
lino-tooland the terminal's challenge; the page never holds it. -
Refusals are shown in words. A command the robot cannot run here (a wheel on simulated wheels,
motor 3on a 2WD,i2cwithout a bus) is answered by the tool in its own words in the terminal, and the control that cannot work shows why beside it. -
The Sim MCU opens a tool too: the cockpit starts the Sim MCU straight into it. Its wheels and sensors are simulated, so its drives are refused and its sensors read
none: the panel can be learned with no board, and the refusals are the ones a real robot gives. Walkthrough mode (a cockpit started withCOCKPIT_TOOL_SIM_WALK=1, for trying the pages and every tool command): the Sim MCU's tool takes its own models as its parts -- the wheel model turns and counts, the IMU and magnetometer read a robot standing still, the battery its simulated pack. It exists on the Sim MCU only; a real board's tool always uses real parts. -
The Arduino UNO Q opens its tool when the cockpit runs on the board itself (its STM32 is on
/dev/ttyHS1); elsewhere the panel says so. -
Firmware image (folded away): Flash MCU writes the release image built for the stack's ROS 2 distro, then the env; Build compiles it. A Wi-Fi robot off the cable is written over the air, found by its name (A Wi-Fi robot leaves the cable); the UNO Q through
flash_mcu.pyover SWD like the other boards (the release'sunoq-<distro>image, fetched, or the copy here when offline, and a fresh env block from the robot's config), its output streamed to the console. On the Sim MCU there is no board to build or flash. -
🔋 ESP32 ADC Calibration Studio (only on a board with a DAC, the classic ESP32; elsewhere a note says the board has none): its Run Hardware Calibration runs the tool's
adcand draws the measured curve. See ADC Calibration. -
Terminal on this computer's USB: a board plugged into the computer running the browser (Web Serial; Chrome or Edge, on
localhostorhttps://), at the robot's serial speed. Reset the board and typetooland Enter at the[boot]line (Getting in).

-
Standalone laser driver: runs the LiDAR driver with no micro-ROS agent. Model (synchronised with the Sensors tab), serial port (stored as
/dev/serial/by-path/…, with ↻ detect), baud rate, and connection: direct serial, LiDAR-over-Wi-Fi-UDP bridge (the MCU relays the UART; the bridge device is/tmp/lidar_udp_bridge), driver-native UDP server or client. A robot whose board forwards its LiDAR (lidar.comm_mode: udp) opens on the UDP server, on the config's port, as bringup reads it. The driver looks for a finished revolution 50 times a second in every mode, so a 10 Hz LiDAR publishes every scan. -
Live
/scanviewer: plotssensor_msgs/LaserScanfrom the ROS graph; the topic can be changed (e.g./robot1/scan). It needs the laser driver or bringup already running.

-
Read from your config: kinematics, micro-ROS port and baud, LiDAR and depth camera as
bringup.launch.pywill receive them. -
Robot bringup: Start bringup, Stop, Stream logs.
bringup.launch.pystarts its own micro-ROS agent. A checkbox auto-launches bringup when Teleop, SLAM, Nav or calibration starts. - MCU topic monitor: the board's own topics, ⬆ what it publishes and ⬇ what it subscribes to, and nothing else on the graph. Measure all rates every one over 7 s. ⚡ Hz streams one topic's rate once a second, with the longest gap and the time since the last message, so a stall shows at once. ▶ Echo streams its messages, a few a second, saying how many it skipped. A rate is always measured, never assumed. A device the robot does not have publishes nothing, so it is not in the list.
-
Bringup health check: probes the ROS graph itself, the publish rates of
/odom/unfiltered(wheel odometry from the MCU),/odom(EKF),/imu/dataand/scan, and theodom → base_footprint → laserTF chain that SLAM and Nav2 need. The robot is ready when the board publishes/odom/unfilteredand, on a robot with a LiDAR (or a depth camera standing in for one),/scanarrives at its rate; a robot with neither shows the/scanrow as not fitted. The summary names what failed: not advertised (no publisher), no messages, or too slow. One probe reads the graph, so a healthy robot answers in a second or two.
-
Virtual gamepad (no hardware needed): drag the stick, or use the arrow keys / WASD while it has focus. Publishes
/cmd_velat 20 Hz, with max linear and angular speed and the topic adjustable. Deadman: the robot is commanded to zero if the page stops sending for half a second. -
USB gamepad on the robot computer (needs hardware): runs
joy_linux_nodeandteleop_twist_joy, with linear/angular axis and scale settings.

- 🖥️ RViz in the browser: RViz on a virtual display served over noVNC (default port 6080). Views: Teleop (odom frame), SLAM, Navigation (full Nav2 costmaps), robot model only, empty. Heavy on a small SBC; the Map Viewer and the LiDAR / Scan viewer are cheaper.
-
SLAM: Start / Stop SLAM, Save map. A saved map goes into the cockpit's
maps/, the one directory the Saved Maps gallery, the Nav2 map list and the 1-Click read, and both lists refresh after the save. - Navigation & Mapping: pick a saved map, Start / Stop Navigation.
- The buttons follow what is running on the robot, checked on every status poll, and a badge beside each of SLAM and Nav2 says which: Running — left up by a 1-Click run (Stop stops it, as it stops the run's stack), Running (started from this cockpit), Running, but not started by the cockpit (Stop is not offered: stop it where it was started), or Not running.
- AI Navigation, SLAM & EKF Tuning Studio: an AI tuning assistant with quick-issue buttons (e.g. 80cm Doorway Hesitation, Stop Goal Oscillation, Costmap Ghost Obstacles), tuning presets, quick parameter sliders, direct YAML editors for Nav2, EKF and SLAM Toolbox, and export / merge / promote of the tuned parameters.
- 🛠️ AI Custom Robot Builder Studio: generates a robot stack from specs or a chassis template.
Frontier exploration is not started here; it is a checkbox on the Operations tab.
-
Pipeline settings: base controller, Mapping spin (s), when Nav2 is skipped (shown only with Skip Nav2), simulated world (From Config, Rooms, Wall, Saved Map, plus the map path; the Sim MCU only), Autonomous Frontier Exploration (on by default; unticked passes
--no-explore, which drives round trips to a fixed Nav2 goal instead), Skip Nav2 (--no-nav2), Auto-update firmware when stale (on by default) and Force firmware update. There is no mode to pick: the robot's config decides it (a bare module runs simulated; a design or any assigned pin runs real), and the header badge shows it. - A real robot (real wheels) shows its own options: the bring-up state, Measure the drivetrain first (test_acc) (on by default; serial and Wi-Fi robots), and Run without a complete bring-up, which asks first and names the missing steps. Until the bring-up checklist's bench steps have passed, the 1-Click does not start and says which steps are missing. Once it starts, a real robot is bounded by the clearance check (inching clear, and a look around a LiDAR's blind sector before the room is judged) and by the motion guard, which stops the run on a slow or stale scan or a flat battery: The six stages.
-
Run options (collapsed; an empty field keeps the pipeline's default, and the start line
echoes what was passed):
-
What to run: Full mission, Topics + drive manoeuvres (
--topics-only) or Topics only, nothing moves (--topics-only --no-drive-test); Skip the drive manoeuvres (--no-drive-test); Map the room (SLAM) or Use a saved map (AMCL) with a map frommaps/(--map; exploration andtest_accare off then, and the robot must start at the map's origin); Save map as (--map-output maps/<name>). -
Exploration (while exploration is ticked): Time limit (s) (
--explore-timeout, 900), Minimum map area (--explore-min-area), and on a real robot Goals may leave the starting room (--explore-beyond-room). -
Fixed goal (while exploration is off): Goal X / Goal Y (a real robot's room goal; the
simulated world's is (3.0, 0.0)), Round trips (
--goal-round-trips, 0 = one way), The goal must be reached (--require-goal), Goal tolerance and Home heading tolerance (in degrees here, sent in radians). -
Room safety (a real robot): Clearance before moving (
--clearance; 0 asks first), Creep slowly to find room (unticked:--no-inch), Inch at most (--inch-max) and test_acc needs (m) all round (--test-acc-clearance).
-
What to run: Full mission, Topics + drive manoeuvres (
-
▶ Start 1-Click Pipeline and ⏹ Abort. A real robot asks first: it moves by itself within
a few minutes, and with
test_accat full power. While a run is going, Abort shows in the header too, and a second Start is refused (one run at a time; the board's tool and a run also refuse each other). What each stage does, in order: The six stages. -
🔋 Battery (under the run's verdict, when the robot publishes
/battery): the pack's worst sag, the step it fell in and how far below the rest voltage, then the lowest reading in each step; whiletest_accruns, its own figures so far. See the battery line. -
ROS 2 Topic Echo & Rate Gate: audits
/odom(min 10 Hz),/imu/data(min 20 Hz) and/scan(min 5 Hz) and their frame IDs, asscripts/verify_topics.pydoes before SLAM and Nav2. -
📡 UDP Syslog Server: collects logs over UDP from Wi-Fi boards (port 5140 by default,
telemetry.syslog_portinsecrets.yaml). -
🗺️ Saved SLAM Maps Gallery: the maps in
maps/, each with a thumbnail. A map is saved as a.pgm, which no browser shows, so the cockpit serves it re-encoded as a grayscale PNG (/api/maps/<map>.pgm/preview), shrunk so the walls stay black.

-
2D canvas over rosbridge: draws
/map,/scanand the robot pose in the browser, with no RViz or X server on the robot. Bringup starts rosbridge on port 9090 (rosbridge:=falseturns it off). -
Map-frame pose: the robot is drawn from
/odomcomposed with themap → odomtransform from/tf, so it sits correctly against the mapped walls. The scan is drawn from where the LiDAR is mounted, throughbase_link → laserfrom/tf_static(its offset and yaw), so it lines up with the mapped walls; until that transform arrives, from the robot's centre. -
Tools: Connect / Disconnect, 📍 2D Pose Estimate (
/initialpose), 🎯 Nav Goal, ⏹ Cancel goal, Fit map, Centre on robot. Scroll to zoom, drag to pan, Esc cancels an armed tool. -
Nav Goal is a
NavigateToPoseaction, sent through rosbridge, so the goal has an answer. While Nav2 drives, a navigation line under the status shows its feedback: distance to go, time so far, ETA and the number of recoveries. The goal ends in its outcome: Goal reached, Goal canceled, or Goal aborted with Nav2's error code and message; "no action server" means Nav2 is not up. The outcome stays on the status line until the next goal. A new goal replaces the one before it. -
⏹ Cancel goal cancels every
NavigateToPosegoal, including one sent from a shell, the 1-Click or another client. The viewer also watches/navigate_to_pose/_action/status, so a goal sent elsewhere is shown ("Nav2 is executing a goal sent elsewhere") and enables Cancel.
-
Wi-Fi station credentials (SSID and password), IP addressing (blank = DHCP), micro-ROS and telemetry endpoints (agent IP and UDP port, syslog server, ArduinoOTA port and password).
-
Saves to
secrets.yamlin the config directory, which its git repository ignores, and regenerates the firmware header. -
Wi-Fi keys come before the first flash of a Wi-Fi robot: with the Wi-Fi transport selected, Flash and the 1-Click refuse without them (
[NO WI-FI KEYS]). The OTA password set here is shared; each Wi-Fi robot also gets its own, generated at its first USB flash and kept insecrets.ota.yamlbesidesecrets.yaml(never shown, never committed), and its own one is used first.
-
Config Directory: where the robot configs live (
COCKPIT_CONFIG_DIRmoves it), with Commit config. -
Robot & Nav2 Configuration Studio: a raw YAML editor for the active
<robot>_config.yaml, with reload, save and download. - Workspace Directory.
- Container Engine & Image Registry: agent engine and registry, with ▶ Start Agent, ■ Stop Agent and 🔍 Check Port for testing the agent on its own.
- ⚡ Robot Boot Autostart: a user systemd service that starts the agent, bringup and Nav2 at power-on (full stack, sensors & teleop, or agent only).
- ROS 2 Distribution & Automation.
-
Import or export a robot profile: one
<robot>_config.yaml. - Install mode: Native, Docker or Podman.
- Native: base install of linorobot2 into the workspace, then optional laser and depth camera drivers with their udev rules.
- Docker / Podman: build an image (hardware, gazebo, gazebo-cuda) with the chosen sensors, install host udev rules, and run a service (kasmvnc, bringup, slam, navigate, save-map, rviz, gazebo, dev).
See Installation & Docker for installing the cockpit itself.
The output console sits below every tab:
- Live output: stdout and stderr of background tasks, flashing, the agent and launch services.
- 1-Line toggle: shrinks the console to one line showing the last log line; click the header to expand it again.
- Clear: empties the console.
- Web UI Guide — the beginner walkthrough.
- The One-Click Pipeline — what the 1-Click run does, stage by stage.
- Pin Matrix & Wiring — pin validation and wiring.
- Simulation Mode & the Bare Module — running with simulated hardware.