MQTT - not working properly yet
This commit is contained in:
@@ -16,3 +16,4 @@ monitor_speed = 115200
|
|||||||
lib_deps =
|
lib_deps =
|
||||||
tzapu/WiFiManager@^2.0.17
|
tzapu/WiFiManager@^2.0.17
|
||||||
bblanchon/ArduinoJson@^7.0.0
|
bblanchon/ArduinoJson@^7.0.0
|
||||||
|
knolleary/PubSubClient@^2.8
|
||||||
|
|||||||
335
src/main.cpp
335
src/main.cpp
@@ -4,6 +4,8 @@
|
|||||||
#include <LittleFS.h>
|
#include <LittleFS.h>
|
||||||
#include <WebServer.h>
|
#include <WebServer.h>
|
||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
|
#include <WiFiClient.h>
|
||||||
|
#include <PubSubClient.h>
|
||||||
|
|
||||||
#define HOSTNAME_DEFAULT "m1730"
|
#define HOSTNAME_DEFAULT "m1730"
|
||||||
#define MAX_METERS 10
|
#define MAX_METERS 10
|
||||||
@@ -16,11 +18,24 @@ struct MeterConfig {
|
|||||||
float currentValue;
|
float currentValue;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct MqttConfig {
|
||||||
|
bool enabled = false;
|
||||||
|
char host[64] = "";
|
||||||
|
uint16_t port = 1883;
|
||||||
|
char user[32] = "";
|
||||||
|
char pass[32] = "";
|
||||||
|
char prefix[32] = "m1730";
|
||||||
|
};
|
||||||
|
|
||||||
static char hostname[32] = HOSTNAME_DEFAULT;
|
static char hostname[32] = HOSTNAME_DEFAULT;
|
||||||
static int meterCount = 0;
|
static int meterCount = 0;
|
||||||
static MeterConfig meters[MAX_METERS] = {};
|
static MeterConfig meters[MAX_METERS] = {};
|
||||||
|
static MqttConfig mqttCfg;
|
||||||
static WiFiManagerParameter hostnameParam("hostname", "Device hostname", hostname, 32);
|
static WiFiManagerParameter hostnameParam("hostname", "Device hostname", hostname, 32);
|
||||||
static WebServer server(80);
|
static WebServer server(80);
|
||||||
|
static WiFiClient wifiClient;
|
||||||
|
static PubSubClient mqttClient(wifiClient);
|
||||||
|
static unsigned long mqttReconnectAt = 0;
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Config persistence (JSON via LittleFS)
|
// Config persistence (JSON via LittleFS)
|
||||||
@@ -39,12 +54,22 @@ static void loadConfig() {
|
|||||||
if (doc["hostname"].is<const char*>())
|
if (doc["hostname"].is<const char*>())
|
||||||
strlcpy(hostname, doc["hostname"], sizeof(hostname));
|
strlcpy(hostname, doc["hostname"], sizeof(hostname));
|
||||||
|
|
||||||
|
JsonObject mq = doc["mqtt"];
|
||||||
|
if (!mq.isNull()) {
|
||||||
|
mqttCfg.enabled = mq["en"] | false;
|
||||||
|
strlcpy(mqttCfg.host, mq["host"] | "", sizeof(mqttCfg.host));
|
||||||
|
mqttCfg.port = mq["port"] | 1883;
|
||||||
|
strlcpy(mqttCfg.user, mq["user"] | "", sizeof(mqttCfg.user));
|
||||||
|
strlcpy(mqttCfg.pass, mq["pass"] | "", sizeof(mqttCfg.pass));
|
||||||
|
strlcpy(mqttCfg.prefix, mq["prefix"] | "m1730", sizeof(mqttCfg.prefix));
|
||||||
|
}
|
||||||
|
|
||||||
JsonArray arr = doc["meters"].as<JsonArray>();
|
JsonArray arr = doc["meters"].as<JsonArray>();
|
||||||
meterCount = min((int)arr.size(), MAX_METERS);
|
meterCount = min((int)arr.size(), MAX_METERS);
|
||||||
for (int i = 0; i < meterCount; i++) {
|
for (int i = 0; i < meterCount; i++) {
|
||||||
JsonObject m = arr[i];
|
JsonObject m = arr[i];
|
||||||
meters[i].pin = m["pin"] | 0;
|
meters[i].pin = m["pin"] | 0;
|
||||||
meters[i].maxDuty = m["maxD"] | 0.0f;
|
meters[i].maxDuty = m["maxD"] | 0.0f;
|
||||||
meters[i].currentValue = m["current"] | 0.0f;
|
meters[i].currentValue = m["current"] | 0.0f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -53,6 +78,14 @@ static void saveConfig() {
|
|||||||
JsonDocument doc;
|
JsonDocument doc;
|
||||||
doc["hostname"] = hostname;
|
doc["hostname"] = hostname;
|
||||||
|
|
||||||
|
JsonObject mq = doc["mqtt"].to<JsonObject>();
|
||||||
|
mq["en"] = mqttCfg.enabled;
|
||||||
|
mq["host"] = mqttCfg.host;
|
||||||
|
mq["port"] = mqttCfg.port;
|
||||||
|
mq["user"] = mqttCfg.user;
|
||||||
|
mq["pass"] = mqttCfg.pass;
|
||||||
|
mq["prefix"] = mqttCfg.prefix;
|
||||||
|
|
||||||
JsonArray arr = doc["meters"].to<JsonArray>();
|
JsonArray arr = doc["meters"].to<JsonArray>();
|
||||||
for (int i = 0; i < meterCount; i++) {
|
for (int i = 0; i < meterCount; i++) {
|
||||||
JsonObject m = arr.add<JsonObject>();
|
JsonObject m = arr.add<JsonObject>();
|
||||||
@@ -90,6 +123,109 @@ static void applyMeters() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// MQTT
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
static void mqttCallback(char* topic, byte* payload, unsigned int len) {
|
||||||
|
String valStr;
|
||||||
|
for (unsigned i = 0; i < len; i++) valStr += (char)payload[i];
|
||||||
|
float val = valStr.toFloat();
|
||||||
|
|
||||||
|
// topic format: <prefix>/meter/<idx>/current/set or .../maxduty/set
|
||||||
|
String t = String(topic);
|
||||||
|
String pref = String(mqttCfg.prefix) + "/meter/";
|
||||||
|
if (!t.startsWith(pref)) return;
|
||||||
|
t = t.substring(pref.length());
|
||||||
|
|
||||||
|
int slash = t.indexOf('/');
|
||||||
|
if (slash < 0) return;
|
||||||
|
int idx = t.substring(0, slash).toInt();
|
||||||
|
if (idx < 0 || idx >= meterCount) return;
|
||||||
|
|
||||||
|
String suffix = t.substring(slash);
|
||||||
|
|
||||||
|
if (suffix == "/current/set") {
|
||||||
|
meters[idx].currentValue = val;
|
||||||
|
if (meters[idx].pin > 0 && meters[idx].maxDuty > 0) {
|
||||||
|
float pct = val / 100.0f * meters[idx].maxDuty / 100.0f;
|
||||||
|
ledcWrite(idx, constrain((int)(pct * 1023), 0, 1023));
|
||||||
|
}
|
||||||
|
saveConfig();
|
||||||
|
} else if (suffix == "/maxduty/set") {
|
||||||
|
meters[idx].maxDuty = constrain(val, 0, 100);
|
||||||
|
attachMeters();
|
||||||
|
applyMeters();
|
||||||
|
saveConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mqttPublishCurrent(int idx) {
|
||||||
|
if (!mqttCfg.enabled || !mqttClient.connected()) return;
|
||||||
|
char topic[128], val[16];
|
||||||
|
snprintf(topic, sizeof(topic), "%s/meter/%d/current", mqttCfg.prefix, idx);
|
||||||
|
snprintf(val, sizeof(val), "%.1f", meters[idx].currentValue);
|
||||||
|
mqttClient.publish(topic, val, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mqttSubscribe() {
|
||||||
|
if (!mqttCfg.enabled) return;
|
||||||
|
for (int i = 0; i < meterCount; i++) {
|
||||||
|
char t[128];
|
||||||
|
snprintf(t, sizeof(t), "%s/meter/%d/current/set", mqttCfg.prefix, i);
|
||||||
|
mqttClient.subscribe(t);
|
||||||
|
snprintf(t, sizeof(t), "%s/meter/%d/maxduty/set", mqttCfg.prefix, i);
|
||||||
|
mqttClient.subscribe(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool mqttConnect() {
|
||||||
|
if (!mqttCfg.enabled || strlen(mqttCfg.host) == 0 || strlen(mqttCfg.user) == 0 || strlen(mqttCfg.pass) == 0) return false;
|
||||||
|
|
||||||
|
char clientId[32];
|
||||||
|
{
|
||||||
|
uint8_t mac[6];
|
||||||
|
WiFi.macAddress(mac);
|
||||||
|
snprintf(clientId, sizeof(clientId), "m1730-%02x%02x%02x", mac[3], mac[4], mac[5]);
|
||||||
|
}
|
||||||
|
|
||||||
|
mqttClient.setServer(mqttCfg.host, mqttCfg.port);
|
||||||
|
mqttClient.setCallback(mqttCallback);
|
||||||
|
|
||||||
|
bool ok = mqttClient.connect(clientId, mqttCfg.user, mqttCfg.pass);
|
||||||
|
|
||||||
|
if (ok) {
|
||||||
|
Serial.printf("MQTT connected to %s:%d\n", mqttCfg.host, mqttCfg.port);
|
||||||
|
mqttSubscribe();
|
||||||
|
} else {
|
||||||
|
Serial.printf("MQTT failed rc=%d\n", mqttClient.state());
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mqttLoop() {
|
||||||
|
if (!mqttCfg.enabled || strlen(mqttCfg.host) == 0 || strlen(mqttCfg.user) == 0 || strlen(mqttCfg.pass) == 0) return;
|
||||||
|
|
||||||
|
if (!mqttClient.connected()) {
|
||||||
|
unsigned long now = millis();
|
||||||
|
if (now > mqttReconnectAt) {
|
||||||
|
// quick TCP probe with short timeout before the blocking MQTT connect
|
||||||
|
WiFiClient probe;
|
||||||
|
bool reachable = probe.connect(mqttCfg.host, mqttCfg.port, 1500);
|
||||||
|
probe.stop();
|
||||||
|
|
||||||
|
if (reachable) {
|
||||||
|
if (mqttConnect()) mqttReconnectAt = 0;
|
||||||
|
else mqttReconnectAt = now + 30000;
|
||||||
|
} else {
|
||||||
|
mqttReconnectAt = now + 30000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mqttClient.loop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// mDNS
|
// mDNS
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -103,6 +239,24 @@ static void startMDNS() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Web helpers
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
static String escHtml(const String& s) {
|
||||||
|
String out;
|
||||||
|
for (unsigned i = 0; i < s.length(); i++) {
|
||||||
|
char c = s.charAt(i);
|
||||||
|
switch (c) {
|
||||||
|
case '&': out += "&"; break;
|
||||||
|
case '\'': out += "'"; break;
|
||||||
|
case '"': out += """; break;
|
||||||
|
default: out += c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Web handlers
|
// Web handlers
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -131,89 +285,84 @@ static void handleRoot() {
|
|||||||
countOpts += ">" + String(i) + "</option>";
|
countOpts += ">" + String(i) + "</option>";
|
||||||
}
|
}
|
||||||
|
|
||||||
String html = R"(
|
String mqttSection;
|
||||||
<!DOCTYPE html>
|
{
|
||||||
<html>
|
char portStr[8];
|
||||||
<head>
|
snprintf(portStr, sizeof(portStr), "%u", mqttCfg.port);
|
||||||
<meta charset=utf-8>
|
String checked = mqttCfg.enabled ? " checked" : "";
|
||||||
<meta name=viewport content='width=device-width,initial-scale=1'>
|
|
||||||
<title>M1730</title>
|
|
||||||
<style>
|
|
||||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
|
||||||
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen,Ubuntu,sans-serif;background:#f2f4f8;color:#1a1a2e;display:flex;justify-content:center;align-items:flex-start;min-height:100vh;padding:24px 16px}
|
|
||||||
.card{background:#fff;border-radius:12px;box-shadow:0 4px 24px rgba(0,0,0,.08);padding:32px;width:100%;max-width:520px}
|
|
||||||
h1{font-size:24px;margin-bottom:4px}
|
|
||||||
.sub{color:#666;font-size:14px;margin-bottom:24px}
|
|
||||||
.info{background:#f8f9fc;border-radius:8px;padding:12px 16px;margin-bottom:20px;font-size:14px}
|
|
||||||
.info div{display:flex;justify-content:space-between;padding:4px 0}
|
|
||||||
.info div span:first-child{color:#666}
|
|
||||||
label{display:block;font-size:14px;font-weight:600;margin-bottom:4px}
|
|
||||||
input,select{width:100%;padding:9px 12px;border:1px solid #d1d5db;border-radius:8px;font-size:14px;outline:none;transition:border-color .2s}
|
|
||||||
input:focus,select:focus{border-color:#4361ee;box-shadow:0 0 0 3px rgba(67,97,238,.15)}
|
|
||||||
fieldset{border:1px solid #e5e7eb;border-radius:10px;padding:16px;margin-bottom:16px}
|
|
||||||
legend{font-weight:600;font-size:14px;padding:0 6px;color:#4361ee}
|
|
||||||
fieldset .row{display:flex;gap:8px;align-items:center;margin-bottom:10px}
|
|
||||||
fieldset .row label{width:60px;margin-bottom:0;flex-shrink:0}
|
|
||||||
.slider-row{display:flex;gap:8px;align-items:center}
|
|
||||||
.slider-row label{width:60px;margin-bottom:0;flex-shrink:0}
|
|
||||||
.slider-row input[type=range]{flex:1;padding:0;border:none;box-shadow:none;accent-color:#4361ee}
|
|
||||||
.slider-row input[type=range]:focus{box-shadow:none}
|
|
||||||
.val{font-size:14px;font-weight:600;min-width:48px;text-align:right;color:#4361ee}
|
|
||||||
.count-row{display:flex;gap:12px;align-items:flex-end;margin-bottom:20px}
|
|
||||||
.count-row > div{flex:1}
|
|
||||||
.count-row label{margin-bottom:4px}
|
|
||||||
.btn{width:100%;margin-top:8px;padding:12px;background:#4361ee;color:#fff;border:none;border-radius:8px;font-size:15px;font-weight:600;cursor:pointer;transition:background .2s}
|
|
||||||
.btn:hover{background:#3651d4}
|
|
||||||
.mac{font-size:12px;color:#999;text-align:center;margin-top:20px}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class=card>
|
|
||||||
<h1>M1730</h1>
|
|
||||||
<div class=sub>Configuration</div>
|
|
||||||
<div class=info>
|
|
||||||
<div><span>Hostname</span><span>)" + String(hostname) + R"(</span></div>
|
|
||||||
<div><span>IP</span><span>)" + WiFi.localIP().toString() + R"(</span></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form action=/config method=post>
|
mqttSection +=
|
||||||
<label>Hostname</label>
|
"<fieldset><legend>MQTT</legend>"
|
||||||
<input name=hostname value=')" + String(hostname) + R"('>
|
"<div class=row>"
|
||||||
|
"<label class=tgl-lbl>Enable</label>"
|
||||||
|
"<label class=switch><input type=checkbox name=mqtt_en" + checked + "><span class=slid></span></label>"
|
||||||
|
"</div>";
|
||||||
|
|
||||||
<div class=count-row>
|
String hostVal = escHtml(mqttCfg.host);
|
||||||
<div>
|
String userVal = escHtml(mqttCfg.user);
|
||||||
<label>Meters</label>
|
String passVal = escHtml(mqttCfg.pass);
|
||||||
<select name=meter_count onchange='this.form.submit()'>)" + countOpts + R"(</select>
|
String prefVal = escHtml(mqttCfg.prefix);
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
)" + meterRows + R"(
|
mqttSection +=
|
||||||
|
"<div class=row><label>Broker</label><input name=mqtt_host value='" + hostVal + "'></div>"
|
||||||
|
"<div class=row><label>Port</label><input name=mqtt_port type=number value=" + String(portStr) + "></div>"
|
||||||
|
"<div class=row><label>User</label><input name=mqtt_user value='" + userVal + "'></div>"
|
||||||
|
"<div class=row><label>Pass</label><input name=mqtt_pass type=password value='" + passVal + "'></div>"
|
||||||
|
"<div class=row><label>Prefix</label><input name=mqtt_prefix value='" + prefVal + "'></div>";
|
||||||
|
mqttSection += "</fieldset>";
|
||||||
|
}
|
||||||
|
|
||||||
<button class=btn type=submit>Save</button>
|
String html;
|
||||||
</form>
|
html.reserve(4096);
|
||||||
|
html += "<!DOCTYPE html><html><head><meta charset=utf-8>";
|
||||||
<div class=mac>)" + WiFi.macAddress() + R"(</div>
|
html += "<meta name=viewport content='width=device-width,initial-scale=1'><title>M1730</title><style>";
|
||||||
</div>
|
html += "*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}";
|
||||||
|
html += "body{font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen,Ubuntu,sans-serif;background:#f2f4f8;color:#1a1a2e;display:flex;justify-content:center;align-items:flex-start;min-height:100vh;padding:24px 16px}";
|
||||||
<script>
|
html += ".card{background:#fff;border-radius:12px;box-shadow:0 4px 24px rgba(0,0,0,.08);padding:32px;width:100%;max-width:560px}";
|
||||||
Array.from(document.querySelectorAll('input[type=range]')).forEach(function(s){
|
html += "h1{font-size:24px;margin-bottom:4px}.sub{color:#666;font-size:14px;margin-bottom:24px}";
|
||||||
var span = document.getElementById(s.name.replace('_cur',''));
|
html += ".info{background:#f8f9fc;border-radius:8px;padding:12px 16px;margin-bottom:20px;font-size:14px}";
|
||||||
if(span){
|
html += ".info div{display:flex;justify-content:space-between;padding:4px 0}.info div span:first-child{color:#666}";
|
||||||
s.addEventListener('input',function(){
|
html += "label{display:block;font-size:14px;font-weight:600;margin-bottom:4px}";
|
||||||
span.textContent=this.value+'%';
|
html += "input,select{width:100%;padding:9px 12px;border:1px solid #d1d5db;border-radius:8px;font-size:14px;outline:none;transition:border-color .2s}";
|
||||||
var x=new XMLHttpRequest();
|
html += "input:focus,select:focus{border-color:#4361ee;box-shadow:0 0 0 3px rgba(67,97,238,.15)}";
|
||||||
x.open('GET','/set?i='+this.name.match(/\d+/)[0]+'&v='+this.value,true);
|
html += "fieldset{border:1px solid #e5e7eb;border-radius:10px;padding:16px;margin-bottom:16px}";
|
||||||
x.send();
|
html += "legend{font-weight:600;font-size:14px;padding:0 6px;color:#4361ee}";
|
||||||
});
|
html += "fieldset .row{display:flex;gap:8px;align-items:center;margin-bottom:8px}";
|
||||||
}
|
html += "fieldset .row label{width:70px;margin-bottom:0;flex-shrink:0}.slider-row{display:flex;gap:8px;align-items:center}";
|
||||||
});
|
html += ".slider-row label{width:70px;margin-bottom:0;flex-shrink:0}";
|
||||||
document.querySelector('select[name=meter_count]').addEventListener('change',function(){
|
html += ".slider-row input[type=range]{flex:1;padding:0;border:none;box-shadow:none;accent-color:#4361ee}";
|
||||||
this.form.submit();
|
html += ".slider-row input[type=range]:focus{box-shadow:none}";
|
||||||
});
|
html += ".val{font-size:14px;font-weight:600;min-width:48px;text-align:right;color:#4361ee}";
|
||||||
</script>
|
html += ".count-row{display:flex;gap:12px;align-items:flex-end;margin-bottom:20px}.count-row > div{flex:1}";
|
||||||
</body>
|
html += ".count-row label{margin-bottom:4px}";
|
||||||
</html>
|
html += ".btn{width:100%;margin-top:8px;padding:12px;background:#4361ee;color:#fff;border:none;border-radius:8px;font-size:15px;font-weight:600;cursor:pointer;transition:background .2s}";
|
||||||
)";
|
html += ".btn:hover{background:#3651d4}.mac{font-size:12px;color:#999;text-align:center;margin-top:20px}";
|
||||||
|
html += ".tgl-lbl{margin-bottom:0!important;line-height:28px}.switch{position:relative;display:inline-block;width:44px;height:24px;margin-left:auto;flex-shrink:0}";
|
||||||
|
html += ".switch input{opacity:0;width:0;height:0}.slid{position:absolute;cursor:pointer;inset:0;background:#ccc;border-radius:24px;transition:.3s}";
|
||||||
|
html += ".slid::before{content:\"\";position:absolute;height:18px;width:18px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.3s}";
|
||||||
|
html += ".switch input:checked+.slid{background:#4361ee}.switch input:checked+.slid::before{transform:translateX(20px)}";
|
||||||
|
html += "</style></head><body><div class=card>";
|
||||||
|
html += "<h1>M1730</h1><div class=sub>Configuration</div><div class=info>";
|
||||||
|
html += "<div><span>Hostname</span><span>" + String(hostname) + "</span></div>";
|
||||||
|
html += "<div><span>IP</span><span>" + WiFi.localIP().toString() + "</span></div></div>";
|
||||||
|
html += "<form action=/config method=post><label>Hostname</label>";
|
||||||
|
html += "<input name=hostname value='" + String(hostname) + "'>";
|
||||||
|
html += "<div class=count-row><div><label>Meters</label>";
|
||||||
|
html += "<select name=meter_count onchange='this.form.submit()'>" + countOpts + "</select></div></div>";
|
||||||
|
html += meterRows;
|
||||||
|
html += mqttSection;
|
||||||
|
html += "<button class=btn type=submit>Save</button></form>";
|
||||||
|
html += "<div class=mac>" + WiFi.macAddress() + "</div></div>";
|
||||||
|
html += "<script>";
|
||||||
|
html += "Array.from(document.querySelectorAll('input[type=range]')).forEach(function(s){";
|
||||||
|
html += "var span=document.getElementById(s.name.replace('_cur',''));";
|
||||||
|
html += "if(span){s.addEventListener('input',function(){";
|
||||||
|
html += "span.textContent=this.value+'%';";
|
||||||
|
html += "var x=new XMLHttpRequest();";
|
||||||
|
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();});";
|
||||||
|
html += "</script></body></html>";
|
||||||
server.send(200, "text/html", html);
|
server.send(200, "text/html", html);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,6 +374,22 @@ static void handleConfig() {
|
|||||||
val.toCharArray(hostname, sizeof(hostname));
|
val.toCharArray(hostname, sizeof(hostname));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (server.hasArg("mqtt_en")) {
|
||||||
|
mqttCfg.enabled = true;
|
||||||
|
if (server.hasArg("mqtt_host"))
|
||||||
|
strlcpy(mqttCfg.host, server.arg("mqtt_host").c_str(), sizeof(mqttCfg.host));
|
||||||
|
if (server.hasArg("mqtt_port"))
|
||||||
|
mqttCfg.port = server.arg("mqtt_port").toInt();
|
||||||
|
if (server.hasArg("mqtt_user"))
|
||||||
|
strlcpy(mqttCfg.user, server.arg("mqtt_user").c_str(), sizeof(mqttCfg.user));
|
||||||
|
if (server.hasArg("mqtt_pass"))
|
||||||
|
strlcpy(mqttCfg.pass, server.arg("mqtt_pass").c_str(), sizeof(mqttCfg.pass));
|
||||||
|
if (server.hasArg("mqtt_prefix"))
|
||||||
|
strlcpy(mqttCfg.prefix, server.arg("mqtt_prefix").c_str(), sizeof(mqttCfg.prefix));
|
||||||
|
} else {
|
||||||
|
mqttCfg.enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
int newCount = meterCount;
|
int newCount = meterCount;
|
||||||
if (server.hasArg("meter_count"))
|
if (server.hasArg("meter_count"))
|
||||||
newCount = constrain(server.arg("meter_count").toInt(), 1, MAX_METERS);
|
newCount = constrain(server.arg("meter_count").toInt(), 1, MAX_METERS);
|
||||||
@@ -247,6 +412,10 @@ static void handleConfig() {
|
|||||||
attachMeters();
|
attachMeters();
|
||||||
applyMeters();
|
applyMeters();
|
||||||
|
|
||||||
|
// let mqttLoop handle connection to avoid blocking the HTTP handler
|
||||||
|
if (mqttClient.connected()) mqttClient.disconnect();
|
||||||
|
mqttReconnectAt = 0;
|
||||||
|
|
||||||
server.sendHeader("Location", "/");
|
server.sendHeader("Location", "/");
|
||||||
server.send(303);
|
server.send(303);
|
||||||
}
|
}
|
||||||
@@ -263,6 +432,7 @@ static void handleSet() {
|
|||||||
ledcWrite(idx, duty);
|
ledcWrite(idx, duty);
|
||||||
}
|
}
|
||||||
saveConfig();
|
saveConfig();
|
||||||
|
mqttPublishCurrent(idx);
|
||||||
server.send(200);
|
server.send(200);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,6 +453,7 @@ void setup() {
|
|||||||
LittleFS.begin(true);
|
LittleFS.begin(true);
|
||||||
|
|
||||||
loadConfig();
|
loadConfig();
|
||||||
|
|
||||||
attachMeters();
|
attachMeters();
|
||||||
applyMeters();
|
applyMeters();
|
||||||
|
|
||||||
@@ -304,6 +475,7 @@ void setup() {
|
|||||||
saveConfig();
|
saveConfig();
|
||||||
startServer();
|
startServer();
|
||||||
startMDNS();
|
startMDNS();
|
||||||
|
mqttReconnectAt = 0; // handshake deferred to mqttLoop in loop()
|
||||||
|
|
||||||
Serial.printf("Board address: http://%s.local or http://%s\n",
|
Serial.printf("Board address: http://%s.local or http://%s\n",
|
||||||
hostname, WiFi.localIP().toString().c_str());
|
hostname, WiFi.localIP().toString().c_str());
|
||||||
@@ -311,4 +483,5 @@ void setup() {
|
|||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
server.handleClient();
|
server.handleClient();
|
||||||
|
mqttLoop();
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user