Skip to content

Troubleshooting

hippo5329 edited this page Oct 18, 2026 · 38 revisions

Troubleshooting

The cockpit's own rule: a failure should tell you the one thing to do next. When a run fails at the bench, look for a banner at the top of the page (it outlives the collapsed Output console) and open the Output console for the full text.

Entries are in the order you meet them: install, flash, bringup and topics, SLAM and Nav2, heading, network. Each gives the symptom, the cause, the check or fix, and the page that explains the topic.

Tip

Instant Search: Press Ctrl + F (or Cmd + F) on this page and paste your exact terminal log or error message to jump directly to its cause and solution.


🔍 Quick-Reference Symptom Directory

Observed Symptom / Error Message Root Cause Area Direct Link
The page asks for a token Web UI authentication Jump to Fix
A robot config exists but the cockpit does not offer it Two files share a robot.name Jump to Fix
MCU MISMATCH — it won't flash Board silicon vs selected robot config Jump to Fix
A flash failed (NEXT ACTION:) Bootloader state / USB port Jump to Fix
Permission denied on /dev/ttyUSB0 / /dev/ttyACM0, or the port is missing (Linux) dialout group, or brltty holding a USB-serial bridge Jump to Fix
[NO RADIO] / [IS A W] A Pico / Pico W (or Pico 2 / Pico 2 W) config on the other kind of board Jump to Fix
[WRONG BOARD] The port leads to another board than its last flash Jump to Fix
[tool] '…' tests the wheels, and this robot's config simulates them A tool command on simulated hardware Jump to Fix
[MIXED SIMULATION REFUSED] A robot config with real and simulated parts Jump to Fix
The board reboots forever after a flash (SPI flash size assert) Image built for a larger flash than the chip Jump to Fix
An ESP32-S3 on native USB answers nothing after its first flash (error -71) USB pins still routed to the old firmware's controller Jump to Fix
A diagnostic tool "worked" but showed nothing Old firmware on the board Jump to Fix
The tool: busy, too slow, locked, stopped: …, SIGN WRONG, the board did not enter the tool … The test tool and lino_tool.py Jump to Fix
The tool answers ok:, fail:, done: or stopped: The tool's answer words Jump to Fix
[SIM REFUSED] A real robot run in Sim mode or on the Sim MCU Jump to Fix
[BRINGUP REQUIRED] A real robot whose bench bring-up is not complete Jump to Fix
The room is short of 0.40 m / CLEARANCE_REFUSED: … Something too near a real robot before it moves Jump to Fix
NOTHING MOVES: … The motion guard never let the robot move Jump to Fix
MOTION GUARD STOPPED THE GOALS (or THE EXPLORATION) Slow or stale scan, low battery Jump to Fix
The board connected but there are no topics / SLAM sees nothing micro-ROS transport / baud / boot banner Jump to Fix
/scan says NO DATA on a bare module Absent LiDAR port vs simulated LD19 Jump to Fix
A real LiDAR wired to the MCU gives no /scan, or a slow one topic over Wi-Fi or on a slow serial link, or udp to the wrong address Jump to Fix
Bringup health card shows red rows on an active robot Stack started as another user Jump to Fix
The map viewer / canvas shows nothing QoS mismatch (SensorDataQoS best-effort) Jump to Fix
SLAM: no map was published, and /scan is fine SLAM lifecycle state or TF lookup Jump to Fix
Nav2 did not activate Lifecycle manager service timeout race Jump to Fix
Nav2 fails a goal leg, or ends "goal not reached" Goal tolerance, detour time, or a surviving pose Jump to Fix
NAV2 LEG … LOST THE BASE The base's own odometry stopped mid-leg (a dropped session) Jump to Fix
Topics are listed, but a reader started after you logged out gets no data logind removed the stack's shared memory Jump to Fix
Nav2 reaches the goal's spot but never finishes the final turn (Failed to make progress, Control loop missed its desired rate) Odometry scale, or a controller short of CPU Jump to Fix
Two 1-Click runs fight over one robot A kept stack from the previous run Jump to Fix
Map view draws the live scan at an angle to the walls Unanchored or uncalibrated heading Jump to Fix
Two robots interfere on one network (or poses jump metres with no error) Two stacks in one DDS domain on one subnet Jump to Fix
[NO WI-FI KEYS] A Wi-Fi robot with no Wi-Fi network set Jump to Fix
[NO BOARD] for a Wi-Fi robot (not on USB, not heard on Wi-Fi) The robot off the cable, unreachable Jump to Fix
the board refused the OTA password The robot's OTA password changed or was lost Jump to Fix
the board refused the env write … flash it once over USB An ESP32 on the old partition layout Jump to Fix
[NOT THE NEW IMAGE] The board booted another image after a flash Jump to Fix
Image pull: lookup ... on [::1]:53 ... connection refused The robot computer lost its IPv4 address Jump to Fix
Where the logs are File locations Jump to Fix

