From f881bc409316fe729c897b6c2199e4c206121782 Mon Sep 17 00:00:00 2001
From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com>
Date: Fri, 11 Aug 2023 14:59:21 +0100
Subject: [PATCH 01/27] Basic home assistant API integration
---
ESPController/include/defines.h | 2 +
.../include/webserver_json_requests.h | 1 +
ESPController/src/main.cpp | 13 +++-
ESPController/src/settings.cpp | 11 ++-
ESPController/src/webserver.cpp | 9 ++-
ESPController/src/webserver_helper_funcs.cpp | 28 ++++----
ESPController/src/webserver_json_requests.cpp | 69 ++++++++++++++++++-
7 files changed, 115 insertions(+), 18 deletions(-)
diff --git a/ESPController/include/defines.h b/ESPController/include/defines.h
index 2077a6f1..a12436f0 100644
--- a/ESPController/include/defines.h
+++ b/ESPController/include/defines.h
@@ -245,6 +245,8 @@ struct diybms_eeprom_settings
// Holds a bit pattern indicating which "tiles" are visible on the web gui
uint16_t tileconfig[5];
+
+ char homeassist_apikey[24+1];
};
typedef union
diff --git a/ESPController/include/webserver_json_requests.h b/ESPController/include/webserver_json_requests.h
index c209d101..5b3747e3 100644
--- a/ESPController/include/webserver_json_requests.h
+++ b/ESPController/include/webserver_json_requests.h
@@ -24,6 +24,7 @@
esp_err_t api_handler(httpd_req_t *req);
esp_err_t content_handler_downloadfile(httpd_req_t *req);
+esp_err_t ha_handler(httpd_req_t *req);
esp_err_t SendFileInChunks(httpd_req_t *req, FS &filesystem, const char *filename);
int fileSystemListDirectory(char *buffer, size_t bufferLen, fs::FS &fs, const char *dirname, uint8_t levels);
diff --git a/ESPController/src/main.cpp b/ESPController/src/main.cpp
index cab2f00d..e982ef9e 100644
--- a/ESPController/src/main.cpp
+++ b/ESPController/src/main.cpp
@@ -82,7 +82,7 @@ extern "C"
#include "history.h"
CurrentMonitorINA229 currentmon_internal = CurrentMonitorINA229();
-
+extern void randomCharacters(char *value, int length);
const uart_port_t rs485_uart_num = UART_NUM_1;
const char *wificonfigfilename = "/diybms/wifi.json";
@@ -3730,6 +3730,15 @@ ESP32 Chip model = %u, Rev %u, Cores=%u, Features=%u)",
LoadConfiguration(&mysettings);
ValidateConfiguration(&mysettings);
+ if (strlen(mysettings.homeassist_apikey) == 0)
+ {
+ // Generate new key
+ memset(&mysettings.homeassist_apikey, 0, sizeof(mysettings.homeassist_apikey));
+ randomCharacters(mysettings.homeassist_apikey, sizeof(mysettings.homeassist_apikey) - 1);
+ saveConfiguration();
+ }
+ ESP_LOGI(TAG, "homeassist_apikey=%s", mysettings.homeassist_apikey);
+
if (!EepromConfigValid)
{
// We don't have a valid WIFI configuration, so force terminal based setup
@@ -3857,7 +3866,7 @@ ESP32 Chip model = %u, Rev %u, Cores=%u, Features=%u)",
}
/// @brief Convert an ESP32 core dump stored in FLASH to a JSON object fragment
-/// @param doc
+/// @param doc
void ESPCoreDumpToJSON(JsonObject &doc)
{
if (esp_core_dump_image_check() == ESP_OK)
diff --git a/ESPController/src/settings.cpp b/ESPController/src/settings.cpp
index 482e7f5f..a5d4829f 100644
--- a/ESPController/src/settings.cpp
+++ b/ESPController/src/settings.cpp
@@ -86,6 +86,7 @@ static const char absorptiontimer_JSONKEY[] = "absorptiontimer";
static const char floatvoltage_JSONKEY[] = "floatvoltage";
static const char floatvoltagetimer_JSONKEY[] = "floatvoltagetimer";
static const char stateofchargeresumevalue_JSONKEY[] = "stateofchargeresumevalue";
+static const char homeassist_apikey_JSONKEY[] = "homeassistapikey";
/* NVS KEYS
THESE STRINGS ARE USED TO HOLD THE PARAMETER IN NVS FLASH, MAXIMUM LENGTH OF 16 CHARACTERS
@@ -174,6 +175,7 @@ static const char absorptiontimer_NVSKEY[] = "absorptimer";
static const char floatvoltage_NVSKEY[] = "floatV";
static const char floatvoltagetimer_NVSKEY[] = "floatVtimer";
static const char stateofchargeresumevalue_NVSKEY[] = "socresume";
+static const char homeassist_apikey_NVSKEY[] = "haapikey";
#define MACRO_NVSWRITE(VARNAME) writeSetting(nvs_handle, VARNAME##_NVSKEY, settings->VARNAME);
#define MACRO_NVSWRITE_UINT8(VARNAME) writeSetting(nvs_handle, VARNAME##_NVSKEY, (uint8_t)settings->VARNAME);
@@ -336,7 +338,6 @@ void SaveConfiguration(diybms_eeprom_settings *settings)
}
else
{
-
// Save settings
MACRO_NVSWRITE(totalNumberOfBanks)
MACRO_NVSWRITE(totalNumberOfSeriesModules)
@@ -430,6 +431,8 @@ void SaveConfiguration(diybms_eeprom_settings *settings)
MACRO_NVSWRITE(floatvoltagetimer);
MACRO_NVSWRITE(stateofchargeresumevalue);
+ MACRO_NVSWRITESTRING(homeassist_apikey);
+
ESP_ERROR_CHECK(nvs_commit(nvs_handle));
nvs_close(nvs_handle);
}
@@ -554,6 +557,8 @@ void LoadConfiguration(diybms_eeprom_settings *settings)
MACRO_NVSREAD(floatvoltagetimer);
MACRO_NVSREAD_UINT8(stateofchargeresumevalue);
+ MACRO_NVSREADSTRING(homeassist_apikey);
+
nvs_close(nvs_handle);
}
@@ -994,6 +999,8 @@ void GenerateSettingsJSONDocument(DynamicJsonDocument *doc, diybms_eeprom_settin
root[rs485stopbits_JSONKEY] = settings->rs485stopbits;
root[language_JSONKEY] = settings->language;
+ root[homeassist_apikey_JSONKEY]=settings->homeassist_apikey;
+
JsonObject mqtt = root.createNestedObject("mqtt");
mqtt[mqtt_enabled_JSONKEY] = settings->mqtt_enabled;
mqtt[mqtt_uri_JSONKEY] = settings->mqtt_uri;
@@ -1168,6 +1175,8 @@ void JSONToSettings(DynamicJsonDocument &doc, diybms_eeprom_settings *settings)
settings->current_value1 = root[current_value1_JSONKEY];
settings->current_value2 = root[current_value2_JSONKEY];
+ strncpy(settings->homeassist_apikey, root[homeassist_apikey_JSONKEY].asCharge/Discharge configuration
+
From e0ad6cf8aa2d15dad1c3e2440b41d6728c88dd4b Mon Sep 17 00:00:00 2001
From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com>
Date: Mon, 30 Oct 2023 14:11:57 +0000
Subject: [PATCH 03/27] MQTT changes for reliability
---
.../include/webserver_json_requests.h | 9 +-
ESPController/src/main.cpp | 41 ++++-----
ESPController/src/mqtt.cpp | 84 +++++++++++--------
ESPController/src/webserver_json_requests.cpp | 8 ++
ESPController/web_src/default.htm | 24 +++++-
ESPController/web_src/pagecode.js | 8 +-
6 files changed, 118 insertions(+), 56 deletions(-)
diff --git a/ESPController/include/webserver_json_requests.h b/ESPController/include/webserver_json_requests.h
index d94ef05c..761f7bd3 100644
--- a/ESPController/include/webserver_json_requests.h
+++ b/ESPController/include/webserver_json_requests.h
@@ -42,7 +42,7 @@ extern uint32_t canbus_messages_received_error;
extern Rules rules;
extern ControllerState _controller_state;
-extern void formatCurrentDateTime(char* buf, size_t buf_size);
+extern void formatCurrentDateTime(char *buf, size_t buf_size);
extern void setNoStoreCacheControl(httpd_req_t *req);
extern char CookieValue[20 + 1];
extern std::string hostname;
@@ -56,4 +56,11 @@ extern CurrentMonitorINA229 currentmon_internal;
extern History history;
extern wifi_eeprom_settings _wificonfig;
extern esp_err_t diagnosticJSON(httpd_req_t *req, char buffer[], int bufferLenMax);
+
+extern bool mqttClient_connected;
+extern uint16_t mqtt_error_connection_count;
+extern uint16_t mqtt_error_transport_count;
+extern uint16_t mqtt_connection_count;
+extern uint16_t mqtt_disconnection_count;
+
#endif
diff --git a/ESPController/src/main.cpp b/ESPController/src/main.cpp
index 7100e4b0..aa8ea37f 100644
--- a/ESPController/src/main.cpp
+++ b/ESPController/src/main.cpp
@@ -1577,28 +1577,31 @@ static void startMDNS()
static void event_handler(void *, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
+ ESP_LOGD(TAG, "WIFI: event=%i, id=%i", event_base, event_id);
+
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_BSS_RSSI_LOW)
{
ESP_LOGW(TAG, "WiFi signal strength low");
}
else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
{
+ ESP_LOGI(TAG, "WIFI_EVENT_STA_START");
wifi_isconnected = false;
- esp_wifi_connect();
+ ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_connect());
}
else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED)
{
+ ESP_LOGI(TAG, "WIFI_EVENT_STA_DISCONNECTED");
wifi_isconnected = false;
if (s_retry_num < 200)
{
- esp_wifi_connect();
+ ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_connect());
s_retry_num++;
- ESP_LOGI(TAG, "Retry %i, connect to Wifi AP", s_retry_num);
+ ESP_LOGI(TAG, "Retry %i, connect to WIFI AP", s_retry_num);
}
else
{
- // xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
- ESP_LOGE(TAG, "Connect to the Wifi AP failed");
+ ESP_LOGE(TAG, "Connect to the WIFI AP failed");
}
}
else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_LOST_IP)
@@ -1616,9 +1619,7 @@ static void event_handler(void *, esp_event_base_t event_base,
}
stopMqtt();
stopMDNS();
-
- esp_wifi_disconnect();
-
+ ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_disconnect());
wake_up_tft(true);
// Try and reconnect
@@ -1628,21 +1629,21 @@ static void event_handler(void *, esp_event_base_t event_base,
{
wifi_isconnected = true;
auto event = (ip_event_got_ip_t *)event_data;
- // ESP_LOGI(TAG, "Got ip:" IPSTR, IP2STR(&event->ip_info.ip));
+ ESP_LOGI(TAG, "Got IP:" IPSTR, IP2STR(&event->ip_info.ip));
s_retry_num = 0;
- // xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
// Start up all the services after TCP/IP is established
configureSNTP(mysettings.timeZone * 3600 + mysettings.minutesTimeZone * 60, mysettings.daylight ? 3600 : 0, mysettings.ntpServer);
if (!server_running)
{
+ // Start web server
StartServer();
server_running = true;
}
- connectToMqtt();
-
+ // This only exists in the loop()
+ // connectToMqtt();
startMDNS();
ip_string = ip4_to_string(event->ip_info.ip.addr);
@@ -4077,18 +4078,18 @@ void loop()
// on first pass wifitimer is zero
if (currentMillis - wifitimer > 30000)
{
- // Attempt to connect to WiFi every 30 seconds, this caters for when WiFi drops
- // such as AP reboot
-
- // wifi_init_sta();
- if (!wifi_isconnected)
+ // Attempt to connect to WiFi every 30 seconds, this caters for when WiFi drops such as AP reboot
+ if (wifi_isconnected)
{
+ // Attempt to connect to MQTT if enabled and not already connected
+ connectToMqtt();
+ }
+ else
+ {
+ ESP_LOGI(TAG, "Trying to connect WIFI");
esp_wifi_connect();
}
wifitimer = currentMillis;
-
- // Attempt to connect to MQTT if enabled and not already connected
- connectToMqtt();
}
}
diff --git a/ESPController/src/mqtt.cpp b/ESPController/src/mqtt.cpp
index a8bd8268..52aafa4b 100644
--- a/ESPController/src/mqtt.cpp
+++ b/ESPController/src/mqtt.cpp
@@ -16,6 +16,10 @@ static constexpr const char *const TAG = "diybms-mqtt";
bool mqttClient_connected = false;
esp_mqtt_client_handle_t mqtt_client = nullptr;
+uint16_t mqtt_error_connection_count = 0;
+uint16_t mqtt_error_transport_count = 0;
+uint16_t mqtt_connection_count = 0;
+uint16_t mqtt_disconnection_count = 0;
bool checkMQTTReady()
{
@@ -23,14 +27,20 @@ bool checkMQTTReady()
{
return false;
}
+
+ if (mqtt_client == nullptr)
+ {
+ ESP_LOGW(TAG, "MQTT enabled, but not yet init");
+ return false;
+ }
if (!wifi_isconnected)
{
- ESP_LOGE(TAG, "MQTT enabled. WIFI not connected");
+ ESP_LOGW(TAG, "MQTT enabled, WIFI not connected");
return false;
}
if (mqttClient_connected == false)
{
- ESP_LOGE(TAG, "MQTT enabled. But not connected");
+ ESP_LOGW(TAG, "MQTT enabled, but not connected");
return false;
}
@@ -47,7 +57,7 @@ static inline void publish_message(std::string &topic, std::string &payload, boo
static constexpr int MQTT_QUALITY_OF_SERVICE = 1;
static constexpr int MQTT_RETAIN_MESSAGE = 0;
- if (mqtt_client && mqttClient_connected)
+ if (mqtt_client != nullptr && mqttClient_connected)
{
int id = esp_mqtt_client_publish(
mqtt_client, topic.c_str(), payload.c_str(), payload.length(),
@@ -76,77 +86,89 @@ static void mqtt_connected_handler(void *, esp_event_base_t, int32_t, void *)
{
ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");
mqttClient_connected = true;
+ mqtt_connection_count++;
}
static void mqtt_disconnected_handler(void *, esp_event_base_t, int32_t, void *)
{
ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");
mqttClient_connected = false;
+ mqtt_disconnection_count++;
}
static void mqtt_error_handler(void *, esp_event_base_t, int32_t, void *event_data)
{
- // ESP_LOGD(TAG, "Event base=%s, event_id=%d", base, event_id);
auto event = (esp_mqtt_event_handle_t)event_data;
- // auto client = event->client;
- // int msg_id;
- ESP_LOGE(TAG, "MQTT_EVENT_ERROR");
+ // ESP_LOGE(TAG, "MQTT_EVENT_ERROR type=%i",event->error_handle->error_type);
+ if (event->error_handle->error_type == MQTT_ERROR_TYPE_CONNECTION_REFUSED)
+ {
+ mqtt_error_connection_count++;
+ // esp_mqtt_connect_return_code_t reason for failure
+ ESP_LOGE(TAG, "MQTT_ERROR_TYPE_CONNECTION_REFUSED code=%i", event->error_handle->connect_return_code);
+ }
+
if (event->error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT)
{
+ mqtt_error_transport_count++;
// log_error_if_nonzero("reported from esp-tls", event->error_handle->esp_tls_last_esp_err);
// log_error_if_nonzero("reported from tls stack", event->error_handle->esp_tls_stack_err);
// log_error_if_nonzero("captured as transport's socket errno", event->error_handle->esp_transport_sock_errno);
- ESP_LOGE(TAG, "Last err no string (%s)", strerror(event->error_handle->esp_transport_sock_errno));
+ ESP_LOGE(TAG, "ERROR_TYPE_TCP (%s)", strerror(event->error_handle->esp_transport_sock_errno));
}
}
void stopMqtt()
{
- if (mqtt_client != nullptr && mqttClient_connected)
+ if (mqtt_client != nullptr)
{
- // ESP_LOGI(TAG, "Stopping MQTT client");
+ ESP_LOGI(TAG, "Stopping MQTT client");
mqttClient_connected = false;
- ESP_LOGI(TAG, "esp_mqtt_client_disconnect");
- ESP_ERROR_CHECK_WITHOUT_ABORT(esp_mqtt_client_disconnect(mqtt_client));
- /*
- Comment out to see if this helps with https://github.com/stuartpittaway/diyBMSv4ESP32/issues/225
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_mqtt_client_stop(mqtt_client));
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_mqtt_client_destroy(mqtt_client));
mqtt_client = nullptr;
- */
+
+ //Reset stats
+ mqtt_error_connection_count = 0;
+ mqtt_error_transport_count = 0;
+ mqtt_connection_count = 0;
+ mqtt_disconnection_count = 0;
}
}
// Connects to MQTT if required
void connectToMqtt()
{
- if (mysettings.mqtt_enabled && mqttClient_connected)
- {
- // Already connected and enabled
- return;
- }
+ ESP_LOGI(TAG, "MQTT counters: Err_Con=%u,Err_Trans=%u,Conn=%u,Disc=%u", mqtt_error_connection_count,
+ mqtt_error_transport_count, mqtt_connection_count, mqtt_disconnection_count);
- if (mysettings.mqtt_enabled)
+ if (mysettings.mqtt_enabled && mqtt_client == nullptr)
{
- // stopMqtt();
-
- ESP_LOGI(TAG, "Connect MQTT");
+ ESP_LOGI(TAG, "esp_mqtt_client_init");
// Need to preset variables in esp_mqtt_client_config_t otherwise LoadProhibited errors
esp_mqtt_client_config_t mqtt_cfg{
- .event_handle = nullptr, .host = "", .uri = mysettings.mqtt_uri, .disable_auto_reconnect = false};
-
- mqtt_cfg.username = mysettings.mqtt_username;
- mqtt_cfg.password = mysettings.mqtt_password;
+ .event_handle = nullptr,
+ .host = "",
+ .uri = mysettings.mqtt_uri,
+ .username = mysettings.mqtt_username,
+ .password = mysettings.mqtt_password,
+ //Reconnect if there server has a problem (or wrong IP/password etc.)
+ .disable_auto_reconnect = false,
+ //30 seconds
+ .reconnect_timeout_ms = 30000,
+ //3 seconds
+ .network_timeout_ms = 3000};
mqtt_client = esp_mqtt_client_init(&mqtt_cfg);
+
if (mqtt_client != nullptr)
{
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_mqtt_client_register_event(mqtt_client, esp_mqtt_event_id_t::MQTT_EVENT_CONNECTED, mqtt_connected_handler, nullptr));
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_mqtt_client_register_event(mqtt_client, esp_mqtt_event_id_t::MQTT_EVENT_DISCONNECTED, mqtt_disconnected_handler, nullptr));
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_mqtt_client_register_event(mqtt_client, esp_mqtt_event_id_t::MQTT_EVENT_ERROR, mqtt_error_handler, nullptr));
+ ESP_LOGI(TAG, "esp_mqtt_client_start");
if (ESP_ERROR_CHECK_WITHOUT_ABORT(esp_mqtt_client_start(mqtt_client)) != ESP_OK)
{
ESP_LOGE(TAG, "esp_mqtt_client_start failed");
@@ -154,13 +176,9 @@ void connectToMqtt()
}
else
{
- ESP_LOGE(TAG, "mqtt_client returned NULL");
+ ESP_LOGE(TAG, "esp_mqtt_client_init returned NULL");
}
}
- /*else
- {
- stopMqtt();
- }*/
}
void GeneralStatusPayload(const PacketRequestGenerator *prg, const PacketReceiveProcessor *receiveProc, uint16_t requestq_count, const Rules *rules)
diff --git a/ESPController/src/webserver_json_requests.cpp b/ESPController/src/webserver_json_requests.cpp
index 5129f6bd..6670b9c7 100644
--- a/ESPController/src/webserver_json_requests.cpp
+++ b/ESPController/src/webserver_json_requests.cpp
@@ -808,6 +808,14 @@ esp_err_t content_handler_integration(httpd_req_t *req)
mqtt["topic"] = mysettings.mqtt_topic;
mqtt["uri"] = mysettings.mqtt_uri;
mqtt["username"] = mysettings.mqtt_username;
+
+ mqtt["connected"]= mqttClient_connected;
+ mqtt["err_conn_count"]=mqtt_error_connection_count;
+ mqtt["err_trans_count"]=mqtt_error_transport_count;
+ mqtt["conn_count"]=mqtt_connection_count;
+ mqtt["disc_count"]=mqtt_disconnection_count;
+
+
// We don't output the password in the json file as this could breach security
// mqtt["password"] =mysettings.mqtt_password;
diff --git a/ESPController/web_src/default.htm b/ESPController/web_src/default.htm
index a562f55b..3c3f2c85 100644
--- a/ESPController/web_src/default.htm
+++ b/ESPController/web_src/default.htm
@@ -343,7 +343,7 @@ Diagnostics
-
+Running tasks: @@ -517,6 +517,28 @@
These settings require an external inverter/charger to be able to integrate using CANBUS to control - charge/discharge parameters. + charge/discharge parameters. Pylontech normally operates at 500k baud. Victron can use other speeds depending on the device.
Temperature control utilises the external temperature sensors on the diyBMS modules. This is very useful for LIFEPO4 cells which cannot be charged when below 0°C.
Current shunt/monitor is required for reliable integration with external inverter/chargers.
-