* feat(driver/heater): Add threshold to prevent the heater from always heating.
It only starts heating if the temperature drops below the specified threshold,
and continues until reset.
Battery::updateDt() clamps _dt to a maximum of 2 seconds:
_dt = math::min((timestamp - _last_timestamp) / 1e6f, 2.f);
This same _dt is then used for two different purposes:
1. Battery::sumDischarged(), the coulomb count that accumulates
discharged_mah: discharged_mah_loop = current_a * dt.
2. The current average low-pass filter update
(_current_average_filter_a.update(current_a, _dt)).
The 2-second clamp makes sense for (2): a low-pass filter update
generally assumes a roughly steady sample interval and shouldn't see a
single very large dt distort its state. It does not make sense for
(1): the coulomb count is a plain physical integral (charge = current
x time), and clamping dt there just means an update that arrives less
often than every 2 seconds (e.g. some DroneCAN/UAVCAN battery devices,
as reported in #27797) has its actual elapsed time silently truncated
to 2 seconds, systematically underestimating discharged_mah for that
update - the error accumulates over the life of the battery.
Fix: track a second, unclamped dt (_dt_discharge) alongside the
existing clamped one, and use it specifically for the coulomb count in
sumDischarged(). The filter update keeps using the existing clamped
_dt unchanged.
Fixes#27797
Test plan:
- I don't have hardware with a >2-second-interval battery update
source to reproduce this end-to-end, so I verified the corrected
arithmetic with a standalone C reproduction of updateDt()/
sumDischarged(): a 5-second update interval at a constant 10A
before the fix accumulates 5.56 mAh (current x 2s, clamped) instead
of the physically correct 13.89 mAh (current x 5s); after the fix it
correctly accumulates 13.89 mAh.
- Verified a normal high-frequency update pattern (10 x 100ms updates
at 5A) is unaffected: both before and after the fix accumulate the
same, correct 1.39 mAh for that 1-second span.
Signed-off-by: yi chen <94xhn1@gmail.com>
Co-authored-by: yi chen <94xhn1@gmail.com>
In navigator_main we use needsStraightLineFallback do determine whether
or not to show the error "RTL: no geofence avoidance path; flying directly".
Without any geofences that is not an error but expected behaviour.
Fix: When there are no geofences, do not declare
needsStraightLineFallback. Behaviour is otherwise the same - the geofence
avoidance path is empty, hasMore() always false, and we return directly.
This is the only double-precision exp in the codebase, and occupies a
hefty chunk of flash.
Relevant history:
- https://github.com/PX4/PX4-Autopilot/pull/9365 / 39bb65ffd7
- Timesync algorithm is initially added with this "sigmoid"
function calculated completely in float
- but _filter_alpha / _filter_beta are double, presumably to be
compatible with adjusting _time_offset in add_sample which is a
double (which it needs to be to represent large offsets coming
from int64_t)
- https://github.com/PX4/PX4-Autopilot/pull/9809 / cf74166801
- to silence warnings, the expf is swapped for an exp and the
entire sigmoid calculation is now in double
I would say the better fix to silence the promotion warning is to keep
the sigmoid function in float (precision not needed) and cast to double
right after, to not accidentally change anything depending on double.
Replaces the cube-root pow() with cbrt() (still double precision, no
log lookup table) and the pow(x, 2) calls with x*x. Removing the last
double pow() callers on the target lets the linker drop __pow_log_data
and __exp_data (~6.3 KB flash) with no loss of geodetic precision.
The ~PublicationBase() destructor was header-inline, so its null-check
plus the orb_get_queue_size()/unadvertise() branch was emitted at every
publication destruction site (PX4 links with bfd ld, no ICF, no LTO).
Move it into Publication.cpp alongside the already out-lined
advertise()/publish(), mirroring #27581.
Saves 2.4 kB of flash on px4_fmu-v6x_default.
publish(), advertise() and get_instance() were instantiated per topic
type and the advertise-check in publish() was additionally inlined at
every call site. Move them into a type-independent PublicationMultiBase
compiled once.
Saves 5.5 kB of flash on px4_fmu-v6x_default.
* refactor(simulation): use single-precision noise in SensorBaroSim
The Box-Muller noise generator ran in double precision, pulling in the
double log() implementation and its __log_data table (~2.2 KB). Noise
generation does not need double precision; converting the block to
float drops the last double log() caller on the target.
* fix(simulation): remove double Box-Muller in SensorBaroSim noise
generate_wgn() already returns a standard normal sample (Marsaglia polar
method), but the pressure-noise block fed it into a _second_ polar
Box-Muller transform as if it were a uniform [0,1) source.
Fix: Use the generator output directly for the intended 1 Pa RMS N(0,1)
noise and remove the second noise transform.
Plan RTL paths that route around geofence boundaries — both inclusion
and exclusion zones — instead of flying straight through them and
breaching. The planner builds a visibility graph over the margin-inflated
geofence polygons and circles and runs Dijkstra to find the shortest
legal return path, falling back to a straight line to the destination
when no valid path exists.
Highlights:
- New reusable libraries: src/lib/dijkstra (generic shortest path) and
src/lib/geofence (fixed-point geometry, polygon inflation, bitangent
visibility); the RTL planner lives in navigator/RTLPlanner.
- Visibility graph keeps only bitangent edges and skips edges that poke
into a forbidden region, greatly reducing edge-cost computation.
- Corner splitting is limited to sharp convex corners.
- Geometry validation: reject self-intersecting polygons and vertices
outside the fixed-point range; centimeter fixed-point scaling keeps
orientation tests exact and avoids drift at large distances.
- Failure handling: a Status enum replaces silent bool returns, and
failures (unbuildable fence, planner capacity overflow, dataman load
errors, NaN waypoints, destinations that breach the fence) are
surfaced to the operator via MAVLink warnings/criticals; the planner
falls back to a straight-line RTL.
- Destination updates are centralized in RTL::setRtlTypeAndDestination;
the path is replanned only when the destination or geometry changes.
- VTOLs always use the fixed-wing margin (FW loiter radius).
- kMaxNodes is exposed as a Kconfig option (default 100); the feature is
disabled on flash-constrained boards (FMUv4 and older, various F4/F7).
- Tests: unit tests for the Dijkstra lib, geofence geometry utils, and
the avoidance planner, plus a MAVSDK SITL test flying an RTL through a
geofence.
- Documentation: new "Geofence Awareness" section in the RTL docs.
Co-authored-by: Balduin <balduin@auterion.com>
* docs(releases): fix malformed SIH wind PR link in v1.18 notes
Signed-off-by: Ramon Roche <mrpollo@gmail.com>
* docs(releases): draft v1.18 release notes
Signed-off-by: Ramon Roche <mrpollo@gmail.com>
* docs(releases): mark v1.18 beta and add changes merged since the draft
Signed-off-by: Ramon Roche <mrpollo@gmail.com>
* docs(releases): add v1.18 upgrade guide, hardware fixes, and section structure
Signed-off-by: Ramon Roche <mrpollo@gmail.com>
* docs(releases): resort v1.18 major changes
Signed-off-by: Ramon Roche <mrpollo@gmail.com>
* docs(releases): link sensor and peripheral docs pages in v1.18 notes
Signed-off-by: Ramon Roche <mrpollo@gmail.com>
* docs(releases): describe new boards and highlight serial passthrough in v1.18 notes
Signed-off-by: Ramon Roche <mrpollo@gmail.com>
* docs(releases): fold standalone sensors section into hardware and common
Signed-off-by: Ramon Roche <mrpollo@gmail.com>
* docs(releases): fill v1.18 ethernet section with networking fixes
Signed-off-by: Ramon Roche <mrpollo@gmail.com>
* docs(releases): merge ROS 2 and middleware sections with DDS and Zenoh subsections
Signed-off-by: Ramon Roche <mrpollo@gmail.com>
* docs(releases): group vehicle-type notes under vehicle-specific section
Signed-off-by: Ramon Roche <mrpollo@gmail.com>
* docs(releases): reorder v1.18 other-changes sections by topic group
Signed-off-by: Ramon Roche <mrpollo@gmail.com>
* docs(releases): drop other-changes wrapper heading in v1.18 notes
Signed-off-by: Ramon Roche <mrpollo@gmail.com>
* docs(releases): organize debug-logging subsections and add infrastructure section
Signed-off-by: Ramon Roche <mrpollo@gmail.com>
* docs(releases): fix main.md template wording and mark v1.18 beta in sidebar
Signed-off-by: Ramon Roche <mrpollo@gmail.com>
---------
Signed-off-by: Ramon Roche <mrpollo@gmail.com>
The --viewer path (tee MAVLink frames to UDP and enable the
HIL_STATE_QUATERNION/HIL_ACTUATOR_CONTROLS streams so Hawkeye could
render an SIH flight live) was never validated end to end: Hawkeye
never actually rendered from it. Rather than ship an unproven feature,
drop it.
Removes px4bench.attach_viewer_tee (and its function-local socket use),
the flight_mission --viewer/--viewer-port args, the viewer tee call and
the stream-enable block, and the now-unused --board-dev arg that only
fed those stream commands. The SIH flight logic, firmware gate,
storage/serial tests, and the arming gate are unchanged. README and the
bench testing docs page drop the Hawkeye section and all --viewer
mentions.
Signed-off-by: Ramon Roche <mrpollo@gmail.com>
The full-suite persistence check failed on hardware with 'after reboot
SDLOG_UTC_OFFSET = 740 (expected 777)'. Firmware persistence is solid;
this was a test race. A PARAM_SET propagates through the param system
asynchronously, and phase_persistence ran 'param save' on the strength
of the PARAM_VALUE echo alone. When save raced ahead of the commit it
persisted the PRIOR value (740, the last value Phase 2 wrote). The echo
was not even proof of the new value: wait_param_echo returned
seen=[740, 777], a stale queued PARAM_VALUE from Phase 2 having slipped
past drain_param_values.
Make persistence deterministic with two gates instead of timing:
1. After setting the marker, read it back from the board until it reports
MARKER_VALUE (read_until) before saving. This confirms the value is
committed to RAM and trusts the board's read over any stale echo.
2. After 'param save', confirm the saved state with 'param show <name>'
and require the '+' (saved) flag AND the marker value before rebooting
(retry save once first). '*' means unsaved; the test never reboots on
an unsaved marker. Flag columns per src/systemcmds/param/param.cpp:822
(x used, + saved, * unsaved, l locked).
phase_set_readback now also trusts read_until over the echo, so the same
stale-queued-echo does not fail it either; its pass criteria stay the
readback match. Restores in phase_persistence and the final cleanup use
read_until too.
New shared helpers in px4bench.params: read_until(mav, name, expected,
timeout) -> (ok, last_seen), and param_is_saved(shell, name) plus its
parse_param_show() parser for the 'param show' saved flag and value.
Signed-off-by: Ramon Roche <mrpollo@gmail.com>
A hardware run exhausted the board's task/fd table after a few probe
cycles: shell open failed while heartbeat still worked, and continued
writes eventually stalled the link. Not a firmware hang, a resource leak
the tooling drove.
The firmware spawns a new nsh task plus two pipes (four fds) on the
first SERIAL_CONTROL write of a session (Mavlink::get_shell) and only
frees them when a SERIAL_CONTROL message arrives with the RESPOND flag
cleared (Mavlink::close_shell). Two tooling bugs combined to leak them:
- MavlinkShell.open() returned True on any buffered byte, including
leftover output from a prior session, so it reported a live shell
without one. It now confirms an actual 'nsh>' prompt or a completed
echo-sentinel round-trip before returning True, keeping the same
timeout-and-report-failure behavior.
- open/close cycles reopened before the single flags=0 teardown was
processed, spawning a second shell before the first was freed. close()
now sends the flags=0 teardown and then briefly drains incoming
SERIAL_CONTROL until quiet (bounded, never a hang) so the firmware
runs close_shell before a caller can reopen.
Lifecycle: every MavlinkShell user now opens one session, runs all its
work on it, and closes it in a finally so no error path leaks a shell.
The SIH flight and storage tests run all their probes on a single shell
(shell_command_exists already takes an open shell); flight_mission no
longer opens a throwaway probe shell separate from the flight session
beyond the unavoidable reboot boundary in enter_sih.
Folds in the sentinel fix (run() sends the echo sentinels on their own
input line, because nsh aborts a ';' chain when a command fails, which
previously lost the sentinel for a failing probe and read as a stall).
Signed-off-by: Ramon Roche <mrpollo@gmail.com>
The flight test is now part of the default suite; both the bench README
and the bench testing guide said it ran separately. Update the sequence
description, the risk map and suite tables (storage_stress and
flight_mission rows, serial_loopback for fixtures), the SIH sections
(probe-and-skip, the typed 'arm' confirmation, --allow-arming for
automation), the production section (--allow-arming on the line,
serial_loopback with a jumper on the fixture), and the firmware gate
--build description (bench capability report with variant and config
line recommendations). Storage threshold defaults are justified where
the flags are documented.
Signed-off-by: Ramon Roche <mrpollo@gmail.com>
The firmware already ships sd_bench, sd_stress, and serial_test; the
suite was not exploiting them.
bench/storage_stress.py joins the default sequence before log_transfer
(storage health first, then the test that depends on storage). It runs
sd_bench with data verification and parses write/read throughput and
fsync latency into named checks with generous FAIL floors (defaults
justified in the README: 100 KB/s write, 200 KB/s read, 500 ms fsync;
a working-but-slow card prints a WARNING instead of flapping), then
sd_stress for file churn with a short default iteration count. Both
phases probe-and-skip independently: a firmware without a command or a
board without an SD card records SKIP with a warning, never FAIL.
bench/serial_loopback.py is a standalone operator/fixture test for
manufacturing: with a TX-RX loopback jumper installed on a chosen UART,
serial_test transmits a known pattern and the session summary
(rx/tx/rx err counts) plus pattern-error lines decide the verdict. Not
in the default sequence because it needs a physical fixture.
The firmware gate's --build path now prints a bench capability report
from one shared table: after reading default.px4board plus the label
overlay (labels merge both, cmake/kconfig.cmake:47-54) it lists whether
the image will contain simulator_sih, sd_bench, sd_stress, and
serial_test and which suite tests will run or skip. For anything
missing it names a sibling variant that has it (exact --target) and
the exact config line, and interactively offers to append the line to
the local board config, stating that the edit is local and uncommitted.
All four options are 'default n' in Kconfig, so the merged .px4board
lines decide presence.
Signed-off-by: Ramon Roche <mrpollo@gmail.com>
The simulated flight is the only test that exercises commander,
navigator, and land-detector flight logic on real NuttX scheduling;
leaving it out of the default suite meant the most valuable coverage
was routinely skipped. It now runs last in the sequence.
Arming safety gets two explicit paths: on a TTY the operator sees a
prominent warning that the flight controller WILL ARM (simulated
flight, pwm_out_sim replaces the real outputs, board must be bare) and
must type 'arm'; declining records a skip, not a failure. For
automation and manufacturing lines --allow-arming bypasses the prompt,
and a non-TTY run without it skips the flight with a warning naming
the flag.
Before touching any configuration the test probes the live firmware
for the SIH module ('simulator_sih status'; NuttX nsh replies
'nsh: <cmd>: command not found', apps/nshlib/nsh_parse.c) and skips
with a warning when the module is absent.
Shared plumbing in px4bench: EXIT_SKIP (75) recorded as SKIP by the
orchestrator, shell_command_exists() for probe-and-skip, and
arming_gate() used by both the orchestrator flow and the standalone
flight test. Config mutation by the flight test is expected; it
restores the original airframe and SYS_HITL afterwards.
Signed-off-by: Ramon Roche <mrpollo@gmail.com>
--target px4_fmu-v6xrt_bench previously skipped the early board_id
check because only bare vendor_model targets were known; a label
selects a .px4board config within the same board, so strip it and
resolve to the board dir. The uploader still enforces board_id against
the bootloader at flash time.
Signed-off-by: Ramon Roche <mrpollo@gmail.com>
New page in Platform Testing and CI covering px4bench: why on-hardware
verification exists alongside SITL and CI, the firmware traceability
gate, the bench test inventory, the SIH hardware-in-the-loop flight
test with Hawkeye, baseline comparison for upgrade regression and
golden-unit workflows, production end-of-line usage, and CI
integration. Linked from the section index and the sidebar.
Signed-off-by: Ramon Roche <mrpollo@gmail.com>
Today's runs executed against whatever firmware happened to be on the
board (an old April build at first) and nothing checked. For a
qualification tool that is unacceptable: before any test starts the suite
now establishes, and can control, exactly which build it is testing.
New px4bench/firmware.py: .px4 metadata parsing and validation (fields
verified against Tools/px_mkfw.py and px4_uploader.py), board identity
via ver all over the nsh shell, flashing through Tools/px4_uploader.py
with streamed output and a hard timeout, HW-arch to build-target
inference by enumerating boards/, an early wrong-board check against
firmware.prototype board_id, and the named firmware_identity check
(git-hash prefix match against the artifact).
run_bench_suite.py preflight supports four firmware sources, all
converging on the same flash + verify path: keep what is on the board
(--any-firmware), flash a local file (--firmware), build the inferred
target from this source tree (--build, --target, --build-timeout), or
download a GitHub release artifact (--release TAG|latest, via gh;
asset naming verified against the v1.17.0 release). --expect-hash
verifies without flashing. Flags are mutually exclusive; with none
given, a TTY gets an operator menu covering the same four sources, and
automation exits with an error before touching the board. The detected
identity is printed, written to firmware.json in the suite report dir,
and stamped into every test's report dir via PX4BENCH_FIRMWARE_INFO.
If the board's mavlink is wedged the uploader cannot soft-reboot it into
the bootloader (hit live today); after repeated reboot attempts the gate
prints an operator instruction to replug USB so the uploader catches the
bootloader at power-on.
sih/flight_mission.py gains a verify-only --expect-hash and stamps the
firmware identity into its report dir; it never flashes.
Signed-off-by: Ramon Roche <mrpollo@gmail.com>
The README opened by framing the whole suite around one merged PR
series. The value is general: CI never boots NuttX and SITL runs on a
host OS, so defects in boot ordering, link lifecycle, storage, loop
rates, and RTOS-only concurrency are invisible until a board is on a
bench. Reword the introduction around that gap and the timeout-first
design rule instead of the originating incident.
Signed-off-by: Ramon Roche <mrpollo@gmail.com>
Turn the flat script pile into a standalone project so contributors can
navigate and extend it:
- px4bench/ shared library package: core primitives in __init__ (Reporter,
connect, MavlinkShell, reboot/replug, viewer tee, mavlink status parsers,
pymavlink add_message workaround) plus protocol modules params.py,
missions.py, and ftp.py extracted from the tests. Zero helper duplication
remains across scripts.
- bench/ holds the real-firmware tests (boot_health, reboot_loop,
usb_replug, link_forwarding, param_stress, mission_stress, log_transfer);
sih/ holds the simulated flight (flight_mission), making the
simulation/no-simulation boundary explicit.
- pyproject.toml (px4bench 0.1.0, BSD-3-Clause, pymavlink/pyserial deps,
pyulog extra) so pip install -e Tools/bench_test works; every script
remains directly runnable without installation via a parent-dir path shim.
- README rewritten contributor-first: architecture, per-test justification
tied to the v1.18 risk areas, why pymavlink over MAVSDK, how to add a
test, baseline workflow.
- Consistent CLI surface (shared connection args; --report-dir replaces
log_transfer's --outdir); decorative section banners removed.
All hardware-learned behavior is preserved exactly: param echo drain and
match-by-value, shell sentinel strip-all, the add_message workaround,
explicit param save before reboot, mission clear before upload, RTL in
MAV_FRAME_MISSION, and the post-flight ULog download.
Signed-off-by: Ramon Roche <mrpollo@gmail.com>
sih_flight.py switches the board to a SIH airframe (SYS_HITL=2, physics
on the FMU), flies takeoff, a 3-waypoint square, and RTL as an auto
mission, asserting arming, takeoff, waypoint progression, landing, and
auto-disarm with per-phase timeouts, then restores the original config.
Covers the flight-logic paths the bench tests cannot reach; pwm_out_sim
replaces real outputs so nothing is driven on the rails.
--viewer tees every received MAVLink frame to UDP (frame per datagram,
same framing as the SITL viewer channel) and enables the
HIL_STATE_QUATERNION/HIL_ACTUATOR_CONTROLS streams on the board, so
Hawkeye renders the flight live from a serial-connected board.
Signed-off-by: Ramon Roche <mrpollo@gmail.com>
First hardware run (fmu-v6xrt over USB, macOS) surfaced three issues:
- param_torture consumed PARAM_VALUE echoes positionally; one duplicate
broadcast desynced every subsequent check. Drain stale messages before
each set and match the echo against the expected value, reporting all
observed values so a genuine wrong-echo bug remains visible.
- the shell sentinel's second safety echo leaked into the next command's
captured output; strip any sentinel line, not just the current one.
- pymavlink 2.4.49 add_message() intermittently crashes with TypeError
when the first message of an instanced type arrives with its instance
field unset; wrap it defensively until fixed upstream.
Full suite now passes 5/5 against fmu-v6xrt.
Signed-off-by: Ramon Roche <mrpollo@gmail.com>
Semi-automated bench tests for qualifying v1.18 on real Pixhawk-class
hardware after the TSAN/concurrency series (#27606, #27809, #27813).
CI builds NuttX but never boots it, and the failure mode of this class
of regression is a silent hang, not a crash: every operation here has a
hard timeout and a timeout is reported as FAIL naming what stalled.
Tests map to the reworked subsystems: boot_health (WorkQueue/uORB
snapshot with baseline diffing), dual_link_forwarding (nested-send lock
path under simultaneous two-link load), param_torture (set/readback and
reboot persistence on SDLOG_UTC_OFFSET), mission_torture (dataman and
the mission shared-state mutex, 220 items, link alternation),
mavftp_log (logger on/off plus MAVFTP download and ULog verification),
reboot_loop and the operator-assisted usb_replug (mavlink instance and
RAM lifecycle across re-enumeration). run_bench_suite.py orchestrates
the non-interactive tests with a per-test watchdog.
Requires pymavlink/pyserial, pyulog optional. Nothing arms the vehicle.
Signed-off-by: Ramon Roche <mrpollo@gmail.com>
Since it's 1:1 reflecting a MAVLink message we better also keep the order otherwise it's less easy to follow.
The "flags" title in the status enum is misleading. Those aren't flags, there can only be one status at a time a uint8 would not even have 12 bits to set.