Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better JSON and Mqtt cleanup #178

Merged
merged 11 commits into from
Feb 21, 2024
1 change: 1 addition & 0 deletions Software/Software.ino
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "src/devboard/utils/events.h"
#include "src/inverter/INVERTERS.h"
#include "src/lib/adafruit-Adafruit_NeoPixel/Adafruit_NeoPixel.h"
#include "src/lib/bblanchon-ArduinoJson/ArduinoJson.h"
#include "src/lib/eModbus-eModbus/Logging.h"
#include "src/lib/eModbus-eModbus/ModbusServerRTU.h"
#include "src/lib/eModbus-eModbus/scripts/mbServerFCs.h"
Expand Down
185 changes: 80 additions & 105 deletions Software/src/devboard/mqtt/mqtt.cpp
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please consider adding an MQTT transmit INFO event. It's not handled consistently here at the moment. One case is handled with a serial print, two ignore the return value and one assigns the return value to a variable but does nothing with it.

Cabooman marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <freertos/FreeRTOS.h>
#include "../../../USER_SETTINGS.h"
#include "../../battery/BATTERIES.h"
#include "../../lib/bblanchon-ArduinoJson/ArduinoJson.h"
#include "../../lib/knolleary-pubsubclient/PubSubClient.h"
#include "../utils/timer.h"

