eraseConfig() passed SPIFFS.format() as an argument to THEENGS_LOG_TRACE,
which expands to ((void)0) below LOG_LEVEL_TRACE and discards its arguments
along with the call. On every build using the default LOG_LEVEL_NOTICE the
format therefore never ran, so {"cmd":"erase"} only performed
nvs_flash_erase(): the WiFi credentials were cleared but /config.json
survived and restored the MQTT server, port, user, password, broker and
client certificates, connection index, topic, gateway name, OTA password
and BLE AES keys on the next boot.
Call SPIFFS.format() on its own statement and report a failed format at
ERROR level, so the failure is visible at the default log level.
This was introduced in #2232, which converted the original runtime-checked
Log.trace() call to the compile-time macro. It affects the SYS erase
command, the WebUI erase button and the GPIO factory-reset hold alike,
since all three go through eraseConfig().
Reproduced on a Theengs Plug built at the default log level: after
{"cmd":"erase"} the NVS namespaces are gone ("SYS config not found",
"ONOFF config not found") and the saved APs are empty, yet the same boot
still logs "Config loaded from flash" and restores the OTA server cert.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Power ETH_PHY_POWER down for 200 ms and back up 200 ms before ETH.begin()
so the PHY always gets a true cold start. A warm PHY left in a stale state
(reproduced on Theengs Bridge v1.1 with PSRAM enabled after an NVS erase)
can flap the link until a manual power cycle: it reports up for one poll,
then down for several seconds, forever, so DHCP never completes. The short
reset the Ethernet driver performs on its own does not clear that state; a
power cycle with a settle window before begin() does. On a hardware-in-the-
loop bench the same provocation went from flapping every trial to flapping
in none of them (4/4).
Enabled via -DETH_PHY_COLD_CYCLE, only active when ETH_PHY_POWER is a valid
pin, at the cost of ~400 ms added boot time. Set by default on both Theengs
Bridge environments.
Co-authored-by: Florian <1technophile@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Boards that carry two radios on one SPI bus need the unused radio's CS held
high, or it answers on MISO and corrupts every transfer to the radio that is
actually in use. Add an optional RF_MODULE_SECONDARY_CS: when defined, drive
that pin high before probing the CC1101.
Bench-proven on a dual-CC1101 ESP32 board where one radio is faulty and the
other is the working one: without the deselect the shared bus is unreliable;
with it, the working module probes cleanly on the first attempt and decodes
live 433 MHz sensors.
No effect on single-radio boards: the whole block compiles out unless
RF_MODULE_SECONDARY_CS is set.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Eight environments were still pinned to espressif32@6.1.0 (arduino-esp32
2.0.7 / IDF 4.4) while the rest of the tree had moved to the shared
${com.esp32_platform} definition. Align them so they build against the
platform that actually ships, rather than silently keeping an old
toolchain alive:
esp32doitv1-aithinker-r01-sx1278
esp32dev-rtl_433, esp32dev-rtl_433-fsk
heltec-rtl_433, heltec-rtl_433-fsk
lilygo-rtl_433, lilygo-rtl_433-fsk
esp32dev-multi_receiver
The stray "* copy.c" duplicate in rtl_433_ESP that previously blocked
these envs on a modern toolchain is gone as of rtl_433_ESP v0.6.0, which
development already pulls in, so no build workaround is needed.
The move alone is not sufficient: the newer core leaves ~35 KB less heap
on BT-less ESP32 builds, which pushes the memory-constrained RTL_433
boards into a boot loop at the first TLS handshake. That is fixed here
too, in main.cpp.
On ESP32-classic builds without ZgatewayBT, arduino-esp32 3.x leaves most
of the Bluetooth controller's reserved DRAM (CONFIG_BTDM_RESERVE_DRAM =
0xdb5c, 56156 B) stranded. arduino-esp32 2.x released the whole BTDM
region unconditionally in initArduino():
if (!btInUse()) esp_bt_controller_mem_release(ESP_BT_MODE_BTDM);
3.x releases CLASSIC and BLE separately, and the CLASSIC pass silently
short-circuits. cores/esp32/esp32-hal-bt.c has:
#if defined(CONFIG_BT_CLASSIC_ENABLED)
static bool _classicMemReleased = false;
#else
static bool _classicMemReleased = true; // "No Classic BT on this chip"
#endif
With a BLE-only controller build CONFIG_BT_CLASSIC_ENABLED is unset, so
_classicMemReleased starts true and btMemRelease(BT_MODE_CLASSIC_BT)
returns "already released" without reaching IDF. Only the ~5 KB BLE slice
is freed. The guard is wrong for this part: ESP32 does have Classic BT
hardware (CONFIG_SOC_BT_CLASSIC_SUPPORTED) and the DRAM is reserved
regardless of the controller's configured mode; the correct guard would be
CONFIG_SOC_BT_CLASSIC_SUPPORTED. Reported upstream separately.
Release the whole BTDM region explicitly. esp_bt_mem_release() is used
rather than esp_bt_controller_mem_release() because it is a superset that
also frees the linker _bt_data / _bt_controller_* sections. The guard is
compile-time and authoritative: OMG knows via ZgatewayBT whether it uses
BT, whereas the core infers it from link-time flags that NimBLE-Arduino
does not set. Safe no-op if the memory was already released or once the
core is fixed. Note this makes btStart() permanently unavailable in these
builds, which is already the case for a !ZgatewayBT build.
Measured on a Heltec WiFi LoRa32 V2 running heltec-rtl_433 (WiFi + MQTT +
TLS + rtl_433 + SSD1306, no BT), pioarduino 55.03.39 / esp32-arduino-libs
0.1.8:
BT DRAM reclaimed: 209928 -> 245048 (+35120 B)
Update check, free heap: 49868 -> 89484
Without it, the boot-time HTTPS update check fails its TLS handshake
("Dynamic Impl: alloc(4437 bytes) failed", then X509 -9984) because the
largest free block is too small, and the MinimumMemory watchdog then
reboots the board in a loop. With it: no allocation failure, handshake
succeeds, freemem ~85 KB / minmem 53432 against the 40000 threshold,
0 reboots over a 3 min soak with live RTL_433 decodes.
The memory fix affects every ESP32-classic env without ZgatewayBT, not
just the ones moved here. The RTL_433 envs fail loudly because they carry
the MinimumMemory watchdog; others (esp32dev-ir, esp32dev-pilight,
ttgo-lora32-v21, heltec-wifi-lora-32, ...) simply run ~35 KB short.
Builds with ZgatewayBT are unaffected.
Hardware validation, two boards:
heltec-rtl_433 (OOK 433.92 MHz, Heltec WiFi LoRa32 V2)
boots, joins WiFi/MQTT, RadioLib/SX127x RSSI calibration completes,
HA discovery publishes, decodes live ambient sensors
(Ambientweather-F007TH, Prologue-TH, WEC-2103).
heltec-rtl_433-fsk (FSK 915 MHz, second Heltec)
same boot path, decodes a live Fineoffset-WH51 soil sensor.
The other six envs have no matching board on hand and are compile-only.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
The per-board libs (M5Stack/M5StickC/M5StickC-Plus/M5Tough) don't build on
Arduino core 3.x, which kept the five M5 BLE envs pinned to espressif32@6.1.0.
Port board support to M5Unified + M5GFX so they run ${com.esp32_platform} like
the rest of the tree.
- environments.ini: 5 M5 envs -> modern platform + m5unified. m5stick-c uses the
pico32 variant (m5stick_c variant absent upstream) and no_coredump partitions
(M5Unified overflows min_spiffs).
- boardM5.cpp / config_M5.h: single <M5Unified.h>; M5.Lcd aliases M5.Display;
unified brightness/sleep/wake.
- main.cpp / mqttDiscovery.cpp: battery telemetry via M5.Power; AXP192-only
Vin/VBus/temp fields dropped (no portable equivalent).
All five M5 envs build; M5Stack Core hardware-verified.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Static IP over Ethernet on ESP32 was unusable, and a fresh board with a
working Ethernet link still brought up the WiFi radio.
setup_ethernet_esp32() did not compile with NetworkAdvancedSetup defined: it
declared ip_adress/gateway_adress/subnet_adress/dns_adress but then called
fromString() on ip/gateway/subnet/Dns, which are declared nowhere. The WiFi
path a few hundred lines above does the same thing correctly, so this looks
like an incomplete copy of it.
Once that compiles, the static IP was still ignored. ETH.config() was called
before ETH.begin(), but NetworkInterface::config() returns false immediately
while _esp_netif is NULL, and the netif is only created inside begin(). The
call was a silent no-op and the board kept asking for DHCP. Swapping the order
is safe: the client branch of config() stops the running DHCP client itself
and sets the static-IP status bit. The return value is now checked and logged
instead of being discarded.
Separately, the "no config in flash" branch of setup() called
setupWiFiManager() guarded only by !failSafeMode, while the "config loaded"
branch a few lines above correctly also checks !ethConnected. So a
factory-fresh board with Ethernet already up would still start the WiFi
manager. Beyond being unnecessary, it powers up the 2.4GHz radio and competes
with BLE on gateways that run both.
Verified on an Olimex ESP32-POE-ISO: the board now takes its static address
immediately with no DHCP wait, does not fall back to the WiFi manager, and
reaches the broker. Formatting checked with clang-format against .clang-format.
On the platform we ship (pioarduino 55.03.39 / arduino-esp32 3.3.9) the pinned
SmartRC-CC1101-Driver-Lib v2.5.7 does not work with the CC1101 at all. Core 3.x's
peripheral manager owns SCK/MISO/MOSI once SPI.begin() has run, so the driver's
digitalWrite() calls on those pins become silent no-ops, and it starts and stops
the SPI bus around every single register access (LSatan/SmartRC-CC1101-Driver-Lib#176).
Bench result on an ESP32 DevKit + CC1101 running esp32dev-pilight-cc1101: setup
hangs in the driver's unbounded `while(digitalRead(MISO_PIN));` before any C1101
log line is emitted, and TG1WDT reboots the board in a loop. The same build on
espressif32@6.8.1 (core 2.0.17) initialises normally, so this is the core, not
the wiring. Every ZradioCC1101 ESP32 environment is affected: Pilight, RF, RF2
and Somfy. The RTL_433 CC1101 path goes through RadioLib and is unaffected.
V3.0.x re-engineered the SPI core for ESP32: gpio_get_level() with a timeout
instead of the unbounded digitalRead() wait, SPI.begin() once instead of per
transaction, and no digitalWrite() on peripheral-owned pins. It also moved
SPI.begin() out of getCC1101() and into Init(), so the connection probe has to
run after Init() rather than before it — reported in #2348.
Both changes are needed together: the reorder alone does not help on core 3.x,
and V3 cannot work without it.
Verified on the bench, core 3.3.9, esp32dev-pilight-cc1101:
N: C1101 SPI connection OK on attempt 1
N: C1101 tuned RX to 433.92 MHz
plus a live round trip — an arctech_switch frame published to MQTTtoPilight was
transmitted and decoded back by the board's own receiver on PilighttoMQTT.
Note for review: this bumps the pin for the ESP8266 CC1101 environments too.
They are not affected by the core 3.x breakage and V3 is untested there, so the
pin may be worth splitting per environment before this lands.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
checkForUpdates() reads an optional "envs" map from the update manifest and,
when it holds an entry for this build's ENV_NAME, offers that version instead
of the global latest_version. Boards with no entry are unaffected.
This lets the OTA server hold a specific product at an older release while the
rest of the fleet advances, with no per-board build flags. The resolved value
is republished as latest_version so the Home Assistant update entity compares
against it, and the map itself is dropped from the retained state topic to
keep it small.
Backward compatible both ways: old firmware ignores "envs", and a manifest
without "envs" behaves exactly as before.
OTA_JSON_URL is also guarded with #ifndef so a build environment can point the
update check at another OTA server. It was defined unconditionally, so a -D
build flag could not retarget it (the header definition always won). This is
needed to exercise the check against a test manifest, and is useful for
self-hosted OTA servers. No behaviour change without an override.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
handleWI() dispatches internally on the "scan" query arg, so the same
handler serves both the scan link and the save form. #2037 narrowed the
route to POST-only, which orphaned the scan hyperlink (a GET) in
config_WebContent.h. Re-register /wi for HTTP_GET alongside HTTP_POST;
the credential Save form stays method=post, so no secrets move to GET.
Co-authored-by: Florian <1technophile@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Serial.begin() was hardcoded to SERIAL_8N1 across all four UART init paths
(UART0/1/2/3), with no way to override it via a build flag -- unlike SERIALBaud,
which already is. Devices using non-8N1 framing (e.g. even-parity RS232 sources)
currently can't be read correctly at all, not just unreliably: a parity mismatch
misaligns the frame, not just adds occasional noise.
Adds SERIAL_CONFIG following the existing #ifndef/#define override pattern used
throughout this codebase, defaulting to SERIAL_8N1 so existing environments are
unaffected byte-for-byte. Verified: default (SERIAL_8N1) and an override
(SERIAL_CONFIG=SERIAL_8E1) both build successfully on ESP32.
Co-authored-by: simowilso <simowilso@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
setupWiFiManager() disables the config portal whenever /config.json exists
(a security measure to avoid exposing the config data + OTA page). But if the
saved WiFi credentials are lost while config.json remains -- e.g. the NVS
partition is erased ({"cmd":"erase"}, or corruption) -- the gateway can no
longer connect AND the portal is suppressed: autoConnect() fails immediately
and the failure path calls ESPRestart(3). The device then reboot-loops
forever, un-onboardable, its SoftAP flickering up ~1s per cycle.
Only suppress the portal when config.json exists AND saved STA credentials
are still present (WiFi.SSID() non-empty). With no credentials the portal
stays enabled, so the gateway raises a stable onboarding AP and can be
re-provisioned instead of reboot-looping. The original security intent is
preserved for provisioned gateways (which always have saved credentials).
Reproduced + verified on a Theengs Plug (esp32dev, min_spiffs): with NVS
erased and config.json present, the SoftAP is now stable and accepts
provisioning via the captive portal, rejoining the broker normally.
Claude-Session: https://claude.ai/code/session_01MbmZRv67fnNvzpyvqPi6vx
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Follow-up to #2321. That PR fixed five strcpy sites in XtoSYS/createOrUpdateDevice
but left adjacent attack surfaces using the same pattern (strncpy with the
destination's own size, leaving the buffer non-NUL-terminated when the source
is at the limit; or strcpy of WiFiManager/HTTP form values that bypass the
client-side maxlength). The over-long string then flows into saveConfig() and
back into a strcpy on next boot, corrupting heap/.bss on every reload.
main/main.cpp - XtoSYS: length-check before copying mqtt_topic, discovery_prefix,
gateway_name, gw_pass (ota_pass), and ble_aes. Reject over-length with
THEENGS_LOG_WARNING, preserving existing values.
main/main.cpp - loadConfigFromFlash: new loadStrField() helper applies the same
guard to every SPIFFS-loaded string (mqtt_server/port/user/pass per slot,
mqtt_topic, discovery_prefix, gateway_name, ota_pass, ble_aes). Defense in
depth in case a pre-fix build left an over-length value in /config.json.
main/main.cpp - WiFiManager save: bound mqtt_topic against the 150-byte portal
buffer overflowing the 66-byte destination (HTML maxlength is client-side
only; a raw POST bypasses it).
main/webUI.cpp - handleCG /gw save: bound gateway-password form input before
the strncpy that previously left ota_pass non-NUL-terminated.
main/blufi.cpp - ESP_BLUFI_EVENT_RECV_STA_SSID/PASSWD: clamp the wire-supplied
ssid_len/passwd_len before the memcpy and the trailing NUL so a crafted
BluFi provisioning frame cannot overrun gl_sta_ssid[32]/gl_sta_passwd[64].
main/gatewayBT.cpp - BLEDecryptor (Victron, encr==3): remove `nonce[16] = {0}`,
a one-byte stack-OOB write reachable from any BLE adv. The line was dead
(the memset two lines later zeroes the whole array); deleting it eliminates
the OOB write.
main/gatewaySRFB.cpp - SRFBtoX: bound the _uartbuf writer so a 433 MHz frame
with no STOP can no longer let _uartpos grow past sizeof(_uartbuf).
main/gatewaySRFB.cpp - _rfbSend: the hex output buffer was sized RF_MESSAGE_SIZE
(9) but _rawToHex writes 2*N+2 bytes per call. Resize to fit.
Tested on a Theengs Bridge: built theengs-bridge-v11 env, OTA-flashed, and
verified that publishing {"discovery_prefix":"Z"*200} to MQTTtoSYS/config
leaves discovery_prefix unchanged (no Z*65 truncation in subsequent discovery
topics), bridge keeps decoding BLE adverts, and SYStoMQTT/LWT stay healthy.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GPIO0 (D3) is the ESP8266 boot strapping pin: an RF receiver holding the
data line low at reset forces the board into flash mode, so a stock
nodemcuv2-rf build never starts after a restart with the receiver wired
to the documented default pin. Default to GPIO4 (D2) instead, which the
docs already recommend as the workaround. Environments that need other
pins (srfb-direct, sonoff-rfr3, rf-wifi-gateway) already override the
value explicitly and are unaffected.
Guard createOrUpdateDevice() in gatewayBT.cpp against null or >17-char MAC
strings before the strcpy into the 18-byte BLEdevice::macAdr buffer. An
MQTT client could previously publish a crafted white-list/black-list/id
field to MQTTtoBT/config (and related topics) and corrupt the heap.
Apply the same defense to the XtoSYS config branch in main.cpp: mqtt_user,
mqtt_pass, mqtt_server, and mqtt_port are now length-checked against their
destination buffer sizes before strcpy. The mqtt_port destination is only
6 bytes, so this path was particularly easy to overflow via MQTTtoSYS/config.
Oversized fields are now rejected with a warning log; existing values are
preserved and the device does not crash.
Co-authored-by: Florian <1technophile@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
- 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>
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>
* Refactor GitHub Actions workflows for build, documentation, and linting
- Consolidated build logic into reusable workflows (`task-build.yml` and `task-docs.yml`) to reduce duplication across multiple workflows.
- Introduced `environments.json` to centralize the list of PlatformIO build environments, improving maintainability and clarity.
- Updated `build.yml` and `build_and_docs_to_dev.yml` to utilize the new reusable workflows and environment definitions.
- Enhanced `release.yml` to streamline the release process and integrate documentation generation.
- Created reusable linting workflow (`task-lint.yml`) to standardize code formatting checks across the repository.
- Simplified manual documentation workflow by leveraging the new reusable documentation workflow.
- Improved artifact management and retention policies across workflows.
- Updated dependencies and versions in workflows to ensure compatibility and performance.
CI/CD pipeline agnostic of Workflow Engine and integrated on github actions
- Implemented ci.sh for orchestrating the complete build pipeline.
- Created ci_00_config.sh for centralized configuration of build scripts.
- Created ci_build_firmware.sh for building firmware for specified PlatformIO environments.
- Created ci_prepare_artifacts.sh for preparing firmware artifacts for upload or deployment.
- Created ci_set_version.sh for updating version tags in firmware configuration files.
- Created ci_build.sh to orchestrate the complete build pipeline.
- Created ci_qa.sh for code linting and formatting checks using clang-format.
- Created ci_site.sh for building and deploying VuePress documentation with version management.
- Implemented checks for required tools and dependencies in the new scripts.
- Improved internal scripts for better error handling and logging.
UPDATE the web installer manifest generation and update documentation structure
- Enhanced ci_list-env.sh to list environments from a JSON file.
- Replaced common_wu.py and gen_wu.py scripts with new npm scripts for site generation and previewing on docsgen/gen_wu.js
- Replaced generate_board_docs.py with docsgen/generated_board_docs.js
- Added new npm scripts for integration of site generation on build phase.
- Created preview_site.js to serve locally generated site over HTTPS with improved error handling.
- Added new CI environments for CI builds in environments.json.
- Deleted lint.yml as part of workflow cleanup.
- Enhanced task-build.yml to include linting as a job and added support for specifying PlatformIO version.
- Improved task-docs.yml to handle versioning more effectively and added clean option.
Enhance documentation
- ADD CLEAR Mark of development version of site
- Updated README.md to include detailed workflow dependencies and relationships using mermaid diagrams.
- Improved development.md with a quick checklist for contributors and clarified the code style guide.
- Enhanced quick_start.md with tips for contributors and streamlined the workflow explanation.
LINT FIX
- Refined User_config.h for better formatting consistency.
- Adjusted blufi.cpp and gatewayBT.cpp for improved code readability and consistency in formatting.
- Updated gatewaySERIAL.cpp and mqttDiscovery.cpp to enhance logging error messages.
- Improved sensorDS1820.cpp for better logging of device information.
Add security scan workflows for vulnerability detection
Add SBOM generation and upload to release workflow; update security scan summary handling
Add shellcheck suppor + FIX shellcheck warning
Enhance documentation for CI/CD scripts and workflows, adding details for security scanning and SBOM generation processes
Fix formatting and alignment in BLE connection handling
Reviewed the full web board presentation and the ESP32 web upload. The project uses a modern pattern where data is divided from the presentation layer.
- Removed the `generate_board_docs` script.
- Updated the `gen_wu` script in order to generate `boards-info.json`: the fail that containe all information about the configuration
- Created and isolate the file `boards-info.js` to streamline the parsing of PlatformIO dependencies, modules, environments and improve the handling of library information.
- Introduced vuepress component `BoardEnvironmentTable.vue` that render `boards-info.json` as UI card component
- Introduced vuepress component `FlashEnvironmentSelector.vue` that render a selectred environment from `boards-info.json` and provide esp-web-upload feature on it
- Introduced a new board page `board-selector.md` for improved firmware selection.
- Updated `web-install.md` to enhance the firmware upload process, including a new board environment table.
- Enhanced custom descriptions in `environments.ini` to include HTML links for better user guidance and board image link
Add CC1101 initialization improvements and logging enhancements
Add installation step for PlatformIO dependencies in documentation workflow
Remove ci_set_version.sh script and associated versioning functionality
* Fix comment provisined
Fix PlatformIO version input reference in documentation workflow
Remove outdated Squeezelite-ESP32 installer documentation
HASS_UNIT_PPM is defined in config_mqttDiscovery.h but was missing from
the availableHASSUnits validation array in mqttDiscovery.cpp. This caused
unit_of_measurement to be silently dropped for CO2 sensors (like the
Govee H5140) during MQTT discovery, resulting in Home Assistant warnings
about invalid units.
* 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>
- Introduced RFConfiguration class to encapsulate RF settings and operations.
- Replaced direct usage of RFConfig structure with iRFConfig instance across multiple files.
- Updated frequency handling in actuatorSomfy, gatewayPilight, gatewayRF, and gatewayRF2 to use iRFConfig.
- Modified webUI to interact with iRFConfig for RF settings management.
- Removed deprecated RFConfig structure and related functions.
- Enhanced JSON handling for RF configuration loading and saving.
- Improved logging for RF configuration operations.
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>
* Changing WebUI to include display device name, and change it to select drop down rather than checkbox
* Fix mqttDiscovery to require WebUI and ESP32 for displayDeviceName
* Fix mqttDiscovery to require WebUI and ESP32 and ESP8266 for displayDeviceName and ForceDeviceName
* Changing WebUI to include display device name, and change it to select drop down rather than checkbox
* Fixes for WebUI and BT for supporting custom setting Display name
* Fixes for WebUI and BT for supporting custom setting Display name
* Move DISPLAY_DEVICE_NAME to User_config.h
* Update docs to include change for Display temperature
* Update docs to include change for Display temperature
* Fix minor cosmetic bug where devices were not linking in HA to the gateway using via_device as it should be the gateway mac address not name
* Add support for decrypting BTHome v2 frames
* Add support for decrypting BTHome v2 frames
* Add support for decrypting BTHome v2 frames
* BTHome fix issue with theengs-plug
* BTHome fix issue with theengs-plug
* Adding support for all BLE encrypted methods, support in UI and gatewayBT for specific MACAddress AES Keys
* Fix lint
* Fix build issue with theengs-bridge-v11 and esp32dev-all-test and revert the documentation to Units of measurement displayed
* Revert docs
* Revert displayDeviceName and Units of measurement
* Revert displayDeviceName and Units of measurement
* Revert displayDeviceName and Units of measurement
* Revert minor typo
* Revert minor typo
* Revert minor typo
* Bug in Victron as nonce should be 16 bytes
* Shortened the client side javascript for BLE key validation that is commented out due to image constrains on theengs-bridge-v11
* blufi status
* Make the manufacturer ID configurable at build time.
* fix missing break.
* Update blufi gateway state when changed
---------
Co-authored-by: h2zero <powellperalata@gmail.com>