EDT enable was sent once, a second after bidirectional telemetry came online, and never checked. AM32 only executes commands once armed, which takes a second of zero throttle plus its arming tune, so that single request can be dropped. Bluejay clears EDT whenever the motor stops, so it was off after the first flight. Treat any EDT frame after a request as confirmation, retry once a second up to five times, and start over on reconnect and on disarm.
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
Bluejay and BLHeli_S ignore commands unless the tlm bit is set, so EDT enable never latched. ESC_INFO keeps it clear: AM32 answers the bit with a KISS frame on the same UART as the EEPROM dump and aborts that frame when the dump starts, so the response would begin with a truncated frame.
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
* fix(mathlib): swap the misnamed AlphaFilter alpha tests
AlphaOneTest configures an alpha of almost zero and asserts the state
does not move, while AlphaZeroTest configures an alpha of one and
asserts pass through. The assertions are correct but each carries the
other's name. Swap the names.
Assisted-by: Claude:claude-fable-5
Signed-off-by: Saibernard Yogendran <bernie97@seas.upenn.edu>
* refactor(lib): take AlphaFilter time parameters in microseconds
The AlphaFilter interfaces took float seconds while most PX4 time
sources are integer microseconds, and the #28415 bug came from exactly
that mismatch: a microseconds sample interval passed into the seconds
interface. Take the time parameters of the constructor, setParameters
and update as uint64_t microseconds and convert once inside the filter,
as suggested in the #28415 review. The float and mixed-type overloads
are deleted, so a caller passing float seconds now fails to compile
instead of silently producing a wrong alpha. FilteredDerivative wraps
the same interface and moves with it.
Call sites that already hold a microseconds timestamp delta pass it
directly and drop their 1e-6 conversion. Call sites that only have a
float seconds value convert explicitly at the call, clamped to zero
first where the value is a user settable parameter, since a negative
float to unsigned conversion is undefined. Constants that also serve
non-filter uses stay in seconds and convert at the call. Behaviour is
equivalent at every site, to within one microsecond of truncation and
one float ulp on reconstructed constants.
Assisted-by: Claude:claude-fable-5
Signed-off-by: Saibernard Yogendran <bernie97@seas.upenn.edu>
* test(ekf2): update change indication baselines for the microsecond filter interface
The microsecond conversion truncates each sample interval to a whole
microsecond before reconstructing the float alpha, which shifts the EKF
outputs by float rounding amounts. 23 values change in each baseline,
all at the least significant digits.
Assisted-by: Claude:claude-fable-5
Signed-off-by: Saibernard Yogendran <bernie97@seas.upenn.edu>
* refactor(lib): use hrt_abstime for microsecond filter times at call sites
The AlphaFilter interface stays uint64_t so mathlib does not depend on
drv_hrt.h, but modules and drivers that already hold hrt_abstime
timestamps declared their filter intervals and time constants as raw
uint64_t next to them.
* style(lib): use time literals for AlphaFilter constants
Files that already include drv_hrt.h spelled microsecond constants as
raw integers with a comment giving the unit.
* refactor(vision_target_estimator): store the bias LPF time constant in microseconds
Every other AlphaFilter constant was converted to microseconds; this
one stayed float seconds and was cast at both use sites.
* style(lib): drop redundant hrt_abstime casts on time literals
The _s and _ms literals already return hrt_abstime.
* style(ekf2): move the time_literals using-directive below the includes
* fix(microstrain): pass the geoid height update timestamp as hrt_abstime
The float parameter received a microsecond timestamp and loses the
microsecond resolution after about 17 seconds of uptime.
---------
Signed-off-by: Saibernard Yogendran <bernie97@seas.upenn.edu>
Co-authored-by: Jacob Dahl <dahl.jakejacob@gmail.com>
* fix(gz_bridge): support GPS failure injection
Modern Gazebo publishes sensor_gps directly from GZBridge, bypassing the shared failure-injection processing. Route each NavSat sample through process_gnss using the actual uORB publication instance so off, stuck, wrong, and recovery work for the addressed receiver.
Add functional regression coverage for the real GZBridge NavSat callback and sensor_gps publication path, plus recovery coverage for the shared GNSS processor.
Fixes#22296
Assisted-by: Codex:gpt-5
Signed-off-by: Saibernard Yogendran <bernie97@seas.upenn.edu>
* docs: gps failure injection is available on Gazebo
The gz_bridge now applies the shared GNSS failure state to the
simulator's NavSat data, so the table entry and the SIM_GZ_EN_GPS
workaround note are out of date.
Assisted-by: Claude:claude-fable-5
Signed-off-by: Saibernard Yogendran <bernie97@seas.upenn.edu>
---------
Signed-off-by: Saibernard Yogendran <bernie97@seas.upenn.edu>
GCC 14 promotes implicit function declarations to hard errors. The
client's POSIX UDP transport is compiled (though never linked) for
NuttX targets and trips this via getaddrinfo/freeaddrinfo. Downgrade
the diagnostic for the external project build only, restoring the
pre-GCC-14 behavior.
Signed-off-by: Nir Mor <nir.mor@gmail.com>
The loiter established after a takeoff was always commanded at the takeoff
altitude. With a time-based climbout the vehicle can be above
that already. Take the higher of the two to prevent diving down right after climb.
Signed-off-by: mahima-yoga <mahima@auterion.com>
Add FW_TKO_CLMB_T, which ends the climbout that many seconds after the
vehicle started climbing. It replaces the altitude.
Defaults to 0, which keeps the climbout ending at the takeoff altitude.
Signed-off-by: mahima-yoga <mahima@auterion.com>
Both the Navigator and the mode manager decided when the climbout was over,
each by comparing an altitude of its own. They only agreed because they read
the same number.
Report the end of the climbout from the mode manager and act on it in
the Navigator, so that it is decided in one place. No behaviour change with
the default parameters.
Signed-off-by: mahima-yoga <mahima@auterion.com>
* fix(motion_planning): aim at the target once the trajectory passes it
The L1 look-ahead point was projected forward along the prev->target line even after the smoothed trajectory passed the target. When a multicopter overshoots a mission waypoint the navigator has not marked reached, the setpoint triplet stays fixed on that waypoint, so the look-ahead kept marching down the extended leg and the vehicle drifted away from it indefinitely (no failsafe) instead of braking onto it.
Once the trajectory is at or past the target along the leg, return the target as the crossing point so the smoother decelerates and holds on the waypoint. Normal cornering is unaffected: while approaching the target the look-ahead is unchanged, and the navigator advances the triplet before the trajectory passes the waypoint.
Part of #27730.
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
* test(motion_planning): cover drift past an unreached waypoint
Add a PositionSmoothing regression test that flies a fixed waypoint
triplet through the target without the navigator advancing, as happens
when a multicopter overshoots a waypoint it cannot accept. It asserts
the smoother brakes and turns back to the target instead of marching the
look-ahead point down the extended leg, which otherwise drives the
setpoint away from the waypoint unbounded with no failsafe.
While here, compute the prev->target vector and its length once in
_getL1Point instead of deriving the unit vector and the leg length
separately. Behavior-neutral.
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
---------
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
Manual heading overrides the current heading. The mag shouldn't be able
to reset back to it again, as long as the manual heading is valid. The
manual heading flag can be reset when a yaw aiding source is fused for a
long period of time.
The low pass filter smoothing barometric altitude for the in-air home
position correction was fed the raw timestamp difference. uORB timestamps
are microseconds, but AlphaFilter::setParameters() documents both of its
arguments as seconds and the filter is constructed with a 5 second time
constant, so the sample interval arrived a million times too large:
alpha = dt / (tau + dt)
rate dt alpha (before) alpha (intended)
50 Hz 0.0200 s 0.999750 0.003984
100 Hz 0.0100 s 0.999500 0.001996
200 Hz 0.0050 s 0.999001 0.000999
At an alpha of 0.9997 the filter passes essentially every raw sample
through, giving an effective time constant of 5 us instead of 5 s, so
_lpf_baro.getState() has been effectively unfiltered barometric altitude.
That state feeds the in-air home altitude correction: it is offset by
_baro_gps_static_offset and then compared against the GNSS altitude, and
home.alt is shifted when the two differ by more than
kAltitudeDifferenceThreshold. A GNSS velocity integral gates that
comparison for consistency.
The same conversion is already done correctly for the GNSS integral a few
lines below in this file, and for the geoid height filter in EKF2.
Note that this does change behaviour: the filter now actually applies its
5 s time constant, so _lpf_baro.getState() lags during a climb by roughly
the time constant times the climb rate. _baro_gps_static_offset is
captured once when the correction window opens, so that lag does not
cancel and it biases baro_alt_corrected while climbing. Reviewers who
know this feature should say whether the 5 s constant and the 1 m
threshold, both tuned while the filter was effectively a pass-through,
still want the same values now that it filters.
Assisted-by: Claude:claude-fable-5
Signed-off-by: Saibernard Yogendran <bernie97@seas.upenn.edu>
TrajectoryConstraintsTest.cpp and ManualVelocitySmoothingXYTest.cpp have
been in this directory since the files were moved into the library, but
neither was ever added to CMakeLists.txt, so `make tests` has never built
or run them. Twelve test cases were sitting dead in the tree.
Both compile and pass unmodified against the current library:
TrajectoryConstraints 10/10, ManualVelocitySmoothingXY 2/2.
Assisted-by: Claude:claude-fable-5
Signed-off-by: Saibernard Yogendran <bernie97@seas.upenn.edu>
* fix(mag_cal): only use mags with a valid fit for rotation detection
A disabled mag whose sphere fit fails is reset to raw
values, but was still used as the auto-rotation reference (and as a
candidate). Comparing against uncalibrated data makes the rotation
determination always fail, so an external mag rotation can never be
determined on vehicles with a biased internal mag.
* fix(mag_cal): report rotation detection failure to the user
The failure was only printed to the console while the calibration
still reported success, leaving external mags with an undetermined
rotation and no indication that the heading may be wrong.
* fix(mag_cal): warn when rotation detection has no valid reference
If no internal mag produced a valid fit, rotation detection was
skipped without warning and external mag rotations left unverified,
with the calibration still reporting success.
Tell the user which CAL_MAGx_ROT to set manually.
Co-authored-by: elisaaferraraa <147555982+elisaaferraraa@users.noreply.github.com>
* fix(mag_cal): fix double normalization of rotation detection error
MSE is already normalized by the sample count, so the error check
compared sqrt(MSE/N) against the 0.25 Ga threshold and effectively
never failed: a 1.7 Ga mismatch evaluates to ~0.1. Use the RMS error
as intended.
Regression from 1a6eb7859c.
---------
Co-authored-by: elisaaferraraa <147555982+elisaaferraraa@users.noreply.github.com>
peek() discards entries whose deadline has passed before returning the head of
the queue, and counted them with a bare registerRejectedFrame(). That is the
fourth drop path; the reported split covers three. The frames landed in the
total and in neither bucket, so `uavcan status` printed lines like
TX rejected: 64 frames (0 expired, 0 no memory)
which reads as "no drops worth caring about" when 64 frames of in-flight
transfers had just been discarded. Serving a DroneCAN node firmware update hits
this path on every interface.
The total stays a separate counter rather than the sum of the two buckets: it is
what makes an unclassified path visible at all, which is how this one was found.
* fix(uavcan): count a TX queue block only once the pool provides it
LimitedPoolAllocator::allocate() incremented used_blocks_ before asking the
underlying allocator, so an allocation the pool could not satisfy still spent
quota. Nothing ever hands that block back, because deallocate() only runs for a
pointer that was returned, so every queue that asked for memory while the pool
was empty stays permanently smaller than its configured limit.
The pool is shared between the RX side and one TX queue per interface, so it
does empty transiently under load -- a DroneCAN node firmware update is enough.
The damage accumulates and is only cleared by restarting the node.
* feat(uavcan): report TX queue depth and why frames were dropped
`uavcan status` gains, per interface:
TX queue peak: 44/84 blocks
TX rejected: 80 frames (78 expired, 2 no memory)
CanTxQueue already counted rejected frames but tx_queues_ is private and
CanIOManager exposed no accessor, so the count was unreachable. It also conflated
three causes: a frame handed over with its deadline already passed, a queued frame
evicted as expired to make room, and a frame dropped because the queue was at its
block limit. Only the last two are memory pressure, and they call for opposite
responses, so a single count sends you the wrong way.
The peak is what makes the count actionable. The limit is
pool_soft / (num_ifaces + 1) + 1, derived from the soft capacity in
allocator.hpp, so a peak sitting at the limit says raising that capacity will
help and a peak well under it says the frames are ageing out for some other
reason and a larger pool will not.
Measured on an FMU-v6XRT serving a DroneCAN node firmware update: one node holds
44 of 84 blocks and drops nothing, another pins the limit and drops hundreds. The
counts alone did not distinguish those.
The imxrt FlexCAN driver left the TX mailbox CS word uninitialised, so
classic frames went out with EDL set and appeared on the wire as CAN FD,
and its TX timeout handler aborted mailboxes that were still
transmitting. Either one takes the interface error passive and it never
recovers.
Picks up PX4/NuttX #395 and #396.
For GNSS fusion, velocity is fused before position, which led to
outdated innovations (all computed before any fusion).
EV pos and fake pos now also use the common horizontal position fusion
function (EV pos transforms its measurent into global pos).
* docs(ros2): reorder setup steps
- Use a standalone ROS 2 workspace for px4_msgs and
(optionally) the uXRCE-DDS Agent
- Build uXRCE-DDS Agent inside the ROS 2 workspace
- remove `px4_ros_com` package as mandatory package in the
workspace setup. Now it is just used in the examples.
Signed-off-by: Beniamino Pozzan <beniamino.pozzan@gmail.com>
* Apply suggestions from code review
Co-authored-by: Hamish Willee <hamishwillee@gmail.com>
* Apply suggestion from @hamishwillee
* docs(docs): fix up the links to broken anchors
---------
Signed-off-by: Beniamino Pozzan <beniamino.pozzan@gmail.com>
Co-authored-by: Hamish Willee <hamishwillee@gmail.com>