Compare commits
8 Commits
9169bb0cb1
...
96998fb9e2
| Author | SHA1 | Date | |
|---|---|---|---|
|
96998fb9e2
|
|||
|
b10c89505c
|
|||
|
acf63a3753
|
|||
|
eafdf87563
|
|||
|
409eeb4895
|
|||
|
5450d21c34
|
|||
|
a09f1dec87
|
|||
|
5bfe3890f2
|
+60
-21
@@ -13,6 +13,9 @@
|
||||
#define PWM_RES 10
|
||||
#define MQTT_MANUFACTURER "Baumann Enkataleiptics"
|
||||
#define AUTH_USERNAME "admin"
|
||||
#define MQTT_PROBE_TIMEOUT_MS 400 // keep short: blocks loop()/handleClient() per reconnect attempt
|
||||
#define MQTT_PAYLOAD_AVAILABLE "online"
|
||||
#define MQTT_PAYLOAD_NOT_AVAILABLE "offline"
|
||||
|
||||
struct MeterConfig {
|
||||
int pin;
|
||||
@@ -138,12 +141,29 @@ static void saveConfig() {
|
||||
// PWM helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Tracks the GPIO currently bound to each PWM channel so channels that get
|
||||
// freed (meter count shrinks, or a pin is changed/cleared) are detached
|
||||
// instead of being left driving PWM on their old pin.
|
||||
static int8_t attachedPin[8] = { -1, -1, -1, -1, -1, -1, -1, -1 };
|
||||
|
||||
static void attachMeters() {
|
||||
for (int i = 0; i < meterCount && i < 8; i++) {
|
||||
if (meters[i].pin > 0) {
|
||||
for (int i = 0; i < 8; i++) {
|
||||
int pin = (i < meterCount && meters[i].pin > 0) ? meters[i].pin : -1;
|
||||
|
||||
// Release the channel if it's no longer used or its pin changed.
|
||||
if (attachedPin[i] != -1 && attachedPin[i] != pin) {
|
||||
ledcDetachPin(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]);
|
||||
attachedPin[i] = -1;
|
||||
}
|
||||
|
||||
if (pin > 0 && attachedPin[i] != pin) {
|
||||
ledcSetup(i, PWM_FREQ, PWM_RES);
|
||||
ledcAttachPin(meters[i].pin, i);
|
||||
Serial.printf("[PWM] attach ch%d pin%d\n", i, meters[i].pin);
|
||||
ledcAttachPin(pin, i);
|
||||
attachedPin[i] = pin;
|
||||
Serial.printf("[PWM] attach ch%d pin%d\n", i, pin);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -238,6 +258,10 @@ static void mqttPublishDiscovery() {
|
||||
doc["name"] = name;
|
||||
doc["state_topic"] = stat;
|
||||
doc["command_topic"] = stat + "/set";
|
||||
doc["retain"] = true; // HA publishes commands retained so values survive restarts
|
||||
doc["availability_topic"] = String(mqttCfg.prefix) + "/status";
|
||||
doc["payload_available"] = MQTT_PAYLOAD_AVAILABLE;
|
||||
doc["payload_not_available"] = MQTT_PAYLOAD_NOT_AVAILABLE;
|
||||
doc["min"] = meters[i].rangeMin;
|
||||
doc["max"] = meters[i].rangeMax;
|
||||
doc["step"] = 0.1;
|
||||
@@ -250,11 +274,21 @@ static void mqttPublishDiscovery() {
|
||||
|
||||
char topic[128];
|
||||
snprintf(topic, sizeof(topic), "homeassistant/number/%s/config", objId.c_str());
|
||||
char payload[512];
|
||||
char payload[768];
|
||||
serializeJson(doc, payload, sizeof(payload));
|
||||
bool pubOk = mqttClient.publish(topic, payload, true);
|
||||
Serial.printf("[MQTT] discovery %s -> %s (ok=%d)\n", topic, payload, pubOk);
|
||||
}
|
||||
|
||||
// Clear retained discovery for slots no longer in use (meters removed) so
|
||||
// Home Assistant drops the stale entities instead of keeping them forever.
|
||||
for (int i = meterCount; i < MAX_METERS; i++) {
|
||||
String objId = String(devId) + "_meter_" + String(i) + "_current";
|
||||
char topic[128];
|
||||
snprintf(topic, sizeof(topic), "homeassistant/number/%s/config", objId.c_str());
|
||||
mqttClient.publish(topic, "", true);
|
||||
Serial.printf("[MQTT] discovery clear %s\n", topic);
|
||||
}
|
||||
}
|
||||
|
||||
static void mqttSubscribe() {
|
||||
@@ -268,9 +302,9 @@ static void mqttSubscribe() {
|
||||
}
|
||||
|
||||
static bool mqttConnect() {
|
||||
if (!mqttCfg.enabled || strlen(mqttCfg.host) == 0 || strlen(mqttCfg.user) == 0 || strlen(mqttCfg.pass) == 0) {
|
||||
Serial.printf("[MQTT] connect skipped en=%d host=%d user=%d pass=%d\n",
|
||||
mqttCfg.enabled, strlen(mqttCfg.host) > 0, strlen(mqttCfg.user) > 0, strlen(mqttCfg.pass) > 0);
|
||||
if (!mqttCfg.enabled || strlen(mqttCfg.host) == 0) {
|
||||
Serial.printf("[MQTT] connect skipped en=%d host=%d\n",
|
||||
mqttCfg.enabled, strlen(mqttCfg.host) > 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -288,13 +322,18 @@ static bool mqttConnect() {
|
||||
mqttClient.setCallback(mqttCallback);
|
||||
mqttClient.setBufferSize(1024);
|
||||
|
||||
// Pass NULL for empty credentials so the broker treats it as an anonymous
|
||||
// connection rather than an empty-string login (which some brokers reject).
|
||||
const char* user = strlen(mqttCfg.user) > 0 ? mqttCfg.user : nullptr;
|
||||
const char* pass = strlen(mqttCfg.pass) > 0 ? mqttCfg.pass : nullptr;
|
||||
|
||||
Serial.printf("[MQTT] connecting to %s:%d as %s\n", mqttCfg.host, mqttCfg.port, clientId);
|
||||
bool ok = mqttClient.connect(clientId, mqttCfg.user, mqttCfg.pass,
|
||||
statusTopic, 0, true, "online: false");
|
||||
bool ok = mqttClient.connect(clientId, user, pass,
|
||||
statusTopic, 0, true, MQTT_PAYLOAD_NOT_AVAILABLE);
|
||||
|
||||
if (ok) {
|
||||
Serial.printf("[MQTT] connected to %s:%d\n", mqttCfg.host, mqttCfg.port);
|
||||
mqttClient.publish(statusTopic, "online: true", true);
|
||||
mqttClient.publish(statusTopic, MQTT_PAYLOAD_AVAILABLE, true);
|
||||
mqttSubscribe();
|
||||
mqttPublishDiscovery();
|
||||
for (int i = 0; i < meterCount; i++)
|
||||
@@ -306,14 +345,14 @@ static bool mqttConnect() {
|
||||
}
|
||||
|
||||
static void mqttLoop() {
|
||||
if (!mqttCfg.enabled || strlen(mqttCfg.host) == 0 || strlen(mqttCfg.user) == 0 || strlen(mqttCfg.pass) == 0) return;
|
||||
if (!mqttCfg.enabled || strlen(mqttCfg.host) == 0) return;
|
||||
|
||||
if (!mqttClient.connected()) {
|
||||
unsigned long now = millis();
|
||||
if (now > mqttReconnectAt) {
|
||||
Serial.printf("[MQTT] probing %s:%d\n", mqttCfg.host, mqttCfg.port);
|
||||
WiFiClient probe;
|
||||
bool reachable = probe.connect(mqttCfg.host, mqttCfg.port, 1500);
|
||||
bool reachable = probe.connect(mqttCfg.host, mqttCfg.port, MQTT_PROBE_TIMEOUT_MS);
|
||||
probe.stop();
|
||||
|
||||
if (reachable) {
|
||||
@@ -353,6 +392,8 @@ static String escHtml(const String& s) {
|
||||
char c = s.charAt(i);
|
||||
switch (c) {
|
||||
case '&': out += "&"; break;
|
||||
case '<': out += "<"; break;
|
||||
case '>': out += ">"; break;
|
||||
case '\'': out += "'"; break;
|
||||
case '"': out += """; break;
|
||||
default: out += c;
|
||||
@@ -516,8 +557,8 @@ static void handleConfig() {
|
||||
val.toCharArray(hostname, sizeof(hostname));
|
||||
}
|
||||
|
||||
if (server.hasArg("mqtt_en")) {
|
||||
mqttCfg.enabled = true;
|
||||
// Persist the connection fields regardless of the enable checkbox so edits
|
||||
// made while disabling MQTT aren't discarded.
|
||||
if (server.hasArg("mqtt_host"))
|
||||
strlcpy(mqttCfg.host, server.arg("mqtt_host").c_str(), sizeof(mqttCfg.host));
|
||||
if (server.hasArg("mqtt_port"))
|
||||
@@ -528,12 +569,10 @@ static void handleConfig() {
|
||||
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));
|
||||
Serial.printf("[HTTP] mqtt enabled host=%s port=%u user=%s prefix=%s\n",
|
||||
mqttCfg.host, mqttCfg.port, mqttCfg.user, mqttCfg.prefix);
|
||||
} else {
|
||||
mqttCfg.enabled = false;
|
||||
Serial.println("[HTTP] mqtt disabled");
|
||||
}
|
||||
|
||||
mqttCfg.enabled = server.hasArg("mqtt_en");
|
||||
Serial.printf("[HTTP] mqtt enabled=%d host=%s port=%u user=%s prefix=%s\n",
|
||||
mqttCfg.enabled, mqttCfg.host, mqttCfg.port, mqttCfg.user, mqttCfg.prefix);
|
||||
|
||||
if (server.hasArg("auth_en")) {
|
||||
if (server.hasArg("auth_pass") && server.arg("auth_pass").length() > 0)
|
||||
|
||||
Reference in New Issue
Block a user