The page asks for a token

You opened the page without its access key. Open the ?token=… link once and the browser remembers it. Where to find the token: Installation & Docker.

A robot config exists but the cockpit does not offer it

Every file in the config directory is listed. Two files with the same robot.name are shown as a conflict: give them distinct names. The CLI resolves --robot <name> by filename.

MCU MISMATCH — it won't flash

The board on the USB bus is not the silicon the selected robot builds for (e.g. an RP2040 with a pico2/RP2350 robot selected). The run stops before writing anything. Pick the robot that matches the board, or correct the silicon in the config, base_controller.mcu (on the MCU & Sim tab), and press Start again. base_controller.name is only a label: changing it changes nothing that is flashed. When you are sure the USB bus is wrong about the board, one_click_pipeline.py --skip-mcu-check flashes anyway.

A flash failed

flash_mcu.py prints a NEXT ACTION: line and the cockpit raises it as a banner:

  • RP2 (Pico / Pico 2): unplug, hold BOOTSEL, replug, release, then Start again.
  • ESP32: unplug/replug the USB cable (or hold BOOT, tap EN, release BOOT), check the port exists, then Start again.

Serial port: permission denied, or the port is missing (Linux)

Permission denied. Serial ports on Linux belong to the dialout group. Inside the cockpit's container this is handled: docker-compose.yml adds the container to dialout, and SERIAL_GID (default 20, Ubuntu's dialout) aligns its group with the host's (Rootless vs rootful); set it to getent group dialout's number if your host differs. Outside the container (the flasher or lino_tool.py run on the host, a browser's Terminal on this computer's USB), add yourself to the group, then log out and in again:

sudo usermod -aG dialout $USER

The port appears and vanishes (a CP210x or CH340 bridge, /dev/ttyUSB0): on Ubuntu the braille-display service brltty claims some USB-serial bridges as soon as they enumerate. sudo dmesg | grep -i brltty shows it. If you use no braille display, remove it (sudo apt remove brltty) and replug the board. The cockpit's installers do not touch either setting.

[NO RADIO] / [IS A W]

Every RP2 image is built from a W (wireless) env, so a plain Pico also enumerates as "Pico W"; only the boot banner's radio= says which board it is (the radio chip answering, or not). After a flash the flasher compares it with the config's base_controller.mcu:

  • [NO RADIO]: the config is a W (picow, pico2w, xrp) and the radio did not answer: the board is a plain Pico. Its LED and Wi-Fi cannot work. Fix: select the plain config (pico or pico2).
  • [IS A W]: the config is plain (pico, pico2) and a radio answered: the board is a W, and the plain LED pin would drive the radio's chip select. Fix: select the W config (picow, pico2w).

See The Env Partition & Prebuilt Images for the banner.

[WRONG BOARD]

The flasher records which board it last wrote on each port. The tty now leads to a different board (another board was renumbered onto that port, or one was swapped), so nothing was written: no reset, no 1200-baud touch. Fix: check which board is plugged where and select its robot. If you replaced the board on purpose, flash once from a shell with --new-board: python3 scripts/flash_mcu.py --params <robot>_config.yaml --port <tty> --new-board.

The tool refuses a simulated part

A command typed at the tool, or pressed on Hardware Tests, is refused and the tool stays at its prompt:

[tool] 'motor' tests the wheels, and this robot's config simulates them -- the tool runs on real parts only. Turn the simulation off on the Sensors tab and press Flash MCU.

