154 lines
5.7 KiB
C++
154 lines
5.7 KiB
C++
#include "mqtt_client.h"
|
|
#include <WiFi.h>
|
|
#include <PubSubClient.h>
|
|
#include <ArduinoJson.h>
|
|
#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<uint8_t>(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
|