Before this commit, there was no way to set a different max hop count
for unscoped messages.
Now with this change, by defaul it tracks the flood.max setting, until
a user provides a flood.max.unscoped value, which tax precidence for
packets if ROUTE_TYPE_FLOOD is true.
Extract eventLabel(event) to centralize per-event label formatting in
the web panel events history. The helper replaces the inline ternary
that was previously inlined in renderEventsSection().
Add a low_memory case to eventLabel: when a low_memory event carries a
non-null value (free heap in KB at the time of the event), the label
is rendered as "low memory (N KB)". The value is already emitted
by appendJsonEvents and stored as free_heap/1024 by recordStatsEvent().
The boot case is unchanged: boot events with a non-null value continue
to display the reset reason string via bootReasonLabel().
renderEventsSection() is simplified back to a plain template literal
now that label construction is delegated to eventLabel().
Implement getResetReason() and getResetReasonString() for ESP32Board,
covering all esp_reset_reason_t values: power-on, external reset,
software reset, panic/exception, interrupt watchdog, task watchdog,
generic watchdog, deep sleep wake, brownout, and SDIO reset.
Extend the pwrmgt.bootreason CLI command to support ESP32 alongside
NRF52. On ESP32 only the reset reason is reported (no shutdown reason).
Pass the reset reason as the value field when recording the
HISTORY_EVENT_BOOT stats event so the reason is stored in the in-memory
ring buffer and persisted to the archive events log.
Add bootReasonLabel() to the web panel and update renderEventsSection()
to annotate boot events with the human-readable reset reason string,
e.g. "boot (panic/exception)", when a non-null value is present.
This is a consolidation of my changes for BME680 on RAK4631 nodes.
I will close my other PRs related to this and link back to this one.
*Background on change:*
This change replaces the Adafruit BME680 driver on RAK4631 with the
Bosch BSEC library. Other boards continue to use the existing
Adafruit path via ENV_INCLUDE_BME680.
This makes the IAQ portion of the sensor functional, and more accurate.
It also contains the math and/or CayenneLPP fixes from my other PRs.
The Bosch code also appears to handle calibrating sensor aging as well,
whereas the Adafruit code is just looking at blind values that can drift
with time. Pretty cool to see this shooting out useful data!
RAK4631 platform.io is set to override to ENV_INCLUDE_BME680_BSEC while
leaving the Adafruit code for other node types. (If this becomes applicable
for other node types in future, awesome! I just don't have hardware
to test against.)
Using the BSEC library introduces IAQ sensor calibration, and saves
the calibration state periodically so it does not have to calibrate
again later.
At startup the IAQ sensor takes 30 minutes to heat and to hit a baseline,
then starts calibrating. Once calibrated, it will save those settings
and will only write settings again if calibration falls back and restores
back to state 3.
This fix also has the gas resistance math fix that was in
[pull 2146](https://github.com/meshcore-dev/MeshCore/pull/2146) so
the adafruit path also can at least show accurate values instead
of looping negative.
Also includes the fix from [pull 2149](https://github.com/meshcore-dev/MeshCore/pull/2149) so the pressure output isn't truncated to 1hPa steps.
*Fixes/Changes:*
- Add bsec_config_iaq[] with the 3.3V/3s-LP/28d calibration profile
- BSEC init applies setConfig() for voltage-correct heater targeting
- IAQ, heat-compensated temperature/humidity, pressure, and altitude
reported over CayenneLPP
- IAQ accuracy reported as analog input over CayenneLPP (0,1,2,3)
- Calibration state persisted to /bsec_state.bin on nRF52 internal
flash; written only when iaqAccuracy improves to >= 2, should
keep write frequency well within flash endurance over device lifetime
- Fix non-BSEC query_bme680: float pressure division, addGenericSensor
for gas resistance (was addAnalogInput, overflows at > 327 Ohm)
- loop() correctly gated for both GPS and BSEC-only builds
- Add fix_bsec_lib.py extra_script to resolve nRF52840 hard-float ABI
mismatch in Bosch's PlatformIO packaging, silly Bosch
One general note outside of this code change: I noticed while BME680
_functions_ in companion nodes, since companion nodes run Bluetooth,
BLE preempts the CPU, and can do so mid-I2C-transaction.
This can cause the BME680 to see an anomaly and drop calibration and
start a recalibrate. This is behavior that will exist (and has existed)
regardless of using the Adafruit or Bosch paths.
This particular companion behavior does not seem to occur in sensor
or repeater nodes since their BLE is off. Probably affects other
I2C devices as well.
*Tests:*
- RAK19003
- RAK19007
- RAK19001
- repeater, sensor, companion
Extract the inline cursor-walk in handleRegionCmd into file-local
helpers (skipSpaces, rtrimSpaces, takeToken, splitNameJump,
processRegionDefSegment), grouped immediately above the consumer.
Behavior is identical; addresses PR #2540 review feedback on
readability.
Tighten the region def docs: collapse five Note callouts into three
grouped paragraphs (Behavior / Existing regions / Limits), add a
case-sensitivity caveat plus an error example, note the cursor reset
between split commands, and use generic placeholder names.
The error_rate graph showed all zeros after ~65535 receive errors had
accumulated. Root cause was a two-part bug:
1. In MyMesh::updateStatsHistory(), recv_errors was clamped to 0xFFFF
via min<uint32_t>(..., 0xFFFF). Once the counter reached 65535,
every subsequent sample stored the same constant value, making the
per-interval delta always zero and therefore error_rate always zero.
2. HistorySample::recv_errors was declared as uint16_t while the
underlying counter (n_recv_errors) is uint32_t. parseSummaryLine()
also truncated the restored value back to uint16_t.
Fix:
- Remove the clamp in MyMesh::updateStatsHistory(); assign
getPacketsRecvErrors() (uint32_t) directly.
- Change HistorySample::recv_errors from uint16_t to uint32_t,
moving it next to the other uint32_t fields.
- Update both parseSummaryLine() code paths to cast recv_errors to
uint32_t instead of uint16_t.
The archive format is unchanged: recv_errors is written as %u and
read back into an unsigned local, which is correct for a 32-bit
value on ESP32.
Call sntp_servermode_dhcp(1) before WiFi.begin() so that lwIP's DHCP
client passes any NTP server address received in DHCP Option 42 to the
SNTP client (slot 0).
Replace configTzTime() with explicit setenv/tzset + sntp_setservername
+ sntp_init() to avoid overwriting the DHCP-provided server. Static
fallback servers (0.ru.pool.ntp.org, ntp.ix.ru, ntp21.vniiftri.ru) are
placed in slots 1-2 when DHCP provides a server, or slots 0-2 otherwise.
This relies on CONFIG_LWIP_DHCP_GET_NTP_SRV=y, which is already enabled
in the pre-built Arduino ESP32 core (framework-arduinoespressif32).
Add debug logging of the initialised NTP server list, guarded by
WIFI_DEBUG_LOGGING.
Adds a hidden `ℹ` button (meshcoretelProfileBtn) in the Public Key
field of the Info panel. After initApp() finishes loading the
"Loading info..." section, the public key value is read from the
DOM and validated against /^[0-9A-F]{64}$/. If it passes, an onclick
handler is attached that opens https://meshcoretel.ru/<PUBLIC_KEY>
in a new tab, and the button is made visible. The button remains
hidden if the key fails to load or does not match the expected 64-char
uppercase hex pattern, so the UI degrades gracefully on any firmware
that does not return a valid public key.
The container div is changed from fieldline (2-column grid: 1fr auto)
to inline-actions (3-column grid: minmax(0,1fr) auto auto) to accommodate
the second action button without wrapping.
renderNeighbourId in the /stats neighbours table now checks for the
"full_id" field emitted by appendJsonNeighbours(). If the value is a
valid 64-character hex string (the node's full 32-byte public key),
the short ID is wrapped in an anchor tag pointing to
https://meshcoretel.ru/<FULL_ID_UPPERCASE> (target="_blank",
rel="noopener noreferrer"). Falls back to plain short ID when
"full_id" is absent or does not match the expected pattern.
The hover index in syncHoverByX was computed as:
Math.round(x / width * (n - 1))
which linearly maps the full canvas width [0, width] to indices
[0, n-1]. For bar charts (packets, gps_satellites) the bars are
drawn in a plot area with plotLeft=4 and plotRight=width-4 margins,
so bar centers are at plotLeft + slotWidth*i + slotWidth/2. This
caused the first and last bars to require the cursor to be at the
very edge of the canvas to highlight correctly, while center bars
appeared fine.
For bar chart keys, use a slot-based formula instead:
Math.floor((x - plotLeft) / slotWidth)
which maps the cursor to whichever slot it falls in, matching the
actual bar layout. Line chart keys retain the original formula.
When hovering over a sparkline, the same X fraction was applied to
all other canvases to compute the highlighted point index. Because
different series can have different numbers of valid samples (some
values are filtered out), the same fraction mapped to different
indices and therefore different timestamps on each graph.
Fix by reading the uptime_secs timestamp (point[0]) of the hovered
point on the source canvas and finding the closest-timestamp point
in each other series, instead of using the fractional position.
Also de-duplicate the near-identical onmousemove/ontouchstart and
onmouseleave/ontouchend handlers into syncHoverByX() and
syncHoverClear() helpers, reducing the block from 34 lines to 16.
restoreSummaryLog() seeds the ring buffer with one sample from the
previous session (summary.latest). The next live sample has reset
counters and uptime starting from zero, so the delta across that
boundary is meaningless.
For the packets series this produced a zero point (same symptom as the
first-sample zero fixed in 1018404b, but at the restore boundary instead).
For error_rate it produced an inflated value because the counter difference
wraps around.
Fix by skipping any sample whose uptime_secs does not exceed the
previous sample's uptime_secs. In buildPointValue this returns false;
in the buildSeriesJson packets loop it skips emission and does not
advance previous.
Also: move have_previous = false into the else branch (the packets
branch does not use it), drop the now-redundant >= guards in favour of
direct uint32_t subtraction, and replace a C-style cast with
static_cast<int> for consistency.