The tool measures real hardware, so a command is refused when the part it tests is simulated or absent in the robot config: motor, motors and ramp when the wheels are simulated (acc refuses in its own words), adc when the battery is, and i2c when the config assigns no I2C pins and names no fixed bus (a bare module has none). Fix: select the robot's reference design, which brings its I2C and motor pins, or describe the hardware as real in its config (use_sim_imu, use_sim_wheel, use_sim_battery false; I2C SDA/SCL on the Pin Matrix), and flash. The commands are listed under The commands.

[MIXED SIMULATION REFUSED]

The flasher stops before writing anything with ❌ [MIXED SIMULATION REFUSED] Sim and real cannot mix. This robot would run real … with simulated …, or bringup stops with the same message.

Sim and real cannot mix, and the result of a mix is useless. Simulated wheels drive a pose the real IMU never feels, a simulated LiDAR scans a room the robot is not in, and a simulated IMU on real wheels reports turns nothing measured. The odometry, the EKF and SLAM are each told about a different robot, so nothing such a run measures or maps means anything. The cockpit refuses it rather than run it.

Fix: a real robot simulates nothing. The web UI turns the simulation off when the first pin is assigned; a config edited by hand can still mix. Turn every simulation toggle off on the Sensors tab (tab 4), name the real IMU (AUTO, or NONE for a robot without one), and run in Real mode. Or run the whole robot in Sim mode, which simulates every part. The message names the real and the simulated parts it found. The diagnostic tools are not judged by this rule: each tests one real part of a half-built robot (Building a Real Robot).

The board reboots forever after a flash and never prints a banner

If the ROM output (which the flasher shows at 115200 when no application banner arrives) reads

E (211) spi_flash: Detected size(4096k) smaller than the size in the binary image header(8192k).
                   Probe failed.
assert failed: do_core_init startup.c:328 (flash_ret == ESP_OK)

the image was built for a bigger flash than the module has. The board is not damaged — the ROM loader still answers, so flash a correct image over it. The usual cause is a stale prebuilt: see Prebuilt images, and check the built … from commit … line the flasher prints before it writes.

An ESP32-S3 on native USB answers nothing after its first flash

The flash succeeds, then the board never prints a banner, the cockpit finds nothing on the bus, and the kernel log says can't set config #1, error -71.

The S3 routes its USB pins to one of two controllers: USB-OTG (TinyUSB firmware such as ESPHome, 303a:0009) or the USB-Serial/JTAG controller the cockpit's S3 image uses (303a:1001). The routing survives every reset except a power-on, so the first cockpit image flashed over a TinyUSB firmware runs behind a controller that is not connected to the port.

Fix: unplug the board and plug it back in, once. It comes up as 303a:1001 and stays there. Its /dev/serial/by-id/ name changes with it, so point anything that used the old name at the new one (or at /dev/serial/by-path/).

A diagnostic tool "worked" but showed nothing