Expand All @@ -28,76 +29,60 @@ static void publish_values(void) {

static void publish_cell_voltages(void) {
static bool mqtt_first_transmission = true;

// If the cell voltage number isn't initialized...
static JsonDocument doc;
static const char* hostname = WiFi.getHostname();
if (nof_cellvoltages == 0u) {
return;
}
// At startup, re-post the discovery message for home assistant

if (mqtt_first_transmission == true) {
mqtt_first_transmission = false;

// Base topic for any cell voltage "sensor"
String topic = "homeassistant/sensor/battery-emulator/cell_voltage";

for (int i = 0; i < nof_cellvoltages; i++) {
// Build JSON message with device configuration for each cell voltage
// Probably shouldn't be BatteryEmulator here, instead "LeafBattery"
// or similar but hey, it works.
// mqtt_msg is a global buffer, should be fine since we run too much
// in a single thread :)
snprintf(mqtt_msg, sizeof(mqtt_msg),
"{"
"\"device\": {"
"\"identifiers\": ["
"\"battery-emulator\""
"],"
"\"manufacturer\": \"DalaTech\","
"\"model\": \"BatteryEmulator\","
"\"name\": \"BatteryEmulator\""
"},"
"\"device_class\": \"voltage\","
"\"enabled_by_default\": true,"
"\"object_id\": \"sensor_battery_voltage_cell%d\","
"\"origin\": {"
"\"name\": \"BatteryEmulator\","
"\"sw\": \"%s-mqtt\","
"\"url\": \"https://github.com/dalathegreat/Battery-Emulator\""
"},"
"\"state_class\": \"measurement\","
"\"name\": \"Battery Cell Voltage %d\","
"\"state_topic\": \"battery-emulator/spec_data\","
"\"unique_id\": \"battery-emulator_battery_voltage_cell%d\","
"\"unit_of_measurement\": \"V\","
"\"value_template\": \"{{ value_json.cell_voltages[%d] }}\""
"}",
i + 1, version_number, i + 1, i + 1, i);
// End each discovery topic with cell number and '/config'
char cellNumber[4]; // Buffer to hold the formatted cell number
sprintf(cellNumber, "%03d", i + 1); // Format the cell number with leading zeros

doc["name"] = "Battery Cell Voltage " + String(cellNumber);
doc["object_id"] = "battery_voltage_cell" + String(cellNumber);
doc["unique_id"] = "battery-emulator_battery_voltage_cell" + String(cellNumber);
doc["device_class"] = "voltage";
doc["state_class"] = "measurement";
doc["state_topic"] = "battery-emulator/spec_data";
doc["unit_of_measurement"] = "V";
doc["enabled_by_default"] = true;
doc["expire_after"] = 240;
doc["value_template"] = "{{ value_json.cell_voltages[" + String(i) + "] }}";
doc["device"]["identifiers"][0] = "battery-emulator";
doc["device"]["manufacturer"] = "DalaTech";
doc["device"]["model"] = "BatteryEmulator";
doc["device"]["name"] = "BatteryEmulator_" + String(hostname);
doc["origin"]["name"] = "BatteryEmulator";
doc["origin"]["sw"] = String(version_number) + "-mqtt";
doc["origin"]["url"] = "https://github.com/dalathegreat/Battery-Emulator";

serializeJson(doc, mqtt_msg, sizeof(mqtt_msg));
String cell_topic = topic + String(i + 1) + "/config";
mqtt_publish_retain(cell_topic.c_str());
mqtt_publish(cell_topic.c_str(), mqtt_msg, true);
}
doc.clear(); // clear after sending autoconfig
} else {
// Every 5-ish seconds, build the JSON payload for the state topic. This requires
// some annoying formatting due to C++ not having nice Python-like string formatting.
// msg_length is a cumulative variable to track start position (param 1) and for
// modifying the maxiumum amount of characters to write (param 2). The third parameter
// is the string content

// If cell voltages haven't been populated...
if (nof_cellvoltages == 0u) {
return;
}
}

size_t msg_length = snprintf(mqtt_msg, sizeof(mqtt_msg), "{\n\"cell_voltages\":[");
for (size_t i = 0; i < nof_cellvoltages; ++i) {
msg_length += snprintf(mqtt_msg + msg_length, sizeof(mqtt_msg) - msg_length, "%s%.3f", (i == 0) ? "" : ", ",
((float)cellvoltages[i]) / 1000);
}
snprintf(mqtt_msg + msg_length, sizeof(mqtt_msg) - msg_length, "]\n}\n");
JsonArray cell_voltages = doc["cell_voltages"].to<JsonArray>();
for (size_t i = 0; i < nof_cellvoltages; ++i) {
cell_voltages.add(cellvoltages[i] / 1000.0);
kharnt0x marked this conversation as resolved.
Show resolved Hide resolved
}

// Publish and print error if not OK
if (mqtt_publish_retain("battery-emulator/spec_data") == false) {
Serial.println("Cell voltage MQTT msg could not be sent");
serializeJson(doc, mqtt_msg, sizeof(mqtt_msg));

if (!mqtt_publish("battery-emulator/spec_data", mqtt_msg, false)) {
Serial.println("Cell voltage MQTT msg could not be sent");
}
doc.clear();
}
}

Expand Down Expand Up @@ -132,59 +117,48 @@ SensorConfig sensorConfigs[] = {
};

static void publish_common_info(void) {
static JsonDocument doc;
static bool mqtt_first_transmission = true;
static char* state_topic = "battery-emulator/info";
static const char* hostname = WiFi.getHostname();
if (mqtt_first_transmission == true) {
mqtt_first_transmission = false;
for (int i = 0; i < sizeof(sensorConfigs) / sizeof(sensorConfigs[0]); i++) {
SensorConfig& config = sensorConfigs[i];
snprintf(mqtt_msg, sizeof(mqtt_msg),
"{"
"\"name\": \"%s\","
"\"state_topic\": \"%s\","
"\"unique_id\": \"battery-emulator_%s\","
"\"object_id\": \"sensor_battery_%s\","
"\"device\": {"
"\"identifiers\": ["
"\"battery-emulator\""
"],"
"\"manufacturer\": \"DalaTech\","
"\"model\": \"BatteryEmulator\","
"\"name\": \"BatteryEmulator\""
"},"
"\"origin\": {"
"\"name\": \"BatteryEmulator\","
"\"sw\": \"%s-mqtt\","
"\"url\": \"https://github.com/dalathegreat/Battery-Emulator\""
"},"
"\"value_template\": \"%s\","
"\"unit_of_measurement\": \"%s\","
"\"device_class\": \"%s\","
"\"enabled_by_default\": true,"
"\"state_class\": \"measurement\""
"}",
config.name, state_topic, config.object_id, config.object_id, version_number, config.value_template,
config.unit, config.device_class);
mqtt_publish_retain(config.topic);
doc["name"] = config.name;
doc["state_topic"] = state_topic;
doc["unique_id"] = "battery-emulator_" + String(hostname) + "_" + String(config.object_id);
doc["object_id"] = String(hostname) + "_" + String(config.object_id);
doc["value_template"] = config.value_template;
doc["unit_of_measurement"] = config.unit;
doc["device_class"] = config.device_class;
doc["enabled_by_default"] = true;
doc["state_class"] = "measurement";
doc["expire_after"] = 240;
doc["device"]["identifiers"][0] = "battery-emulator";
doc["device"]["manufacturer"] = "DalaTech";
doc["device"]["model"] = "BatteryEmulator";
doc["device"]["name"] = "BatteryEmulator_" + String(hostname);
doc["origin"]["name"] = "BatteryEmulator";
doc["origin"]["sw"] = String(version_number) + "-mqtt";
doc["origin"]["url"] = "https://github.com/dalathegreat/Battery-Emulator";
serializeJson(doc, mqtt_msg);
mqtt_publish(config.topic, mqtt_msg, true);
}
doc.clear();
} else {
snprintf(mqtt_msg, sizeof(mqtt_msg),
"{\n"
" \"SOC\": %.3f,\n"
" \"state_of_health\": %.3f,\n"
" \"temperature_min\": %.3f,\n"
" \"temperature_max\": %.3f,\n"
" \"stat_batt_power\": %.3f,\n"
" \"battery_current\": %.3f,\n"
" \"cell_max_voltage\": %.3f,\n"
" \"cell_min_voltage\": %.3f,\n"
" \"battery_voltage\": %d\n"
"}\n",
((float)SOC) / 100.0, ((float)StateOfHealth) / 100.0, ((float)((int16_t)temperature_min)) / 10.0,
((float)((int16_t)temperature_max)) / 10.0, ((float)((int16_t)stat_batt_power)),
((float)((int16_t)battery_current)) / 10.0, ((float)cell_max_voltage) / 1000,
((float)cell_min_voltage) / 1000, battery_voltage / 10.0);
bool result = client.publish(state_topic, mqtt_msg, true);
doc["SOC"] = ((float)SOC) / 100.0;
doc["state_of_health"] = ((float)StateOfHealth) / 100.0;
doc["temperature_min"] = ((float)((int16_t)temperature_min)) / 10.0;
doc["temperature_max"] = ((float)((int16_t)temperature_max)) / 10.0;
doc["stat_batt_power"] = ((float)((int16_t)stat_batt_power));
doc["battery_current"] = ((float)((int16_t)battery_current)) / 10.0;
doc["cell_max_voltage"] = cell_max_voltage / 1000.0;
doc["cell_min_voltage"] = cell_min_voltage / 1000.0;
doc["battery_voltage"] = battery_voltage / 10.0;
kharnt0x marked this conversation as resolved.
Show resolved Hide resolved

serializeJson(doc, mqtt_msg);
bool result = mqtt_publish(state_topic, mqtt_msg, false);
}

//Serial.println(mqtt_msg); // Uncomment to print the payload on serial
Expand All @@ -206,9 +180,10 @@ static void reconnect() {
// attempt one reconnection
Serial.print("Attempting MQTT connection... ");
const char* hostname = WiFi.getHostname();
String clientId = "LilyGoClient-" + String(hostname);
char clientId[64]; // Adjust the size as needed
snprintf(clientId, sizeof(clientId), "LilyGoClient-%s", hostname);
// Attempt to connect
if (client.connect(clientId.c_str(), mqtt_user, mqtt_password)) {
if (client.connect(clientId, mqtt_user, mqtt_password)) {
Serial.println("connected");

for (int i = 0; i < mqtt_nof_subscriptions; i++) {
Expand Down Expand Up @@ -238,20 +213,20 @@ void mqtt_loop(void) {
client.loop();
if (publish_global_timer.elapsed() == true) // Every 5s
{
publish_values(); // Update values heading towards inverter. Prepare for sending on CAN, or write directly to Modbus.
publish_values();
}
} else {
if (millis() - previousMillisUpdateVal >= 5000) // Every 5s
{
previousMillisUpdateVal = millis();
reconnect(); // Update values heading towards inverter. Prepare for sending on CAN, or write directly to Modbus.
reconnect();
}
}
}

bool mqtt_publish_retain(const char* topic) {
bool mqtt_publish(const char* topic, const char* mqtt_msg, bool retain) {
if (client.connected() == true) {
return client.publish(topic, mqtt_msg, true);
return client.publish(topic, mqtt_msg, retain);
}
return false;
}
3 changes: 2 additions & 1 deletion Software/src/devboard/mqtt/mqtt.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ extern uint16_t cellvoltages[120]; //mV 0-4350 per cell
extern uint8_t nof_cellvoltages; // Total number of cell voltages, set by each battery.
extern uint16_t battery_voltage; //V+1, 0-500.0 (0-5000)
extern uint16_t battery_current; //A+1, Goes thru convert2unsignedint16 function (5.0A = 50, -5.0A = 65485)
extern uint16_t stat_batt_power;

extern const char* mqtt_user;
extern const char* mqtt_password;
Expand All @@ -59,6 +60,6 @@ extern char mqtt_msg[MQTT_MSG_BUFFER_SIZE];

void init_mqtt(void);
void mqtt_loop(void);
bool mqtt_publish_retain(const char* topic);
bool mqtt_publish(const char* topic, const char* mqtt_msg, bool retain);

#endif
Loading
Loading