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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
After an upload times out, a delayed copy of the last received item can receive MAV_MISSION_ACCEPTED even though the remaining items were never uploaded.
Assisted-by: Codex
Signed-off-by: onelittlechildawa <onelittlechild@outlook.com>
The legacy numeric mapping reports Position Slow and Guided Course as RTL, and Altitude Cruise as Landing.
Assisted-by: Codex
Signed-off-by: onelittlechildawa <onelittlechild@outlook.com>
The persistent rate setpoint is also populated by its subscription.
Generating a manual fixed-wing rate setpoint only replaces the forward
thrust, leaving the other components from previously received setpoints.
Clear those components before publishing the Acro setpoint.
Assisted-by: Codex
Signed-off-by: onelittlechildawa <onelittlechild@outlook.com>
Acknowledged entries are retained until timeout so lagging channels can
register the same command. Exclude their acknowledged channel state
from ACK matching so it cannot consume ACKs for later pending entries.
Assisted-by: Codex
Signed-off-by: onelittlechildawa <onelittlechild@outlook.com>
The old boolean is serialized as BSON_INT32, but the migration reads
the double member and can disable an enabled setting. Rename the node
and use the normal parameter import path.
Validation: four focused host checks cover enabled/disabled values,
a mismatched BSON type, and an unrelated parameter.
Assisted-by: Codex
Signed-off-by: onelittlechildawa <onelittlechild@outlook.com>
GPS_UBX_CFG_INTF declares six bits (0-5), so its range is 0-63, but it
declares max: 32. 32 is the value of the highest bit on its own
(I2C_OUT_PROT_RTCM3X = 1 << 5), not the all-bits-set value, so every
value from 33 to 63 is published to ground stations as out of range.
The smallest of them is UBX input plus RTCM3X output on I2C, 1 + 32.
The parameter was introduced in df441ac202 with all six bits and
max: 32 in the same hunk, so the mask never outgrew its maximum.
GPS_1_GNSS and GPS_2_GNSS in the same file are also six-bit masks and
both declare max: 63. They were corrected from 31 to 63 in c90ccabbe0
when NAVIC was added as their sixth bit, which is the same arithmetic.
Tools/module_config/generate_params.py derives (1 << (max_bit + 1)) - 1
for a bitmask with no explicit max, which is 63 here.
The metadata check in srcparser.py validates each bit on its own,
int(min) <= 2**index <= int(max), so 2**5 = 32 <= 32 passes by exactly
one and the inconsistency was never flagged.
This is ground-station metadata only: min and max are not compiled into
the firmware, so runtime behaviour is unchanged.
Assisted-by: Claude:claude-opus-5
Signed-off-by: Qutibah Ananzeh <38795261+Ti-03@users.noreply.github.com>
The job ran brew update and then installed whatever Homebrew was publishing, except Gazebo, which stayed on one commit. Those were built against different protobufs, so a release upstream failed the build with no commit here. Check the package repos out at commits recorded in the tree and do not update them. Run on macos-15 only, so the OS label cannot move either.
Assisted-by: Grok:grok-4.7
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
The build runs brew update and then pours Gazebo from one pinned tap commit while the rest of the install, including protobuf, comes from current Homebrew. A release in either repo fails the check with no commit here, and the GitHub-hosted image moves on its own, so the job cannot be a closed set. Drop the build and the weekly pin refresh. The setup script, the pins, and the refresh script stay for local use.
Assisted-by: Grok:grok-4.7
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
make px4_sitl fails when Homebrew's protobuf is not the one the pinned Gazebo bottles were generated against. Those generated headers are a fatal error against any other version, and that has been failing these legs since 2026-09-02. Take the check off push and pull requests. workflow_dispatch still runs it.
Assisted-by: Grok:grok-4.7
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
* fix(ekf2): tag EKF2_MAG_TYPE STATUSTEXT for event dedup
Missing trailing tab meant a modern GCS wouldn't suppress this legacy
STATUSTEXT in favor of the paired event, showing the message twice.
Signed-off-by: Balduin <balduin@auterion.com>
* fix(navigator): tag in-air landing STATUSTEXT for event dedup
Missing trailing tab meant a modern GCS wouldn't suppress this legacy
STATUSTEXT in favor of the paired feasibility_mis_in_air_landing_req
event, showing the message twice.
Signed-off-by: Balduin <balduin@auterion.com>
* fix(mavlink): stop dropping the CAN/camera ID collision warning
This STATUSTEXT was tagged for event-dedup suppression but has no
paired event, so a modern GCS silently discarded it instead of
showing it.
Signed-off-by: Balduin <balduin@auterion.com>
---------
Signed-off-by: Balduin <balduin@auterion.com>
A completed mission upload only updated the active mission when the
uploaded mission differed from the stored one:
// Only need to update if the mission actually changed
if (_transfer_current_crc32 != _crc32[MAV_MISSION_TYPE_MISSION]) {
update_active_mission(...);
}
update_active_mission() is what resets the current index and tells the
navigator about it, so re-uploading an identical mission left the index
wherever it was. After a reboot that index is restored from the mission
state in dataman, which is the last item of the previous flight, and the
mission then reports itself finished a few milliseconds after being
started:
INFO [navigator] Executing Mission
INFO [navigator] Mission finished, landed
WARN [navigator] No valid mission available, refusing takeoff
An upload means the mission is to be flown from the start, so update the
active mission on every completed upload.
* feat(boards): add LSM6DSV support for Kakute H7 Wing
* fix(boards): lower Kakute H7 Wing OSD work queue priority
Run SPI2 below the SPI3 IMU queue to reduce delays caused by ATXXXX updates.
Every gz .pb.h refuses to compile against anything but the exact protobuf
its gencode came from, so the bottles gz-tap-pin.txt holds osrf/simulation
at only build against the protobuf homebrew-core shipped the day OSRF built
them. homebrew-core moves protobuf on its own schedule and OSRF rebuilds
days later, so `make px4_sitl` has been failing on macOS on every branch
since homebrew-core shipped protobuf 36.2.
Pin protobuf as well, to the homebrew-core revision that installs the
version the pinned gz bottles carry, and derive that pin from the bottles
themselves so the two can never disagree. A protobuf release then becomes a
no-op here instead of days of red CI while OSRF catches up.
The pinned install has to run after the other simulation packages and with
the dependents check off: opencv@4 depends on protobuf too, brew resolves a
dependency against the versions recorded in the dependent's bottle, and it
reinstalls dependents whose linkage it has just broken. Pinning the formula
with `brew pin` is not an option either, brew then refuses to install
anything that depends on it.
Assisted-by: Claude:claude-opus-5
Signed-off-by: Julian Oes <julian@oes.ch>
The NuttX 12.12.0 defconfig refresh dropped CONFIG_MTD_W25N and
CONFIG_W25N_SPIFREQUENCY from this board while the other W25N boards
(kakuteh7mini, kakuteh7v2) kept them. The option still exists in NuttX
12.12 and defaults to off, and the board's init.c only registers the
flash under #ifdef CONFIG_MTD_W25N, so the onboard 128 MB NAND was no
longer initialised: no /fs/flash, no logging, no flash-backed dataman.
Restore both options with the 104 MHz SPI frequency the board used
before.
Signed-off-by: Julian Oes <julian@oes.ch>
The BSON document length is the first four bytes of the document and is signed.
bson_decoder_init_buf() checked it against the buffer size, but the check only
ran when the value was positive, so a negative length skipped it and decoding
proceeded with no bound. bson_decoder_init_file() did not check the length at
all.
Reject a negative length in both, and add a regression test, since the existing
BSON test only covers a round trip of data it encoded itself.
Reported by @lihnucs.
Assisted-by: Claude:claude-opus-5[1m]
Signed-off-by: Julian Oes <julian@oes.ch>
GIMBAL_DEVICE_INFORMATION carries three 32-byte name fields. The handler copies
each into the uORB message and then writes the terminator into
gimbal_device_info_msg, the decoded MAVLink struct it is about to discard,
rather than into the topic it publishes.
The copies are the same size in both directions, so nothing overflows. What
publishes is a field that can hold 32 bytes with no terminator, which any
consumer reading it as a string would run off.
No consumer does today, so this changes no behaviour. It is wrong as written and
the next reader of those fields would inherit it.
Reported by @lihnucs.
Assisted-by: Claude:claude-opus-5[1m]
Signed-off-by: Julian Oes <julian@oes.ch>
whitelisting only PX4 source directory made git commands
fail when executed on the submodules
Signed-off-by: Beniamino Pozzan <beniamino.pozzan@gmail.com>
FLASH usage analysis / Analyzing px4_fmu-v5x (push) Canceled after 0s
FLASH usage analysis / Analyzing px4_fmu-v6x (push) Canceled after 0s
Python CI Checks / build (push) Canceled after 0s
FLASH usage analysis / Publish Results (push) Canceled after 0s
Static Analysis / Clang-Tidy (push) Canceled after 0s
The PR title was interpolated directly into the shell command, so bash
parsed it as syntax before argparse ever saw it. A title containing double
quotes split into several arguments and failed the check for the wrong
reason, and a hostile title could run arbitrary commands on the runner.
Pass it via the environment like the labeler workflow already does, so it
is data rather than syntax.
Also make sure a report exists when the check fails without writing one,
otherwise the posting step dies on a missing comment.md and hides the
actual error.
Assisted-by: Claude:claude-opus-5
Signed-off-by: Julian Oes <julian@oes.ch>
* docs(mavlink): present link encryption alongside message signing
The hardening guide told integrators that production deployments must
enable message signing, presenting it as the only way to secure a MAVLink
link. Encrypting the link below MAVLink, with an encrypted radio, a VPN or
IPsec, is at least as strong: it uses standard, reviewed cryptography, it
gives confidentiality as well as authentication, and it covers every
interface on the link rather than only MAVLink.
Present both options, and reword the passages that assumed signing was the
only one.
Assisted-by: Claude:claude-opus-5
Signed-off-by: Julian Oes <julian@oes.ch>
* docs(security): develop security fixes in public pull requests
Record that fixes are developed as ordinary public pull requests, with no
private forks and no embargoed branches.
Assisted-by: Claude:claude-opus-5
Signed-off-by: Julian Oes <julian@oes.ch>
* docs(security): add security scope document
Describe the boundary the code implements today: what PX4 protects, what it
assumes about the layers beneath it, what the shipped default does and does
not do, and two lists for what is always in scope and what is out. It
describes the boundary rather than pre-deciding reports. A finding that fits
neither list stays a judgement call that maintainers make on the report.
Assisted-by: Claude:claude-opus-5
Signed-off-by: Julian Oes <julian@oes.ch>
* docs(security): list a security maintainer for report triage
Security report triage had no named owner, so an unacknowledged report had
nowhere to go except the release managers. Add a Security block to the
maintainers list, and point the follow-up path in SECURITY.md at it.
Assisted-by: Claude:claude-opus-5
Signed-off-by: Julian Oes <julian@oes.ch>
* docs(mavlink): note that the first signing key is unauthenticated
The guide already says to provision over a trusted link, and the info box says
that changing or disabling a key requires a signed message. It does not say that
setting the first one cannot, since there is no key to sign with yet, so a reader
can come away believing provisioning itself is protected.
Say plainly that the window exists, what an attacker gets from it, and how to
recover.
Assisted-by: Claude:claude-opus-5[1m]
Signed-off-by: Julian Oes <julian@oes.ch>
* Apply batched suggestions from code review
Co-authored-by: Sheren N <sherenyn@ad.uni-paderborn.de>
Co-authored-by: Hamish Willee <hamishwillee@gmail.com>
Co-authored-by: Julian Oes <julian@oes.ch>
* feat(security): more review fixup
* fix(security): only 1.18 gets security bugfixes
* fix(security): link to SECURITY_SCOPE
* fix(securiy): small wording fixups
* docs(security): scope by attacker position, not bug class
The "Always in scope" list made any memory corruption, race or hang a
vulnerability regardless of who can reach it. That is not how reports
have been handled: sanitizer runs in SITL have found and fixed many
such bugs as ordinary PRs, and a peer on an unsecured link already has
a shell.
Replace it with one test: a finding is a vulnerability when it gives
capability to an attacker who has neither the operator's access nor
physical access. Keep the in-tree board configuration rule, point to
the sanitizer docs, and fix two typos.
Assisted-by: Claude:claude-opus-5[1m]
Signed-off-by: Julian Oes <julian@oes.ch>
* fix(docs): formatting
* fix(maintainers): add Ramon to security as well
* docs(security): draw the boundary as a mermaid diagram
GitHub renders mermaid natively, so the boundary diagram no longer has to
be maintained as hand-aligned ASCII. Label the two zones with the wording
of the sentences below it, so the picture and the prose say the same
thing.
Assisted-by: Claude:claude-opus-5
Signed-off-by: Julian Oes <julian@oes.ch>
* fix(docs): review fixups
* docs(security): say how to isolate the offboard transports
A direct cable between flight controller and companion is not enough on
its own: the agent or router republishes into the DDS or Zenoh network
on the companion, so that network is inside the boundary too.
Keep the scope document to what is inside the boundary and put the
isolation advice next to the uXRCE-DDS and Zenoh setup instructions.
Assisted-by: Claude:claude-opus-5
Signed-off-by: Julian Oes <julian@oes.ch>
---------
Signed-off-by: Julian Oes <julian@oes.ch>
Co-authored-by: Sheren N <sherenyn@ad.uni-paderborn.de>
Co-authored-by: Hamish Willee <hamishwillee@gmail.com>
The ListDirectoryWithTime (opcode 16) entry format is
<type><name>\t<size>\t<mtime>\0, but PX4 only applied it to files and
sent directories as a bare D<name>. MAVSDK's server already sends
D<name>\t0\t<mtime>, so the two servers disagreed and a client had to
special-case which one it was talking to.
Directories are now stat()ed for their modification time and reported
with size 0, matching MAVSDK. The plain ListDirectory (opcode 3)
response is unchanged: existing clients take everything after the D as
the directory name there.
Assisted-by: Claude:claude-fable-5-1
Signed-off-by: Julian Oes <julian@oes.ch>