skycore_cli.py is a standalone, host-run MAVLink/ArduPilot maintenance CLI for a SkyCore drone. An engineer runs it directly on the Jetson host (not inside a container) to switch the EKF navigation source between GPS and SLAM, read and write ArduPilot parameters, stream live MAVLink messages, pull dataflash logs, upload terrain tiles, and manage the payload camera’s SD card.
Source: skycore_cli.py (~2,757 lines). The whole tool is three moving parts: MAVLinkConnection (the raw pymavlink client), NavigationToggle (a higher-level wrapper that holds the GPS vs SLAM parameter sets and orchestrates reboots), and a main() / execute_command() dispatcher that maps ~30 command strings onto those two classes.
Running the CLI
The tool has two modes, selected by whether you pass an argument:menu/help re-prints the command list and q quits (skycore_cli.py:2266). In one-shot mode the first argv token is the command and the rest are its arguments (skycore_cli.py:2249).
Connection auto-probe
MAVLinkConnection._connect() (skycore_cli.py:163) tries these endpoints in order and keeps the first that returns a heartbeat:
| Order | Endpoint | Typical use |
|---|---|---|
| 1–2 | /dev/ttyACM0, /dev/ttyACM1 | USB CDC to Pixhawk |
| 3–4 | /dev/ttyUSB0, /dev/ttyUSB1 | FTDI/USB serial FC |
| 5–6 | udpin:127.0.0.1:14550/14551 | Listen for MAVLink UDP |
| 7–8 | udpout:127.0.0.1:14550/14551 | Dial out to a router/SITL |
| 9–10 | udp://127.0.0.1:14550/14551 | Generic UDP |
/dev/ttyACM0. Note that mavp2p (the MAVLink router) also fans the link out over UDP 14550 to core MAVROS — so if you run the CLI while the stack is up, it can compete for the serial device. Prefer running it against a free UDP endpoint or when the router is stopped.
Command reference
Grouping below matchesshow_help() (skycore_cli.py:2714) and the dispatcher in execute_command() (skycore_cli.py:2292).
Arguments vs. interactive prompts. Some commands read positional argv (
stream, export_params, download_log, download_last_log, download_all_logs, upload_terrain, list_camera_sd, clear_camera_sd). Others always prompt via input() even in one-shot mode — get_param, set_param, custom_ekf, and filter_baro ignore argv and ask for their values interactively. Destructive commands (clean_sd, reset_params, delete_logs, download_all_logs, upload_terrain) additionally require a typed yes/y confirmation.Navigation source (GPS ⇄ SLAM)
These are the CLI’s headline feature: flipping which sensor the ArduPilot EKF3 trusts for position, velocity, and yaw. See Isaac Visual SLAM & Pose Bridge for what actually feeds the external-nav estimate, and RTK NTRIP GPS Corrections for the GPS side.| Command | Args | Behavior |
|---|---|---|
status | — | Prints current source by reading EK3_SRC1_POSXY (3 = GPS, else SLAM). skycore_cli.py:1647 |
gps | — | Writes the GPS source set, then reboots the FC. skycore_cli.py:1624 |
slam | — | Writes the SLAM (external-nav) source set, then reboots. skycore_cli.py:1629 |
toggle | — | Reads EK3_SRC1_POSXY; if 3 switches to SLAM, otherwise switches to GPS. skycore_cli.py:1634 |
ekf | — | Sets EKF origin and home at (0, 0, 0). skycore_cli.py:2312 |
custom_ekf | — | Prompts for lat/lon/alt, then sets EKF origin + home there. skycore_cli.py:2317 |
set_source_set, skycore_cli.py:1602). The two parameter sets are hardcoded in NavigationToggle.__init__ (skycore_cli.py:1566):
- GPS source set
- SLAM source set
skycore_cli.py:1566
EK3_SRC_OPTIONS is forced to 0 at construction time to disable velocity fusion (skycore_cli.py:1600).
set_ekf_and_home() first reboots the FC and waits ~20 s, then sends SET_GPS_GLOBAL_ORIGIN (set_ekf_origin, skycore_cli.py:278) and MAV_CMD_DO_SET_HOME (set_home_position, skycore_cli.py:256), then reads back GLOBAL_POSITION_INT and warns if the position drifted beyond ~1e-6° / 0.1 m tolerance (skycore_cli.py:1654). The ekf and full_restart commands call it with (0, 0, 0); the function’s own default coordinates (42.160952, 24.767237, 380.0) are only used if it is invoked directly.Telemetry inspection
| Command | Args | Behavior |
|---|---|---|
monitor | — | Prints GLOBAL_POSITION_INT lat/lon/alt once per second for 30 s. skycore_cli.py:1718 |
listen | — | Interactive typed-message monitor (60 s default). skycore_cli.py:1816 |
stream | — | Interactive continuous stream in compact format. skycore_cli.py:1879 |
stream all | — | Every message, detailed format, ~5 Hz. skycore_cli.py:2343 |
stream filtered | — | All messages except noisy types (TIMESYNC, PARAM_VALUE, SYSTEM_TIME, RAW_IMU, SCALED_IMU, RC_CHANNELS_RAW, SERVO_OUTPUT_RAW, VELOCITY_ESTIMATE). skycore_cli.py:2384 |
stream statustext | — | Only STATUSTEXT — ideal for reading PreArm failure reasons. skycore_cli.py:2335 |
stream filter X,Y | comma list | All messages except the listed types (uppercased). skycore_cli.py:2432 |
stream compact | — | All messages, compact one-line format. skycore_cli.py:2339 |
recent_msgs | — | Dumps the last 30 messages from the in-memory ring buffer (max_log_size = 100). skycore_cli.py:2006 |
Parameters
| Command | Args | Behavior |
|---|---|---|
get_param | (prompts) | Prompts for a name, prints its value. skycore_cli.py:2539 |
set_param | (prompts) | Prompts for name + numeric value and writes it. skycore_cli.py:2549 |
export_params | [file] | Dumps the full param list to CSV (NAME,value). Default file pixhawk_params_<timestamp>.param. skycore_cli.py:2581, skycore_cli.py:672 |
filter_baro | (prompts) | Prompts for input/output files, writes a copy with all barometer params removed (keeps everything else). skycore_cli.py:2564 |
reset_params | — | Factory-reset all params via MAV_CMD_PREFLIGHT_STORAGE (param1=2), after a yes confirmation and reboot. skycore_cli.py:2579, skycore_cli.py:371 |
Naming trap: the
filter_baro command invokes filter_out_baro_params() (skycore_cli.py:82), which excludes baro-matching lines (BARO, GND_BARO, EK3_ALT, EK3_BARO, COMPASS_BARO, ARSPD_BARO, MS5, BMP, LPS, altitude, pressure) and keeps the rest. The sibling filter_baro_params() (skycore_cli.py:23), which keeps only baro params, exists in the file but is not wired to any command.System / reboot
| Command | Args | Behavior |
|---|---|---|
reboot | — | MAV_CMD_PREFLIGHT_REBOOT_SHUTDOWN (param1=1), wait 20 s, reconnect. skycore_cli.py:2480, skycore_cli.py:310 |
full_restart | — | Reboot FC → reconnect → docker restart isaac_ros_dev-aarch64-container → wait 30 s → set EKF/home at (0,0,0). skycore_cli.py:2490 |
clean_sd | — | Format the Pixhawk SD via MAV_CMD_PREFLIGHT_STORAGE (param1=2, param3=1). Requires yes. skycore_cli.py:2562, skycore_cli.py:332 |
Dataflash logs
ArduPilot.bin log management over MAVLink LOG_REQUEST_*.
| Command | Args | Behavior |
|---|---|---|
list_logs | — | Lists log ID, size, and UTC timestamp. skycore_cli.py:2093, skycore_cli.py:728 |
download_log | [id] [file] | Downloads one log by ID (prompts for ID if omitted). skycore_cli.py:2589 |
download_last_log | [dir] | Downloads the highest-ID log via the fast chunked + resume path to <dir>/log_<id>_<ts>.bin (default dir logs). skycore_cli.py:2608, skycore_cli.py:1032 |
download_all_logs | [dir] | Downloads every log to a directory (default logs), after a yes confirmation. skycore_cli.py:2602 |
delete_logs | — | Erases all logs (yes confirmation). skycore_cli.py:2618, skycore_cli.py:1114 |
MAVLink offers no per-log delete —
delete_logs erases the entire dataflash. To keep one log, download_log it first. Automatic post-flight log pulls are a separate feature of the gamepad service’s ExecutionTracker/LogDownloader (see Executions, Assets & Reports); this CLI is for manual retrieval.Terrain
| Command | Args | Behavior |
|---|---|---|
upload_terrain | [dir] | Uploads every .DAT tile from a directory to the FC’s /APM/terrain/ over MAVLink-FTP. Default dir /home/skycore/skyhub_core/terrain. skycore_cli.py:2620, skycore_cli.py:1150 |
upload_terrain_files() uses pymavlink.mavftp to mkdir /APM then /APM/terrain, and uploads each .DAT with a progress callback; a hand-rolled FTP-opcode implementation (_upload_terrain_files_direct, skycore_cli.py:1275) exists as a fallback. It prompts for a yes confirmation and prints the file list and total size first.
Camera SD card (SIYI)
| Command | Args | Behavior |
|---|---|---|
list_camera_sd | [ip] | Lists videos + images on the SIYI camera SD with sizes. skycore_cli.py:2673 |
clear_camera_sd | [ip] | Deletes videos and images (per-category y/N confirmations). skycore_cli.py:2626 |
SiyiDownloader, imported from the gamepad service via a sys.path hack at the top of the file (skycore_cli.py:11). If that import fails, SIYI_AVAILABLE is False and the commands print an error and return. The camera IP defaults to CAMERA_IP env (or <camera-ip>) and can be overridden by the positional arg.
Command surface at a glance
Gotchas & guardrails
It is a maintenance tool, not the OS — keep the lifecycles separate
It is a maintenance tool, not the OS — keep the lifecycles separate
The name implies an orchestrator; it is not. Boot and supervision are handled by
docker-compose profiles, supervisord (core container), and the gamepad service. The CLI runs on the host over local MAVLink and shares nothing with those runtimes. Do not merge them in a refactor.No heartbeat = hard exit
No heartbeat = hard exit
MAVLinkConnection raises ConnectionError at construction if no device/UDP endpoint answers, and main() exits before running the requested command (skycore_cli.py:2277). Ensure a FC or SITL is reachable on one of the probed endpoints first.Interactive prompts leak into one-shot mode
Interactive prompts leak into one-shot mode
get_param, set_param, custom_ekf, and filter_baro call input() and ignore argv, so python skycore_cli.py set_param still stops to ask for name and value. Don’t script them expecting positional args — only the argv-driven commands in the table above are scriptable.filter_baro removes baro params (despite the name)
filter_baro removes baro params (despite the name)
The wired command excludes barometer parameters and keeps the rest. The keep-only-baro variant (
filter_baro_params) is dead code.Related pages
Drone OS Overview
Where the CLI sits relative to the containerized service stack.
Microservices & Profiles
The real orchestrator: docker-compose profiles and supervisord.
MAVLink Routing (mavp2p)
The 14550/14777/14560/14900 port map the CLI’s UDP probes share.
Isaac Visual SLAM
What feeds the external-nav estimate the
slam command selects.RTK NTRIP Corrections
The GPS side of the nav-source toggle.
Local Dev with SITL
Run the CLI against ArduPilot SITL over UDP 14550/14551.

