Commit Graph
51296 Commits
Author SHA1 Message Date
Anil KircalialiandMatthias Grob f20ec45f7f fix(commander): decouple ESC arming timeout from ESC telemetry timeout (#28915)
* fix(commander): decouple ESC arming timeout from ESC telemetry timeout

* fix(escCheck): increase arming timeout to be more permissive for certain ESCs

* fix(escChecks): small additions to the unit tests

---------

Co-authored-by: Matthias Grob <maetugr@gmail.com>
2026-09-30 09:17:26 -07:00
Michael Fritsche b07a720cfd fix(rover): actively stop vehicle if not armed (#28840)
* fix(rover): actively stop vehicle if not armed

* fix(rover): update timestamp upon calling stopVehicle()
2026-09-30 16:28:18 +02:00
PX4BuildBot 596889f8b6 docs: auto-sync metadata [skip ci]
Co-Authored-By: PX4 BuildBot <bot@px4.io>
2026-09-30 11:33:48 +00:00
Daniel Buleandră 1dbf64d67e fix(vtx): Update peak thor parameter name and remove Rush MAX SOLO (#28692)
Rename the VTX_DEVICE entry for the Peak THOR so it lists the supported
models, and drop the Rush MAX SOLO device entirely: its VTX_DEVICE enum
value and the DEVICE_RUSH_MAX_SOLO constant in Vtx.msg.

A stored VTX_DEVICE of 10240 (Rush MAX SOLO) is migrated to Generic with
Tramp, the only protocol it speaks.
2026-09-30 13:27:50 +02:00
Saibernardandjonas b9c745ffde fix(navigator): do not follow a DO_JUMP whose counter cannot be stored (#28752)
* fix(navigator): do not follow a DO_JUMP whose counter cannot be stored

When the write of the DO_JUMP repetition counter to dataman failed, the
navigator reported the failure and followed the jump anyway. The stored
counter never advanced, so the jump was taken again on every pass and the
mission repeated the segment until a failsafe ended the flight.

Skip the jump when its counter cannot be stored and continue with the item
after it, the way an exhausted jump is handled. The operator message says
so. Resolving a set-current index still follows the jump, since that path
does not consume a repetition and writes nothing.

The mission item write goes through a virtual writeMissionItemToCache() so
the tests can inject a storage failure, matching the existing load hook.
Four tests cover the counted jump, the skipped jump forward and backward,
and the set-current path.

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

* test(navigator): update mission test store only after successful writes

---------

Signed-off-by: Saibernard Yogendran <bernie97@seas.upenn.edu>
Co-authored-by: jonas <jonas.perolini@rigi.tech>
2026-09-30 12:27:12 +02:00
Daniel BuleandrăandHamish Willee c3afa795c6 docs(debug): add DroneCAN shell guide (#28404)
Peripheral nodes that only have a CAN connection expose no serial debug
port, so their NSH console is reachable only through the
uavcan.protocol.AccessCommandShell service served by the uavcannode
driver. Document how to open that shell, its options and keys, and its
limitations (one session per node, last_exit_status), and link it from
the consoles overview and the docs sidebar.

Signed-off-by: danielbuleandra <daniel.buleandra@auterion.com>
Co-authored-by: Hamish Willee <hamishwillee@gmail.com>
2026-09-30 10:57:05 +02:00
Daniel Buleandră 5f28bf9a86 feat(nsh via dronecan): Implement nsh via dronecan (#28403)
Queue unwritten stdin instead of dropping it when a request is larger
than the pipe can take at once, and drain the queue without blocking
the CAN work queue. Only stdin needs to stay nonblocking (it's written
from that work queue); stdout reads stay blocking, gated on FIONREAD,
like the mavlink shell already does.

Report a shell session that has exited as an explicit error instead of
silently swallowing EPIPE and returning empty output. Surface that
error in the Python client as well.

Flush the queued stdin on idle polls too, not just when new input
arrives, since an idle poll is all the client sends while the user
isn't typing. Only redirect fd 0/1 for the child once both stdio
backups succeeded, so a dup() failure can't leave the node's own
stdin/stdout pointing into the shell pipes. Distinguish EAGAIN (retry
later) from EPIPE (never will succeed) when flushing queued stdin, so
a dead shell doesn't hold the queue forever refusing new input.

Only compile the shell sources when CONFIG_UAVCANNODE_COMMAND_SHELL is
enabled, matching how every other optional uavcannode feature is
gated, instead of pulling nshlib into every board's image. Detect a
dead shell task with nxsched_get_tcb(), the same lookup top/cpuload.cpp
use, instead of a POSIX-only helper with no NuttX implementation. Fix
GetNodeInfo name decoding in the client to tolerate non-UTF-8 bytes
instead of crashing the scan.
Signed-off-by: danielbuleandra <daniel.buleandra@auterion.com>
2026-09-30 10:34:19 +02:00
Silvan 7e602f4598 fix(fwLatLong): add logic to handle altitude step based on z_reset_counter
Signed-off-by: Silvan <silvan@auterion.com>
2026-09-30 10:10:58 +02:00
A.Burak Tektas 1e03043924 fix(fw_mode_manager): clear the course setpoint in Offboard
A new Offboard trajectory setpoint resets the position setpoint triplet
with a zero initializer, which leaves the current course at 0 instead of
NaN. Since course hold was added, a finite course makes the position
controller fly that bearing, so a fixed-wing in Offboard ignored its
position setpoint and flew north.

Reset the course to NaN with the other fields.

Fixes #28743

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: A.Burak Tektas <15087318+Abtektas@users.noreply.github.com>
2026-09-30 08:59:55 +02:00
abhijithandFarhang 59e27436c7 fix(boards/agam): match v6xrt bootloader BOARD_TYPE to board_id (#28769)
BOARD_TYPE changed to the proper one for this board. 

Co-authored-by: Farhang <46557204+farhangnaderi@users.noreply.github.com>
2026-09-30 02:19:25 -04:00
PX4BuildBot 852fc4e9a7 docs: auto-sync metadata [skip ci]
Co-Authored-By: PX4 BuildBot <bot@px4.io>
2026-09-29 22:44:52 +00:00
Jacob Dahl 20eabe6184 refactor(sensors): remove GNSS blending (#28921)
Blending averages receivers whose errors are largely shared, drifts when the weights change, and hands every consumer a synthetic sample that no receiver produced, so no per-receiver check result applies to it. The sensors module now always outputs one receiver, chosen by the selection it already used with SENS_GNSS_MASK 0: the SENS_GNSS_PRIME receiver while it has a 3D fix, otherwise the best fix, then the most satellites. Receiver selection replaces that next. SENS_GNSS_MASK and SENS_GNSS_TAU are removed.

Assisted-by: Claude:claude-opus-5-5
2026-09-29 16:38:31 -06:00
PX4BuildBot 65b67eafa6 docs: auto-sync metadata [skip ci]
Co-Authored-By: PX4 BuildBot <bot@px4.io>
2026-09-29 22:09:16 +00:00
Julian Oes 107d8bb038 fix(mavlink): forward received frames unchanged (#28891)
* fix(mavlink): forward received frames unchanged

Forwarded messages were queued as a truncated copy of mavlink_message_t
and re-serialized on the way out. The signature bytes were never
copied, so forwarded signed messages went out with a garbage signature.

Messages which are not in our dialect were not forwarded at all: the
parser can't check their CRC without CRC_EXTRA, reports them as bad CRC,
and mavlink_parse_char() drops them.

Frames are now queued as they arrived, checksum and signature included,
and written out as is. Following the MAVLink routing guide, frames that
can't be processed locally are still forwarded but not handled: unknown
MAVLink 2 messages, and messages whose signature can't be verified. The
signature isn't checked or removed, that's up to the receiver, which
may well have a different key than PX4. SETUP_SIGNING is still never
forwarded.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julian Oes <julian@oes.ch>

* fix(mavlink): only apply SETUP_SIGNING addressed to us

The target of SETUP_SIGNING was ignored, so a key meant for another
system or component, e.g. a companion computer, was applied to PX4 and
all its links instead.

SETUP_SIGNING is now only applied when it is broadcast or addressed to
us. Otherwise it is dropped, as it must never be forwarded, and a
warning tells the user that it didn't reach its target.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julian Oes <julian@oes.ch>

* refactor(mavlink): forward messages from one place

Messages which were handled locally were forwarded from the end of
Mavlink::handle_message(), while frames which can only be forwarded
took a separate path in the receive loop. Forwarding is now decided in
one place in the receive loop for both, and Mavlink::handle_message()
only deals with SETUP_SIGNING.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julian Oes <julian@oes.ch>

* refactor(mavlink): check forwarding enabled only once

Whether to forward is decided by forward_if_enabled(), so
forward_only_frame() only needs to decide whether a frame is valid but
can't be processed locally. As a result, unknown and incorrectly signed
frames no longer count as parse errors, independent of whether
forwarding is enabled.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julian Oes <julian@oes.ch>

* feat(mavlink): count unknown and incorrectly signed messages

Messages which are not in our dialect or whose signature can't be
verified are not processed and no longer count as parse errors. Count
them per link in telemetry_status instead, so they show up in the log
and in mavlink status, e.g. to find a sender using the wrong key.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julian Oes <julian@oes.ch>

* fix(mavlink): count lost messages correctly across sequence wrap-around

The sequence number wraps from 255 to 0, so the gap across the wrap is
seq + 256 - expected, not seq + 255 - expected. Every wrap-around with
loss undercounted by one message.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julian Oes <julian@oes.ch>

* fix(mavlink): report rx message loss as a fraction of all messages

rx_message_lost_rate was lost / received, which exceeds 1 with heavy
loss and is 0/0 before anything was received, and mavlink status then
printed that ratio as a percentage without scaling it. It is now
lost / (received + lost), 0 if nothing was received yet, and printed
as a percentage.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julian Oes <julian@oes.ch>

* fix(mavlink): don't forward unauthenticated frames when signing is active

With a signing key loaded, frames which fail PX4's signature check were
forwarded to the other links. PX4 keeps unauthenticated traffic from
e.g. the radio away from components which don't sign themselves, as the
signing docs describe, so drop them again, and count them as bad
signatures.

Unknown messages are reported as bad CRC by the parser, which doesn't
report their signature result, so apply the same rules to them: with
signing active they are only forwarded if signed with PX4's key or
allowed unsigned.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julian Oes <julian@oes.ch>

* fix(mavlink): don't count forwarded unknown messages as lost

Unknown messages didn't advance the sender's sequence, so the next
message from it counted them as lost. Track their sequence as well, but
only for components we have already seen: the header of an unknown
message isn't CRC checked, and garbage IDs would fill the component
table, which is also used for routing.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julian Oes <julian@oes.ch>

* fix(mavlink): forward signed frames PX4 can't verify

With signing active, a signed frame which fails PX4's check might be
signed with the receiver's key, so forward it for the receiver to
check, as the MAVLink routing guide says, but don't process it.
Unsigned frames are still dropped, apart from the unsigned allowlist,
so an attacker still can't send unsigned messages through PX4.

Suggested by @dakejahl in review.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julian Oes <julian@oes.ch>

* fix(mavlink): drop signed frames PX4 can't verify again

This reverts commit 80639d05d8. Setting the signed flag and appending a
random signature is trivial, so forwarding signed frames that fail
verification would let an attacker reach components behind PX4 that
don't check signatures themselves, just like unsigned frames. In
practice all systems share one key anyway, so with signing active only
forward what verifies with PX4's key, or is on the unsigned allowlist.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julian Oes <julian@oes.ch>

* docs(mavlink): signing assumes one shared key

Make explicit that all systems and components share one symmetric key,
that components communicating through PX4 need to use PX4's key, and
that incorrectly signed messages are not forwarded, so they don't reach
components behind PX4 which don't check signatures themselves.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julian Oes <julian@oes.ch>

---------

Signed-off-by: Julian Oes <julian@oes.ch>
2026-09-30 10:55:25 +13:00
0424edb0da refactor(ekf2): stop resetting the GNSS checks when GNSS fusion stops (#28610)
* feat(ekf2)!: remove gnss_checks reset inside gps_control.cpp

* fix(ekf2): add timeout on gnss sample at top of gnss checks

* fix(ekf2): hold off GNSS fusion restarts after a stop without resetting the checks

Removing the GnssChecks reset from the fusion stops also removed the only delay before GNSS fusion restarted: after a stop with the checks still passing, fusion started again on the next sample. The EKF now waits after a stop as long as the checks wait after a failure, so restarts behave as before while the checks keep reporting on every sample.

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

* fix(ekf2): time the checks-failing GNSS stop in the EKF

The EKF read the checker's last-pass timestamp to decide when failing checks stop GNSS fusion, and the checker's stale-pass reset zeroes that timestamp, so the checker's 7 s constant silently bounded the EKF's own reset timeout from below. Keep the time of the last sample that passed in the EKF, drop the checker's timestamp getters so the coupling cannot come back, and name the checker's constant.

Assisted-by: Claude:claude-fable-5-1

* fix(ekf2): own the GNSS restart hold-off duration in the EKF

The hold-off after a GNSS fusion stop took its duration from GnssChecks, which exposed the checker's strict/relaxed latch to the EKF. The checks are moving to the sensors module, where the EKF only sees a usable flag, so the EKF computes the duration itself from its own health time and armed/in-air state: the same timing as before, without the dependency.

Assisted-by: Claude:claude-fable-5-1

* fix(ekf2): keep GNSS height fusion through a horizontal GNSS stop

GNSS height fusion gated its continuing conditions on the restart hold-off that only velocity and position stops set, so disabling horizontal GNSS fusion or losing yaw alignment stopped height fusion for the hold-off and could reset height on the restart. Height control only runs on samples that have just passed the checks, so before the hold-off existed this gate was never false. Gate height on the checks alone; the hold-off is a horizontal restart policy.

Assisted-by: Claude:claude-fable-5-1

* docs(ekf2): describe the GNSS restart hold-off as following the current arming state

The comment claimed the hold-off had the same timing as the old checks reset, but the duration is re-evaluated every cycle from armed/in_air. Arming while a ground hold-off is running shortens the remainder to the in-flight value, where the reset kept the strict health time until the checker re-latched.

Assisted-by: Claude:claude-fable-5-1

---------

Co-authored-by: Matheo Taillandier <matheo.taillandier@rigi.tech>
Co-authored-by: Jacob Dahl <dahl.jakejacob@gmail.com>
2026-09-29 15:37:12 -06:00
Beniamino Pozzan 4dc58bad90 build(uxrce_dds_client): disable UDP profile on NuttX without CONFIG_NET (#28919)
The module only uses the UDP transport when CONFIG_NET or __PX4_POSIX is
defined, but the Micro-XRCE-DDS-Client library was always built with UDP
enabled. Match the library profile to the module's usage and drop
the -Wno-error=implicit-function-declaration workaround for it.

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

Signed-off-by: Beniamino Pozzan <beniamino.pozzan@gmail.com>
2026-09-29 14:34:27 -06:00
Saibernard f908c91543 fix(boards): drop the opt-in q attitude estimator on mamba-f405-mk2 (#28916)
The board is over its 992 KB of flash on main, by 777 to 881 B depending
on the commit, so every build of it fails. attitude_estimator_q only
runs when ATT_EN is set to 1, a parameter whose own description calls it
unsupported and that defaults to 0, and this board never sets it. The
flywoo gn-f405 already leaves the module out. Without it the board is at
99.59 %, 1011677 B, with 4.2 KB to spare.

Assisted-by: Claude:claude-fable-5-1

Signed-off-by: Saibernard Yogendran <bernie97@seas.upenn.edu>
2026-09-29 14:07:57 -06:00
Ramon Roche 39f8b7bbfd ci(build_all): keep build artifacts for 1 day
Every build_all_targets run uploads about 1.3 GB of firmware, and
without retention-days it took the 90-day repository default. At
1,200-1,500 runs a month that held roughly 10 TB of Actions storage,
about $2k/month gross. PR artifacts only exist so authors can download
a build to test, and on pushes and tags the artifacts job copies them
to S3 and the GitHub Release in the same run.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Ramon Roche <mrpollo@gmail.com>
2026-09-29 12:56:15 -07:00
Ramon Roche 47ce39d468 ci(macos): run on pull requests only when macOS setup changes; add weekly run
macOS runners cost roughly 10x Linux per minute, and this workflow was
about $1.8k/month gross running on every pull request. Pull requests now
run it only when they touch the macOS setup scripts, Homebrew pins, pixi
environment, Python requirements or the workflow itself. Pushes to main
still run it on every non-docs change, and a weekly scheduled run catches
upstream Homebrew/conda-forge drift. Non-PR runs save the ccache so pull
requests keep restoring from a fresh entry.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Ramon Roche <mrpollo@gmail.com>
2026-09-29 12:47:45 -07:00
Saibernard f19558335b refactor(commander): one function per estimator status check (#28898)
* test(commander): pin the estimator checks before restructuring them

Sixteen cases on EstimatorChecks driven through its topics, one per
behaviour of the file that the next commit moves: the preflight
innovation and magnetic interference checks, GNSS fusion starting and
stopping, spoofing and jamming, a failing GNSS check under each
COM_ARM_WO_GPS setting, the sensor bias check, the compass fault and
heading reference checks, the imminent position failure warning, low
position accuracy, and the attitude, angular velocity and altitude
validity flags. They observe the health report of a cycle and the
events it sends. All pass on the file as it is.

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

* refactor(commander): one function per estimator status check

checkEstimatorStatus ran four hundred lines through eight levels of
nesting, covering the preflight innovation checks, the magnetic
interference check and everything about GNSS. Each of those is its own
function now, named for what it checks, with an early return where a
condition used to wrap the whole block, and the GNSS part is split
further into the fusion change, the spoofing and jamming latches and
the preflight quality check. In setModeRequirementFlags the one nested
block, the warning of an imminent position failure, is extracted the
same way, and the rest is left as flat sections.

Three small simplifications on the way: the mavlink severity of a
failed GNSS check follows the log level chosen for it instead of a
second switch on the parameter, the spoofing and jamming latches are one
comparison each, and the heading innovation flag that
setModeRequirementFlags never read is no longer passed to it.

The ITCM lists of the i.MX RT boards name setModeRequirementFlags by
its new signature and take the functions split out of
checkEstimatorStatus, so the same code stays in ITCM there.

No event, message, condition or order of side effects changes. The
extracted events are identical, and the twenty seven functional tests
pass before and after.

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

---------

Signed-off-by: Saibernard Yogendran <bernie97@seas.upenn.edu>
2026-09-29 12:01:54 -06:00
Vilas Kumar Chitrakaran 68c0a6496c fix(hott): fixes header guards (#28900)
(cherry picked from commit 07e5763ebe851423b6d6180993285dcc76375662)
2026-09-29 11:57:57 -06:00
Jacob Dahl 923e340c3d refactor(ekf2): index GNSS check failures by the estimator_status constants (#28812)
The fail status union and the check mask enum each restated a check layout by hand next to the msg constants, joined by a per-check map, so appending a check to one copy silently misassigned it in the others. GnssChecks now keeps one fail word indexed by the msg constants, and one table maps each check to its EKF2_GPS_CHECK bit, checked at compile time. Saved parameters, logs and events keep their bit layouts.

Assisted-by: Claude:claude-opus-5-5
2026-09-29 11:36:22 -06:00
PX4BuildBot 8f106ed256 docs: auto-sync metadata [skip ci]
Co-Authored-By: PX4 BuildBot <bot@px4.io>
2026-09-29 17:23:59 +00:00
Jacob Dahl 247b0b3423 fix(commander): report GNSS position loss from vehicle_gnss (#28912)
#28873 merged after the GPS to GNSS rename was tested, so its position-loss report still read sensor_gps_s from vehicle_gps_position and main doesn't build.

Assisted-by: Claude:claude-opus-5-5
2026-09-29 11:17:43 -06:00
Andrea Bernasconi 80e494bb79 refactor(rate_control): replace gain compression template with setters (#28910)
Templating GainCompression3d on its parameter IDs made the compiler emit
every method twice on boards that build both the fixed-wing and the
multicopter rate controller, as each instantiation is a separate class.
Inlining does not remove the duplicate because update() is too large to
be inlined.

Make GainCompression3d a plain class configured through setters and let
each rate controller own its FW_GC_* or MC_GC_* parameters. This saves
1040 bytes of flash on px4_fmu-v6x_default with no change in behaviour.

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

Signed-off-by: Andrea Bernasconi <andrea.bernasconi@auterion.com>
2026-09-29 19:09:12 +02:00
Jacob Dahl e40501eba0 refactor(msg): rename GPS to GNSS in messages, topics and sensor parameters (#24399)
* refactor(msg): rename SensorGps to SensorGnss and add VehicleGnss

GPS is one constellation; the receivers and the rest of PX4 handle GNSS. Field names lose their units (latitude, speed_accuracy, course, ...), which move into the metadata comments. The sensors module's output becomes VehicleGnss on vehicle_gnss: the selected receiver's sample next to what the sensors module adds to it (corrected sample time, antenna position, selected instance), so a consumer can tell the two apart and later selection and check results have a place to go.

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

* refactor(drivers/gps): publish sensor_gnss

Follows the SensorGps to SensorGnss and vehicle_gps_position to VehicleGnss rename.

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

* refactor(drivers/septentrio): publish sensor_gnss

Follows the SensorGps to SensorGnss and vehicle_gps_position to VehicleGnss rename.

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

* refactor(drivers/pps_capture): read sensor_gnss

Follows the SensorGps to SensorGnss and vehicle_gps_position to VehicleGnss rename.

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

* refactor(lib/gnss): select receivers on sensor_gnss

Follows the SensorGps to SensorGnss and vehicle_gps_position to VehicleGnss rename.

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

* refactor(sensors): publish VehicleGnss on vehicle_gnss and rename SENS_GPS* to SENS_GNSS*

The sensors module's output nests the selected receiver's sample, with the corrected sample time, antenna position and selected instance next to it. SENS_GPS_MASK/TAU/PRIME and SENS_GPSn_ID/OFFX/OFFY/OFFZ/DELAY take the SENS_GNSS name the heading parameters already use; saved parameters migrate on import, and the EKF2_GPS_POS/EKF2_GPS_DELAY translations now land on the new names directly.

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

* refactor(logger): log sensor_gnss and vehicle_gnss

Follows the SensorGps to SensorGnss and vehicle_gps_position to VehicleGnss rename.

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

* refactor(ekf2): fuse vehicle_gnss

Follows the SensorGps to SensorGnss and vehicle_gps_position to VehicleGnss rename.

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

* refactor(replay): replay vehicle_gnss

Follows the SensorGps to SensorGnss and vehicle_gps_position to VehicleGnss rename.

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

* refactor(local_position_estimator): read vehicle_gnss

Follows the SensorGps to SensorGnss and vehicle_gps_position to VehicleGnss rename.

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

* refactor(attitude_estimator_q): read vehicle_gnss

Follows the SensorGps to SensorGnss and vehicle_gps_position to VehicleGnss rename.

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

* refactor(lib/terrain_estimation): take sensor_gnss

Follows the SensorGps to SensorGnss and vehicle_gps_position to VehicleGnss rename.

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

* refactor(lib/wind_estimator): read vehicle_gnss in the replay script

Follows the SensorGps to SensorGnss and vehicle_gps_position to VehicleGnss rename.

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

* refactor(vision_target_estimator): read vehicle_gnss

Follows the SensorGps to SensorGnss and vehicle_gps_position to VehicleGnss rename.

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

* refactor(navigator): read vehicle_gnss

Follows the SensorGps to SensorGnss and vehicle_gps_position to VehicleGnss rename.

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

* refactor(commander): read sensor_gnss and vehicle_gnss

Follows the SensorGps to SensorGnss and vehicle_gps_position to VehicleGnss rename.

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

* refactor(mavlink): stream sensor_gnss and vehicle_gnss

Follows the SensorGps to SensorGnss and vehicle_gps_position to VehicleGnss rename.

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

* refactor(uxrce_dds_client): bridge vehicle_gnss

Follows the SensorGps to SensorGnss and vehicle_gps_position to VehicleGnss rename.

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

* refactor(zenoh): bridge vehicle_gnss

Follows the SensorGps to SensorGnss and vehicle_gps_position to VehicleGnss rename.

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

* refactor(failure_injection): inject GNSS failures on sensor_gnss

Follows the SensorGps to SensorGnss and vehicle_gps_position to VehicleGnss rename.

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

* refactor(drivers/ins): publish sensor_gnss

Follows the SensorGps to SensorGnss and vehicle_gps_position to VehicleGnss rename.

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

* refactor(drivers/uavcan): publish sensor_gnss

Follows the SensorGps to SensorGnss and vehicle_gps_position to VehicleGnss rename.

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

* refactor(drivers/uavcannode): read sensor_gnss

Follows the SensorGps to SensorGnss and vehicle_gps_position to VehicleGnss rename.

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

* refactor(drivers/cyphal): bridge sensor_gnss

Follows the SensorGps to SensorGnss and vehicle_gps_position to VehicleGnss rename.

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

* refactor(drivers/telemetry): read vehicle_gnss

Follows the SensorGps to SensorGnss and vehicle_gps_position to VehicleGnss rename.

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

* refactor(drivers/rc): read vehicle_gnss for telemetry

Follows the SensorGps to SensorGnss and vehicle_gps_position to VehicleGnss rename.

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

* refactor(drivers/osd): read vehicle_gnss

Follows the SensorGps to SensorGnss and vehicle_gps_position to VehicleGnss rename.

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

* refactor(drivers/transponder): read sensor_gnss

Follows the SensorGps to SensorGnss and vehicle_gps_position to VehicleGnss rename.

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

* refactor(boards/modalai): publish sensor_gnss from the voxl2 drivers

Follows the SensorGps to SensorGnss and vehicle_gps_position to VehicleGnss rename.

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

* refactor(examples): publish sensor_gnss and read vehicle_gnss

Follows the SensorGps to SensorGnss and vehicle_gps_position to VehicleGnss rename.

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

* refactor(simulation): publish sensor_gnss

Follows the SensorGps to SensorGnss and vehicle_gps_position to VehicleGnss rename.

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

* refactor(gnss): name GNSS variables gnss in the sensors module and receiver drivers

GPS is one constellation. Identifiers that hold or handle GNSS samples, or bind to the renamed parameters, follow the message and parameter rename; classes, files, modules and names tied to parameters that keep GPS stay.

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

* refactor(gnss): name GNSS variables gnss in the estimators and navigator

GPS is one constellation. Identifiers that hold or handle GNSS samples, or bind to the renamed parameters, follow the message and parameter rename; classes, files, modules and names tied to parameters that keep GPS stay.

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

* refactor(gnss): name GNSS variables gnss in commander, mavlink and failure injection

GPS is one constellation. Identifiers that hold or handle GNSS samples, or bind to the renamed parameters, follow the message and parameter rename; classes, files, modules and names tied to parameters that keep GPS stay.

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

* refactor(gnss): name GNSS variables gnss in the remaining drivers and simulation

GPS is one constellation. Identifiers that hold or handle GNSS samples, or bind to the renamed parameters, follow the message and parameter rename; classes, files, modules and names tied to parameters that keep GPS stay.

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

* docs(gnss): reference sensor_gnss, vehicle_gnss and SENS_GNSS* parameters

The upgrade guide covers the topic, field, ROS 2, parameter and Cyphal register renames.

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

* fix(boards): name EKF2::UpdateGnssSample in the fmu-v6xrt ITCM lists

The rename left the old UpdateGpsSample symbol in the lists, so the ITCM check fails and the function drops out of ITCM on the boards it doesn't check.

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

* chore(drivers/gps): bump PX4-GPSDrivers to the merged sensor_gnss rename

The submodule pointed at the PX4-GPSDrivers#243 branch commit; #243 is merged.

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

* docs(msg): restore WGS84 in SensorGnss and point VehicleGlobalPosition at vehicle_gnss

The rename dropped the only mention of the WGS84 datum and ellipsoid, and left VehicleGlobalPosition referring to the removed vehicle_gps_position topic.

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

* refactor(msg): drop selected_instance from VehicleGnss

receiver.device_id already names the selected receiver, and 0 while blended. The sensor_gnss instance depends on the order drivers advertise at boot, so it doesn't identify a receiver across boots or logs, and nothing reads it. It would also be meaningless on the per-receiver VehicleGnss instances proposed in #28894.

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

* refactor(gnss): fuse vehicle_gnss.timestamp_sample directly and document the receiver's corrected time

vehicle_gnss.timestamp is now the sensors module's publish time, so EKF2's check of timestamp_sample against it was always true; the sensors module always fills timestamp_sample. The message docs described receiver.timestamp_sample as the driver's uncorrected value, but the sensors module writes the corrected time into it as well.

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

* docs(msg): point VehicleGnss users at the top-level timestamp_sample

uXRCE-DDS shifts only top-level timestamp and timestamp_sample into agent time, so receiver.timestamp_sample reaches ROS 2 on the PX4 boot clock even though it holds the same value on uORB.

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

* docs(releases): tell ROS 2 users which VehicleGnss timestamps to read

Only top-level timestamps are converted to ROS time, so a port that reads receiver.timestamp gets the PX4 boot clock, and vehicle_gnss.timestamp is now the sensors module's publish time rather than the receiver's.

Assisted-by: Claude:claude-opus-5-5
2026-09-29 10:36:49 -06:00
Gürkan Yılmaz 5afe025b73 docs(flight_task): fix typo in FlightTaskAuto (#28911) 2026-09-29 16:52:20 +01:00
Davide Iafrate ea495a418e docs(simulation): fix name of baylands world sdf file (#28909) 2026-09-29 14:31:08 +01:00
Matthias Grob bf52da8598 fix(GotoControl): same EKF reset solution but save some flash 2026-09-29 13:46:24 +02:00
Matthias Grob 049ea8c079 fix(GotoControl): handle EKF resets correctly 2026-09-29 13:46:24 +02:00
Matthias Grob 6b7a4cede2 fix(MulticopterPositionControl): correct units for goto setpoint yaw smoothing parameters 2026-09-29 13:46:24 +02:00
PX4BuildBot e81e29d933 docs: auto-sync metadata [skip ci]
Co-Authored-By: PX4 BuildBot <bot@px4.io>
2026-09-29 10:30:13 +00:00
Andrea Bernasconi 0d104226d3 feat(mc_rate_control): add rate loop gain compression for MC (#28719)
* feat(mc_rate_control): add rate loop gain compression

Extend the gain compression already available on fixed-wing to the
multicopter rate controller, so that an oscillation (limit cycle) on the
torque setpoint dynamically reduces the loop gain instead of requiring a
manual retune. The gain recovers to 1.0 once the oscillation stops.

GainCompression3d is templated on the enable and minimum gain parameter
IDs so both airframe types share one implementation while keeping their
own MC_GC_* / FW_GC_* parameters. Behaviour is opt-in: MC_GC_EN defaults
to disabled.

The dt clamp in the spectral damper is widened from 1 ms to 0.125 ms
because multicopter rate loops run well above the 1 kHz that the previous
floor assumed, which would otherwise mis-scale the filter coefficients.
Compression is reset while landed so that ground contact vibration does
not compress the gains before takeoff.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Andrea Bernasconi <andrea.bernasconi@auterion.com>

* fix(rate_control): fully restart gain compression on reset

reset() only restored the scalar compression gains, leaving the spectral
damper history and the cached gain vector untouched. A reset therefore did
not really restart at gain 1: the next cycle still multiplied the torque
setpoint by the previously compressed vector, and the stale HPF/LPF state
could immediately recreate compression. Clear the detector history and the
cached gains so that disarming, a non-rotary-wing transition or ground
contact genuinely restart from an uncompressed loop.

Seed the high-pass filter from the first sample after a reset instead of
zeroing the input history. Since the multicopter controller resets on every
cycle while landed, a zeroed history would make the first airborne sample
look like a step edge and inject a spurious spike into the spectral damper
right at takeoff.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Andrea Bernasconi <andrea.bernasconi@auterion.com>

* docs(mc_rate_control): document multicopter gain compression

Add a feature page for gain compression on multicopters, mirroring the
fixed-wing one but covering the multicopter specifics: it is opt-in
(MC_GC_EN defaults to disabled), and compression is reset while disarmed
or landed so ground contact cannot compress the gains before takeoff.

The fixed-wing block diagram is linked rather than duplicated, since it
contains an airspeed scaling stage that does not exist in the multicopter
rate loop.

Also note in the multicopter PID tuning guide that compression should be
off during manual tuning, as it damps out the very oscillation the tuning
process looks for.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Andrea Bernasconi <andrea.bernasconi@auterion.com>

---------

Signed-off-by: Andrea Bernasconi <andrea.bernasconi@auterion.com>
2026-09-29 12:22:31 +02:00
bresch 19ed79efa1 fix(mc_att_control): gate the yaw rate command during the arming gesture
This prevents the yawspeed filter from accumulating a large value during
stick gesture arming
2026-09-29 10:36:45 +02:00
bresch d6817b8fdf fix(stick_yaw): clear the yawspeed filter on reset
This prevents a stale yawspeed setpoint from being released when the module
is activated again with the stick centred
2026-09-29 10:36:45 +02:00
bresch 3a2baa472f test(mc_att_control): assert no heading windup at both ends of MC_REF_FF
MC_REF_FF does not scale the path a wound-up reference takes to the output,
so the fixture's default gain of 1 was not covering the flown configuration
2026-09-29 10:36:45 +02:00
bresch d9fa6201f4 fix(mc_att_control): stop the reference heading winding up on a yaw rate command
This keeps the reference heading on the setpoint heading instead of
integrating the commanded yaw rate, which grew the yaw rate setpoint far
past the command whenever the vehicle could not follow it
2026-09-29 10:36:45 +02:00
SaibernardandJacob Dahl 27d3a62df1 feat(commander): name the GNSS reason when position is lost in flight (#28873)
* feat(commander): name the GNSS reason when position is lost in flight

When GNSS goes bad in flight and takes the local position estimate with
it, the operator got "GNSS data fusion stopped" about two seconds after
the position went, and nothing said why. EKF2 computes which receiver
check failed every cycle and publishes it in
estimator_status.gps_check_fail_flags, and outside of spoofing and
jamming commander only reports those bits while disarmed. A receiver
that stops sending altogether sets no bit at all.

Report it the moment the local position estimate becomes invalid in
flight, when GNSS position was fused within the last ten seconds. A
failing check gives one event carrying every failing check as a
bitfield, a receiver whose last sample is over a second old gives one
event saying so, and boards with room for it also get a statustext. A
vehicle that never fused GNSS gets nothing, since neither says anything
about why its estimate went. What the estimator decides is untouched.

In flight EKF2 raises only the fix, horizontal, vertical and speed
accuracy, spoofing and jamming checks, so any bit set at that moment is a
real cause. Once the position is invalid the existing fusion stopped
event already drops to Info, so the log now reads reason, then
consequence.

Closes #24355

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

* test(commander): cover the GNSS reason reported when position is lost

Eight cases on EstimatorChecks driven directly, with the failsafe flags
kept across cycles as commander keeps them: the failing check is named
when position is lost in flight, with every bit in the event, it is
reported once and not again while the position stays lost, spoofing is
named ahead of an accuracy check, a receiver that stopped sending is
named as such, and nothing is reported when no check fails with the
receiver alive, when GNSS was not in use, when the vehicle has no GNSS
configured, or while disarmed.

Without the change four of them fail.

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

* fix(commander): remember the failing GNSS checks and name a silent receiver first

The flags in estimator_status describe only the newest sample, while
EKF2 keeps rejecting samples for at least a second after one failed and
keeps fusing for up to seven, so the check that kept GNSS out can have
passed again by the time the position goes and nothing was reported.
Every check that failed is now remembered until none has failed for the
same ten seconds that bound the recently fused window.

EKF2 runs the checks only on new samples, so a receiver that stopped
keeps the flags of its last sample. The silence is tested first and is
the reason reported.

The statustext could never be sent. Commander runs the checks without a
mavlink log publisher first, that pass consumes the position validity
edge, and the legacy pass that has the publisher sees the flag already
set. The event reaches the ground station and the log on its own, so the
statustext, its text selection and the test hook for it are gone.

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

* test(commander): cover a check that cleared before position was lost

Two more cases: the check fails on one sample and passes on the next,
and the position goes a cycle later, which still names that check; and a
receiver that stops after a failing sample is reported as silent rather
than as that check. The statustext assertions are gone with the
statustext.

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

* fix(commander): expire each failed GNSS check on its own timer

The shared latch cleared only after ten seconds without any failure, so a check that kept flapping held every earlier failure in the reason, e.g. a one-off spoofing flag or a drift check from the ground before takeoff. Each check now drops out ten seconds after it last failed.

The fail time is the estimator_status timestamp, when EKF2 reported the check, which also lets the test age a report.

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

---------

Signed-off-by: Saibernard Yogendran <bernie97@seas.upenn.edu>
Co-authored-by: Jacob Dahl <dahl.jakejacob@gmail.com>
2026-09-28 20:41:31 -06:00
PX4BuildBot 16922e6694 docs: auto-sync metadata [skip ci]
Co-Authored-By: PX4 BuildBot <bot@px4.io>
2026-09-29 02:01:38 +00:00
Julian Oes c1f998dda7 fix(battery): assume full cell voltage at 4.2v (#28889)
It never really made sense to me to have the max cell voltage at this
arbitrary value, just to - presumably - make the estimation better.

I'm tempted to set this to what is "right" and then see what the effects
are.
2026-09-28 19:54:22 -06:00
Jacob Dahl c39dfca8cc feat(gnss): inject corrections from the serial port's work queue (#28890)
* fix(gps): keep fixed-base corrections away from a moving-base rover

Since #27097 every u-blox receiver gets the autopilot's fixed-base corrections, on the claim that a UART2 moving-base rover still wants them over its main link. An RTK engine works against one reference station: u-blox's moving-base application note (UBX-19009093, figure 2) sends static-base corrections to the moving base only, so the rover inherits an absolute fix through the baseline, and the ZED-F9P integration manual (UBX-18010802, 3.1.5.4) requires the reference station ID of the observations to match the station message, which a rover fed two bases cannot rely on. ArduPilot's AP_GPS::inject_data skips moving-baseline rovers, and before #27097 this driver skipped the UART2 rover too. A moving-base rover on either UART now takes only its baseline.

* fix(gps): skip the autopilot's corrections for a UART2 static-base rover

GPS_UBX_MODE 5 feeds the rover a static base's RTCM on UART2, yet the driver still injected the autopilot's fixed-base corrections on UART1. Unless both links carry the same base, the rover then sees two reference stations and can settle on either. It now skips injection like the moving-base rovers.

* feat(gnss): add a correction injector that runs on the serial port's work queue

The gps and septentrio drivers each select a corrections source, reassemble frames and write them from their reader thread, which blocks on the UART between epochs, so a correction waits for the read to return: up to one output interval in the septentrio driver. The injector does this once for every driver, woken by the correction topics on the port's work queue. It writes a frame only when the TX buffer has room for all of it, since a cut frame fails its CRC at the receiver; it feeds a receiver one stream, fixed-base corrections or its moving base's baseline, since an RTK engine works against one reference station; and it drops frame formats the receiver does not accept. It opens its own descriptor on the work queue because on NuttX a descriptor belongs to the task that opened it.

* refactor(gps): inject corrections through the gnss correction injector

Corrections were written from the driver's reader thread between reads, so each waited for a read to time out, and the driver carried its own copy of the source selection, framing and TX-space checks. The injector writes them from the port's work queue as they are published. It runs only while receiverReady() holds, as the injection did before, and is stopped before the driver reconfigures the receiver.

Receivers other than u-blox now get RTCM3 only: SPARTN and AssistNow MGA frames are u-blox formats. A UART1 moving-base rover reports its baseline's injection rate in rtcm_injection_rate. The full communication dump (GPS_DUMP_COMM 1) no longer includes injected corrections, which are written outside the driver thread.

* refactor(septentrio): inject corrections through the gnss correction injector

Corrections were written only after receive() returned with a complete SBF block, so each could wait up to one output interval, and they were written in every driver state, baud-rate detection and configuration included, where they share the line with commands. The injector writes them from the port's work queue as they are published, only while the receiver is streaming, and is stopped around a scheduled reset so no correction lands between forcing command input and the reset command.

A moving-base rover now takes only its moving base's stream, not fixed-base corrections as well, since an RTK engine works against one reference station; the fixed-base corrections still reach the moving base. Only RTCM3 is written: SPARTN and AssistNow MGA are u-blox formats. Injected corrections no longer count toward the controller -> receiver data rate or appear in the SEP_DUMP_COMM dump.

* refactor(gnss): give the correction injector the baudrate, not the driver's port

The injector held a reference to the driver's serial port only to read its baudrate, from the work queue while the driver thread owns the port. The baudrate is fixed while the injector runs, so the driver now passes it in the start() config.
2026-09-28 17:46:52 -06:00
PX4BuildBot db8bc69c4a docs: auto-sync metadata [skip ci]
Co-Authored-By: PX4 BuildBot <bot@px4.io>
2026-09-28 23:46:19 +00:00
Jacob Dahl eb8f2f9b42 refactor(ekf2): separate GNSS heading from position into independent topic (#27102)
* feat(sensors/vehicle_gps_position): publish GNSS heading on its own topic

Dual-antenna heading was only reachable through the blended
vehicle_gps_position, so it carried the position timestamp, followed the
position blending weights and was published at the position rate.
vehicle_gnss_heading carries one selected source at a time, preferring
sensor_gnss_relative (callback driven) and falling back to
sensor_gps.heading for receivers that do not publish it. Per-receiver
delay and the driver heading offset are resolved by device_id; a sample
timestamp within 10 ms of the publish timestamp is treated as unset so
the configured delay applies.

* refactor(ekf2): fuse GNSS heading from its own sample buffer

Heading rode on gnssSample and was only fused when a position sample was
popped: faster heading was dropped by the observation rate limit, its
timestamp was the position timestamp and a position outage blocked the
start. gnssYawSample has its own TimestampedRingBuffer and pop. Non-finite
yaw is rejected at the interface and a NaN heading_offset is treated as
zero instead of reaching the fusion Jacobian. The test simulator feeds
heading as an independent sensor.

* feat(sensors/vehicle_gps_position): per-receiver baseline rotation SENS_GPSn_ROT

The antenna baseline orientation lived in three places depending on the
transport: the serial driver (GPS_YAW_OFFSET), the Septentrio receiver
(SEP_YAW_OFFS, SEP_PITCH_OFFS) and EKF2 for anything arriving with a NaN
offset, in practice CAN receivers (EKF2_GPS_YAW_OFF). A second receiver
with a different baseline had no parameter at all. Drivers now publish
the raw baseline heading and vehicle_gps_position rotates it into the
body frame from the receiver's SENS_GPSn_* slot, the same Rotation enum
the other sensors use, before blending and before publishing
vehicle_gnss_heading. Bumps PX4-GPSDrivers so sensor_gnss_relative
carries a real UTC time and no parse-time sample timestamp, which lets
the relative-position heading be delay-compensated and PPS-aligned.

* refactor(drivers/gps): drop the UBX heading_offset setting

Bumps PX4-GPSDrivers to PX4/PX4-GPSDrivers#241, which removes the
driver-side heading offset now that SENS_GPSn_ROT is the only place the
baseline is rotated.

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

* refactor(sensors/vehicle_gps_position): remove sensor_gps.heading_offset

With the baseline rotation in SENS_GPSn_ROT, a finite offset only meant
"the driver already rotated this heading, skip SENS_GPSn_ROT". The only
remaining writer was older DroneCAN node firmware through the Fix2 ECEF
hack, which made a CAN node's heading switch between two conventions
depending on whether a RelPosHeading arrived between Fix2 messages: with
a non-zero SENS_GPSn_ROT yaw the heading jumped by that angle. Every
receiver's heading is now the measured baseline and SENS_GPSn_ROT is the
only rotation. MicroStrain external heading aiding, which recovered the
baseline heading from vehicle_gps_position, reads vehicle_gnss_heading.

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

* fix(mavlink): report the body-frame GNSS heading in GPS_RAW_INT and GPS2_RAW

sensor_gps now carries the measured baseline heading, so for a baseline
not along the body x axis the yaw field was off by the mounting angle.
The body-frame heading is taken from vehicle_gnss_heading when the
streamed receiver is the heading source.

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

* fix(replay): replay vehicle_gnss_heading to EKF2

ReplayEkf2 publishes only the topics it lists from inside the lockstep
barrier, so EKF2 replay never received the heading.

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

* feat(parameters): migrate GNSS heading offsets to SENS_GPSn_YAW

GPS_YAW_OFFSET, SEP_YAW_OFFS and EKF2_GPS_YAW_OFF were removed without a
migration, so an upgraded vehicle silently lost its antenna mounting
offset and its GNSS yaw was off by that angle.

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

* fix(sensors/vehicle_gps_position): build GNSS heading only where it is consumed

The heading selection, baseline rotation and SENS_GPSn_ROT parameters
overflowed diatone_mamba-f405-mk2 flash by 657 bytes. Boards built
without EKF2 GNSS yaw fusion or MicroStrain have no consumer for
vehicle_gnss_heading, so SENSORS_VEHICLE_GNSS_HEADING defaults on only
with one of them. The parameter migration also drops fmodf, since the
old parameters never exceeded one wrap.

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

* feat(sensors/vehicle_gps_position): carry the heading receiver's jamming and spoofing state

So that EKF2 can gate GNSS yaw on the receiver providing the heading, which is not necessarily the position receiver.

Assisted-by: Claude:claude-fable-5-1

* fix(ekf2): gate GNSS yaw on the heading, not the position checks

Yaw fusion could only start once the position checks passed, and stopped with them. On an ARK G5 outdoors the heading was good from 36 s after power-up and was first fused at 104 s, held back by EPV while Galileo HAS converged; a 0.45 s PDOP failure while the vehicle was handled on the ground stopped yaw for 10 s with its innovations under 5.4 deg.

The heading is its own observation, often from its own receiver: yaw now starts on fresh data, tilt alignment and the heading receiver's spoofing and jamming state under the EKF2_GPS_CHECK bits, and keeps running when position and velocity fusion stop. The hold-off after a yaw fusion failure was a side effect of the position checks resetting; it is now a yaw timer that withholds the heading from a reset for the GNSS health time.

Assisted-by: Claude:claude-fable-5-1

* fix(sensors/vehicle_gps_position): carry an unknown GNSS sample time as 0

A GNSS sample time within 10 ms of the publish time was treated as unset and replaced by SENS_GPSn_DELAY. An ARK G5 over DroneCAN measures 8.3 to 21.5 ms from epoch to receipt, so 0.4% of its headings were re-stamped 110 ms early; at 20 Hz EKF2 dropped them as out of order, at a lower rate they would have been fused 100 ms late. A stamp meaning unset that crossed a busy bus in more than 10 ms would have been trusted instead.

Unknown is now 0 end to end: the serial driver leaves timestamp_sample at 0, the CAN node sends RelPosHeading's timestamp as UNKNOWN for it, and the autopilot's bridge publishes 0 when the node timestamp can't be used. Any other sample time before the publish time is trusted.

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

* feat(sensors/vehicle_gps_position): derive the GNSS heading baseline from antenna positions

SENS_GPSn_ROT and its Euler angles carried one number: roll never moves a baseline along x, pitch only flagged a vertical one, and the heading model uses the baseline yaw alone. Nothing checked the heading against the installation either, so a receiver that flagged float solutions as valid reset yaw 87 deg wrong at first fix on an ARK G5 moving-base pair.

As in ArduPilot, the baseline now comes from the antenna positions: SENS_GPSn_HDG selects the other receiver's antenna (moving base, from both slots' SENS_GPSn_OFF) or a custom SENS_GPSn_BLX/Y/Z (single dual-antenna receiver), and a heading is used only when the receiver reports a baseline within 20% of it in length and in its vertical component over the sample's attitude. Those float solutions reported 10.9 m and 1.04 m for a 0.347 m baseline. A source is published once it has passed for 1 s: the headings right after the base antenna was re-plugged passed for 0.9 s while up to 25 deg wrong. vehicle_gnss_heading logs the reported baseline length.

A yaw offset can't be turned into a baseline, so GPS_YAW_OFFSET, SEP_YAW_OFFS and EKF2_GPS_YAW_OFF are not migrated; GNSS yaw stays off, with a warning, until SENS_GPSn_HDG is set.

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

* fix(ekf2): stop GNSS yaw on a spoofed or jammed heading receiver

The heading receiver's spoofing and jamming state only kept yaw fusion from starting; a receiver that reported spoofing in flight kept feeding yaw, where position fusion stops. Its samples are now not fused, the fusion stops once none has been for the reset timeout, and a reset to its heading waits for the GNSS health time, as for position.

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

* docs(dronecan): configure the ARK G5 heading baseline on the autopilot

The page still configured the heading offset with the module's SEP_OFFS_YAW, and required GPS blending for the heading to be published, which no longer carries it.

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

* fix(sensors/vehicle_gps_position): drop a mismatched GNSS heading without restarting the settle

Outdoors on an ARK G5 moving-base pair 0.35 m apart, the reported down component of the baseline scattered by up to 0.16 m while the airframe was turned, and 9 cm at rest, against the 7 cm the vertical check allows. The rejected headings were as good as the accepted ones, but every rejection restarted the 1 s settle, so during 720 deg turns a heading was published for 4 to 62% of the time.

A sample that fails the baseline checks is now only dropped; the settle restarts when the receiver reports no heading. Replayed on that log, 85 to 99% of the turns get a heading.

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

* fix(sensors/vehicle_gps_position): drop the GNSS heading baseline's vertical check

The check compared the reported down component of the baseline with the configured baseline rotated by the vehicle attitude, within 20% of the baseline length. On an ARK G5 moving-base pair 0.35 m apart the down component scattered by up to 9 cm at rest and 0.16 m while the airframe was turned, so it rejected good headings: across seven outdoor logs it rejected 184 samples under 5 deg of error and 6 over 10 deg, all of which the EKF innovation gate would handle, and nothing the length check missed on a receiver that withholds float headings.

The length check and the near-vertical check on the reported baseline stay. The sensors module no longer reads the vehicle attitude to check the heading.

* feat(sensors/vehicle_gps_position): SENS_GNSSn_HDG with the auxiliary antenna position

The heading parameters are new, so they take the GNSS name the rest of the SENS_GPS family moves to rather than being renamed again shortly. The setup names what the receiver is: the rover of a moving base pair, or a receiver with two antennas. For the latter the second antenna is given by its position, like the main one's SENS_GPSn_OFF, so every setup derives the baseline from antenna positions.

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

* feat(drivers/septentrio): publish the heading baseline on sensor_gnss_relative

The heading only reached PX4 through sensor_gps, without the baseline the receiver measured, so the sensors module could not check it against the configured one and a heading from float ambiguities, confident on a baseline metres off, was used. AuxAntPositions (dual antenna) or BaseVectorGeod (moving base rover) now go out with the heading of the same epoch on sensor_gnss_relative at EndOfAtt, the heading valid only with fixed ambiguities. Ported from ARK's driver, where it was tested outdoors on mosaic-G5 P3H, P6 and P8.

A receiver that rejects the extra blocks falls back to the previous output without a heading. The heading accuracy is the square root of the reported variance; it was the deg^2 variance scaled as if it were degrees.

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

* refactor(sensors/vehicle_gps_position): take GNSS heading only from sensor_gnss_relative

The sensor_gps fallback carried no reported baseline, so its headings skipped the length check that rejects a wrong ambiguity fix. Every receiver that matters now reports on sensor_gnss_relative: u-blox, Septentrio, the Unicore UM982 (PX4/PX4-GPSDrivers bump) and DroneCAN RelPosHeading. The heading fields of sensor_gps go, with the paths that filled them: NMEA HDT, Trimble MB-Two, Femtomes, SBG and MicroStrain, the heading in DroneCAN Fix2 on both the node and the autopilot, and the RelPosHeading copy into sensor_gps.

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

* docs(gps_compass): configure GNSS heading with SENS_GNSSn_HDG

The heading parameters were renamed and the heading now only comes from receivers that report their baseline, so the Trimble MB-Two and Femtomes pages no longer describe a heading setup. The antenna spacing is the recommended 30 cm rather than the 5 cm the code rejects below.

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

* chore(drivers/gps): point PX4-GPSDrivers at the merged PX4/PX4-GPSDrivers#242

The submodule tracked the PR's branch commit; the squash-merged commit has the same tree.

Assisted-by: Claude:claude-opus-5-5
2026-09-28 17:37:58 -06:00
PX4BuildBot 095b1af76d docs: auto-sync metadata [skip ci]
Co-Authored-By: PX4 BuildBot <bot@px4.io>
2026-09-28 22:52:00 +00:00
Jacob DahlandHamish Willee 7e913fa766 docs(params): shorten verbose descriptions of commonly used parameters (#28850)
* docs(params): shorten verbose descriptions of commonly used parameters

Descriptions ship xz-compressed in ROMFS on boards without
CONSTRAINED_FLASH, and these carried restatements of the short
description, option lists already in the enum/bitmask labels, and
boilerplate. Every fact is kept; the text is 54% shorter.

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

* Apply batched suggestions from code review

Co-authored-by: Hamish Willee <hamishwillee@gmail.com>

---------

Co-authored-by: Hamish Willee <hamishwillee@gmail.com>
2026-09-28 16:45:22 -06:00
Ramon Roche ec2687abc3 ci(usb-ids): check board USB IDs on PRs that change them
The workflow triggers on pull requests that touch a board defconfig or
the USB ID tooling, and checks only the defconfigs the PR adds or
modifies. The file list comes from diffing the checked-out PR merge
commit against its base parent, so no API call or token is needed. A PR that changes the tooling itself is checked against every
board, so a checker change is proven on the real tree and not only by
its unit tests. A test keeps the tooling list and the workflow paths
filter in sync.

A manual run checks every board. There is no push or scheduled run: the
registry repo checks its own changes against PX4 main, and board
changes reach main only through PRs.

The mypy and flake8 step for the checker and runner lives in
python_checks.yml with the other Python tooling checks.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Ramon Roche <mrpollo@gmail.com>
2026-09-28 14:44:48 -07:00
Ramon Roche 89549434e8 ci(usb-ids): add a checker for board USB IDs against the Dronecode registry
Boards that ship the Dronecode USB vendor ID 0x3643 must use a product
ID allocated in the Dronecode/usb-ids registry, and nothing in the tree
enforces that. A board reusing another board's PID, or picking an
unallocated one, becomes ambiguous to host tools that identify boards
by VID/PID.

The contract is the registry mapping: a defconfig under
boards/<vendor>/<board>/ using VID 0x3643 must use a registered PID
whose px4_board is exactly <vendor>/<board>. The USB vendor string is
deliberately not checked, since the registry does not govern it. Boards
on other vendor IDs are ignored.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Ramon Roche <mrpollo@gmail.com>
2026-09-28 14:44:48 -07:00
Holybro-Zebulon 3fe7e7af35 feat(boards): connect Kakute H7 Wing VTX power control (#28793)
Reuse RC_MAP_PAY_SW through the board payload power interface and keep sensor reset from overriding VTX power.
2026-09-28 12:49:59 -07:00
Ramon Roche 1fd3a84780 fix(lockstep_scheduler): stop swallowing wakeups in cond_timedwait
cond_timedwait() re-waited on a 10 ms wall-clock timeout until either a
signal or its virtual timeout arrived. A signal from the caller's own
signaler (px4_sem_post, or the unit test's broadcast) that lands while
the waiter is between that wall-clock timeout and re-acquiring the mutex
finds no waiter and is lost, so the waiter sleeps until its virtual
timeout. In lockstep_scheduler_test nothing else advances time and the
test hangs; in SITL a posted semaphore can report ETIMEDOUT late.

The loop is not needed: the caller holds the mutex until
pthread_cond_wait() releases it and set_absolute_time() takes that
mutex before broadcasting, so a timeout broadcast cannot be missed. The
original signal loss came from the MAX_WAKEUPS cap that the signal_next
list already removed. Go back to a single wait and keep the three-phase
signaling.

Refs #28862

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Ramon Roche <mrpollo@gmail.com>
2026-09-28 12:25:27 -07:00
Ramon Roche 2ce73120bd test(ekf2): init the hrt in EKF2SelectorTest
The test starts the hrt worker without hrt_init(), so the callout lock
stays a default non-recursive mutex and the worker deadlocks on its own
nested lock in hrt_tim_isr() -> hrt_call_invoke() the first time a tick
fires. The selector's 10 ms backup reschedule normally keeps pushing the
tick out, so it only fires when Run() pauses under load or in the slower
Coverage build, where it hangs the test until the ctest timeout.
VehicleAirDataTest and VehicleMagnetometerTest already do this.

Refs #28862

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Ramon Roche <mrpollo@gmail.com>
2026-09-28 12:25:27 -07:00