Skip to content

Fix camera shell examples - #832

Merged
julianoes merged 5 commits into
mavlink:mainfrom
vishwagw:fix-camera-shell-examples
Oct 15, 2026
Merged

julianoes merged 5 commits into
mavlink:mainfrom
vishwagw:fix-camera-shell-examples

Conversation

@vishwagw

Copy link
Copy Markdown
Contributor

Commit messages for the web upload. Upload each file to the same branch as its own commit, in this order, with these subjects. The full messages, with explanations, are in the .patch files.

  1. examples/camera.py: examples: camera: address the camera by its component ID
  2. examples/camera_params.py: examples: camera_params: address the camera by its component ID
  3. examples/shell.py: examples: shell: keep running, and pass the shell device
  4. examples/gimbal.py: examples: gimbal: return the gimbals found instead of None
  5. examples/px4_ev_automation.py and examples/px4_ev_automation_keyboard.py together: examples: px4_ev_automation: drop close() calls that don't exist

Several examples don't run against the current API. Each was found by checking every example's plugin calls against the generated plugin signatures, then confirmed by running it (see Testing).

camera.py and camera_params.py

  • The problem: since the camera plugin became multi-camera, set_mode(), take_photo() and set_setting() take the camera's component_id. mode(), current_settings() and possible_setting_options() yield per-camera update objects, and status() is gone. Both examples crash on their first camera command:
    TypeError: Camera.set_mode() missing 1 required positional argument: 'mode'
    
  • The fix: both now wait for the first camera in camera_list(), pass its component_id, and read the update objects. camera.py prints storage() updates in place of status().

shell.py

  • Exit: the example exited right after printing nsh> . Since #6dc989f ("examples: fix RUF006") replaced run_forever() with asyncio.run(run()), run() returns as soon as the stdin reader is added. It now waits on an event, like telemetry.py and the other long-running examples.
  • Device: Shell.send() also requires a device. Without it, every command raised a TypeError inside a fire-and-forget task, so nothing reached the vehicle and no error was shown. It now passes Device.SHELL, the plugin's documented default.

gimbal.py

  • The problem: gimbal_list() never ends, so wait_for() always timed out and get_gimbals() returned None, even when gimbals were found. run() then crashed with TypeError: 'NoneType' object is not iterable.
  • The fix: it now returns as soon as a list with gimbals arrives, and returns the empty list on timeout.
  • ASYNC109: the timeout parameter is renamed wait_s. ruff's ASYNC109 flags a timeout parameter on async functions, and with the current ruff (0.17) that makes the CI style check fail on main. asyncio.timeout() would need Python 3.11, and setup.py allows 3.7.

px4_ev_automation.py and px4_ev_automation_keyboard.py
Neither System nor Telemetry has a close() method. In px4_ev_automation.py the call is reached when the telemetry streams end, and raises AttributeError. In the keyboard version it sits after an endless loop. Both calls are removed.

Testing

Run on Linux with Python 3.13, mavsdk-grpc 4.0.5 and its bundled mavsdk_server v4.0.5. With no PX4 available, I used two stand-ins, both included in test-harness/:

  • fake_autopilot.py: PX4 heartbeats from system 1 / component 1. It routes MAVLink between the example and the camera, logs SERIAL_CONTROL, and answers gimbal commands.
  • fake_camera.py: a MAVLink camera built on MAVSDK's own CameraServer plugin. It answers mode changes, photos and storage requests, and logs each one.
Example Before After
camera.py crashes with the TypeError above finds the camera, sets PHOTO mode and takes a photo; the camera logs both requests; exits 0
camera_params.py (input p, m, 1, p) crashes with the TypeError at m prints settings, and setting PHOTO reaches the camera with --> Succeeded
shell.py (input ls, param show SYS_AUTOSTART) exits after nsh> ; with only the exit fixed, 0 commands reach the autopilot both commands arrive as SERIAL_CONTROL with device=10 (SERIAL_CONTROL_DEV_SHELL)
gimbal.py, no gimbal present prints "Timeout: No gimbals found.", then TypeError: 'NoneType' object is not iterable prints the timeout and exits 0
get_gimbals() with a stub stream: [], then one gimbal, then never ending (test_get_gimbals.py) returns None after the full timeout returns [gimbal] after 0.4 s, and [] after the timeout with no gimbal

The CI style commands pass: ruff format --check, ruff check --select=ASYNC,RUF006,E,F and ruff check --select=PERF (all with --line-length=100), plus codespell. ASYNC109 currently fails on main, and this PR fixes it.

Not tested: camera_params.py's s (change a setting) path, which needs a camera definition file. It only changes to pass the component_id, the same as set_mode().

@julianoes julianoes left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@julianoes
julianoes merged commit 8fa280f into mavlink:main Oct 15, 2026
9 checks passed
@vishwagw

Copy link
Copy Markdown
Contributor Author

Hi @julianoes, Thank you for merging. Cheers!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants