Compare commits
3 Commits
c55eea0e14
...
aa977e7c89
| Author | SHA1 | Date | |
|---|---|---|---|
|
aa977e7c89
|
|||
|
82621398b0
|
|||
|
547f9a42b4
|
+27
-21
@@ -8,37 +8,43 @@
|
|||||||
; Please visit documentation for the other options and examples
|
; Please visit documentation for the other options and examples
|
||||||
; https://docs.platformio.org/page/projectconf.html
|
; 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]
|
[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]
|
[env:esp32-s3-devkitc-1]
|
||||||
platform = espressif32
|
platform = ${common.platform_esp32}
|
||||||
board = esp32-s3-devkitc-1
|
board = esp32-s3-devkitc-1
|
||||||
framework = arduino
|
|
||||||
monitor_speed = 115200
|
|
||||||
board_build.filesystem = littlefs
|
board_build.filesystem = littlefs
|
||||||
lib_deps =
|
|
||||||
tzapu/WiFiManager@^2.0.17
|
|
||||||
bblanchon/ArduinoJson@^7.0.0
|
|
||||||
knolleary/PubSubClient@^2.8
|
|
||||||
|
|
||||||
[env:esp32dev]
|
[env:esp32dev]
|
||||||
|
platform = ${common.platform_esp32}
|
||||||
board = esp32dev
|
board = esp32dev
|
||||||
framework = arduino
|
|
||||||
platform = espressif32
|
|
||||||
monitor_speed = 115200
|
|
||||||
board_build.filesystem = littlefs
|
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]
|
[env:esp32-C3-devkitm-1]
|
||||||
|
platform = ${common.platform_esp32}
|
||||||
board = esp32-c3-devkitm-1
|
board = esp32-c3-devkitm-1
|
||||||
framework = arduino
|
|
||||||
platform = espressif32
|
|
||||||
monitor_speed = 115200
|
|
||||||
board_build.filesystem = littlefs
|
board_build.filesystem = littlefs
|
||||||
lib_deps =
|
|
||||||
tzapu/WiFiManager@^2.0.17
|
[env:esp32-c6-devkitc-1]
|
||||||
bblanchon/ArduinoJson@^7.4.3
|
platform = ${common.platform_esp32}
|
||||||
knolleary/PubSubClient@^2.8
|
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
|
||||||
|
|||||||
+45
-12
@@ -152,7 +152,7 @@ static void attachMeters() {
|
|||||||
|
|
||||||
// Release the channel if it's no longer used or its pin changed.
|
// Release the channel if it's no longer used or its pin changed.
|
||||||
if (attachedPin[i] != -1 && attachedPin[i] != pin) {
|
if (attachedPin[i] != -1 && attachedPin[i] != pin) {
|
||||||
ledcDetachPin(attachedPin[i]);
|
ledcDetach(attachedPin[i]);
|
||||||
pinMode(attachedPin[i], OUTPUT);
|
pinMode(attachedPin[i], OUTPUT);
|
||||||
digitalWrite(attachedPin[i], LOW); // drive freed pin low so the meter reads zero
|
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]);
|
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) {
|
if (pin > 0 && attachedPin[i] != pin) {
|
||||||
ledcSetup(i, PWM_FREQ, PWM_RES);
|
ledcAttach(pin, PWM_FREQ, PWM_RES);
|
||||||
ledcAttachPin(pin, i);
|
|
||||||
attachedPin[i] = pin;
|
attachedPin[i] = pin;
|
||||||
Serial.printf("[PWM] attach ch%d pin%d\n", 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;
|
if (meters[i].pin <= 0 || meters[i].maxDuty <= 0) continue;
|
||||||
float pct = meters[i].currentValue / 100.0f * meters[i].maxDuty / 100.0f;
|
float pct = meters[i].currentValue / 100.0f * meters[i].maxDuty / 100.0f;
|
||||||
int duty = constrain((int)(pct * 1023), 0, 1023);
|
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);
|
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);
|
meters[idx].currentValue = physicalToPct(val, idx);
|
||||||
if (meters[idx].pin > 0 && meters[idx].maxDuty > 0) {
|
if (meters[idx].pin > 0 && meters[idx].maxDuty > 0) {
|
||||||
float pct = meters[idx].currentValue / 100.0f * meters[idx].maxDuty / 100.0f;
|
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);
|
mqttPublishCurrent(idx);
|
||||||
}
|
}
|
||||||
@@ -432,6 +431,15 @@ static void handleRoot() {
|
|||||||
if (!requireAuth()) { Serial.println("[HTTP] GET / -> 401"); return; }
|
if (!requireAuth()) { Serial.println("[HTTP] GET / -> 401"); return; }
|
||||||
Serial.println("[HTTP] GET /");
|
Serial.println("[HTTP] GET /");
|
||||||
String meterRows;
|
String meterRows;
|
||||||
|
bool useTabs = meterCount > 1;
|
||||||
|
if (useTabs) {
|
||||||
|
meterRows += "<div class=tabs><div class=tab-nav>";
|
||||||
|
for (int i = 0; i < meterCount; i++) {
|
||||||
|
String label = strlen(meters[i].name) > 0 ? escHtml(meters[i].name) : "Meter " + String(i);
|
||||||
|
meterRows += "<button type=button class=tab-btn" + String(i == 0 ? " active" : "") + " data-tab=" + String(i) + ">" + label + "</button>";
|
||||||
|
}
|
||||||
|
meterRows += "</div>";
|
||||||
|
}
|
||||||
for (int i = 0; i < meterCount; i++) {
|
for (int i = 0; i < meterCount; i++) {
|
||||||
char maxStr[8], curStr[8];
|
char maxStr[8], curStr[8];
|
||||||
dtostrf(meters[i].maxDuty, 1, 1, maxStr);
|
dtostrf(meters[i].maxDuty, 1, 1, maxStr);
|
||||||
@@ -443,7 +451,9 @@ static void handleRoot() {
|
|||||||
dtostrf(meters[i].rangeMin, 1, 1, rMinStr);
|
dtostrf(meters[i].rangeMin, 1, 1, rMinStr);
|
||||||
dtostrf(meters[i].rangeMax, 1, 1, rMaxStr);
|
dtostrf(meters[i].rangeMax, 1, 1, rMaxStr);
|
||||||
|
|
||||||
meterRows += "<fieldset><legend>Meter " + String(i) + "</legend>";
|
if (useTabs) meterRows += "<div class=tab-panel" + String(i == 0 ? " active" : "") + " data-tab=" + String(i) + ">";
|
||||||
|
meterRows += "<fieldset>";
|
||||||
|
if (!useTabs) meterRows += "<legend>Meter " + String(i) + "</legend>";
|
||||||
meterRows += "<div class=row><label>Pin</label><input name=m" + String(i) + "_pin type=number min=0 max=99 value=" + String(meters[i].pin) + "></div>";
|
meterRows += "<div class=row><label>Pin</label><input name=m" + String(i) + "_pin type=number min=0 max=99 value=" + String(meters[i].pin) + "></div>";
|
||||||
meterRows += "<div class=row><label>Name</label><input name=m" + String(i) + "_name value='" + nameVal + "'></div>";
|
meterRows += "<div class=row><label>Name</label><input name=m" + String(i) + "_name value='" + nameVal + "'></div>";
|
||||||
meterRows += "<div class=row><label>Unit</label><input name=m" + String(i) + "_unit value='" + unitVal + "'></div>";
|
meterRows += "<div class=row><label>Unit</label><input name=m" + String(i) + "_unit value='" + unitVal + "'></div>";
|
||||||
@@ -455,7 +465,9 @@ static void handleRoot() {
|
|||||||
meterRows += "<input name=m" + String(i) + "_cur type=range min=0 max=100 step=0.1 value=" + String(curStr) + ">";
|
meterRows += "<input name=m" + String(i) + "_cur type=range min=0 max=100 step=0.1 value=" + String(curStr) + ">";
|
||||||
meterRows += "<span class=val id=m" + String(i) + ">" + String(curStr) + "%</span>";
|
meterRows += "<span class=val id=m" + String(i) + ">" + String(curStr) + "%</span>";
|
||||||
meterRows += "</div></fieldset>";
|
meterRows += "</div></fieldset>";
|
||||||
|
if (useTabs) meterRows += "</div>";
|
||||||
}
|
}
|
||||||
|
if (useTabs) meterRows += "</div>";
|
||||||
|
|
||||||
String countOpts;
|
String countOpts;
|
||||||
for (int i = 1; i <= MAX_METERS; i++) {
|
for (int i = 1; i <= MAX_METERS; i++) {
|
||||||
@@ -536,7 +548,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 += ".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 += ".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 += ".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 += "</style></head><body><div class=card>";
|
html += "</style></head><body><div class=card>";
|
||||||
html += "<div class=hdr><img src=/logo.png alt=logo><div><h1>M1730</h1><div class=sub>Configuration</div></div></div><div class=info>";
|
html += "<div class=hdr><img src=/logo.png alt=logo><div><h1>M1730</h1><div class=sub>Configuration</div></div></div><div class=info>";
|
||||||
html += "<div><span>Hostname</span><span>" + String(hostname) + "</span></div>";
|
html += "<div><span>Hostname</span><span>" + String(hostname) + "</span></div>";
|
||||||
@@ -561,6 +577,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 += "x.open('GET','/set?i='+this.name.match(/\\d+/)[0]+'&v='+this.value,true);x.send();";
|
||||||
html += "});}});";
|
html += "});}});";
|
||||||
html += "document.querySelector('select[name=meter_count]').addEventListener('change',function(){this.form.submit();});";
|
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 += "</script></body></html>";
|
html += "</script></body></html>";
|
||||||
server.send(200, "text/html", html);
|
server.send(200, "text/html", html);
|
||||||
Serial.printf("[HTTP] GET / -> 200 (%u bytes)\n", html.length());
|
Serial.printf("[HTTP] GET / -> 200 (%u bytes)\n", html.length());
|
||||||
@@ -666,7 +685,7 @@ static void handleSet() {
|
|||||||
if (meters[idx].pin > 0 && meters[idx].maxDuty > 0) {
|
if (meters[idx].pin > 0 && meters[idx].maxDuty > 0) {
|
||||||
float pct = val / 100.0f * meters[idx].maxDuty / 100.0f;
|
float pct = val / 100.0f * meters[idx].maxDuty / 100.0f;
|
||||||
int duty = constrain((int)(pct * 1023), 0, 1023);
|
int duty = constrain((int)(pct * 1023), 0, 1023);
|
||||||
ledcWrite(idx, duty);
|
ledcWrite(meters[idx].pin, duty);
|
||||||
}
|
}
|
||||||
mqttPublishCurrent(idx);
|
mqttPublishCurrent(idx);
|
||||||
server.send(200);
|
server.send(200);
|
||||||
@@ -743,8 +762,7 @@ static void factoryReset(bool clearWifi) {
|
|||||||
// Attach meters temporarily for the sweep acknowledgement
|
// Attach meters temporarily for the sweep acknowledgement
|
||||||
for (int i = 0; i < count && i < 8; i++) {
|
for (int i = 0; i < count && i < 8; i++) {
|
||||||
if (meters[i].pin > 0) {
|
if (meters[i].pin > 0) {
|
||||||
ledcSetup(i, PWM_FREQ, PWM_RES);
|
ledcAttach(meters[i].pin, PWM_FREQ, PWM_RES);
|
||||||
ledcAttachPin(meters[i].pin, i);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -756,7 +774,7 @@ static void factoryReset(bool clearWifi) {
|
|||||||
for (int i = 0; i < count && i < 8; i++) {
|
for (int i = 0; i < count && i < 8; i++) {
|
||||||
if (meters[i].pin > 0 && meters[i].maxDuty > 0) {
|
if (meters[i].pin > 0 && meters[i].maxDuty > 0) {
|
||||||
float duty = pct / 100.0f * meters[i].maxDuty / 100.0f;
|
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);
|
delay(50);
|
||||||
@@ -796,6 +814,11 @@ static void applyFactoryResetIfNeeded(int count) {
|
|||||||
void setup() {
|
void setup() {
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
Serial.println("\n\n=== M1730 boot ===");
|
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)) {
|
if (!LittleFS.begin(false)) {
|
||||||
Serial.println("[FS] mount failed, formatting...");
|
Serial.println("[FS] mount failed, formatting...");
|
||||||
LittleFS.begin(true);
|
LittleFS.begin(true);
|
||||||
@@ -809,6 +832,14 @@ void setup() {
|
|||||||
loadConfig();
|
loadConfig();
|
||||||
hostnameParam.setValue(hostname, sizeof(hostname) - 1);
|
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();
|
int resetCount = incrementResetCount();
|
||||||
|
|
||||||
attachMeters();
|
attachMeters();
|
||||||
@@ -834,7 +865,8 @@ void setup() {
|
|||||||
Serial.println("[WIFI] failed, restarting");
|
Serial.println("[WIFI] failed, restarting");
|
||||||
ESP.restart();
|
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));
|
strlcpy(hostname, hostnameParam.getValue(), sizeof(hostname));
|
||||||
saveConfig();
|
saveConfig();
|
||||||
@@ -844,6 +876,7 @@ void setup() {
|
|||||||
|
|
||||||
Serial.printf("[BOOT] ready at http://%s.local or http://%s\n",
|
Serial.printf("[BOOT] ready at http://%s.local or http://%s\n",
|
||||||
hostname, WiFi.localIP().toString().c_str());
|
hostname, WiFi.localIP().toString().c_str());
|
||||||
|
Serial.printf("[BOOT] free heap=%u\n", ESP.getFreeHeap());
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
|
|||||||
Reference in New Issue
Block a user