The tests are commands of the tool inside the one firmware image. A test that needs current firmware (e.g. the ADC calibrator's [ADC_JSON] line) prints nothing useful on a board running older firmware: flash the base image once to update every test.

The tool's messages

The robot runs a test at every power-on (its wheels turn, or it prints a test's lines and never starts micro-ROS): an older firmware, whose env's app names a test (app=test_motors …, written by an older script or cockpit). Current firmware boots the robot whatever app says. On Hardware Tests, Enter tool and then Back to robot: the cockpit writes app=base (the env only, never a reflash). From a terminal: flash_mcu.py --params <robot>_config.yaml --port <tty> --env-only --app base. Flashing the current firmware fixes it for good.

What the tool and lino_tool.py say, and what to do:

Message Meaning, and the fix
[lino_tool] the board did not enter the tool (is tool_wait 0, or is this firmware older?) No [app] tool after the restart. The firmware predates the tool (press Flash MCU), or the config sets tool_wait to 0.
[lino_tool] the reboot failed: nothing to catch The board could not be restarted: the port is wrong, or the robot stack still holds it (Bringup ⏹), or --env names another board.
[lino_tool] pyserial is missing: … Run it inside the cockpit's container: docker compose exec -it cockpit python3 /ws/scripts/lino_tool.py ….
[lino_tool] --env is needed to reboot the board over USB Give --params <robot>_config.yaml, or --env esp32, esp32s3, pico, pico2, xrp or unoq.
lino-tool denied (no OTA password: use USB) The board has no OTA password, so it takes no tool request over the network. Enter the tool on USB; a USB flash of a Wi-Fi robot gives it a password.
lino-tool busy (another request is pending) Someone else asked within the last 10 s. Try again.
[tool] no terminal on the network: this board has no OTA password As above: no password, no network terminal.
busy: another terminal is connected (an idle one closes after 2 min) One terminal at a time. A laptop that slept or a Wi-Fi blip can leave a session half open: it closes after 2 minutes without input.
wrong answer / too slow The login was not md5(password:nonce) within 10 s. Use lino_tool.py --params …: it answers with the robot's own password.
locked: too many wrong answers, try again shortly Three wrong answers from your address: wait 10 s.
[tool] idle 2 min: closing this terminal Nothing came from the terminal for 2 minutes. lino_tool.py sends a keep-alive; reconnect.
stopped: <command> (the terminal went quiet -- motors off) A drive started on the Wi-Fi terminal heard nothing for 3 s (2 s during acc): the link dropped. Only lino_tool.py keeps a drive alive.
stopped: <command> (the terminal went away -- motors off) The Wi-Fi terminal that started the drive closed.
stopped: <command> (the console went quiet -- motors off) The console that started a drive (with lino_tool.py's keep-alives) stopped: its ssh or USB session died.
stopped: <command> (lino-stop -- motors off) The lino-stop datagram stopped it.
[tool] busy: <command> is running -- 'stop' first (answered fail: … -- busy: <command> is running -- stop first) Stop the running command first (an empty line).
[tool] X is not a test setting -- change it in the cockpit and press Flash MCU set takes only tm_pct, tm_only, tm_secs, ta_ms, ta_pct. Everything else is read at boot.
[tool] this robot has 2 wheels: motor 1 or 2, or all A 2WD has motors 1 and 2.
[tool] 'motor' tests the wheels, and this robot's config simulates them … Turn the simulation off on the Sensors tab and press Flash MCU (Building a Real Robot, step 3).
[tool] 'adc' would drive the DAC pin, which a motor driver uses … (or the battery pin) That pin is one of the motor driver's: set dac_pin / battery_pin to free pins.
IDENT plant wheel=N SIGN WRONG: driven forward, its encoder reads -… rpm That wheel's motor or encoder is inverted: fix its invert (Building a Real Robot, step 5) and run acc again.
MOTOR N FWD … DIR WRONG (motor or encoder inverted) The wheel counts against its power. Under enc, turn it forward by hand: counting up means the motor is inverted, down means the encoder is (Building a Real Robot, step 5).
RAMP motor N FWD: nothing moved up to 40 % The wheel did not turn at all: check the motor's supply, its driver channel and its wires; ramp N 80 tries higher.
IDENT loop wheel=N RUNAWAY: full power, turning against the setpoint acc stopped a wheel its PID was driving the wrong way: fix that wheel's invert, then run acc again.
[sensors] IMU: none -- the config says 'sim' … The tool uses only chips that answer on the bus: the config simulates the IMU, or names one that is not there.

The tool's answer words: ok, fail, done, stopped

Every command typed into the tool is answered first, in one word:

Answer Meaning
ok: <command> the tool took the command ((running) when it keeps running)
fail: <command> -- <why> it refused the command, or the command ended without doing its work: read the reason
done: <command> a running command finished its work
stopped: <command> (<why>) you, lino-stop, or a lost terminal stopped it; -- motors off after a command that drives

No answer in 2 s means the link is in trouble; the Web UI and lino_tool.py say so, and stop a drive command that went unanswered.

The board connected but there are no topics / SLAM sees nothing

The pipeline's rate gate checks /odom, /imu/data and /scan before SLAM. If it fails:

  • Confirm the micro-ROS agent transport and baud match the board (serial --dev … -b …). A binary linked against a different ROS 2 build dies with *** stack smashing detected *** the moment it opens a tty — use the transport the robot actually has.
  • Check the board's boot banner over serial: it prints app=, distro=, git=. If the git revision isn't the build you expect, the board is running something else — reflash.
  • Rates prove the board talks; only driving proves it moves. Run the eight-manoeuvre drive suite, python3 scripts/drive_suite.py: perfect 50 Hz topics on a robot that spins in place is a failure the rate gate cannot see. See Proving the base actually moves.

A real LiDAR wired to the MCU gives no /scan, or a slow one

The LiDAR's TX is on the board (lidar.rx_pin) and the robot is real. Check lidar.comm_mode against the robot's micro-ROS link (Waveshare General Driver):

  • micro-ROS over Wi-Fi with topic: the 1-Click refuses it before the run (❌ [RAW_SCAN OVER UART REFUSED]): over Wi-Fi the scan floods the micro-ROS session (measured /scan 0.8 Hz, the IMU starved). Use udp; the board then sends the LiDAR outside micro-ROS.
  • topic on a serial link under 1.5 Mbaud: refused the same way: the scan exhausts the link. topic needs native USB or a UART at 1.5 Mbaud or more (the General Driver's 1.5 Mbaud, the UNO Q's 4 Mbaud). On such a link the firmware reads the real LiDAR into raw_scan, an ESP32's as well as the UNO Q's. Otherwise put the LiDAR on the robot computer (serial).
  • udp: the board sends to lidar_ip, which must be the robot computer's address on the robot's Wi-Fi, and nothing else may hold UDP 8889 there.

/scan says NO DATA on a bare module

/odom and /imu/data are fine but /scan is not. The board's own emulator is the scan, through the LD driver (Where the virtual room is raycast).

  • [bringup] NO /scan in the log: the LiDAR is serial on the robot computer and its port does not exist. Sim mode cannot raycast into the robot computer's port, so a bare ESP32 DevKit (921600 baud, too slow for raw_scan) has no scan in Sim mode. Wire a bridge on LIDAR_RXD, use Wi-Fi (udp), or let the robot computer raycast the room (--host-laser, host_laser:=true).
  • topic mode: check raw_scan in the Bringup tab's MCU topic monitor. If it is there and /scan is not, the ld19 node is not reading it: look for it in ros2 node list.
  • serial mode: check that nothing else holds the LiDAR port (default /dev/ttyUSB1). Another USB-serial device on that node gives [ld19]: ldlidar communication is abnormal. and process has died on repeat. Unplug it, or set base_controller.lidar.serial_port to a port that cannot collide.

The bringup health card shows red rows on a robot that is clearly publishing

/odom/unfiltered and /imu/data read "no messages" while the 1-Click gate measured both at 50 Hz.

  • A stack started as root (docker exec without -u) leaves shared-memory segments the cockpit's user cannot read: every row goes red. Start the stack from the page.
  • A terminal without FASTDDS_DEFAULT_PROFILES_FILE cannot see the cockpit's stack at all. Read from inside its environment.

The map viewer / canvas shows nothing

/scan and /imu/data are published best-effort (SensorDataQoS). A tool subscribing reliable never matches them and receives nothing, silently — which is also why ros2 topic hz can report "none" on a topic that is publishing. rosbridge and the host filters subscribe best-effort; if you probe by hand, match the QoS.

SLAM: no map was published, and /scan is fine

Four faults print that one line. The pipeline reads logs/slam.log and prints which it was (the last lifecycle transition, the node's own lifecycle answer, and the distinct complaints):

  • slam_toolbox stuck in inactive (a missed configure event): the pipeline activates it directly (ros2 lifecycle set --no-daemon /slam_toolbox activate) and says so.
  • slam_toolbox stuck inside its configure (no lifecycle answer at all): the pipeline restarts SLAM once into logs/slam2.log (restarting SLAM once: a new process configures).
  • A solver plugin would not load.
  • Every scan dropped because odom→laser was not in the TF buffer yet. /scan still publishes at its full rate, so the rate gate is green.

Nav2 then fails with Invalid frame ID "map" ... frame does not exist, often a hundred times. That is the symptom, not the fault: a map frame only exists once something publishes a map.

Nav2 did not activate

nav2.log shows failed to send response to /<node>/change_state (timeout): a lifecycle reply was lost (a DDS discovery race, about 1.5% of Nav2 starts) and the manager waits for ever. The pipeline restarts Nav2 once into logs/nav2_retry.log and says so. If Nav2 instead reports Failed to bring up all requested nodes, a server failed for a real reason (a plugin, a parameter): the pipeline names the node and does not retry.

On a small robot computer Nav2 can also hang with neither: its container never loads its nodes because memory ran out. Check free -m while it starts. Keep the compose file's open-file limit (see Arduino UNO Q: the stack on the board), and close what else runs there.

Nav2 fails a goal leg, or the run ends "goal not reached"

A default 1-Click run maps by frontier exploration (its log is logs/explore_lite.log). The goal legs run with --no-explore or --require-goal: --goal-round-trips (default 4) round trips to a goal behind the obstacle wall, each leg required to arrive within 180 s (The six stages).

  • logs/nav2.log and the pipeline's verdict line say how far out a leg stopped and what Nav2 reported.
  • On a simulated robot, a leg starts pressed against the wall: the pose from an earlier run survived. The board's syslog says simulated pose reset to origin (new run) or the same run, simulated pose kept. Before SLAM the pipeline checks the start pose ([4.7/6] [POSE]) and restarts the bringup if the robot is not at the origin, unless you pass --no-pose-reset; a board reboot also zeroes it.

NAV2 LEG … LOST THE BASE

/odom/unfiltered, the base's own odometry, stopped for 6 s during a leg: the micro-ROS session dropped (on Wi-Fi, usually a link gap), so nothing the leg measured after that was the robot. The verdict comes before any judgement of the filtered pose, which keeps moving on the EKF's prediction.

  • Read the board's syslog for the session: on udp4 the board declares the session lost after 5 s of unanswered pings, and if the agent still does not answer 10 s later it rejoins its Wi-Fi once ([wifi] rejoin: …).
  • Fix the link first (Two robots interfere on one network, signal, power saving), then run again.
  • A robot config that still fuses ax/ay (imu0_config 12 and 13) makes the pose run off while the base is silent; bringup prints a warning. Set both false.

Nav2 reaches the goal's spot but never finishes the final turn

A leg ends a few centimetres from its goal but facing the wrong way ("home without its heading"), and logs/nav2.log shows Failed to make progress, a recovery (Running backup), then the same again until the leg times out.

  • Check the speeds first: run the speed check. A robot that turns less than its wheels say (a tracked or skid-steer base scrubbing, mecanum rollers slipping) makes the controller's final turn crawl or stall. Correct kinematics.wheel_diameter and, on 2wd or skid-steer, kinematics.angular_scale from its verdicts.
  • Control loop missed its desired rate of 20.0000 Hz. Current loop rate is 15 Hz, over and over: the controller cannot keep up on this computer. The MPPI controller a mecanum base uses is the heaviest; a small robot computer (a single-board Linux, the UNO Q's) runs it below rate, and a slow loop turns a fine final rotation into an oscillation. Close what else runs on the robot computer (a browser, the map viewer), and check its load with top during a goal.
  • A tracked or skid-steer robot circles at the goal, spinning whole turns before the leg ends: turning in place moves its centre (the tracks slip), out of xy_goal_tolerance, and the controller then turns to face a goal point a few centimetres away, which moves it again. Set xy_goal_tolerance above that drift (0.15 m on a small tracked chassis).
  • It stops 20–30° past the goal heading: the controller turns at rotate_to_heading_angular_vel right up to the goal heading and the base coasts on. Lower it in the FollowPath block (0.5 rad/s on a small tracked chassis).
  • Failed to make progress at the start of a goal behind the robot, while it turns in place: the config's progress checker counts only distance (SimpleProgressChecker). The template uses nav2_controller::PoseProgressChecker, with required_movement_angle: 0.5 (radians) beside required_movement_radius: 0.5 and movement_time_allowance: 10.0, so a slow turn counts as progress; copy those into an older config's progress_checker. The trade-off: a robot that keeps turning in place near its goal is not aborted by the progress checker either, which is what the next item prevents.
  • It turns in place for minutes a few centimetres from the goal, just outside xy_goal_tolerance, always the same way: once the goal is nearer than lookahead_dist, the Regulated Pure Pursuit controller aims straight at it, asking for an arc tighter than the base can drive; a tracked base pivots instead, the pivot moves it, and the next cycle turns again (each turn counts as progress, so nothing aborts it). The template projects RPP's curvature point past the goal along the path's last segment, so the arc never tightens below half that distance and the robot drives through the goal. In the FollowPath block: use_fixed_curvature_lookahead: true, curvature_lookahead_dist: 0.6 (equal to lookahead_dist, so mid-path nothing changes) and interpolate_curvature_after_goal: true; copy them into an older config. Until then, stop it with ⏹ Cancel goal in the Map Viewer.

[SIM REFUSED]

The selected robot is real (a reference design, built from one, or with any pin assigned), and the run asked for Sim mode or the Sim MCU. A real robot simulates nothing, so nothing was started. Fix: leave --mode out (a real robot then runs in auto) or give --mode real, and run it on its own board. To simulate, select a generated robot instead: the board's bare module (bare_<mcu>) or the Sim MCU (bare_sim).

[BRINGUP REQUIRED]

A robot with real wheels moves only once its bench bring-up is on record. The run lists the missing steps (a pass whose values changed since counts as missing) and starts nothing. Fix: finish them on Hardware Tests > Real-robot bring-up (steps 1 to 5, for the parts the robot has), or with scripts/robot_bringup.py (status lists them); see The bring-up checklist. Run without a complete bring-up (--skip-bringup-check) overrides it at your own risk. --topics-only is never held.

The room is short of / CLEARANCE_REFUSED

Before SLAM, Nav2 or test_acc moves a real robot, the LiDAR's whole circle must show nothing within the clearance (0.40 m, or the robot's radius + 0.15 m when that is more; for test_acc 1.0 m, or more for a fast robot), and the robot is inched clear when it can be. ❌ The room is short of 0.40 m. Nothing else moves the robot: carry it to … follows a CLEARANCE_REFUSED: line that says why: no step improved the room, the inching reached its limit (--inch-max), a LiDAR's blind sector could not be looked at, or /odom or the scan stopped during a move (a pause up to 1.5 s is waited out). In a corner, a LiDAR's blind sector is filled from the last run's saved map when the live scan fits it at exactly one pose, and the robot turns where it stands when the map covers its whole turning circle. Fix: carry the robot to open floor and run again; --clearance 0 skips the check. See The six stages.

NOTHING MOVES

❌ NOTHING MOVES: <why>: on a real robot the motion guard judges the scan and the battery for 6 s before Nav2 may move it, and here it did not say yes. The reason is the guard's: /scan at … Hz, under … or /scan stopped, battery at N %, under 10 %, or the guard never said the robot may move (logs/motion_guard.log). Fix: as for the next entry; read logs/motion_guard.log.

MOTION GUARD STOPPED THE GOALS

On a real robot the motion guard watches every exploration and goal leg. It trips, cancels every goal and ends the legs, when the scan falls under 80 % of the LiDAR model's rate over 5 s or is more than 1 s old (2 s for a LiDAR forwarded over Wi-Fi; a LiDAR short of power slows before it stops), or when the battery falls under 5 % (10 % to start): the board's percentage, or its voltage placed between pins.battery min_v and max_v where it reports none, as a median over 3 s. After a trip test_acc is not run either. Fix: charge the pack, check the LiDAR's power and cable, and run again. A goal whose plan ends outside the room the LiDAR saw at the start (less 0.30 m) is only cancelled and reported, not a trip; the floor the robot stood on then counts as room, even behind a masked LiDAR sector, so the leg home is not refused. --explore-beyond-room allows such goals for a robot meant to cross rooms. See The six stages.

Two 1-Click runs fight over one robot

A run leaves its stack running on purpose. A second stack beside it would mean two agents on one serial port, /imu/data silent, and the old Nav2 driving the new run's robot, so a new run stops the kept stack first and says so (Stopping what the previous 1-Click run left running: …). If it warns that something did not stop -- started by another user?, stop it as that user with python3 scripts/robot_stack.py --stop. See After it finishes.

The map view draws the live scan at an angle to the walls it just mapped

The angle grows all run, and nothing prints it: the error lives in map -> odom. It has several independent causes (no magnetometer, an uncalibrated one, or the EKF not fusing absolute yaw), listed under How you find out it is wrong.

Quick check: compare ros2 topic echo /odom/unfiltered with /odom at rest. A steady gap of several degrees is the heading chain. Then calibrate: Heading & Magnetometer Calibration.

An angle that is there from the first scan and does not grow is the LiDAR's mount: the map viewer draws the scan through base_link → laser, so set the LiDAR's yaw (and X/Y) on the Base tab (geometry.laser) to how it really sits.

Two robots interfere on one network

Give each a distinct topic_prefix — see Multi-Robot Namespacing. Without it, both publish /odom, /scan, /map and the same TF frames, and they fight.

It does not have to be two robots you know about. Any two stacks on the same subnet in the same ROS_DOMAIN_ID discover each other — a second robot computer, a test box, a container on the host network — and the failure is a wrong answer, not an error: the EKF fuses both robots' odometry, SLAM matches the other robot's scans, and the map pose jumps by metres. Check:

ros2 topic info -v /odom/unfiltered      # Publisher count: 1 is right; 2 is another robot

The extra publisher's Node name names the other robot. Separate them by topic_prefix, by a different ROS_DOMAIN_ID (and base_controller.domain_id for the board), or by a separate network.

A reader gets no data after you logged out

ros2 topic list shows every topic, but ros2 topic echo, a Web UI view or a new node started after you logged out of the robot computer gets nothing; restarting bringup fixes it until the next logout. systemd-logind's default RemoveIPC=yes deletes every shared-memory file a user owns when that user's last session ends, and the stack's Fast DDS segments are owned by that user. Fix, once per robot computer:

sudo scripts/install_keep_ipc.sh     # installs /etc/systemd/logind.conf.d/50-linorobot2-keep-ipc.conf

It prints logind RemoveIPC: false when it took effect. Restart bringup once after it.

[NO WI-FI KEYS]

A robot on the Wi-Fi transport (an ESP32 or ESP32-S3 with transport: udp4, such as the Maker's Pet mini) gets its network at its first flash, and then leaves the cable. With no Wi-Fi network set, or the seeded placeholder YOUR_WIFI_SSID, the 1-Click, Flash MCU and the flasher stop before writing anything. Fix: enter the network and its password in the Secrets tool tab, then run the 1-Click again. A bare module runs over USB serial and needs none.

[NO BOARD] for a Wi-Fi robot

'<robot>' is a Wi-Fi robot … not on USB, did not answer a ping and sent no syslog banner: the cockpit asked for the robot's board at its recorded IP, at <name>.local and by a broadcast, and nothing answered as that board. Check: the robot is powered and on the network its env names (the Wi-Fi keys it was flashed with); this computer is on the same network; nothing blocks UDP 3233 (ping), 5353 (mDNS) and 3232 (OTA). python3 scripts/wifi_boards.py ping lists every board that answers. this computer has never flashed '<robot>' over USB means the robot must be flashed over USB once on this computer first: that is when the cockpit learns which board is that robot.

The board refused the OTA password

Each Wi-Fi robot has its own OTA password, generated at its first USB flash and kept in secrets.ota.yaml in the config directory. A board flashed by another computer, or a lost or edited secrets.ota.yaml, no longer matches. Nothing was written. Fix: plug the robot in and flash it once over USB from this computer: the flash writes the password this computer holds.

An ESP32 refuses the env over the air

the board refused the env write … its partition table or image predates env-over-OTA; flash it once over USB. The env can be written over the air only on the partition layout that names it as the OTA filesystem target. Fix: one USB flash of the current release brings the layout; from then on the env goes over the air too.

[NOT THE NEW IMAGE]

After a USB flash the board reported another commit than the release image just written, so the flash is failed rather than recorded as done. Fix: flash again; if it repeats, report it with logs/flash.log.

The robot computer is "connected" but has no IPv4 address

An image pull or a 1-Click on the robot fails with a DNS error such as lookup registry-1.docker.io on [::1]:53: ... connection refused, and the robot does not answer on its usual address. On the robot (keyboard, USB shell or Ethernet):

nmcli -t dev show wlan0 | grep -E 'STATE|IP4.ADDRESS'

GENERAL.STATE:100 (connected) with no IP4.ADDRESS line is this fault: the Wi-Fi re-associated, DHCP got no answer in time, and NetworkManager gave up while IPv6 kept the connection "up". Fix now: sudo nmcli con up <connection name>. Fix for good: make DHCP retry forever and add the watchdog, as in Keep the robot computer on the network.

Where the logs are

Each 1-Click run writes its own logs/<stamp>/ next to the repo, with logs/latest and a top-level symlink per file (logs/nav2.log …), so a later run does not overwrite an earlier one. Under Docker, logs/ is the cockpit-logs volume (/ws/logs in the container). The file list: Running it from a shell.


Back to Home.

Clone this wiki locally