diff --git a/src/main.cpp b/src/main.cpp index ead50bd..0221ab7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,11 +11,15 @@ #define MAX_METERS 10 #define PWM_FREQ 5000 #define PWM_RES 10 +#define MQTT_MANUFACTURER "Baumann Enkataleiptics" struct MeterConfig { int pin; float maxDuty; float currentValue; + char name[32] = ""; + char unit[16] = ""; + float range = 100.0; }; struct MqttConfig { @@ -72,6 +76,9 @@ static void loadConfig() { meters[i].pin = m["pin"] | 0; meters[i].maxDuty = m["maxD"] | 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); } @@ -94,6 +101,9 @@ static void saveConfig() { m["pin"] = meters[i].pin; m["maxD"] = meters[i].maxDuty; 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"); @@ -134,6 +144,8 @@ static void applyMeters() { // MQTT // --------------------------------------------------------------------------- +static void mqttPublishCurrent(int idx); + static void mqttCallback(char* topic, byte* payload, unsigned int len) { String valStr; 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)); } saveConfig(); - } else if (suffix == "/maxduty/set") { - Serial.printf("[MQTT] set meter%d maxduty=%.1f\n", idx, val); - meters[idx].maxDuty = constrain(val, 0, 100); - attachMeters(); - applyMeters(); - saveConfig(); + mqttPublishCurrent(idx); } } @@ -179,6 +186,45 @@ static void mqttPublishCurrent(int idx) { 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() { if (!mqttCfg.enabled) return; 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); mqttClient.subscribe(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.setCallback(mqttCallback); + mqttClient.setBufferSize(1024); Serial.printf("[MQTT] connecting to %s:%d as %s\n", mqttCfg.host, mqttCfg.port, clientId); 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); mqttClient.publish(statusTopic, "online: true", true); mqttSubscribe(); + mqttPublishDiscovery(); + for (int i = 0; i < meterCount; i++) + mqttPublishCurrent(i); } else { 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].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 += "
"; @@ -430,6 +485,12 @@ static void handleConfig() { String pf = "m" + String(i) + "_"; if (server.hasArg(pf + "pin")) 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")) newMeters[i].maxDuty = server.arg(pf + "maxD").toFloat(); if (server.hasArg(pf + "cur")) @@ -439,7 +500,8 @@ static void handleConfig() { meterCount = newCount; memcpy(meters, newMeters, sizeof(meters)); 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(); attachMeters();