51179 Commits

Author SHA1 Message Date
Jacob Dahl
d551d33b61 docs(agents): keep agents out of the Crowdin-owned translation trees (#28807)
* docs(instructions): keep agents out of the Crowdin-owned translation trees

docs/ko, docs/zh and docs/uk regenerate from docs/en, but nothing in the scoped guidance said so, and repo-wide sweeps edit them along with the English source.

Assisted-by: Claude:claude-opus-5-5

* docs(agents): state the translation rule in AGENTS.md

Agents load AGENTS.md directly; the .github/instructions file only reached them through a pointer to go read it.

Assisted-by: Claude:claude-opus-5-5
2026-09-22 18:24:11 -06:00
Jacob Dahl
2ec03ff1c3 fix(ci): skip the flash comment for sub-30 B deltas and agent-doc changes (#28806)
The commit hash compiled into px4_firmware_version_binary() changes its code size, so a PR with no code change reported -8 B on v6x and got a comment. Changes that only touch agent instructions also no longer build the targets.

Assisted-by: Claude:claude-opus-5-5

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
2026-09-22 17:52:55 -06:00
Jacob Dahl
9e9871d403 docs(agents): user-set worktree location, italic Assisted-by in PR bodies (#28805)
* docs(agents): let a user's own instructions set the worktree location

A fixed ../PX4-Autopilot-worktrees/ path overrode contributors who keep worktrees elsewhere. Their own agent instructions now win, with the sibling directory as the default.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* docs(agents): italicize the PR body's Assisted-by line

Set apart in italics, the disclosure reads as a footnote rather than part of the Solution section above it.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

---------

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
2026-09-22 17:33:12 -06:00
Jacob Dahl
11ad677f8e fix(commander): clarify sensor startup diagnostics (#28620)
Use the arming-check events to explain initialization waits without misleading legacy preflight messages.

Assisted-by: Codex

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
2026-09-22 17:00:32 -06:00
Jacob Dahl
3d154f5d45 docs(maintainers): retire inactive owners and set Architecture (#28570)
Daniel Agar, Paul Riseborough, and David Sidrane no longer work on the project. Architecture has been vacant in practice.

Assisted-by: Grok:grok-4.6

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
2026-09-22 16:58:24 -06:00
Jacob Dahl
1d761cdf2e fix(ci): report flash and static RAM usage separately (#28789)
* fix(ci): report flash and static RAM usage separately

Bloaty's VM total includes zero-initialized RAM and understates the combined cost of data and code copied from flash into RAM. Report the flash load image and static RAM allocations independently so the PR headline reflects each resource's actual change.

Assisted-by: Codex

* docs(ci): trim memory analysis comment

Assisted-by: Codex

* fix(ci): diff PR flash usage against the merge commit's base

The base branch tip is fetched when the job runs, so a PR merged while its job was queued was compared against itself and reported a near-zero change, and commits landed in the meantime showed up as reverted.

* fix(ci): measure flash image from section headers

ld maps the ELF header into the first LOAD segment when its max page size exceeds the flash origin's alignment (binutils 2.34 defaults to 64 KiB), so segment-based accounting rejected valid firmware as outside the flash region.

* feat(ci): flag notable flash and RAM changes

Growth over 100 B and 1000 B gets yellow and red markers, savings over 100 B green, so reviewers can spot size regressions without reading the numbers.

* refactor(ci): derive flash and RAM totals without per-target constants

The matrix duplicated each board's flash-analysis linker region, which would drift silently. Section headers alone distinguish code executing in place (VMA == LMA) from data copied to or reserved in RAM, and the image span matches objcopy -O binary.

* fix(ci): limit the bloaty breakdown to VM sizes

Debug, symbol and string table rows only change the file size and made up most of the comment.

* fix(ci): post flash analysis only when size changes

A push that puts flash and static RAM back to zero was still rewriting the sticky comment, so a PR with no size change carried a zero report. Remove that comment instead. Fork tokens cannot delete it, so those PRs hand a delete artifact to the poster.

Assisted-by: Grok:grok-4.7
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(ci): retain zero-delta flash analysis comments

Existing reports can be updated when size deltas return to zero, avoiding a separate privileged deletion path.

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
Assisted-by: Codex:gpt-6

---------

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
2026-09-22 16:56:58 -06:00
Jacob Dahl
85459fae96 fix(lockstep): stop SIH from passing the barrier on banked releases (#28802) 2026-09-23 09:35:18 +12:00
onelittlechild
6c16c34944 fix(navigator): leave landing abort yaw unspecified (#28791)
Assisted-by: Codex

Signed-off-by: onelittlechildawa <onelittlechild@outlook.com>
2026-09-22 14:42:46 -06:00
Jacob Dahl
eff5e1b350 chore(agents): move Claude instructions and skills to AGENTS.md and .agents (#28803)
* chore(agents): move Claude instructions and skills to AGENTS.md and .agents

Claude Code now reads AGENTS.md and .agents/skills, so one client-agnostic copy serves every assistant and the Codex adapters that pointed back into .claude go away. The build skill splits by host: Linux builds natively, macOS needs the container.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* chore(agents): expose .agents to Claude Code via imports and symlinks

Claude Code reads AGENTS.md only when no CLAUDE.md or CLAUDE.local.md exists and never reads .agents/, so the skills and instructions were invisible to it. Worktrees move outside the repository, where no tool's recursive search reaches them.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* chore(agents): refine build, commit, pr and review-pr skills

Linux builds run in whatever checkout the agent is in. PR bodies now carry the Assisted-by disclosure and an optional Testing section for testing worth reporting. Commits preserve history for reviewers since PRs are squash-merged. Reviews can check the area's history for intentional or in-flight work.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* feat(agents): push and refresh the PR description from the commit skill

A commit on a pushed branch otherwise leaves the PR stale until someone remembers to push and re-edit it.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(agents): drop trailing blank line in .gitignore

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

---------

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
2026-09-22 14:26:39 -06:00
onelittlechild
cc49b8be30 fix(navigator): ignore zero-initialized takeoff coordinates (#28792)
Assisted-by: Codex

Signed-off-by: onelittlechildawa <onelittlechild@outlook.com>
2026-09-22 12:59:27 -06:00
Jacob Dahl
da1c2a25cb fix(flashparams): append unsaved deltas and compact at boot (#28532)
* fix(flashparams): append unsaved deltas and compact at boot

Same-bank program of a full BSON snapshot stalls instruction fetch for tens of ms, which drops a high-rate IMU FIFO and DShot while disarmed. The 1 s sector erase belongs at boot, not on a wrap mid-session.

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(flashparams): serialize compact and encode before erase

param_reset_all() queued an autosave that could program flash during the boot compact erase. Reset without autosave under file_mutex, build the snapshot in RAM before erasing, and compact only when a burst of deltas would not fit so COM_FLIGHT_UUID is not a 1 s erase every flight.

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(flashparams): append in log order and encode resets as null

Walk order was not write order on multi-sector maps, and a torn H7 header skipped the rest of the sector so a successful retry vanished. Resets encoded as the current default also came back as overrides after a firmware default change.

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(flashparams): version the log token and keep default-equal overrides

Records carry a new `plog` token. Firmware before the log format wrote one
`parm` snapshot and cannot replay deltas; it ignores `plog` records and
treats a store without `parm` as empty (STM32H7 erases it at init), so a
downgrade boots on defaults instead of loading a stale snapshot and then
failing every save into the deltas behind it. A `parm` record is imported
alone as the newest state and the store is rewritten as a `plog` snapshot.

The boot snapshot and import no longer drop values equal to the current
default. They run before the airframe applies its set-default values, so
the comparison discarded user overrides that happened to match the firmware
default. Deltas still tombstone a value matching the default in force, as
the file backend does at save time.

Boot compaction stages the snapshot before deciding, and a failure before
the erase keeps the log and returns success: RAM holds what the log said,
so the store is not corrupt. Compaction is skipped when the compacted
layout would lack the headroom too, so a large snapshot does not erase on
every boot. Appends are row aligned on H7, header fields are read only
once the header lies inside the sector, and the append scan stops at a
blank header like the replay walker.

* fix(param): clear only the unsaved bits that were pending when the save began

A param_set that lands while the export runs is not in what gets written.
Clearing the whole bitset afterwards lost its bit. Full-snapshot saves
picked the value up on the next save; flash deltas never wrote it.

* fix(flashfs): keep every record inside the range the walkers read

The walkers stop at a sector's last word and reject a record that ends past it, but the writer accepted a record that ends anywhere inside the sector. A record landing in the last four bytes was reported written and never replayed, and a snapshot of exactly max_payload bytes was unreadable, so import returned -EILSEQ. The F7 writer also padded the entry to a word inside a heap block allocated without room for the padding.

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(param): keep the unsaved bit of a param set during a save

Clearing the bits that were pending when the save began still dropped a param_set that landed after the exporter had read that param's value. Move the pending bits to a saving set before the export so a set during the save marks the param again, and put them back if the save fails.

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(flashparams): cover file imports, tombstones and stale records

A delta carries only params with the unsaved bit, and three paths left RAM ahead of the log with no bit set. A file import (param import, param load, the rcS backup recovery) set values as saved, so the param save that followed wrote nothing and the values reverted on reboot; on flash boards an import now leaves its changes unsaved, as the file backend leaves them until its next full export. A BSON null reached param_modify_on_import with the previous node's value still in the union, so a translation reading it without a type check could fire on a tombstone; decode tombstones first. A record that no longer names a param, or names one of another type, was replayed through a translation on every boot and kept until a compaction happened to run; treat it as a reason to compact at boot. A log whose replay failed inside a CRC-valid record, or that has no snapshot, gets a snapshot on the next save instead of a delta nothing would reach.

The buffer encoder wrote 0 as the document length, which the file importer takes as a blank store; record the real length so a buffered document is a valid BSON file.

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

---------

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
2026-09-22 12:27:49 -06:00
Qiaosen Liu
3160543cd1 fix(mc_att_control): latch spool-up state until disarm (#28777)
* mc_att_control: latch spool-up state until disarm

* Run make format

---------

Signed-off-by: lqs66 <liuqiaosen666@163.com>
2026-09-22 17:00:02 +02:00
Marco Hauswirth
ca0f31070d feat(replay): drive ekf2 sensor fusion state from estimator_fusion_control
Signed-off-by: Marco Hauswirth <marco.hauswirth@auterion.com>
2026-09-22 16:01:46 +02:00
Marco Hauswirth
cc60107203 fix(replay): synthesize IMU samples missing from the log instead of shifting the stream
Signed-off-by: Marco Hauswirth <marco.hauswirth@auterion.com>
2026-09-22 16:01:46 +02:00
Marco Hauswirth
6411262049 fix(replay): keep one message id per instance of multi-instance topics
Signed-off-by: Marco Hauswirth <marco.hauswirth@auterion.com>
2026-09-22 16:01:46 +02:00
Marco Hauswirth
bc0343cf61 fix(replay): wait for ekf2 to process each IMU sample instead of the lockstep component barrier
Signed-off-by: Marco Hauswirth <marco.hauswirth@auterion.com>
2026-09-22 16:01:46 +02:00
bresch
412723352b fix(replay): replay sensor_selection and launch_detection_status
Neither topic was published, so ekf2 kept its accel and gyro device ids at
0, skipped the bias reset that follows a device change, and reported
estimator_sensor_bias with zeroed ids and accel_bias_valid false.

Assisted-by: Claude:claude-opus-5[1m]
Signed-off-by: bresch <brescianimathieu@gmail.com>
2026-09-22 16:01:46 +02:00
bresch
8ee00cc618 fix(ekf2): stop publishing a placeholder attitude in replay
Until the attitude became valid, replay mode published a zero-filled
vehicle_attitude carrying a null quaternion, so a replayed log opened with
samples a reader has to know to discard.

Assisted-by: Claude:claude-opus-5[1m]
Signed-off-by: bresch <brescianimathieu@gmail.com>
2026-09-22 16:01:46 +02:00
bresch
7526943204 fix(init): start the estimator right before logging
The estimator was started near the top of the script and logging at the
very end, so its startup - the alignment and the first resets - was never
in the log and an ekf2 replay could not reproduce it.

Assisted-by: Claude:claude-opus-5[1m]
Signed-off-by: bresch <brescianimathieu@gmail.com>
2026-09-22 16:01:46 +02:00
bresch
b16eebf1fe fix(ekf2): register a lockstep component in replay mode
The replay barrier only waited for the logger, so the next imu sample
could be published before ekf2, running on a work queue, had consumed the
previous one.

Assisted-by: Claude:claude-opus-5[1m]
Signed-off-by: bresch <brescianimathieu@gmail.com>
2026-09-22 16:01:46 +02:00
bresch
4aeaafef8e fix(replay): keep the imu stream aligned with the sensor topics
sensor_combined carries the same timestamp as ekf2_timestamps and won the
main loop's tie, so it was consumed without being published and every
barometer, magnetometer and range sample reached ekf2 one update late.

Assisted-by: Claude:claude-opus-5[1m]
Signed-off-by: bresch <brescianimathieu@gmail.com>
2026-09-22 16:01:46 +02:00
bresch
15b0a24149 fix(replay): publish gnss and system flags inside the lockstep barrier
vehicle_gps_position, vehicle_land_detected and vehicle_status were
published from the main loop, so which ekf2 update they landed in was a
race that came out differently on two replays of the same log.

Assisted-by: Claude:claude-opus-5[1m]
Signed-off-by: bresch <brescianimathieu@gmail.com>
2026-09-22 16:01:46 +02:00
bresch
95466c2fa8 fix(replay): poll the logger on the last topic ekf2 publishes
ekf2_timestamps is the last publication of an ekf2 update and the
only one produced on every update, so waking on it logs whole updates
and keeps the lockstep barrier fed before the attitude becomes valid.

Assisted-by: Claude:claude-opus-5[1m]
Signed-off-by: bresch <brescianimathieu@gmail.com>
2026-09-22 16:01:46 +02:00
Jacob Dahl
f90b40d61e fix(sensors): do not register a callback for a disabled barometer (#28788)
* fix(sensors): do not register a callback for a disabled barometer

Since disabled barometers are fed to the voter at priority 0 they also
register a sensor_baro callback while nothing is selected. Callbacks are
only cleared on a selection change, and with every barometer disabled
the selection stays at -1, so a lone disabled barometer woke Run() and
UpdateStatus() at its sample rate indefinitely instead of on the 50 ms
schedule. The scheduled cycle already keeps the disabled validator fed,
and a re-enabled sensor registers again when it is selected.

Assisted-by: Claude:claude-fable-5-1
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* test(sensors): cover the disabled-barometer callback gate

Same harness as the vehicle_magnetometer functional test: a lone
barometer disabled from boot must leave no sensor_baro callback
registered, and must register one once it is re-enabled and selected.
Fails without the gate in Run().

Assisted-by: Claude:claude-fable-5-1
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

---------

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
2026-09-21 22:57:37 -06:00
Saibernard
ea1560691a fix(sensors): keep disabled magnetometers in the voter at priority 0 (#28750)
* fix(sensors): keep disabled magnetometers in the voter at priority 0

The magnetometer module skipped a disabled sensor entirely, so the voter
never learned it was disabled. Disabling the selected sensor at runtime
timed it out, reported it as failed and raised its priority back to 1,
which put it into the inconsistency check with a frozen field vector. A
sensor disabled at boot never got a validator, so the samples of the next
uORB instance were dropped and it could not be selected.

Feed every advertised magnetometer to the voter with its priority, where 0
excludes it from selection and failover accounting, sum and publish only
enabled ones, never raise a disabled sensor's priority on failover, and
clear the selection when no magnetometer is left. This is the handling
#28351 gave the barometer.

Three functional tests cover the runtime disable, a disabled slot ahead of
a live sensor, and disabling the only magnetometer and enabling it again.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Saibernard Yogendran <bernie97@seas.upenn.edu>

* fix(sensors): do not register a callback for a disabled magnetometer

Feeding disabled sensors to the voter also made them register a
sensor_mag callback while nothing is selected. Callbacks are only
cleared on a selection change, and with every magnetometer disabled the
selection stays at -1, so a lone disabled magnetometer woke Run() and
UpdateStatus() at its sample rate indefinitely instead of on the 50 ms
schedule. The scheduled cycle already keeps the disabled validator fed,
and a re-enabled sensor registers again when it is selected.

Assisted-by: Claude:claude-fable-5-1
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

---------

Signed-off-by: Saibernard Yogendran <bernie97@seas.upenn.edu>
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
Co-authored-by: Jacob Dahl <dahl.jakejacob@gmail.com>
2026-09-21 22:54:08 -06:00
Jacob Dahl
0e9d8f6bea refactor(platform): share I2C/SPI driver startup failure handling (#28786)
Each default driver instantiation emits the same allocation and initialization failure handling. Share that code outside the template while keeping init() resolved on the concrete driver. Allocation failures are now logged under SPI_I2C.

Extracted from Balduin (mbjd) in PX4/PX4-Autopilot#27816, commit 8a2391730f.

Assisted-by: Codex

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
Co-authored-by: Balduin <balduin@auterion.com>
2026-09-21 22:50:23 -06:00
Jacob Dahl
b9d630aa47 refactor(modes): share navigation state names across translation units (#28787)
The header currently gives each consumer its own copy of the navigation state name table. Use C++17 inline linkage so the linker can retain one copy.

Extracted from Balduin (mbjd) in PX4/PX4-Autopilot#27816, commit a0a0ebf69e.

Assisted-by: Codex

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
Co-authored-by: Balduin <balduin@auterion.com>
2026-09-21 22:50:05 -06:00
PX4BuildBot
ece16faa5d docs: auto-sync metadata [skip ci]
Co-Authored-By: PX4 BuildBot <bot@px4.io>
2026-09-22 03:23:39 +00:00
Julian Oes
b7e991cd8c fix(boards): remove Septentrio GNSS driver from fmu-v2
px4_fmu-v2_default was already at 99.98% flash on main (1031972 of
1032192 bytes), leaving 220 bytes of headroom, so any change touching
common code overflows it.

The Septentrio driver was inherited by this board when it was split out
of the gps driver, rather than chosen for it, and it costs 12.5 KB here.
A high-end RTK receiver is an unlikely pairing with fmu-v2 anyway, and
the other v2 variants are overlays on this config, so all four regain
the same headroom:

  default      100.03% -> 98.81%
  fixedwing     99.59% -> 98.42%
  multicopter   99.19% -> 98.02%
  rover         97.86% -> 96.69%

It remains enabled on fmu-v3 and fmu-v4, which have 2 MB of flash.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Julian Oes <julian@oes.ch>
2026-09-22 15:15:30 +12:00
Julian Oes
ab4becabbb fix(docs): add external gimbal manager and multiple gimbal support 2026-09-22 15:15:30 +12:00
Julian Oes
f22731ed89 fix(gimbal): set frame for RC rate input
In rate mode the RC input only flagged the axes as angular rate, without
saying whether they are locked or following the vehicle. The outputs
then kept using the frame of whatever setpoint came before, so e.g. RC
yaw rate went out with yaw lock after a ROI but without it after boot.

Set the frame the same way as it is already done for RC angle input:
roll and pitch relative to the horizon, and yaw according to
MNT_DO_STAB. This matches what the MAVLink gimbal v2 input does for
rate-only setpoints.

Assisted-by: Claude:claude-fable-5-1
Signed-off-by: Julian Oes <julian@oes.ch>
2026-09-22 15:15:30 +12:00
Julian Oes
c46912f1c9 fix(gimbal): re-request control while nobody is in control
Control of the external gimbal manager was only requested once, when
onboard intent started. RC input stays active once it has been used, so
if that request got lost or the manager restarted, we never asked again
and a manager which only accepts setpoints from whoever is in control
ignored us from then on.

Ask again, rate limited, while we have onboard intent and the manager
reports that nobody is in control. This can't take control away from
another client, so the arbitration stays with the manager.

Assisted-by: Claude:claude-fable-5-1
Signed-off-by: Julian Oes <julian@oes.ch>
2026-09-22 15:15:30 +12:00
Julian Oes
9ff48836eb fix(mavlink): support external gimbal manager on gimbal mode link
A gimbal with its own gimbal manager is typically connected to a MAVLink
instance in gimbal mode. That mode only handles a whitelist of incoming
messages and only streams a few messages, so the external gimbal manager
was never discovered there and would not have received any setpoints.

Accept GIMBAL_MANAGER_STATUS as well as COMMAND_ACK (for the
DO_GIMBAL_MANAGER_CONFIGURE that we send, otherwise it is retried until
it times out) and stream GIMBAL_MANAGER_SET_PITCHYAW in gimbal mode.

This also makes the check to not ingest the gimbal device information of
an external gimbal manager work on such a link, as that depends on
having seen its GIMBAL_MANAGER_STATUS.

While at it, ignore the status of gimbal managers of other systems, e.g.
forwarded from another vehicle. Only a manager on our vehicle is ours to
talk to.

Assisted-by: Claude:claude-fable-5-1
Signed-off-by: Julian Oes <julian@oes.ch>
2026-09-22 15:15:30 +12:00
Julian Oes
fad6590345 refactor(gimbal): let external manager do control arbitration
Tracking the manager's control state ourselves could desync from the
manager and stop setpoints. Acquire once when onboard intent starts,
stream setpoints while it lasts, release with -3 (was -2, which means
"set myself in control").

Assisted-by: Claude:claude-opus-5
Signed-off-by: Julian Oes <julian@oes.ch>
2026-09-22 15:15:30 +12:00
Julian Oes
a707206a28 fix(gimbal): mark autopilot as primary control for ROI
vehicle_roi (ROI_WPNEXT/ROI_LOCATION) set the gimbal setpoint but left
sysid/compid_primary_control untouched. The external gimbal manager output only
forwards intent that originated onboard (primary control == autopilot), so ROI
was not forwarded unless something else (e.g. a gimbal test) had already seeded
the primary control. ROI is autopilot-driven, so set it accordingly.

Signed-off-by: Julian Oes <julian@oes.ch>
2026-09-22 15:15:30 +12:00
Julian Oes
d38af445d8 fix(mavlink): don't treat external gimbal manager device info as our own
When another component is a gimbal manager (we see its GIMBAL_MANAGER_STATUS),
its GIMBAL_DEVICE_INFORMATION describes a gimbal we don't manage. Publishing it
into our own gimbal_device_information topic made OutputMavlinkV2 latch onto the
foreign device id, so PX4 then advertised its own gimbal manager with the wrong
gimbal_device_id - and a ground station could no longer match it to its device
attitude, dropping our gimbal entirely in a two-gimbal setup.

Track external gimbal manager component ids and skip ingesting their device
information. The message is still forwarded to the ground station, so it can
still show that gimbal's name. A plain gimbal device never sends
GIMBAL_MANAGER_* and is therefore unaffected.

Signed-off-by: Julian Oes <julian@oes.ch>
2026-09-22 15:15:30 +12:00
Julian Oes
10e3401d21 fix(gimbal): arbitrate external gimbal manager control cleanly
Refine the external gimbal manager client so it coexists with a ground
station instead of fighting it:

- Only forward setpoints that originated onboard (primary control is the
  autopilot). A ground station commands the external manager directly, so
  relaying its commands would make PX4 fight it for control.
- Acquire control only on a fresh onboard-intent edge, and yield instead
  of re-grabbing when another controller takes over - this removes the
  acquire/release ping-pong. A new onboard edge is needed to reclaim.
- Apply the setpoint every cycle while in control, not only on the
  new_setpoints edge: the control handshake can delay reaching the
  in-control state past that edge, which would otherwise stream NaN.

Signed-off-by: Julian Oes <julian@oes.ch>
2026-09-22 15:15:30 +12:00
Julian Oes
7a538d55d1 fix(gimbal): set angular_velocity to NaN for test angle command
The test input's angle branch set only the quaternion, leaving
angular_velocity at its stale (zero) value. A downstream that prioritizes
rate over angle - e.g. the SITL gz gimbal, which holds position on a
finite near-zero rate - then ignores the commanded angle, so
'gimbal test pitch X' did not move the gimbal. Set the rates to NaN for
angle commands, matching InputRC's angle mode.

Signed-off-by: Julian Oes <julian@oes.ch>
2026-09-22 15:15:30 +12:00
Julian Oes
bc79cfb2fa fix(boards): remove some dev tooling on fmu-v6s
This gives us back some flash.
2026-09-22 15:15:30 +12:00
Julian Oes
14dae2682d fix(boards): remove some dev tooling from corvon_743v1
It was filling up flash.
2026-09-22 15:15:30 +12:00
Julian Oes
e16fbcc2f9 refactor(gimbal): drop unused external manager info topic to fit flash
The ROI forwarding path only needs GIMBAL_MANAGER_STATUS from an external
manager: it provides discovery (manager sysid/compid, gimbal device id) and
control ownership. GIMBAL_MANAGER_INFORMATION only added capability flags and
angle limits, which nothing uses until RC forwarding (deferred).

Carrying the extra uORB topic and its receiver handler pushed the tightest
board variant over its flash budget (ark_pi6x_encrypted_logs overflowed by
500 bytes). Drop the external_gimbal_manager_information topic; discovery and
ownership come from the status topic. It can return with RC, which actually
needs the angle limits.

Signed-off-by: Julian Oes <julian@oes.ch>
2026-09-22 15:15:30 +12:00
Julian Oes
e3cf1e7163 fix(gimbal): mark OutputRC methods override
Adding publishes_mount_orientation() with 'override' made OutputRC mix
marked and unmarked overrides, which clang rejects under
-Winconsistent-missing-override (macOS build and clang-tidy). gcc doesn't
flag it, so the NuttX builds passed. Mark update()/print_status() override.

Signed-off-by: Julian Oes <julian@oes.ch>
2026-09-22 15:15:30 +12:00
Julian Oes
73962ee81e fix(mavlink): drop stale gps_inject_data include after rebase
Upstream renamed the gps_inject_data topic to rtcm_data. A rebase left the
old include alongside the new rtcm_data one, breaking a clean build of the
mavlink module (the header no longer exists).

Signed-off-by: Julian Oes <julian@oes.ch>
2026-09-22 15:15:30 +12:00
Hamish Willee
d325e443e2 docs(docs): Uorb topic docs to standard
Co-authored-by: Hamish Willee <hamishwillee@gmail.com>
2026-09-22 15:15:30 +12:00
Hamish Willee
ff2817be15 docs(docs): Uorb topic docs to standard
Co-authored-by: Hamish Willee <hamishwillee@gmail.com>
2026-09-22 15:15:30 +12:00
Julian Oes
67b0060031 feat(gimbal): forward ROI to an external gimbal manager
Add an output mode (MNT_MODE_OUT[2]=3) that makes PX4 act as a gimbal
manager client rather than driving a gimbal device: it discovers an
external gimbal manager from the awareness topics, requests primary
control via DO_GIMBAL_MANAGER_CONFIGURE while there is an active setpoint,
and streams the computed pitch/yaw as GIMBAL_MANAGER_SET_PITCHYAW.

This lets a smart camera-gimbal that runs its own manager still be pointed
by PX4's ROI/mission logic: PX4 does the geo-pointing (reusing the same
OutputBase math as the v2 device output) and forwards the resulting
pitch/yaw, using the standard MAVLink gimbal manager messages.

Control ownership follows the spec: it changes only via acquire/release,
and control is released on an idle (neutral) setpoint so a ground station
can take over. While this mode is selected PX4 no longer advertises itself
as the gimbal manager, since it is a client of the external one.

RC forwarding (GIMBAL_MANAGER_SET_MANUAL_CONTROL) and vehicle-yaw-assisted
ROI are intentionally left for follow-ups.

Signed-off-by: Julian Oes <julian@oes.ch>
2026-09-22 15:15:30 +12:00
Julian Oes
c237be45c8 feat(mavlink): receive external gimbal manager information/status
PX4 today only produces GIMBAL_MANAGER_INFORMATION/STATUS (as its own gimbal
manager); it has no way to learn about a gimbal manager running on another
component. To interoperate with an external gimbal manager (e.g. a smart
camera-gimbal that runs its own manager), PX4 needs to discover it and track
who currently controls it.

Decode incoming GIMBAL_MANAGER_INFORMATION and GIMBAL_MANAGER_STATUS from
other components into the new external_gimbal_manager_information and
external_gimbal_manager_status uORB topics, carrying the manager's sysid and
compid from the message frame. Messages originating from our own
system/component are ignored so we don't ingest our own streamed manager
messages.

This is the awareness layer that a gimbal-manager client output builds on.

Signed-off-by: Julian Oes <julian@oes.ch>
2026-09-22 15:15:30 +12:00
Julian Oes
0ff8d3270c refactor(gimbal): support multiple outputs in parallel
Generalize the single output backend into a list of up to two outputs,
driven with the same setpoints each cycle. A new MNT_MODE_OUT2 parameter
selects an optional second output (disabled by default).

This is behavior-preserving groundwork: with MNT_MODE_OUT2 disabled the
driver runs exactly one output as before. It enables driving a second
gimbal in addition to the primary one, e.g. forwarding to an external
gimbal manager.

The two outputs must use different modes to avoid two instances fighting
over the same device or uORB topic. Whether the estimated mount
orientation is published is now decided per output via a virtual, since a
MAVLink gimbal reports its own orientation while an AUX gimbal does not.

Signed-off-by: Julian Oes <julian@oes.ch>
2026-09-22 15:15:30 +12:00
Jacob Dahl
e034dd91c5 fix(metadata): validate component metadata against current schemas (#28785)
The schema path still says component_information/, so --skip-if-no-schema makes the check a no-op. QGC only accepts parameter metadata version 1, while the schema floor rose for optional fields. Override that floor and keep emitting version 1.

Assisted-by: Grok:4.7

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
2026-09-22 14:43:05 +12:00
小小小朋友
8c4262781d fix(airship_att_control): correct manual pitch torque sign (#28739)
Positive manual pitch requests nose-down rotation, but the airship controller publishes it as positive pitch torque.


Assisted-by: Codex

Signed-off-by: onelittlechildawa <onelittlechild@outlook.com>
2026-09-21 20:09:46 -06:00