Nothing checked the reported heading accuracy before GNSS yaw fusion started, and the reset always claimed the default heading noise, so a receiver still resolving its baseline could align yaw to a heading it reported with a 172 deg accuracy.
Assisted-by: Claude:claude-opus-5-5
* fix(ekf2): update the sensor low-pass filters with the real sample interval
_mag_lpf and the optical flow filters (_flow_vel_body_lpf and
_flow_rate_compensated_lpf) are stepped once per sensor sample, but their
alpha was computed for the EKF update interval. The magnetometer reaches the
EKF at SENS_MAG_RATE (15 Hz by default), much slower than the EKF, so the
real time constant is several times the intended 90 ms.
The filtered mag field is used to reset the mag states, including the
in-flight yaw reset at 1.5 m. A reset shortly after an attitude change
therefore combines an outdated field with the current attitude. The filtered
flow rate feeds the terrain reset.
Pass the time since the previous sample to the filters, and drop the unused
sample interval from their constructors.
* test(ekf2): update change indication baselines
The mag low-pass filter change alters the EKF output in the replay tests.
---------
Co-authored-by: matteo <197973584+megavedl@users.noreply.github.com>
* fix(mc_nn_control): use single precision sqrt and correct two parameter/comment errors
Three small corrections in the neural network controller, none of which
change the commanded thrust for a valid configuration:
- RescaleActions() called sqrt() rather than sqrtf(), promoting to double
in a loop that runs once per angular velocity sample. On an FPU without
double precision that is a software routine, four times per cycle, in
the path whose execution time the module exists to measure.
- MC_NN_THRST_COEF declared a minimum of 0.0, but the value is used as a
divisor in RescaleActions(). Zero is therefore an in-range setting that
makes the motor scaling non-finite. Raise the minimum off zero.
- The comment above PopulateInputTensor() gives the observation order as
[pos_err(3), lin_vel(3), att(6), ang_vel(3)], but the code below it,
NeuralControl.msg and the module usage text all use
[pos_err(3), att(6), lin_vel(3), ang_vel(3)]. Attitude and linear
velocity are transposed in the comment only.
Assisted-by: Claude:claude-fable-5
Signed-off-by: Saibernard Yogendran <bernie97@seas.upenn.edu>
* refactor(mc_nn_control): extract the action rescaling into a testable header
The mapping from network action to motor command lived inside a private
member operating on the TFLM output tensor, so it could not be unit
tested. Move the per element math into a dependency free header
function. Behaviour is unchanged.
Assisted-by: Claude:claude-fable-5
Signed-off-by: Saibernard Yogendran <bernie97@seas.upenn.edu>
* fix(mc_nn_control): keep motor commands between idle and full scale
The action to motor command mapping ran past both ends of the motor. On
the shipped defaults action -1 mapped to -0.0077, which the motor output
treats as a stopped motor while the other three keep thrusting, and every
action above 0.61 mapped past full scale. With a minimum rpm above a ninth
of the maximum the last stage also ran backwards for the first few
thousandths of the range. All three come from normalizing the rpm against
limits the thrust coefficient does not reach. Reported in #28417.
Bound the normalized rpm between the limits, so a thrust below what the
minimum rpm gives idles the motor and one above the maximum runs it at
full scale, and write the thrust curve compensation as a x^2 + (1 - a) x
so idle is exactly 0 and full scale exactly 1. The curve inside the
limits is unchanged.
Validate the three parameters at start and on every parameter update:
finite, the coefficient above zero, the minimum at least zero and below
the maximum, and a part of the action range wider than float resolution
reaching the motor, so rounding cannot pass a window with no action
inside it. Raw parameter writes are not bounded by the metadata, so this
is checked in code. The mapping only ever uses a set that passed. An invalid set is
reported as an error event, repeated while it stands, and the arming
check for the mode is refused until it is corrected, so the commander
neither arms into the mode nor keeps flying it. The mapping keeps the
last valid set meanwhile, so the write itself never steps the motors. A
valid set that covers less than the whole action range is reported as a
warning naming what it does cover, when it changes and again when the
mode is entered.
The arming check reply was not zero initialized, which left two mode
requirement fields to whatever was on the stack. Also from #28417: the
loop performance counter was left open on the two early returns after
inference, and actuator_motors.timestamp_sample was never set.
Assisted-by: Claude:claude-fable-5
Signed-off-by: Saibernard Yogendran <bernie97@seas.upenn.edu>
* test(mc_nn_control): add the module's first unit tests
The action to motor command mapping had no tests. These assert the
properties it has to keep rather than any particular output value:
actions beyond the boundary clamp, every command stays between 0 and 1,
the lowest action idles the motor and the highest runs it at full scale,
the mapping never decreases and rises inside the achievable range,
actions outside that range sit on the bounds, the output stays finite
for every normal action and across the documented parameter ranges, a
non finite action gives no command, and invalid limits are rejected.
Every threshold comes from the mapping's own achievable range, and each
property is checked for the shipped defaults, a smaller motor, and a
motor whose limits cover the whole action range, so a change to the
defaults only fails them when a property is broken.
Assisted-by: Claude:claude-fable-5
Signed-off-by: Saibernard Yogendran <bernie97@seas.upenn.edu>
* ci(tests): run the mc_nn_control unit tests in the neural configuration
make tests builds px4_sitl_test, which does not enable MC_NN_CONTROL, so
the unit tests the module registers were never built or run upstream. The
only configuration that enables the module is px4_sitl_neural.
Add a tests_neural target that builds that configuration with testing on
and runs the module's tests, the same shape as tests_daa_crosstrack and
tests_vtest_moving, and run it from the Unit Tests job.
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>
* refactor(ekf2): stop reading the sensors module's GNSS params
When the GNSS checks moved to the sensors module, EKF2 kept reading
three of their parameters for its own logic: GNSS_REQ_TIME for the wait
before restarting GNSS fusion and before a GNSS heading reset,
GNSS_REQ_SACC for trusting GNSS vertical velocity while the
accelerometer clips and for the yaw estimator, and GNSS_CHECK for the
heading receiver's spoofing and jamming. That coupled EKF2 to the
sensors module's parameters, and tied EKF2's waits and thresholds,
which judge a sample against the estimate, to thresholds that judge a
receiver on its own.
EKF2_REQ_GPS_H and EKF2_REQ_SACC come back as EKF2 parameters for
EKF2's own use. An imported value is also copied to GNSS_REQ_TIME and
GNSS_REQ_SACC, as it drove the checks as well before they moved, and
airframes that set the check parameter set the EKF2 one too. The
sensors module marks a heading unusable when its receiver reports
spoofing or jamming and those checks are enabled, and EKF2 fuses
vehicle_gnss_heading.usable instead of reading GNSS_CHECK.
Assisted-by: Claude:claude-opus-5-5
* refactor(ekf2): use EKF2 constants for its own GNSS thresholds
EKF2_REQ_GPS_H and EKF2_REQ_SACC duplicated GNSS_REQ_TIME and
GNSS_REQ_SACC for the few places EKF2 reused the check thresholds: the
wait before restarting GNSS fusion and before a GNSS heading reset, and
the speed accuracy for the clipping case and the yaw estimator. They
aren't tuned apart from the checks, and a second parameter for the same
threshold in another module only invites the two to disagree.
EKF2 now uses its 10 s GNSS health time default, and 1 m/s, which
fixed-wing, PX4 Vision and SITL already set and the EKF2 tests always
used. The requirement parameters exist once, with the checks.
Assisted-by: Claude:claude-opus-5-5
* revert(ekf2): keep EKF2_REQ_GPS_H and EKF2_REQ_SACC as EKF2 parameters
Reverts 7fc8503dd3d. EKF2's waits and speed accuracy threshold aren't
duplicates of GNSS_REQ_TIME and GNSS_REQ_SACC: the checks decide when a
receiver is good enough to use, EKF2 decides how long to wait after it
gave up on GNSS and when a velocity is good enough to override a clipped
accelerometer or feed the yaw estimator. They shared a parameter only
because EKF2 ran the checks. As constants they lose tuning that is in
use: SITL sets a 0.5 s health time, fixed-wing a 1 m/s speed accuracy.
Assisted-by: Claude:claude-opus-5-5
The ": " inside the plain multi-line scalar is parsed as a mapping key, so every param generator fails to load navigator_params.yaml and all builds on main break.
Assisted-by: Claude:claude-opus-5-5
Moving logic between modules invites three mistakes, all made while
reworking the GNSS pipeline: the consumer keeps reading the moved
parameters, gets a duplicate parameter, or gets a hardcoded constant for
what was a separate, tunable concept. Translations also get written for
renames that never shipped in a release.
Assisted-by: Claude:claude-opus-5-5
* docs(navigator): shorten verbose descriptions of navigator and return parameters
Second batch split from #27758, rewritten to its spec against current
main: 19 NAV_* and RTL_* descriptions drop restatements of the short
description, "Note:" boilerplate, and multi-line literal blocks, keeping
every configuration fact. RTL_LOITER_RAD now says it is overridden by a
landing approach's own radius, as the code does.
Assisted-by: Claude:claude-opus-5-5
* Apply batched suggestions from code review
Co-authored-by: Hamish Willee <hamishwillee@gmail.com>
* Apply batched suggestions from code review
Co-authored-by: Hamish Willee <hamishwillee@gmail.com>
* refactor(AlphaFilter): revert back to allowing configuration using seconds
I agree having
setParameters(uint64_t sample_interval_us, uint64_t time_constant_us)
is useful for multiple call sights but the refactor to only allow 64-Bit microseconds timestamps went too far and made the library more error prone and consume more resources.
The original mistake of
float dt = get_timestamp() - last_timestamp;
cannot easily be prevented this way and mixed uint64_t, float use anyways leads to a compile error with the overloaded functions.
* [AUTO COMMIT] update EKF change indication
See .github/workflows/checks.yml for more details
* fix(AlphaFilter): explicitly delete mixing float and unit64_t parameters to improve the compiler error
* fix(dt-calculations): consistent static_cast<float>(now) * 1e-6f conversion pattern
Message comments describe the message and its fields. These named parameters, a threshold and the logic of the code that fills or reads them, which goes stale when that code changes.
Assisted-by: Claude:claude-fable-5-1
* refactor(gnss): move GnssChecks to src/lib/gnss
The checks judge a receiver, not the estimate, and the sensors module needs one checker per receiver. GnssChecks now takes its thresholds as a params struct and armed, in air and at rest per call instead of referencing EKF2's parameters and control status, and the thresholds become GNSS_CHECK and GNSS_REQ_*, with GNSS_REQ_TIME for the health time. EKF2 still runs it, so the checks behave as before.
Co-authored-by: jonas <jonas.perolini@rigi.tech>
Assisted-by: Claude:claude-opus-5-5
* feat(gnss): carry the GNSS check result on the sample
A latest-wins status can't gate fusion: it describes the newest sample, which runs ahead of the one EKF2 fuses by the GNSS delay plus the EKF buffer. The sensors module now runs one GnssChecks per receiver and stamps the selected receiver's result on each vehicle_gnss sample (usable, and failed_checks in GNSS_CHECK bit order), and EKF2 fuses a sample only if that sample is usable. EKF2 drops its own checker and estimator_gps_status; estimator_status.gps_check_fail_flags forwards the sample's failures in its own bit order until commander reads vehicle_gnss. Every receiver's diagnostics go on sensors_status_gnss.
Co-authored-by: Matheo Taillandier <matheo.taillandier@rigi.tech>
Co-authored-by: jonas <jonas.perolini@rigi.tech>
Assisted-by: Claude:claude-opus-5-5
* feat(commander): count GNSS receivers that pass their checks
A 3D fix says nothing about accuracy, drift, spoofing or jamming, so the redundancy check counted receivers the sensors module rejects. It now counts a receiver while sensors_status_gnss says it passes its checks.
Co-authored-by: jonas <jonas.perolini@rigi.tech>
Assisted-by: Claude:claude-opus-5-5
* fix(boards): drop EKF2::PublishGpsStatus from the i.MX RT ITCM lists
The function went with estimator_gps_status, and the ITCM check fails on a listed symbol that is missing from the ELF.
Assisted-by: Claude:claude-fable-5-1
* fix(ekf2): report GNSS check failures only while GNSS fusion is enabled
EKF2 ran the checks only past its GNSS-intended gate, so with EKF2_GPS_CTRL 0 estimator_status.gps_check_fail_flags stayed 0. Forwarding the sample's failures unconditionally made commander turn a receiver EKF2 ignores into pre-arm failures, and into an arming block with COM_ARM_WO_GPS 0.
Assisted-by: Claude:claude-fable-5-1
* refactor(commander): index the GNSS status by receiver instance
The sensors module fills sensors_status_gnss by sensor_gnss instance, the index the redundancy check already subscribes with, so the device_id lookup was redundant. Its sizeof idiom also tripped clang-tidy's bugprone-sizeof-expression.
Assisted-by: Claude:claude-fable-5-1
* refactor(ekf2): rename the GNSS checks pass time to sample accepted
It is set only for a sample that is both usable and within EKF2_VEL_LIM, so it is the last time a sample was accepted for fusion, not the last time the checks passed.
Assisted-by: Claude:claude-fable-5-1
* docs(msg): keep thresholds and parameters out of the GNSS check comments
Message comments describe the message and its fields, not the code that fills them.
Assisted-by: Claude:claude-fable-5-1
---------
Co-authored-by: Matheo Taillandier <matheo.taillandier@rigi.tech>
Co-authored-by: jonas <jonas.perolini@rigi.tech>
Co-authored-by: Jacob Dahl <dahl.jakejacob@gmail.com>
* docs(contributing): discourage force-pushing during review
A force-push after review has begun erases the commits a reviewer has read, so they cannot see what changed since. The guidance told contributors to squash and reword instead, which contradicts the commit and pr agent skills. PRs are typically squash-merged, so branch history does not need cleaning up before merge.
Assisted-by: Claude:claude-fable-5-1
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
* ci(workflows): remove commit message check
PRs are squash-merged under the PR title, which is still checked. Flagging the individual commits pushed contributors to rewrite history mid-review.
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>
* fix(ekf2): reject GNSS samples with vel above EKF2_VEL_LIM in EKF instead of in the on ground GNSS checks
* fix(ekf2): drop the GNSS yaw coupling from the velocity limit skip path
GNSS yaw fusion has had its own buffer and stopped-data timeout since the heading moved to its own topic, and stopGnssFusion() no longer clears gnss_yaw. Keeping gnss_yaw in using_gnss left it true after the first stop, so stopGnssFusion() and the EKF-GSF reset re-fired on every skipped sample while yaw fusion was active, and the yaw-only timeout test could no longer pass.
Assisted-by: Claude:claude-fable-5-1
* fix(ekf2): keep the GNSS checks running while over-limit samples are skipped
Short-circuiting the checks on an over-limit sample froze the published fail flags and checks_passed at the last evaluated sample for the whole timeout window, and the eventual stop was reported as poor quality. The checks now run on every sample so their status stays truthful, the velocity limit is its own skip reason with its own stop message, and the skipped samples are counted in an EKF2 perf counter so the ulog shows why nothing was fused until estimator_status gains a fusion-state flag.
Assisted-by: Claude:claude-fable-5-1
* fix(ekf2): apply EKF2_VEL_LIM per axis to GNSS velocity samples
constrainStates() clamps each velocity component to EKF2_VEL_LIM, so the state can hold a horizontal speed up to sqrt(2) times the limit. Testing the horizontal norm rejected samples the filter could represent, and a vehicle between 100 and 141 m/s ground speed lost GNSS after the timeout. The gate now uses the same per-axis test as the clamp, and the parameter description says that samples beyond it are rejected.
Assisted-by: Claude:claude-fable-5-1
* fix(ekf2): stop GNSS height fusion when the checks time out
Height control only evaluates its fusion timeout under _gps_data_ready and its no-data branch keys on the buffer push time, which keeps advancing while samples are skipped. With HPOS and VEL disabled, a sustained check failure or over-limit velocity therefore left gps_hgt latched with nothing fused and the height reference never released. Including gps_hgt in the skip-path timeout stops it with the other GNSS aiding.
Assisted-by: Claude:claude-fable-5-1
* fix(ekf2): drop the parameter name from the velocity limit stop message
No other ECL message names a parameter.
Assisted-by: Claude:claude-fable-5-1
* refactor(ekf2): drop the velocity limit skip counter
The skipped sample still reaches updateGnssVel(), so its velocity is logged in estimator_aid_src_gnss_vel.observation with fused false while the check flags stay clear, which already names the cause. The count-delta between the EKF library and the module was scaffolding for a rare case, and a fusion-state flag in estimator_status is the planned indication. The tests observe the behaviour instead: a skipped sample stops the fusion after the timeout, an accepted one at the limit resets to it and continues.
Assisted-by: Claude:claude-fable-5-1
---------
Co-authored-by: jonas <jonas.perolini@rigi.tech>
Co-authored-by: Jacob Dahl <dahl.jakejacob@gmail.com>
* 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>
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.
* 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>
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>
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>
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>
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
* 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>
* 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>
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>
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>
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>
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>
* 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>
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
#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
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>
* 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
* 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>