From 547f9a42b4d4fea3c3b3a33e8586b315f27f9b1c Mon Sep 17 00:00:00 2001 From: "Adrian A. Baumann" Date: Sat, 4 Jul 2026 20:35:28 +0200 Subject: [PATCH 1/3] Display multiple meters in tabbed interface --- src/main.cpp | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 15e8db2..f01adc2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -432,6 +432,15 @@ static void handleRoot() { if (!requireAuth()) { Serial.println("[HTTP] GET / -> 401"); return; } Serial.println("[HTTP] GET /"); String meterRows; + bool useTabs = meterCount > 1; + if (useTabs) { + meterRows += "
"; + for (int i = 0; i < meterCount; i++) { + String label = strlen(meters[i].name) > 0 ? escHtml(meters[i].name) : "Meter " + String(i); + meterRows += ""; + } + meterRows += "
"; + } for (int i = 0; i < meterCount; i++) { char maxStr[8], curStr[8]; dtostrf(meters[i].maxDuty, 1, 1, maxStr); @@ -443,7 +452,9 @@ static void handleRoot() { dtostrf(meters[i].rangeMin, 1, 1, rMinStr); dtostrf(meters[i].rangeMax, 1, 1, rMaxStr); - meterRows += "
Meter " + String(i) + ""; + if (useTabs) meterRows += "
"; + meterRows += "
"; + if (!useTabs) meterRows += "Meter " + String(i) + ""; meterRows += "
"; meterRows += "
"; meterRows += "
"; @@ -455,7 +466,9 @@ static void handleRoot() { meterRows += ""; meterRows += "" + String(curStr) + "%"; meterRows += "
"; + if (useTabs) meterRows += "
"; } + if (useTabs) meterRows += ""; String countOpts; for (int i = 1; i <= MAX_METERS; i++) { @@ -536,7 +549,11 @@ static void handleRoot() { html += ".tgl-lbl{margin-bottom:0!important;line-height:18px}.switch{position:relative;display:inline-block;width:32px;height:18px;margin-left:auto;flex-shrink:0}fieldset .row label.switch{width:32px}"; html += ".switch input{opacity:0;width:0;height:0}.slid{position:absolute;cursor:pointer;top:0;left:0;right:0;bottom:0;background:#ccc;border-radius:18px;transition:.3s}"; html += ".slid::before{content:\"\";position:absolute;height:14px;width:14px;left:2px;top:2px;background:#fff;border-radius:50%;transition:.3s;box-shadow:0 1px 3px rgba(0,0,0,.2)}"; -html += ".switch input:checked+.slid{background:#4361ee}.switch input:checked+.slid::before{transform:translateX(14px)}"; + html += ".switch input:checked+.slid{background:#4361ee}.switch input:checked+.slid::before{transform:translateX(14px)}"; + html += ".tab-nav{display:flex;gap:4px;margin-bottom:16px;flex-wrap:wrap}"; + html += ".tab-btn{padding:8px 16px;border:1px solid #d1d5db;background:#f8f9fc;border-radius:8px 8px 0 0;cursor:pointer;font-size:13px;font-weight:600;color:#666;transition:all .2s;font-family:inherit}"; + html += ".tab-btn:hover{background:#e8eaf0}.tab-btn.active{background:#fff;border-color:#4361ee;color:#4361ee;border-bottom-color:#fff}"; + html += ".tab-panel{display:none}.tab-panel.active{display:block}"; html += "
"; html += "
logo

M1730

Configuration
"; html += "
Hostname" + String(hostname) + "
"; @@ -561,6 +578,9 @@ html += ".switch input:checked+.slid{background:#4361ee}.switch input:checked+.s html += "x.open('GET','/set?i='+this.name.match(/\\d+/)[0]+'&v='+this.value,true);x.send();"; html += "});}});"; html += "document.querySelector('select[name=meter_count]').addEventListener('change',function(){this.form.submit();});"; + if (meterCount > 1) { + html += "document.querySelectorAll('.tab-btn').forEach(function(b){b.addEventListener('click',function(){var t=this.dataset.tab;document.querySelectorAll('.tab-btn').forEach(function(x){x.classList.remove('active')});document.querySelectorAll('.tab-panel').forEach(function(x){x.classList.remove('active')});this.classList.add('active');document.querySelector('.tab-panel[data-tab=\"'+t+'\"]').classList.add('active')})});"; + } html += ""; server.send(200, "text/html", html); Serial.printf("[HTTP] GET / -> 200 (%u bytes)\n", html.length()); From 82621398b01751328fdcb4ad725497c6d20ed2a4 Mon Sep 17 00:00:00 2001 From: "Adrian A. Baumann" Date: Sat, 4 Jul 2026 22:23:42 +0200 Subject: [PATCH 2/3] More debugging --- src/main.cpp | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index f01adc2..65cd03c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -437,7 +437,7 @@ static void handleRoot() { meterRows += "
"; for (int i = 0; i < meterCount; i++) { String label = strlen(meters[i].name) > 0 ? escHtml(meters[i].name) : "Meter " + String(i); - meterRows += ""; + meterRows += ""; } meterRows += "
"; } @@ -816,6 +816,11 @@ static void applyFactoryResetIfNeeded(int count) { void setup() { Serial.begin(115200); Serial.println("\n\n=== M1730 boot ==="); + Serial.printf("[BOOT] chip=%s rev=%d cores=%d cpu=%dMHz flash=%uMB\n", + ESP.getChipModel(), ESP.getChipRevision(), ESP.getChipCores(), + ESP.getCpuFreqMHz(), ESP.getFlashChipSize() / (1024 * 1024)); + Serial.printf("[BOOT] free heap=%u sketch=%u / %u\n", + ESP.getFreeHeap(), ESP.getSketchSize(), ESP.getFreeSketchSpace()); if (!LittleFS.begin(false)) { Serial.println("[FS] mount failed, formatting..."); LittleFS.begin(true); @@ -829,6 +834,14 @@ void setup() { loadConfig(); hostnameParam.setValue(hostname, sizeof(hostname) - 1); + for (int i = 0; i < meterCount; i++) + Serial.printf("[BOOT] meter[%d] pin=%d name=\"%s\" unit=\"%s\" maxD=%.1f range=[%.1f,%.1f] cur=%.1f\n", + i, meters[i].pin, meters[i].name, meters[i].unit, + meters[i].maxDuty, meters[i].rangeMin, meters[i].rangeMax, meters[i].currentValue); + Serial.printf("[BOOT] mqtt=%d broker=%s:%d prefix=%s\n", + mqttCfg.enabled, mqttCfg.host, mqttCfg.port, mqttCfg.prefix); + Serial.printf("[BOOT] auth=%d\n", authCfg.enabled); + int resetCount = incrementResetCount(); attachMeters(); @@ -854,7 +867,8 @@ void setup() { Serial.println("[WIFI] failed, restarting"); ESP.restart(); } - Serial.printf("[WIFI] connected, IP: %s\n", WiFi.localIP().toString().c_str()); + Serial.printf("[WIFI] connected, IP: %s MAC: %s RSSI: %d\n", + WiFi.localIP().toString().c_str(), WiFi.macAddress().c_str(), WiFi.RSSI()); strlcpy(hostname, hostnameParam.getValue(), sizeof(hostname)); saveConfig(); @@ -864,6 +878,7 @@ void setup() { Serial.printf("[BOOT] ready at http://%s.local or http://%s\n", hostname, WiFi.localIP().toString().c_str()); + Serial.printf("[BOOT] free heap=%u\n", ESP.getFreeHeap()); } void loop() { From aa977e7c89a91cf6859517e0c433ef75b1eda300 Mon Sep 17 00:00:00 2001 From: "Adrian A. Baumann" Date: Sun, 5 Jul 2026 00:24:11 +0200 Subject: [PATCH 3/3] Migrate all ESP32 envs to Arduino-ESP32 3.x (pioarduino) The registry espressif32 platform still ships Arduino core 2.0.17, which lacks ESP32-C6 support. Switch every ESP32 env to the pioarduino fork (Arduino-ESP32 3.x) via a shared ${common.platform_esp32} variable and consolidate shared settings into [env]. Rewrite the PWM code for the 3.x LEDC API: - ledcSetup + ledcAttachPin -> ledcAttach(pin, freq, res) - ledcDetachPin -> ledcDetach - ledcWrite now addresses PWM by pin instead of channel index Adds the esp32-c6-devkitc-1 env; all four ESP32 envs build clean. Co-Authored-By: Claude Opus 4.8 --- platformio.ini | 48 +++++++++++++++++++++++++++--------------------- src/main.cpp | 16 +++++++--------- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/platformio.ini b/platformio.ini index 3b247a9..a5e5e6f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -8,37 +8,43 @@ ; Please visit documentation for the other options and examples ; https://docs.platformio.org/page/projectconf.html +[common] +; All ESP32 targets use the pioarduino fork, which ships Arduino-ESP32 3.x +; (the registry espressif32 platform is still on 2.0.17 and lacks C6 support). +platform_esp32 = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.39/platform-espressif32.zip + [env] +framework = arduino +monitor_speed = 115200 +lib_deps = + tzapu/WiFiManager@^2.0.17 + bblanchon/ArduinoJson@^7.4.3 + knolleary/PubSubClient@^2.8 [env:esp32-s3-devkitc-1] -platform = espressif32 +platform = ${common.platform_esp32} board = esp32-s3-devkitc-1 -framework = arduino -monitor_speed = 115200 board_build.filesystem = littlefs -lib_deps = - tzapu/WiFiManager@^2.0.17 - bblanchon/ArduinoJson@^7.0.0 - knolleary/PubSubClient@^2.8 [env:esp32dev] +platform = ${common.platform_esp32} board = esp32dev -framework = arduino -platform = espressif32 -monitor_speed = 115200 board_build.filesystem = littlefs -lib_deps = - tzapu/WiFiManager@^2.0.17 - bblanchon/ArduinoJson@^7.4.3 - knolleary/PubSubClient@^2.8 [env:esp32-C3-devkitm-1] +platform = ${common.platform_esp32} board = esp32-c3-devkitm-1 -framework = arduino -platform = espressif32 -monitor_speed = 115200 board_build.filesystem = littlefs -lib_deps = - tzapu/WiFiManager@^2.0.17 - bblanchon/ArduinoJson@^7.4.3 - knolleary/PubSubClient@^2.8 \ No newline at end of file + +[env:esp32-c6-devkitc-1] +platform = ${common.platform_esp32} +board = esp32-c6-devkitc-1 +board_build.filesystem = littlefs + +[env:rpipico2w] +; RP2350 support requires the maxgerhardt fork of the raspberrypi platform +; and the earlephilhower arduino-pico core. +platform = https://github.com/maxgerhardt/platform-raspberrypi.git +board = rpipico2w +board_build.core = earlephilhower +board_build.filesystem_size = 0.5m diff --git a/src/main.cpp b/src/main.cpp index 65cd03c..26054c5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -152,7 +152,7 @@ static void attachMeters() { // Release the channel if it's no longer used or its pin changed. if (attachedPin[i] != -1 && attachedPin[i] != pin) { - ledcDetachPin(attachedPin[i]); + ledcDetach(attachedPin[i]); pinMode(attachedPin[i], OUTPUT); digitalWrite(attachedPin[i], LOW); // drive freed pin low so the meter reads zero Serial.printf("[PWM] detach ch%d pin%d\n", i, attachedPin[i]); @@ -160,8 +160,7 @@ static void attachMeters() { } if (pin > 0 && attachedPin[i] != pin) { - ledcSetup(i, PWM_FREQ, PWM_RES); - ledcAttachPin(pin, i); + ledcAttach(pin, PWM_FREQ, PWM_RES); attachedPin[i] = pin; Serial.printf("[PWM] attach ch%d pin%d\n", i, pin); } @@ -173,7 +172,7 @@ static void applyMeters() { if (meters[i].pin <= 0 || meters[i].maxDuty <= 0) continue; float pct = meters[i].currentValue / 100.0f * meters[i].maxDuty / 100.0f; int duty = constrain((int)(pct * 1023), 0, 1023); - ledcWrite(i, duty); + ledcWrite(meters[i].pin, duty); Serial.printf("[PWM] ch%d duty=%d (cur=%.1f maxD=%.1f)\n", i, duty, meters[i].currentValue, meters[i].maxDuty); } } @@ -222,7 +221,7 @@ static void mqttCallback(char* topic, byte* payload, unsigned int len) { meters[idx].currentValue = physicalToPct(val, idx); if (meters[idx].pin > 0 && meters[idx].maxDuty > 0) { float pct = meters[idx].currentValue / 100.0f * meters[idx].maxDuty / 100.0f; - ledcWrite(idx, constrain((int)(pct * 1023), 0, 1023)); + ledcWrite(meters[idx].pin, constrain((int)(pct * 1023), 0, 1023)); } mqttPublishCurrent(idx); } @@ -686,7 +685,7 @@ static void handleSet() { if (meters[idx].pin > 0 && meters[idx].maxDuty > 0) { float pct = val / 100.0f * meters[idx].maxDuty / 100.0f; int duty = constrain((int)(pct * 1023), 0, 1023); - ledcWrite(idx, duty); + ledcWrite(meters[idx].pin, duty); } mqttPublishCurrent(idx); server.send(200); @@ -763,8 +762,7 @@ static void factoryReset(bool clearWifi) { // Attach meters temporarily for the sweep acknowledgement for (int i = 0; i < count && i < 8; i++) { if (meters[i].pin > 0) { - ledcSetup(i, PWM_FREQ, PWM_RES); - ledcAttachPin(meters[i].pin, i); + ledcAttach(meters[i].pin, PWM_FREQ, PWM_RES); } } @@ -776,7 +774,7 @@ static void factoryReset(bool clearWifi) { for (int i = 0; i < count && i < 8; i++) { if (meters[i].pin > 0 && meters[i].maxDuty > 0) { float duty = pct / 100.0f * meters[i].maxDuty / 100.0f; - ledcWrite(i, constrain((int)(duty * 1023), 0, 1023)); + ledcWrite(meters[i].pin, constrain((int)(duty * 1023), 0, 1023)); } } delay(50);