MQTT: add HA auto-discovery, meter metadata, drop maxduty

- Add name/unit/range fields per meter (saved to config, shown in web form)
- Publish Home Assistant MQTT discovery configs for each meter (number entity)
- Add MQTT_MANUFACTURER constant at top of file
- Increase PubSubClient buffer to 1024 bytes for discovery payloads
- Remove maxduty from MQTT subscription and HA discovery
- Publish current state on MQTT connect and on MQTT-set changes
This commit is contained in:
2026-06-14 23:08:58 +02:00
parent e20ef2ffbb
commit 097da42ea3

View File

@@ -11,11 +11,15 @@
#define MAX_METERS 10 #define MAX_METERS 10
#define PWM_FREQ 5000 #define PWM_FREQ 5000
#define PWM_RES 10 #define PWM_RES 10
#define MQTT_MANUFACTURER "Baumann Enkataleiptics"
struct MeterConfig { struct MeterConfig {
int pin; int pin;
float maxDuty; float maxDuty;
float currentValue; float currentValue;
char name[32] = "";
char unit[16] = "";
float range = 100.0;
}; };
struct MqttConfig { struct MqttConfig {
@@ -72,6 +76,9 @@ static void loadConfig() {
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;
strlcpy(meters[i].name, m["name"] | "", sizeof(meters[i].name));
strlcpy(meters[i].unit, m["unit"] | "", sizeof(meters[i].unit));
meters[i].range = m["range"] | 100.0f;
} }
Serial.printf("[CFG] loaded hostname=%s meters=%d mqtt_en=%d\n", hostname, meterCount, mqttCfg.enabled); Serial.printf("[CFG] loaded hostname=%s meters=%d mqtt_en=%d\n", hostname, meterCount, mqttCfg.enabled);
} }
@@ -94,6 +101,9 @@ static void saveConfig() {
m["pin"] = meters[i].pin; m["pin"] = meters[i].pin;
m["maxD"] = meters[i].maxDuty; m["maxD"] = meters[i].maxDuty;
m["current"] = meters[i].currentValue; m["current"] = meters[i].currentValue;
m["name"] = meters[i].name;
m["unit"] = meters[i].unit;
m["range"] = meters[i].range;
} }
File f = LittleFS.open("/config.json", "w"); File f = LittleFS.open("/config.json", "w");
@@ -134,6 +144,8 @@ static void applyMeters() {
// MQTT // MQTT
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
static void mqttPublishCurrent(int idx);
static void mqttCallback(char* topic, byte* payload, unsigned int len) { static void mqttCallback(char* topic, byte* payload, unsigned int len) {
String valStr; String valStr;
for (unsigned i = 0; i < len; i++) valStr += (char)payload[i]; for (unsigned i = 0; i < len; i++) valStr += (char)payload[i];
@@ -161,12 +173,7 @@ static void mqttCallback(char* topic, byte* payload, unsigned int len) {
ledcWrite(idx, constrain((int)(pct * 1023), 0, 1023)); ledcWrite(idx, constrain((int)(pct * 1023), 0, 1023));
} }
saveConfig(); saveConfig();
} else if (suffix == "/maxduty/set") { mqttPublishCurrent(idx);
Serial.printf("[MQTT] set meter%d maxduty=%.1f\n", idx, val);
meters[idx].maxDuty = constrain(val, 0, 100);
attachMeters();
applyMeters();
saveConfig();
} }
} }
@@ -179,6 +186,45 @@ static void mqttPublishCurrent(int idx) {
Serial.printf("[MQTT] publish topic=%s val=%s ok=%d\n", topic, val, ok); Serial.printf("[MQTT] publish topic=%s val=%s ok=%d\n", topic, val, ok);
} }
static void mqttPublishDiscovery() {
if (!mqttCfg.enabled || !mqttClient.connected()) return;
uint8_t mac[6];
WiFi.macAddress(mac);
char devId[32];
snprintf(devId, sizeof(devId), "m1730_%02x%02x%02x", mac[3], mac[4], mac[5]);
for (int i = 0; i < meterCount; i++) {
String objId = String(devId) + "_meter_" + String(i) + "_current";
String stat = String(mqttCfg.prefix) + "/meter/" + String(i) + "/current";
String name = strlen(meters[i].name) > 0
? String(meters[i].name)
: "Meter " + String(i);
JsonDocument doc;
doc["unique_id"] = objId;
doc["name"] = name;
doc["state_topic"] = stat;
doc["command_topic"] = stat + "/set";
doc["min"] = 0;
doc["max"] = 100;
doc["step"] = 0.1;
if (strlen(meters[i].unit) > 0)
doc["unit_of_measurement"] = meters[i].unit;
doc["device"]["identifiers"][0] = devId;
doc["device"]["name"] = hostname;
doc["device"]["manufacturer"] = MQTT_MANUFACTURER;
doc["device"]["model"] = "ESP32";
char topic[128];
snprintf(topic, sizeof(topic), "homeassistant/number/%s/config", objId.c_str());
char payload[512];
serializeJson(doc, payload, sizeof(payload));
bool pubOk = mqttClient.publish(topic, payload, true);
Serial.printf("[MQTT] discovery %s -> %s (ok=%d)\n", topic, payload, pubOk);
}
}
static void mqttSubscribe() { static void mqttSubscribe() {
if (!mqttCfg.enabled) return; if (!mqttCfg.enabled) return;
for (int i = 0; i < meterCount; i++) { for (int i = 0; i < meterCount; i++) {
@@ -186,9 +232,6 @@ static void mqttSubscribe() {
snprintf(t, sizeof(t), "%s/meter/%d/current/set", mqttCfg.prefix, i); snprintf(t, sizeof(t), "%s/meter/%d/current/set", mqttCfg.prefix, i);
mqttClient.subscribe(t); mqttClient.subscribe(t);
Serial.printf("[MQTT] subscribed %s\n", t); Serial.printf("[MQTT] subscribed %s\n", t);
snprintf(t, sizeof(t), "%s/meter/%d/maxduty/set", mqttCfg.prefix, i);
mqttClient.subscribe(t);
Serial.printf("[MQTT] subscribed %s\n", t);
} }
} }
@@ -211,6 +254,7 @@ static bool mqttConnect() {
mqttClient.setServer(mqttCfg.host, mqttCfg.port); mqttClient.setServer(mqttCfg.host, mqttCfg.port);
mqttClient.setCallback(mqttCallback); mqttClient.setCallback(mqttCallback);
mqttClient.setBufferSize(1024);
Serial.printf("[MQTT] connecting to %s:%d as %s\n", mqttCfg.host, mqttCfg.port, clientId); Serial.printf("[MQTT] connecting to %s:%d as %s\n", mqttCfg.host, mqttCfg.port, clientId);
bool ok = mqttClient.connect(clientId, mqttCfg.user, mqttCfg.pass, bool ok = mqttClient.connect(clientId, mqttCfg.user, mqttCfg.pass,
@@ -220,6 +264,9 @@ static bool mqttConnect() {
Serial.printf("[MQTT] connected to %s:%d\n", mqttCfg.host, mqttCfg.port); Serial.printf("[MQTT] connected to %s:%d\n", mqttCfg.host, mqttCfg.port);
mqttClient.publish(statusTopic, "online: true", true); mqttClient.publish(statusTopic, "online: true", true);
mqttSubscribe(); mqttSubscribe();
mqttPublishDiscovery();
for (int i = 0; i < meterCount; i++)
mqttPublishCurrent(i);
} else { } else {
Serial.printf("[MQTT] failed rc=%d\n", mqttClient.state()); Serial.printf("[MQTT] failed rc=%d\n", mqttClient.state());
} }
@@ -294,11 +341,19 @@ static void handleRoot() {
dtostrf(meters[i].maxDuty, 1, 1, maxStr); dtostrf(meters[i].maxDuty, 1, 1, maxStr);
dtostrf(meters[i].currentValue, 1, 1, curStr); dtostrf(meters[i].currentValue, 1, 1, curStr);
String nameVal = escHtml(meters[i].name);
String unitVal = escHtml(meters[i].unit);
char rangeStr[8];
dtostrf(meters[i].range, 1, 1, rangeStr);
meterRows += "<fieldset><legend>Meter " + String(i) + "</legend>"; meterRows += "<fieldset><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>Max Duty %</label><input name=m" + String(i) + "_maxD type=number step=any min=0 max=100 value=" + String(maxStr) + "></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>Range</label><input name=m" + String(i) + "_range type=number step=any min=0 value=" + String(rangeStr) + "></div>";
meterRows += "<div class=row><label>Max Duty</label><input name=m" + String(i) + "_maxD type=number step=any min=0 max=100 value=" + String(maxStr) + "></div>";
meterRows += "<div class=slider-row>"; meterRows += "<div class=slider-row>";
meterRows += "<label>Output %</label>"; meterRows += "<label>Output</label>";
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>";
@@ -430,6 +485,12 @@ static void handleConfig() {
String pf = "m" + String(i) + "_"; String pf = "m" + String(i) + "_";
if (server.hasArg(pf + "pin")) if (server.hasArg(pf + "pin"))
newMeters[i].pin = server.arg(pf + "pin").toInt(); newMeters[i].pin = server.arg(pf + "pin").toInt();
if (server.hasArg(pf + "name"))
strlcpy(newMeters[i].name, server.arg(pf + "name").c_str(), sizeof(newMeters[i].name));
if (server.hasArg(pf + "unit"))
strlcpy(newMeters[i].unit, server.arg(pf + "unit").c_str(), sizeof(newMeters[i].unit));
if (server.hasArg(pf + "range"))
newMeters[i].range = server.arg(pf + "range").toFloat();
if (server.hasArg(pf + "maxD")) if (server.hasArg(pf + "maxD"))
newMeters[i].maxDuty = server.arg(pf + "maxD").toFloat(); newMeters[i].maxDuty = server.arg(pf + "maxD").toFloat();
if (server.hasArg(pf + "cur")) if (server.hasArg(pf + "cur"))
@@ -439,7 +500,8 @@ static void handleConfig() {
meterCount = newCount; meterCount = newCount;
memcpy(meters, newMeters, sizeof(meters)); memcpy(meters, newMeters, sizeof(meters));
for (int i = 0; i < meterCount; i++) for (int i = 0; i < meterCount; i++)
Serial.printf("[HTTP] meter%d pin=%d maxD=%.1f cur=%.1f\n", i, meters[i].pin, meters[i].maxDuty, meters[i].currentValue); Serial.printf("[HTTP] meter%d pin=%d name=%s unit=%s range=%.1f maxD=%.1f cur=%.1f\n",
i, meters[i].pin, meters[i].name, meters[i].unit, meters[i].range, meters[i].maxDuty, meters[i].currentValue);
saveConfig(); saveConfig();
attachMeters(); attachMeters();