Skip to content

Merge/upstream dev 20260828 - #48

Merged
agessaman merged 54 commits into
observer-firmware-devfrom
merge/upstream-dev-20260828
Aug 29, 2026
Merged

Merge/upstream dev 20260828#48
agessaman merged 54 commits into
observer-firmware-devfrom
merge/upstream-dev-20260828

Conversation

@agessaman

Copy link
Copy Markdown
Owner

Summary

  • Merge upstream/dev through 65650bb into observer-firmware-dev.
  • Reconcile upstream dynamic radio and board preferences with the observer CLI and migrate Heltec V4 R8 FEM gain persistence.
  • Preserve observer OTA and manifest support while retaining compatibility with boards that implement the legacy two-argument OTA entry point.
  • Fix two integration regressions found during validation: signed negative TX power values and reload of generated empty custom preference objects.

Validation

  • pio test -e native: 451/451 tests passed.
  • Firmware builds passed:
    • heltec_v4_r8_repeater_observer_mqtt
    • Station_G3_ESP32_repeater_observer_mqtt
    • heltec_v4_r8_room_server_observer_mqtt
    • heltec_v4_r8_sensor
    • Heltec_v3_repeater_observer_mqtt
    • RAK_4631_repeater

Testing notes

This is intended for observer-firmware-dev integration and hardware testing before any promotion to the stable observer-firmware branch. Hardware validation should cover persisted FEM gain behavior, OTA on representative ESP32 and RP2040 boards, and observer MQTT/alert operation.

An RP2040 firmware image was not built locally because its platform toolchain was not cached; OTA compatibility is covered by a native API contract test.

fdlamotte and others added 30 commits August 11, 2026 17:37
RadioLibWrapper::setTxPower() called setOutputPower() and nothing else. On
LR2021 that writes the PA config and TxParams, which are standby-only commands,
and it never re-arms the receiver.

This only affects LR2021 boards. recvRaw() has:

    #if defined(USE_LR2021)
      state = STATE_RX;    // LR2021 stays in Rx after readData
    #else
      state = STATE_IDLE;  // need another startReceive()
    #endif

so on SX126x the next recvRaw() re-arms Rx anyway and the write is harmless,
while on LR2021 startReceive() is never called again on its own. A 'set tx'
issued while the radio was listening could therefore stop reception until the
next reboot. Observed a few times on an LR2021 repeater; recovery required a
power cycle.

Dispatcher's stuck-radio check does not catch it: isInRecvMode() reports the
wrapper's own `state` flag rather than the chip's actual mode, so it still
believes the radio is in Rx, and it only raises ERR_EVENT_STARTRX_TIMEOUT
without attempting recovery.

Fix: on LR2021, drop to standby via idle() before writing the PA config and let
checkRecv() re-arm Rx - the same pattern resetAGC() and
applySideDetectorConfig() already use. Other radios are left untouched.

Affects meshtracker_x1 and meshnology_w12 (both USE_LR2021).

Tested on hardware with an LR2021 repeater: six consecutive 'set tx' changes
(15/18/21/14/19/14), after which the radio kept receiving and forwarding
traffic (rawrx/rxpkts increasing, no missed IRQs). Noise-floor sampling also
kept updating, which only happens while state == STATE_RX.
MAX_CLIENTS is defined in both src/helpers/ClientACL.h and examples/simple_repeater/MyMesh.h. Appears it was centralised in the former header file some time ago. Both are included in some places and, depending on which order they're in, either value can win.

This change drops the duplicate entry from the repeater firmware and bumps the central limit up to 32 (per the original repeater firmware value).

Changes:
- Remove MAX_CLIENTS from repeater MyMesh.h
- Increase MAX_CLIENTS limit in ClientACL.h to 32
…power-rx-stall

Fix LR2021 occasionally stopping reception after a TX power change
Several table entries share an address and not every driver verifies a
chip ID: INA226::begin() only checks that the address ACKs, so an SHT4x
at 0x44 was also registered as an INA226 and reported junk current on a
second channel. Mark the address consumed once a driver initializes it
so later entries cannot re-claim the same device.
Grove and other Bosch modules strap SDO high, so the 0x76-only table
never initialized them. Add an alternate-address entry per Bosch sensor;
the bus scan still gates every probe, and all four drivers verify a chip
ID before claiming an address.

Each sensor type has a single static driver instance, so skip an entry
whose query is already active: the alternate address is a fallback, not
a second device.
…minimal

repeater: set `loop.detect` to `minimal` as default for new installs
…-relocate

fix: move MeshadventurerBoard.h to variants/meshadventurer
thinknode_m8: initial support

adds some functionalities to ui_task and epddisplay:

epd display, 
- supports screen off, and clear screen
- supports forcing full refresh every N partial refreshes (off by default, configured to 60 for M8)

ui_task
- UI_SHOW_CLOCK flag to display clock on first screen
- rotary button can act like joystick (with regards to Enter key), defaults to old config for existing nodes
- screen off on long press when using rotary button (very handy for the watch)
Update GCC toolchain for nRF52 boards to fix builds on Raspberry Pi
…t-fix

Fix for CMD_SEND_RAW_DATA and multi-byte paths
An unconfigured slot has an all-zero secret, so it matches null-key group traffic (a sender with an unset PSK). The zero-key MAC validates against the empty slot and the foreign message is delivered as if it belonged to that channel — every node with a free slot is a null-key sink. Skip empty slots.
Show a small lightning-bolt icon to the left of the battery indicator on
the ui-new home screen while the device is externally powered, and a plug
icon once the battery reads full. The bolt/plug sits beside the battery so
the fill bar stays clean and uninterrupted. When a buzzer is present, the
mute icon shifts one slot further left so the two never overlap.

Charging state is derived from board.isExternalPowered(), so this works on
any board that reports external power (e.g. the nRF52 VBUS-detect path).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Boards without a charge-complete signal only infer "full" from voltage, and
a real pack rarely reads the full BATT_MAX_MILLIVOLTS (4.2V), so the plug icon
was effectively never shown. Treat "full" as a high band (>= 95%) so the plug
appears when the battery is charged rather than requiring an exact 100%.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ng-indicator

Add charging indicator to companion_radio battery icon
Skip empty channel slots in searchChannelsByHash
…ion_setup

Fix Seeed C3 radio BLE exceeding flash size
…-alt-i2c-address

Fix I2C address claiming and probe both BMP/BME addresses
…clients

Fix duplicate MAX_CLIENTS definition
…/serial interface, or remotely via txt message)
liamcottle and others added 24 commits August 22, 2026 16:16
Add support for RAK12500 and RAK12501 GPS on RAK3401
* refactor: moving various radio CLI handling to CommonRadioPrefs
…ic code

* CommonCLI: 'FEM' commands removed
* introduced static no-op attachDynamicPrefs() for all boards
…bernate

add UI_NO_HIBERNATE build flag to disable hibernate screen
* board/variant KeyValueStore now can write to 'custom' object in Json prefs
@agessaman
agessaman merged commit 3eec2d2 into observer-firmware-dev Aug 29, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.