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:
86
src/main.cpp
86
src/main.cpp
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user