#include "mqtt_client.h" #include #include #include #include "config.h" #include "json_codec.h" static WiFiClient wifi_client; static PubSubClient mqtt_client(wifi_client); static WifiMqttConfig g_cfg; static String g_client_id; void mqtt_init(const WifiMqttConfig &config, const char *device_id) { g_cfg = config; mqtt_client.setServer(config.mqtt_host.c_str(), config.mqtt_port); if (device_id && device_id[0] != '\0') { g_client_id = String("dd3-bridge-") + device_id; } else { g_client_id = String("dd3-bridge-") + String(random(0xffff), HEX); } } static bool mqtt_connect() { if (mqtt_client.connected()) { return true; } String client_id = g_client_id.length() > 0 ? g_client_id : String("dd3-bridge-") + String(random(0xffff), HEX); if (g_cfg.mqtt_user.length() > 0) { return mqtt_client.connect(client_id.c_str(), g_cfg.mqtt_user.c_str(), g_cfg.mqtt_pass.c_str()); } return mqtt_client.connect(client_id.c_str()); } void mqtt_loop() { if (!mqtt_connect()) { return; } mqtt_client.loop(); } bool mqtt_is_connected() { return mqtt_client.connected(); } bool mqtt_publish_state(const MeterData &data) { if (!mqtt_connect()) { return false; } String payload; if (!meterDataToJson(data, payload)) { return false; } String topic = String("smartmeter/") + data.device_id + "/state"; return mqtt_client.publish(topic.c_str(), payload.c_str()); } bool mqtt_publish_faults(const char *device_id, const FaultCounters &counters, FaultType last_error, uint32_t last_error_age_sec) { if (!device_id || device_id[0] == '\0') { return false; } if (!mqtt_connect()) { return false; } StaticJsonDocument<192> doc; doc["err_m"] = counters.meter_read_fail; doc["err_d"] = counters.decode_fail; doc["err_tx"] = counters.lora_tx_fail; if (last_error != FaultType::None) { doc["err_last"] = static_cast(last_error); doc["err_last_age"] = last_error_age_sec; } String payload; size_t len = serializeJson(doc, payload); if (len == 0) { return false; } String topic = String("smartmeter/") + device_id + "/faults"; return mqtt_client.publish(topic.c_str(), payload.c_str(), true); } static bool publish_discovery_sensor(const char *device_id, const char *key, const char *name, const char *unit, const char *device_class, const char *state_topic, const char *value_template) { StaticJsonDocument<256> doc; String unique_id = String("dd3_") + device_id + "_" + key; String sensor_name = String(device_id) + " " + name; doc["name"] = sensor_name; doc["state_topic"] = state_topic; doc["unique_id"] = unique_id; if (unit && unit[0] != '\0') { doc["unit_of_measurement"] = unit; } if (device_class && device_class[0] != '\0') { doc["device_class"] = device_class; } doc["value_template"] = value_template; JsonObject device = doc.createNestedObject("device"); JsonArray identifiers = device.createNestedArray("identifiers"); identifiers.add(String("dd3-") + device_id); device["name"] = String("DD3 ") + device_id; device["model"] = "DD3-LoRa-Bridge"; device["manufacturer"] = "DD3"; String payload; size_t len = serializeJson(doc, payload); if (len == 0) { return false; } String topic = String("homeassistant/sensor/") + device_id + "/" + key + "/config"; return mqtt_client.publish(topic.c_str(), payload.c_str(), true); } bool mqtt_publish_discovery(const char *device_id) { if (!device_id || device_id[0] == '\0') { return false; } if (!mqtt_connect()) { return false; } String state_topic = String("smartmeter/") + device_id + "/state"; bool ok = true; ok = ok && publish_discovery_sensor(device_id, "energy", "Energy", "kWh", "energy", state_topic.c_str(), "{{ value_json.e_kwh }}"); ok = ok && publish_discovery_sensor(device_id, "power", "Power", "W", "power", state_topic.c_str(), "{{ value_json.p_w }}"); ok = ok && publish_discovery_sensor(device_id, "p1", "Power L1", "W", "power", state_topic.c_str(), "{{ value_json.p1_w }}"); ok = ok && publish_discovery_sensor(device_id, "p2", "Power L2", "W", "power", state_topic.c_str(), "{{ value_json.p2_w }}"); ok = ok && publish_discovery_sensor(device_id, "p3", "Power L3", "W", "power", state_topic.c_str(), "{{ value_json.p3_w }}"); ok = ok && publish_discovery_sensor(device_id, "bat_v", "Battery Voltage", "V", "voltage", state_topic.c_str(), "{{ value_json.bat_v }}"); ok = ok && publish_discovery_sensor(device_id, "bat_pct", "Battery", "%", "battery", state_topic.c_str(), "{{ value_json.bat_pct }}"); ok = ok && publish_discovery_sensor(device_id, "rssi", "LoRa RSSI", "dBm", "signal_strength", state_topic.c_str(), "{{ value_json.rssi }}"); ok = ok && publish_discovery_sensor(device_id, "snr", "LoRa SNR", "dB", "", state_topic.c_str(), "{{ value_json.snr }}"); String faults_topic = String("smartmeter/") + device_id + "/faults"; ok = ok && publish_discovery_sensor(device_id, "err_m", "Meter Read Errors", "count", "", faults_topic.c_str(), "{{ value_json.err_m }}"); ok = ok && publish_discovery_sensor(device_id, "err_d", "Decode Errors", "count", "", faults_topic.c_str(), "{{ value_json.err_d }}"); ok = ok && publish_discovery_sensor(device_id, "err_tx", "LoRa TX Errors", "count", "", faults_topic.c_str(), "{{ value_json.err_tx }}"); ok = ok && publish_discovery_sensor(device_id, "err_last_age", "Last Error Age", "s", "", faults_topic.c_str(), "{{ value_json.err_last_age }}"); return ok; } #ifdef ENABLE_TEST_MODE bool mqtt_publish_test(const char *device_id, const String &payload) { if (!mqtt_connect()) { return false; } String topic = String("smartmeter/") + device_id + "/test"; return mqtt_client.publish(topic.c_str(), payload.c_str()); } #endif