When the network drops at runtime and the WiFi stack is wedged (e.g. the
ESP32 driver getting stuck after a long uptime, see #2276), the main loop
kept calling wifi_reconnect_bypass() forever with no recovery: the device
stayed stale until a manual reboot. The reboot watchdog only existed in the
ESPWifiManualSetup path, which the default WiFiManager build doesn't compile.
Count consecutive reconnection failures in the runtime loop and ESPRestart(2)
once they exceed maxRetryWatchDog, mirroring the existing manual-setup
watchdog. Gated behind the new WifiReconnectWatchDog flag (default on) so
environments where an unexpected reboot is undesirable can opt out; disabled
for theengs-plug to avoid interrupting the relay state.
Co-authored-by: Claude <noreply@anthropic.com>
Home Assistant rejects an MQTT discovery payload that carries `options`
(an enum sensor) together with `state_class` or `unit_of_measurement`:
these fields are mutually exclusive. createDiscovery() was emitting all
three for Mi Body Composition Scale (XMTZC0x) weighing_mode/unit sensors,
so HA discarded the discovery message.
Gate the stat_cla and unit_of_meas writes on enum_options == nullptr so
enum sensors no longer carry the conflicting fields.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
iBeacon uuid/mfid (and any decoder property with unit "hex") were built
as HA sensors with state_class "measurement" and unit "hex". Home
Assistant rejects a measurement sensor whose value is non-numeric, so it
logged "non-numeric value" errors on every iBeacon decode. Route "hex"
units through the same path as "string" units: plain text sensor, no
state_class, no unit_of_measurement.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
immediateBTAction() sets BTProcessLock=true and stops the scan before an
outbound BLE action, and resets it on the success path and the
"BLE busy" path — but the branch where semaphoreCreateOrUpdateDevice
can't be taken left it true. Under scan contention that wedges the BLE
scanner/connect subsystem until a restart (same BTProcessLock-mishandling
family as the long-uptime NimBLE scanner hang). Reset it on that branch
too.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [SYS] Don't clobber MQTT/portal defaults with empty form fields
The /wifisave handler unconditionally strcpy'd every form value into the
persisted config, even when WiFiManager surfaced an empty string for a
field the caller omitted. A partial provisioning POST (e.g. only s/p)
silently wiped the compile-time MQTT defaults so the DUT joined WiFi but
never reached the broker.
Mirror the mqtt_pass sentinel guard with strlen() > 0 checks for the
remaining textual fields (server, port, user, topic, name, ota). The
boolean checkboxes and certificate fields already had non-empty guards.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [DOCS] Document /wifisave provisioning portal API
Add a reference page enumerating the HTTP form fields accepted by the
on-device configuration portal so companion apps and provisioning scripts
can integrate without reading firmware source. Documents the empty-field
ignore semantics introduced in the previous commit and the AP-teardown
response race that callers must tolerate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [SYS] Also length-guard the MQTT password sentinel branch
The sentinel-only check (form-value != MQTT_PASS) was meant to protect
the stored password against accidental re-saves of the form default,
but it also persists an empty string from a partial / programmatic POST
that omits the `pass` field — empty differs from the sentinel, so the
broker password gets wiped. Add a strlen() > 0 precondition so partial
POSTs preserve the existing password.
Verified on the HIL bench (test_sys/test_wifimanager_portal_partial):
without this commit the DUT joined WiFi but never reached the broker;
with it, LWT-online lands within the reconnect budget.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [DOCS] Reflect length-guard in /wifisave mqtt_pass semantics
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [BT] Default BLE AES key empty, skip decryption without a key
Shipping a non-empty default key caused fresh installs to attempt
decryption of every encrypted PVVX/BTHome v2/Victron advertisement with
the placeholder string from User_config.h. Frames that didn't happen to
share that key were silently dropped at the CCM auth step, masking
otherwise-decodable devices (notably Victron MPPT) until users found and
entered a per-MAC key.
Initialise ble_aes[] to "" and treat an empty default as "no default
configured" in the decryption path - mirroring Theengs Gateway, which
has no default and skips frames whose MAC has no bindkey. Per-MAC keys
in ble_aes_keys still take precedence; the BLE_AES macro remains as the
WebUI placeholder hint.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [BT] clang-format: move BLE_AES default comment above #define
clang-format-9 rejected the trailing-comment continuation on the empty
BLE_AES default line. Move the explanation to a leading // block above
the #ifndef so the directive line is comment-free.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [BT] Use THEENGS_LOG_TRACE for missing-AES-key skip log
Previous commit referenced THEENGS_LOG_DEBUG, which OMG does not define
(only VERBOSE/TRACE/NOTICE/WARNING/ERROR exist), breaking every BLE
firmware build. Switch to THEENGS_LOG_TRACE — same level as the adjacent
"Custom AES key" / "Default AES key" trace lines.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The VuePress->VitePress migration replaced the `themeConfig.mode === 'dev'`
gate with a heuristic on `site.title`. CI overrides version with a short
SHA (dev) or git tag (prod), so the title never contains "edge"/"dev" and
the banner never renders on live deploys. Surface `meta.mode` in
`themeConfig` and check it directly, mirroring the original behaviour.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The PlatformIO library registry can no longer resolve
SmartRC-CC1101-Driver-Lib@2.5.7:
Library Manager: Installing SmartRC-CC1101-Driver-Lib @ 2.5.7
Warning! Could not find the package with 'SmartRC-CC1101-Driver-Lib @ 2.5.7'
requirements for your system 'linux_x86_64'
This breaks every CC1101-based environment in the nightly build:
esp32dev-rtl_433, esp32dev-pilight-cc1101, esp32dev-multi_receiver,
esp32dev-somfy-cc1101, nodemcuv2-rf2-cc1101, and others fail with:
main/commonRF.cpp:30:14: fatal error: ELECHOUSE_CC1101_SRC_DRV.h: No such file
Switch to the upstream GitHub release URL, matching how other
removed-from-registry libs in this file are pinned (m5tough, stl,
shtc3, etc). The v2.5.7 tag still exists in the upstream repo.
Co-authored-by: Florian <1technophile@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Switch Ethernet RMII clock from GPIO16 to GPIO0 output mode,
freeing GPIO16 for PSRAM chip select (SPICS1) on WROVER-IE
- Enable PSRAM with QIO flash mode and cache workaround flags
- Add PHY pre-power support for ETH_CLOCK_GPIO0_IN mode
- Report PSRAM status and free memory in SYS MQTT topic
Requires Theengs lib-builder update: remove LIBTIME from IRAM
cache workaround (CONFIG_SPIRAM_CACHE_LIBTIME_IN_IRAM=n) to
free ~3.5KB IRAM for the -mfix-esp32-psram-cache-issue flag.
Tested on Theengs Bridge v1.1: 4MB PSRAM detected, Ethernet
100Mbps on GPIO0, BLE scanning nominal.
Co-authored-by: Florian <1technophile@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
createDiscovery sits at the bottom of the discovery call chain
(loop → launchBTDiscovery → <device>Discovery → createDiscoveryFromList
(9-col, VLA) → createDiscoveryFromList(13-col) → here). Keeping
2×1024 B StaticJsonDocuments on the stack burned loopTask headroom
down to ~100 B free under a full BLE + WebUI load. Moving the two
buffers to the heap drops that 2 KB off the stack peak.
Measured over the full test_perf + test_webui suite on
esp32dev-ble-hil-fastsys (dev HEAD):
freestck_min_bytes: 292 → 2324 B (+2032, target was ≥1500)
minmem_soak_bytes: 18200 → 20652 B (+2452)
all 17 tests still pass
The docs are function-scoped, so net heap usage is unchanged long-term
(free-on-return), and the soak heap floor actually improved.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The BLE gateway only connects to one device at a time sequentially,
so 3 connection slots are unnecessary. This saves ~4-8KB of RAM.
Co-authored-by: Florian <1technophile@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace VuePress 1.9.10 with VitePress 1.6.4
- Convert config from CommonJS to ESM TypeScript (config.mts)
- Convert shared Theengs nav config to ESM (commonConfig.mts)
- Rewrite BoardEnvironmentTable in Vue 3 Composition API
- Rewrite FlashEnvironmentSelector in Vue 3 Composition API
- Enable built-in local search and dark mode
- Add medium-zoom for image zoom
- Use VitePress CSS variables for dark mode compatibility
- Update CI script paths from .vuepress to .vitepress
- Update .gitignore for VitePress build artifacts
- Rename README.md files to index.md (VitePress convention)
- Fix markdown compatibility (angle-bracket escaping, image paths)
- Drop PWA plugin (not needed)
Co-authored-by: Florian <1technophile@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ble_script is defined as "" (empty string). Passing it to
server.sendContent("") signals end of HTTP chunked transfer,
closing the response prematurely. All subsequent chunks then
fail with errno 104 "Connection reset by peer".
Guard the call with a length check so the empty string is
never sent as a chunk terminator.
Co-authored-by: Florian <1technophile@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Expand the BLE config page from encryption-only (BLEDecryptor) to a
comprehensive settings page available for all ZgatewayBT builds, covering
scan, publish, presence, external decoder, and encryption settings.
Add MQTT Discovery checkbox to the gateway configuration page.
Co-authored-by: Florian <1technophile@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a search input and filter chips to the web-install board grid,
allowing users to quickly find boards by name, MCU family (ESP32,
ESP32-S3, ESP32-C3, ESP8266), or gateway type (BLE, RF, IR, LoRa,
RTL_433, Pilight). Filters are derived dynamically from the board
data. Shows result count and a clear-all button when filters are
active.
Co-authored-by: Florian <1technophile@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The prepare job now checks for commits in the last 24 hours before
proceeding. If there are no changes, the firmware build and docs
deploy are skipped, saving CI minutes. Manual triggers always run.
Co-authored-by: Florian <1technophile@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The ci_site.sh script was overriding the VuePress dest to
"generated/site/dev" in dev mode. Since task-docs.yml already sets
destination_dir: "dev" in the GitHub Pages deploy step, this caused
the site to be nested at /dev/dev/ on gh-pages, leaving
docs.openmqttgateway.com/dev/ empty.
Remove the dest override and let the deploy action handle the
subdirectory placement.
Co-authored-by: Florian <1technophile@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* [CI] Fix Claude review action failing on fork PRs
Use pull_request_target instead of pull_request so OIDC tokens and
secrets are available when reviewing PRs from external contributors.
Explicitly checkout the PR head SHA to review the actual changes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* [CI] Harden review workflow against prompt injection from forks
Checkout the base branch instead of the PR head to prevent CLAUDE.md
poisoning and code-level prompt injection. Claude uses gh pr diff to
review changes without exposing the runner to untrusted fork code.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* [CI] Remove Claude auto-review workflow on PRs
Remove the pull_request_target review workflow due to prompt injection
risks from fork PRs. Claude can still be invoked on-demand via @claude
mentions through the claude.yml workflow, which is safer as it requires
a trusted maintainer to trigger.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* [CI] Restrict @claude trigger to repo owner only
Add actor check so only 1technophile can invoke Claude via @claude
mentions. Prevents untrusted users from triggering the action and
consuming API credits or attempting prompt injection via comments.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* [CI] Remove Claude @mention workflow
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Florian <1technophile@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Fix: Set WiFi hostname to gateway name
Add WiFi.setHostname() calls to ensure the ESP's WiFi hostname matches
the configured gateway name instead of using the default ESP hostname.
- Add wifiManager.setHostname(gateway_name) in setupWiFiManager() for
WiFiManager-based setups
- Add WiFi.setHostname(gateway_name) in setupWiFiFromBuild() for manual
WiFi setup configurations (ESPWifiManualSetup)
This matches the existing behavior for Ethernet connections where
ETH.setHostname(gateway_name) is already called.
Fixes#2150
Co-authored-by: Florian <1technophile@users.noreply.github.com>
* Change WiFi mode position
* Fix setHostname when using WiFi Manager
---------
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Florian <1technophile@users.noreply.github.com>
The bug was a race condition where saved configuration was applied
to the LoRa hardware BEFORE LoRa.begin() was called, causing settings
like syncword to be lost after reset/power cycle.
Changes:
- Created LORAConfig_apply() to centralize hardware configuration
- Removed hardware application from LORAConfig_fromJson() (now only
updates struct values)
- Call LORAConfig_apply() after LoRa.begin() in setupLORA()
- Call LORAConfig_apply() after LORAConfig_fromJson() in runtime
handlers (MQTT/WebUI) since hardware is already initialized
- Fixed syncword logging to properly show 'unchanged' vs 'changed'
Fixes#2270
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Florian <1technophile@users.noreply.github.com>
When a saved RF configuration exists in NVS, loadFromStorage() was loading
the config but not calling iRFReceiver.enable(), which meant initCC1101()
was never invoked during boot. This caused the CC1101 to not receive signals
until the user manually toggled the RF receiver via the WebUI.
This fix adds a reinitReceiver parameter to loadFromStorage() that controls
whether to disable/enable the receiver after loading config. By default it's
true (for boot-time initialization), but loadFromMessage() passes false to
avoid double initialization when loading config from MQTT messages.
Fixes#2264
Co-authored-by: Florian <1technophile@users.noreply.github.com>
Co-authored-by: Odyno <odyno@users.noreply.github.com>
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
* [CI] Configure Claude GitHub Actions to use Opus model
- Add --model opus flag to claude.yml workflow
- Add --model opus flag to claude-code-review.yml workflow
- Ensures advanced reasoning capabilities for complex firmware codebase
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* Fix model ID to use full Claude Opus identifier
---------
Co-authored-by: Florian <1technophile@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
- Convert nightly workflow to matrix builds to fix FRAMEWORK_DIR issues
caused by platform_packages conflicts when building sequentially
- Each environment now builds on its own runner, matching build.yml
- Add deploy job that collects all artifacts and deploys docs
- Add extra_scripts to esp32dev-pilight environment to fix implicit
function declaration error with newer GCC toolchain
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Florian <1technophile@users.noreply.github.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Fix duplicate W and keep units and classes in flash
Use macro for discovery classes and units
[BREAKING] change gateway sensor unique id
module - parameter
Standardize the use of createDiscoveryFromList
Co-authored-by: Florian <1technophile@users.noreply.github.com>
The filter on NTWK_CONNECTED was too restrictive when asking for WiFi status.
Get the WiFi name and BSSID from the WiFi object
Co-authored-by: Florian <1technophile@users.noreply.github.com>
They are not anymore needed as we keep the scan and presence parameters always displayed
Co-authored-by: Florian <1technophile@users.noreply.github.com>
And remove restart parameter as it is not used (the ESP always restart after erase for our use cases)
Co-authored-by: Florian <1technophile@users.noreply.github.com>
Erasing the nvs before the start of WM prevent WM from starting, revert a part of #2075
Also remove the erase from setupWiFiManager method
Co-authored-by: Florian <1technophile@users.noreply.github.com>
-Add a semaphore to avoid msg overlap
-Clean partial buffer
-Enable erase and restart of the secondary module from the primary
Co-authored-by: Florian <1technophile@users.noreply.github.com>
Before this fix, connections with Index 1 and 2 were supposed valid when not present in the configuration, generating attempts to connect with those indexes.
Also improve the erase config by adding an erase when the config file is not present (ESP is connected to WiFi without the config file). This way the ESP will not reconnect to the WiFi AP.
Co-authored-by: Florian <1technophile@users.noreply.github.com>
And enable to launch the loop when offline: false and serial communication layer is activated
Co-authored-by: Florian <1technophile@users.noreply.github.com>
Add Serial as a communication layer so as to transmit data to/from serial
Enable discovery of BT sensors through serial
Add serial heartbeat
Change discovery principle for interval and intervalacts, keep them always discovered and deactivate adaptivescan when changed. Instead of removing their discovery when adaptive scan is activated
Increase JSON_MSG_BUFFER_MAX as we are now using const char* and it requires more memory for Arduino Json Deserialization
Co-authored-by: Florian <1technophile@users.noreply.github.com>
Refactor LED handling into a library
Use a task instead of relying on the loop for precise timing
Co-authored-by: Florian <1technophile@users.noreply.github.com>
Add also a msgrcv (messages received) indicators into SYStoMQTT to measure the performances
Co-authored-by: Florian <1technophile@users.noreply.github.com>
Add a Bluetooth timer to automatically disconnect the Bluetooth client
Add custom data notifications
Add AP Scan list
Change MQTTtoSYS API to accept server or port alone
Co-authored-by: Florian <1technophile@users.noreply.github.com>
* [SERIAL] Add the capability send info to Serial instead of/added MQTT
Add the capability to send json data to Serial as a communication layer, added/instead of MQTT
* Use delay rather than vTaskDelay
---------
Co-authored-by: Florian <1technophile@users.noreply.github.com>
Gaterway states to improve LED management
Make the sleep function accessible outside BT
BREAKING remove sleep for ESP8266
BREAKING lowpowermode API changed to powermode and new mode definition
Co-authored-by: Florian <1technophile@users.noreply.github.com>
ON per default at start, never OFF unless the board loose power or the program does not start
Co-authored-by: Florian <1technophile@users.noreply.github.com>