From 2d97b635287861938fed0df88ed6cf074471a086 Mon Sep 17 00:00:00 2001 From: No-Signal Date: Fri, 3 Jan 2025 10:04:15 +0000 Subject: [PATCH 01/11] Adding ability to remotely trigger a BMS reset via MQTT --- Software/src/devboard/mqtt/mqtt.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Software/src/devboard/mqtt/mqtt.cpp b/Software/src/devboard/mqtt/mqtt.cpp index e0aef893..63cc6d3b 100644 --- a/Software/src/devboard/mqtt/mqtt.cpp +++ b/Software/src/devboard/mqtt/mqtt.cpp @@ -6,6 +6,7 @@ #include "../../../USER_SETTINGS.h" #include "../../battery/BATTERIES.h" #include "../../communication/contactorcontrol/comm_contactorcontrol.h" +#include "../../communication/contactorcontrol/comm_contactorcontrol.h" #include "../../datalayer/datalayer.h" #include "../../lib/bblanchon-ArduinoJson/ArduinoJson.h" #include "../../lib/knolleary-pubsubclient/PubSubClient.h" From ba3717738246f13d8f56d12435bdc98658191f2f Mon Sep 17 00:00:00 2001 From: No-Signal Date: Tue, 7 Jan 2025 22:21:27 +0000 Subject: [PATCH 02/11] Replacing non maintained mqtt library with ESP-MQTT --- Software/src/devboard/mqtt/mqtt.cpp | 119 +-- .../knolleary-pubsubclient/PubSubClient.cpp | 769 ------------------ .../lib/knolleary-pubsubclient/PubSubClient.h | 184 ----- min_spiffs.csv | 7 + platformio.ini | 1 + 5 files changed, 49 insertions(+), 1031 deletions(-) delete mode 100644 Software/src/lib/knolleary-pubsubclient/PubSubClient.cpp delete mode 100644 Software/src/lib/knolleary-pubsubclient/PubSubClient.h create mode 100644 min_spiffs.csv diff --git a/Software/src/devboard/mqtt/mqtt.cpp b/Software/src/devboard/mqtt/mqtt.cpp index 63cc6d3b..603a01e3 100644 --- a/Software/src/devboard/mqtt/mqtt.cpp +++ b/Software/src/devboard/mqtt/mqtt.cpp @@ -9,12 +9,12 @@ #include "../../communication/contactorcontrol/comm_contactorcontrol.h" #include "../../datalayer/datalayer.h" #include "../../lib/bblanchon-ArduinoJson/ArduinoJson.h" -#include "../../lib/knolleary-pubsubclient/PubSubClient.h" #include "../utils/events.h" #include "../utils/timer.h" +#include "mqtt_client.h" -WiFiClient espClient; -PubSubClient client(espClient); +esp_mqtt_client_config_t mqtt_cfg; +esp_mqtt_client_handle_t client; char mqtt_msg[MQTT_MSG_BUFFER_SIZE]; MyTimer publish_global_timer(5000); //publish timer MyTimer check_global_timer(800); // check timmer - low-priority MQTT checks, where responsiveness is not critical. @@ -24,12 +24,6 @@ static String object_id_prefix = ""; static String device_name = ""; static String device_id = ""; -// Tracking reconnection attempts and failures -static unsigned long lastReconnectAttempt = 0; -static uint8_t reconnectAttempts = 0; -static const uint8_t maxReconnectAttempts = 5; -static bool connected_once = false; - static void publish_common_info(void); static void publish_cell_voltages(void); static void publish_events(void); @@ -453,18 +447,18 @@ static void subscribe() { #ifdef DEBUG_LOG logging.printf("Subscribing to topic: [%s]\n", topic); #endif // DEBUG_LOG - client.subscribe(topic); + esp_mqtt_client_subscribe(client, topic, 0); } } -void mqtt_message_received(char* topic, byte* payload, unsigned int length) { +void mqtt_message_received(char* topic, int topic_len, char* data, int data_len) { #ifdef DEBUG_LOG - logging.printf("MQTT message arrived: [%s]\n", topic); + logging.printf("MQTT message arrived: [%.*s]\n", topic_len, topic); #endif // DEBUG_LOG #ifdef REMOTE_BMS_RESET const char* bmsreset_topic = generateButtonTopic("BMSRESET").c_str(); - if (strcmp(topic, bmsreset_topic) == 0) { + if (strncmp(topic, bmsreset_topic, topic_len) == 0) { #ifdef DEBUG_LOG logging.println("Triggering BMS reset"); #endif // DEBUG_LOG @@ -473,40 +467,29 @@ void mqtt_message_received(char* topic, byte* payload, unsigned int length) { #endif // REMOTE_BMS_RESET } -/* If we lose the connection, get it back */ -static bool reconnect() { -// attempt one reconnection -#ifdef DEBUG_LOG - logging.print("Attempting MQTT connection... "); -#endif // DEBUG_LOG - char clientId[64]; // Adjust the size as needed - snprintf(clientId, sizeof(clientId), "BatteryEmulatorClient-%s", WiFi.getHostname()); - // Attempt to connect - if (client.connect(clientId, mqtt_user, mqtt_password)) { - connected_once = true; - clear_event(EVENT_MQTT_DISCONNECT); - set_event(EVENT_MQTT_CONNECT, 0); - reconnectAttempts = 0; // Reset attempts on successful connection -#ifdef HA_AUTODISCOVERY - publish_buttons_discovery(); -#endif - subscribe(); +static void mqtt_event_handler(void* handler_args, esp_event_base_t base, int32_t event_id, void* event_data) { + esp_mqtt_event_handle_t event = (esp_mqtt_event_handle_t)event_data; + switch ((esp_mqtt_event_id_t)event_id) { + case MQTT_EVENT_CONNECTED: + clear_event(EVENT_MQTT_DISCONNECT); + set_event(EVENT_MQTT_CONNECT, 0); + + publish_buttons_discovery(); + subscribe(); #ifdef DEBUG_LOG - logging.println("connected"); + logging.println("MQTT connected"); #endif // DEBUG_LOG - clear_event(EVENT_MQTT_CONNECT); - } else { - if (connected_once) + break; + case MQTT_EVENT_DISCONNECTED: set_event(EVENT_MQTT_DISCONNECT, 0); - reconnectAttempts++; // Count failed attempts #ifdef DEBUG_LOG - logging.print("failed, rc="); - logging.print(client.state()); - logging.println(" try again in 5 seconds"); + logging.println("MQTT disconnected!"); #endif // DEBUG_LOG - // Wait 5 seconds before retrying + break; + case MQTT_EVENT_DATA: + mqtt_message_received(event->topic, event->topic_len, event->data, event->data_len); + break; } - return client.connected(); } void init_mqtt(void) { @@ -527,54 +510,34 @@ void init_mqtt(void) { #endif #endif - client.setServer(MQTT_SERVER, MQTT_PORT); - client.setCallback(mqtt_message_received); + char clientId[64]; // Adjust the size as needed + snprintf(clientId, sizeof(clientId), "BatteryEmulatorClient-%s", WiFi.getHostname()); + mqtt_cfg.broker.address.transport = MQTT_TRANSPORT_OVER_TCP; + mqtt_cfg.broker.address.hostname = MQTT_SERVER; + mqtt_cfg.broker.address.port = MQTT_PORT; + mqtt_cfg.credentials.client_id = clientId; + mqtt_cfg.credentials.username = MQTT_USER; + mqtt_cfg.credentials.authentication.password = MQTT_PASSWORD; + client = esp_mqtt_client_init(&mqtt_cfg); + esp_mqtt_client_register_event(client, MQTT_EVENT_ANY, mqtt_event_handler, client); + esp_mqtt_client_start(client); + #ifdef DEBUG_LOG logging.println("MQTT initialized"); #endif // DEBUG_LOG - - client.setKeepAlive(30); // Increase keepalive to manage network latency better. default is 15 - - lastReconnectAttempt = millis(); - reconnect(); } void mqtt_loop(void) { // Only attempt to publish/reconnect MQTT if Wi-Fi is connectedand checkTimmer is elapsed if (check_global_timer.elapsed() && WiFi.status() == WL_CONNECTED) { - if (client.connected()) { - client.loop(); - if (publish_global_timer.elapsed()) // Every 5s - { - publish_values(); - } - } else { - if (connected_once) - set_event(EVENT_MQTT_DISCONNECT, 0); - unsigned long now = millis(); - if (now - lastReconnectAttempt >= 5000) // Every 5s - { - lastReconnectAttempt = now; - if (reconnect()) { - lastReconnectAttempt = 0; - } else if (reconnectAttempts >= maxReconnectAttempts) { -#ifdef DEBUG_LOG - logging.println("Too many failed reconnect attempts, restarting client."); -#endif - client.disconnect(); // Force close the MQTT client connection - reconnectAttempts = 0; // Reset attempts to avoid infinite loop - } - } + if (publish_global_timer.elapsed()) // Every 5s + { + publish_values(); } } } bool mqtt_publish(const char* topic, const char* mqtt_msg, bool retain) { - if (client.connected() == true) { - return client.publish(topic, mqtt_msg, retain); - } - if (connected_once) - set_event(EVENT_MQTT_DISCONNECT, 0); - - return false; + int msg_id = esp_mqtt_client_publish(client, topic, mqtt_msg, strlen(mqtt_msg), 0, retain); + return msg_id > -1; } diff --git a/Software/src/lib/knolleary-pubsubclient/PubSubClient.cpp b/Software/src/lib/knolleary-pubsubclient/PubSubClient.cpp deleted file mode 100644 index 2b48d2b6..00000000 --- a/Software/src/lib/knolleary-pubsubclient/PubSubClient.cpp +++ /dev/null @@ -1,769 +0,0 @@ -/* - - PubSubClient.cpp - A simple client for MQTT. - Nick O'Leary - http://knolleary.net -*/ - -#include "PubSubClient.h" -#include "Arduino.h" - -PubSubClient::PubSubClient() { - this->_state = MQTT_DISCONNECTED; - this->_client = NULL; - this->stream = NULL; - setCallback(NULL); - this->bufferSize = 0; - setBufferSize(MQTT_MAX_PACKET_SIZE); - setKeepAlive(MQTT_KEEPALIVE); - setSocketTimeout(MQTT_SOCKET_TIMEOUT); -} - -PubSubClient::PubSubClient(Client& client) { - this->_state = MQTT_DISCONNECTED; - setClient(client); - this->stream = NULL; - this->bufferSize = 0; - setBufferSize(MQTT_MAX_PACKET_SIZE); - setKeepAlive(MQTT_KEEPALIVE); - setSocketTimeout(MQTT_SOCKET_TIMEOUT); -} - -PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client& client) { - this->_state = MQTT_DISCONNECTED; - setServer(addr, port); - setClient(client); - this->stream = NULL; - this->bufferSize = 0; - setBufferSize(MQTT_MAX_PACKET_SIZE); - setKeepAlive(MQTT_KEEPALIVE); - setSocketTimeout(MQTT_SOCKET_TIMEOUT); -} -PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client& client, Stream& stream) { - this->_state = MQTT_DISCONNECTED; - setServer(addr,port); - setClient(client); - setStream(stream); - this->bufferSize = 0; - setBufferSize(MQTT_MAX_PACKET_SIZE); - setKeepAlive(MQTT_KEEPALIVE); - setSocketTimeout(MQTT_SOCKET_TIMEOUT); -} -PubSubClient::PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) { - this->_state = MQTT_DISCONNECTED; - setServer(addr, port); - setCallback(callback); - setClient(client); - this->stream = NULL; - this->bufferSize = 0; - setBufferSize(MQTT_MAX_PACKET_SIZE); - setKeepAlive(MQTT_KEEPALIVE); - setSocketTimeout(MQTT_SOCKET_TIMEOUT); -} -PubSubClient::PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) { - this->_state = MQTT_DISCONNECTED; - setServer(addr,port); - setCallback(callback); - setClient(client); - setStream(stream); - this->bufferSize = 0; - setBufferSize(MQTT_MAX_PACKET_SIZE); - setKeepAlive(MQTT_KEEPALIVE); - setSocketTimeout(MQTT_SOCKET_TIMEOUT); -} - -PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client& client) { - this->_state = MQTT_DISCONNECTED; - setServer(ip, port); - setClient(client); - this->stream = NULL; - this->bufferSize = 0; - setBufferSize(MQTT_MAX_PACKET_SIZE); - setKeepAlive(MQTT_KEEPALIVE); - setSocketTimeout(MQTT_SOCKET_TIMEOUT); -} -PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client& client, Stream& stream) { - this->_state = MQTT_DISCONNECTED; - setServer(ip,port); - setClient(client); - setStream(stream); - this->bufferSize = 0; - setBufferSize(MQTT_MAX_PACKET_SIZE); - setKeepAlive(MQTT_KEEPALIVE); - setSocketTimeout(MQTT_SOCKET_TIMEOUT); -} -PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) { - this->_state = MQTT_DISCONNECTED; - setServer(ip, port); - setCallback(callback); - setClient(client); - this->stream = NULL; - this->bufferSize = 0; - setBufferSize(MQTT_MAX_PACKET_SIZE); - setKeepAlive(MQTT_KEEPALIVE); - setSocketTimeout(MQTT_SOCKET_TIMEOUT); -} -PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) { - this->_state = MQTT_DISCONNECTED; - setServer(ip,port); - setCallback(callback); - setClient(client); - setStream(stream); - this->bufferSize = 0; - setBufferSize(MQTT_MAX_PACKET_SIZE); - setKeepAlive(MQTT_KEEPALIVE); - setSocketTimeout(MQTT_SOCKET_TIMEOUT); -} - -PubSubClient::PubSubClient(const char* domain, uint16_t port, Client& client) { - this->_state = MQTT_DISCONNECTED; - setServer(domain,port); - setClient(client); - this->stream = NULL; - this->bufferSize = 0; - setBufferSize(MQTT_MAX_PACKET_SIZE); - setKeepAlive(MQTT_KEEPALIVE); - setSocketTimeout(MQTT_SOCKET_TIMEOUT); -} -PubSubClient::PubSubClient(const char* domain, uint16_t port, Client& client, Stream& stream) { - this->_state = MQTT_DISCONNECTED; - setServer(domain,port); - setClient(client); - setStream(stream); - this->bufferSize = 0; - setBufferSize(MQTT_MAX_PACKET_SIZE); - setKeepAlive(MQTT_KEEPALIVE); - setSocketTimeout(MQTT_SOCKET_TIMEOUT); -} -PubSubClient::PubSubClient(const char* domain, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) { - this->_state = MQTT_DISCONNECTED; - setServer(domain,port); - setCallback(callback); - setClient(client); - this->stream = NULL; - this->bufferSize = 0; - setBufferSize(MQTT_MAX_PACKET_SIZE); - setKeepAlive(MQTT_KEEPALIVE); - setSocketTimeout(MQTT_SOCKET_TIMEOUT); -} -PubSubClient::PubSubClient(const char* domain, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) { - this->_state = MQTT_DISCONNECTED; - setServer(domain,port); - setCallback(callback); - setClient(client); - setStream(stream); - this->bufferSize = 0; - setBufferSize(MQTT_MAX_PACKET_SIZE); - setKeepAlive(MQTT_KEEPALIVE); - setSocketTimeout(MQTT_SOCKET_TIMEOUT); -} - -PubSubClient::~PubSubClient() { - free(this->buffer); -} - -boolean PubSubClient::connect(const char *id) { - return connect(id,NULL,NULL,0,0,0,0,1); -} - -boolean PubSubClient::connect(const char *id, const char *user, const char *pass) { - return connect(id,user,pass,0,0,0,0,1); -} - -boolean PubSubClient::connect(const char *id, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage) { - return connect(id,NULL,NULL,willTopic,willQos,willRetain,willMessage,1); -} - -boolean PubSubClient::connect(const char *id, const char *user, const char *pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage) { - return connect(id,user,pass,willTopic,willQos,willRetain,willMessage,1); -} - -boolean PubSubClient::connect(const char *id, const char *user, const char *pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage, boolean cleanSession) { - if (!connected()) { - int result = 0; - - - if(_client->connected()) { - result = 1; - } else { - if (domain != NULL) { - result = _client->connect(this->domain, this->port); - } else { - result = _client->connect(this->ip, this->port); - } - } - - if (result == 1) { - nextMsgId = 1; - // Leave room in the buffer for header and variable length field - uint16_t length = MQTT_MAX_HEADER_SIZE; - unsigned int j; - -#if MQTT_VERSION == MQTT_VERSION_3_1 - uint8_t d[9] = {0x00,0x06,'M','Q','I','s','d','p', MQTT_VERSION}; -#define MQTT_HEADER_VERSION_LENGTH 9 -#elif MQTT_VERSION == MQTT_VERSION_3_1_1 - uint8_t d[7] = {0x00,0x04,'M','Q','T','T',MQTT_VERSION}; -#define MQTT_HEADER_VERSION_LENGTH 7 -#endif - for (j = 0;jbuffer[length++] = d[j]; - } - - uint8_t v; - if (willTopic) { - v = 0x04|(willQos<<3)|(willRetain<<5); - } else { - v = 0x00; - } - if (cleanSession) { - v = v|0x02; - } - - if(user != NULL) { - v = v|0x80; - - if(pass != NULL) { - v = v|(0x80>>1); - } - } - this->buffer[length++] = v; - - this->buffer[length++] = ((this->keepAlive) >> 8); - this->buffer[length++] = ((this->keepAlive) & 0xFF); - - CHECK_STRING_LENGTH(length,id) - length = writeString(id,this->buffer,length); - if (willTopic) { - CHECK_STRING_LENGTH(length,willTopic) - length = writeString(willTopic,this->buffer,length); - CHECK_STRING_LENGTH(length,willMessage) - length = writeString(willMessage,this->buffer,length); - } - - if(user != NULL) { - CHECK_STRING_LENGTH(length,user) - length = writeString(user,this->buffer,length); - if(pass != NULL) { - CHECK_STRING_LENGTH(length,pass) - length = writeString(pass,this->buffer,length); - } - } - - write(MQTTCONNECT,this->buffer,length-MQTT_MAX_HEADER_SIZE); - - lastInActivity = lastOutActivity = millis(); - - while (!_client->available()) { - unsigned long t = millis(); - if (t-lastInActivity >= ((int32_t) this->socketTimeout*1000UL)) { - _state = MQTT_CONNECTION_TIMEOUT; - _client->stop(); - return false; - } - } - uint8_t llen; - uint32_t len = readPacket(&llen); - - if (len == 4) { - if (buffer[3] == 0) { - lastInActivity = millis(); - pingOutstanding = false; - _state = MQTT_CONNECTED; - return true; - } else { - _state = buffer[3]; - } - } - _client->stop(); - } else { - _state = MQTT_CONNECT_FAILED; - } - return false; - } - return true; -} - -// reads a byte into result -boolean PubSubClient::readByte(uint8_t * result) { - uint32_t previousMillis = millis(); - while(!_client->available()) { - yield(); - uint32_t currentMillis = millis(); - if(currentMillis - previousMillis >= ((int32_t) this->socketTimeout * 1000)){ - return false; - } - } - *result = _client->read(); - return true; -} - -// reads a byte into result[*index] and increments index -boolean PubSubClient::readByte(uint8_t * result, uint16_t * index){ - uint16_t current_index = *index; - uint8_t * write_address = &(result[current_index]); - if(readByte(write_address)){ - *index = current_index + 1; - return true; - } - return false; -} - -uint32_t PubSubClient::readPacket(uint8_t* lengthLength) { - uint16_t len = 0; - if(!readByte(this->buffer, &len)) return 0; - bool isPublish = (this->buffer[0]&0xF0) == MQTTPUBLISH; - uint32_t multiplier = 1; - uint32_t length = 0; - uint8_t digit = 0; - uint16_t skip = 0; - uint32_t start = 0; - - do { - if (len == 5) { - // Invalid remaining length encoding - kill the connection - _state = MQTT_DISCONNECTED; - _client->stop(); - return 0; - } - if(!readByte(&digit)) return 0; - this->buffer[len++] = digit; - length += (digit & 127) * multiplier; - multiplier <<=7; //multiplier *= 128 - } while ((digit & 128) != 0); - *lengthLength = len-1; - - if (isPublish) { - // Read in topic length to calculate bytes to skip over for Stream writing - if(!readByte(this->buffer, &len)) return 0; - if(!readByte(this->buffer, &len)) return 0; - skip = (this->buffer[*lengthLength+1]<<8)+this->buffer[*lengthLength+2]; - start = 2; - if (this->buffer[0]&MQTTQOS1) { - // skip message id - skip += 2; - } - } - uint32_t idx = len; - - for (uint32_t i = start;istream) { - if (isPublish && idx-*lengthLength-2>skip) { - this->stream->write(digit); - } - } - - if (len < this->bufferSize) { - this->buffer[len] = digit; - len++; - } - idx++; - } - - if (!this->stream && idx > this->bufferSize) { - len = 0; // This will cause the packet to be ignored. - } - return len; -} - -boolean PubSubClient::loop() { - if (connected()) { - unsigned long t = millis(); - if ((t - lastInActivity > this->keepAlive*1000UL) || (t - lastOutActivity > this->keepAlive*1000UL)) { - if (pingOutstanding) { - this->_state = MQTT_CONNECTION_TIMEOUT; - _client->stop(); - return false; - } else { - this->buffer[0] = MQTTPINGREQ; - this->buffer[1] = 0; - _client->write(this->buffer,2); - lastOutActivity = t; - lastInActivity = t; - pingOutstanding = true; - } - } - if (_client->available()) { - uint8_t llen; - uint16_t len = readPacket(&llen); - uint16_t msgId = 0; - uint8_t *payload; - if (len > 0) { - lastInActivity = t; - uint8_t type = this->buffer[0]&0xF0; - if (type == MQTTPUBLISH) { - if (callback) { - uint16_t tl = (this->buffer[llen+1]<<8)+this->buffer[llen+2]; /* topic length in bytes */ - memmove(this->buffer+llen+2,this->buffer+llen+3,tl); /* move topic inside buffer 1 byte to front */ - this->buffer[llen+2+tl] = 0; /* end the topic as a 'C' string with \x00 */ - char *topic = (char*) this->buffer+llen+2; - // msgId only present for QOS>0 - if ((this->buffer[0]&0x06) == MQTTQOS1) { - msgId = (this->buffer[llen+3+tl]<<8)+this->buffer[llen+3+tl+1]; - payload = this->buffer+llen+3+tl+2; - callback(topic,payload,len-llen-3-tl-2); - - this->buffer[0] = MQTTPUBACK; - this->buffer[1] = 2; - this->buffer[2] = (msgId >> 8); - this->buffer[3] = (msgId & 0xFF); - _client->write(this->buffer,4); - lastOutActivity = t; - - } else { - payload = this->buffer+llen+3+tl; - callback(topic,payload,len-llen-3-tl); - } - } - } else if (type == MQTTPINGREQ) { - this->buffer[0] = MQTTPINGRESP; - this->buffer[1] = 0; - _client->write(this->buffer,2); - } else if (type == MQTTPINGRESP) { - pingOutstanding = false; - } - } else if (!connected()) { - // readPacket has closed the connection - return false; - } - } - return true; - } - return false; -} - -boolean PubSubClient::publish(const char* topic, const char* payload) { - return publish(topic,(const uint8_t*)payload, payload ? strnlen(payload, this->bufferSize) : 0,false); -} - -boolean PubSubClient::publish(const char* topic, const char* payload, boolean retained) { - return publish(topic,(const uint8_t*)payload, payload ? strnlen(payload, this->bufferSize) : 0,retained); -} - -boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength) { - return publish(topic, payload, plength, false); -} - -boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength, boolean retained) { - if (connected()) { - if (this->bufferSize < MQTT_MAX_HEADER_SIZE + 2+strnlen(topic, this->bufferSize) + plength) { - // Too long - return false; - } - // Leave room in the buffer for header and variable length field - uint16_t length = MQTT_MAX_HEADER_SIZE; - length = writeString(topic,this->buffer,length); - - // Add payload - uint16_t i; - for (i=0;ibuffer[length++] = payload[i]; - } - - // Write the header - uint8_t header = MQTTPUBLISH; - if (retained) { - header |= 1; - } - return write(header,this->buffer,length-MQTT_MAX_HEADER_SIZE); - } - return false; -} - -boolean PubSubClient::publish_P(const char* topic, const char* payload, boolean retained) { - return publish_P(topic, (const uint8_t*)payload, payload ? strnlen(payload, this->bufferSize) : 0, retained); -} - -boolean PubSubClient::publish_P(const char* topic, const uint8_t* payload, unsigned int plength, boolean retained) { - uint8_t llen = 0; - uint8_t digit; - unsigned int rc = 0; - uint16_t tlen; - unsigned int pos = 0; - unsigned int i; - uint8_t header; - unsigned int len; - int expectedLength; - - if (!connected()) { - return false; - } - - tlen = strnlen(topic, this->bufferSize); - - header = MQTTPUBLISH; - if (retained) { - header |= 1; - } - this->buffer[pos++] = header; - len = plength + 2 + tlen; - do { - digit = len & 127; //digit = len %128 - len >>= 7; //len = len / 128 - if (len > 0) { - digit |= 0x80; - } - this->buffer[pos++] = digit; - llen++; - } while(len>0); - - pos = writeString(topic,this->buffer,pos); - - rc += _client->write(this->buffer,pos); - - for (i=0;iwrite((char)pgm_read_byte_near(payload + i)); - } - - lastOutActivity = millis(); - - expectedLength = 1 + llen + 2 + tlen + plength; - - return (rc == expectedLength); -} - -boolean PubSubClient::beginPublish(const char* topic, unsigned int plength, boolean retained) { - if (connected()) { - // Send the header and variable length field - uint16_t length = MQTT_MAX_HEADER_SIZE; - length = writeString(topic,this->buffer,length); - uint8_t header = MQTTPUBLISH; - if (retained) { - header |= 1; - } - size_t hlen = buildHeader(header, this->buffer, plength+length-MQTT_MAX_HEADER_SIZE); - uint16_t rc = _client->write(this->buffer+(MQTT_MAX_HEADER_SIZE-hlen),length-(MQTT_MAX_HEADER_SIZE-hlen)); - lastOutActivity = millis(); - return (rc == (length-(MQTT_MAX_HEADER_SIZE-hlen))); - } - return false; -} - -int PubSubClient::endPublish() { - return 1; -} - -size_t PubSubClient::write(uint8_t data) { - lastOutActivity = millis(); - return _client->write(data); -} - -size_t PubSubClient::write(const uint8_t *buffer, size_t size) { - lastOutActivity = millis(); - return _client->write(buffer,size); -} - -size_t PubSubClient::buildHeader(uint8_t header, uint8_t* buf, uint16_t length) { - uint8_t lenBuf[4]; - uint8_t llen = 0; - uint8_t digit; - uint8_t pos = 0; - uint16_t len = length; - do { - - digit = len & 127; //digit = len %128 - len >>= 7; //len = len / 128 - if (len > 0) { - digit |= 0x80; - } - lenBuf[pos++] = digit; - llen++; - } while(len>0); - - buf[4-llen] = header; - for (int i=0;i 0) && result) { - bytesToWrite = (bytesRemaining > MQTT_MAX_TRANSFER_SIZE)?MQTT_MAX_TRANSFER_SIZE:bytesRemaining; - rc = _client->write(writeBuf,bytesToWrite); - result = (rc == bytesToWrite); - bytesRemaining -= rc; - writeBuf += rc; - } - return result; -#else - rc = _client->write(buf+(MQTT_MAX_HEADER_SIZE-hlen),length+hlen); - lastOutActivity = millis(); - return (rc == hlen+length); -#endif -} - -boolean PubSubClient::subscribe(const char* topic) { - return subscribe(topic, 0); -} - -boolean PubSubClient::subscribe(const char* topic, uint8_t qos) { - size_t topicLength = strnlen(topic, this->bufferSize); - if (topic == 0) { - return false; - } - if (qos > 1) { - return false; - } - if (this->bufferSize < 9 + topicLength) { - // Too long - return false; - } - if (connected()) { - // Leave room in the buffer for header and variable length field - uint16_t length = MQTT_MAX_HEADER_SIZE; - nextMsgId++; - if (nextMsgId == 0) { - nextMsgId = 1; - } - this->buffer[length++] = (nextMsgId >> 8); - this->buffer[length++] = (nextMsgId & 0xFF); - length = writeString((char*)topic, this->buffer,length); - this->buffer[length++] = qos; - return write(MQTTSUBSCRIBE|MQTTQOS1,this->buffer,length-MQTT_MAX_HEADER_SIZE); - } - return false; -} - -boolean PubSubClient::unsubscribe(const char* topic) { - size_t topicLength = strnlen(topic, this->bufferSize); - if (topic == 0) { - return false; - } - if (this->bufferSize < 9 + topicLength) { - // Too long - return false; - } - if (connected()) { - uint16_t length = MQTT_MAX_HEADER_SIZE; - nextMsgId++; - if (nextMsgId == 0) { - nextMsgId = 1; - } - this->buffer[length++] = (nextMsgId >> 8); - this->buffer[length++] = (nextMsgId & 0xFF); - length = writeString(topic, this->buffer,length); - return write(MQTTUNSUBSCRIBE|MQTTQOS1,this->buffer,length-MQTT_MAX_HEADER_SIZE); - } - return false; -} - -void PubSubClient::disconnect() { - this->buffer[0] = MQTTDISCONNECT; - this->buffer[1] = 0; - _client->write(this->buffer,2); - _state = MQTT_DISCONNECTED; - _client->flush(); - _client->stop(); - lastInActivity = lastOutActivity = millis(); -} - -uint16_t PubSubClient::writeString(const char* string, uint8_t* buf, uint16_t pos) { - const char* idp = string; - uint16_t i = 0; - pos += 2; - while (*idp) { - buf[pos++] = *idp++; - i++; - } - buf[pos-i-2] = (i >> 8); - buf[pos-i-1] = (i & 0xFF); - return pos; -} - - -boolean PubSubClient::connected() { - boolean rc; - if (_client == NULL ) { - rc = false; - } else { - rc = (int)_client->connected(); - if (!rc) { - if (this->_state == MQTT_CONNECTED) { - this->_state = MQTT_CONNECTION_LOST; - _client->flush(); - _client->stop(); - } - } else { - return this->_state == MQTT_CONNECTED; - } - } - return rc; -} - -PubSubClient& PubSubClient::setServer(uint8_t * ip, uint16_t port) { - IPAddress addr(ip[0],ip[1],ip[2],ip[3]); - return setServer(addr,port); -} - -PubSubClient& PubSubClient::setServer(IPAddress ip, uint16_t port) { - this->ip = ip; - this->port = port; - this->domain = NULL; - return *this; -} - -PubSubClient& PubSubClient::setServer(const char * domain, uint16_t port) { - this->domain = domain; - this->port = port; - return *this; -} - -PubSubClient& PubSubClient::setCallback(MQTT_CALLBACK_SIGNATURE) { - this->callback = callback; - return *this; -} - -PubSubClient& PubSubClient::setClient(Client& client){ - this->_client = &client; - return *this; -} - -PubSubClient& PubSubClient::setStream(Stream& stream){ - this->stream = &stream; - return *this; -} - -int PubSubClient::state() { - return this->_state; -} - -boolean PubSubClient::setBufferSize(uint16_t size) { - if (size == 0) { - // Cannot set it back to 0 - return false; - } - if (this->bufferSize == 0) { - this->buffer = (uint8_t*)malloc(size); - } else { - uint8_t* newBuffer = (uint8_t*)realloc(this->buffer, size); - if (newBuffer != NULL) { - this->buffer = newBuffer; - } else { - return false; - } - } - this->bufferSize = size; - return (this->buffer != NULL); -} - -uint16_t PubSubClient::getBufferSize() { - return this->bufferSize; -} -PubSubClient& PubSubClient::setKeepAlive(uint16_t keepAlive) { - this->keepAlive = keepAlive; - return *this; -} -PubSubClient& PubSubClient::setSocketTimeout(uint16_t timeout) { - this->socketTimeout = timeout; - return *this; -} diff --git a/Software/src/lib/knolleary-pubsubclient/PubSubClient.h b/Software/src/lib/knolleary-pubsubclient/PubSubClient.h deleted file mode 100644 index 0a950acf..00000000 --- a/Software/src/lib/knolleary-pubsubclient/PubSubClient.h +++ /dev/null @@ -1,184 +0,0 @@ -/* - PubSubClient.h - A simple client for MQTT. - Nick O'Leary - http://knolleary.net -*/ - -#ifndef PubSubClient_h -#define PubSubClient_h - -#include -#include "IPAddress.h" -#include "Client.h" -#include "Stream.h" - -#define MQTT_VERSION_3_1 3 -#define MQTT_VERSION_3_1_1 4 - -// MQTT_VERSION : Pick the version -//#define MQTT_VERSION MQTT_VERSION_3_1 -#ifndef MQTT_VERSION -#define MQTT_VERSION MQTT_VERSION_3_1_1 -#endif - -// MQTT_MAX_PACKET_SIZE : Maximum packet size. Override with setBufferSize(). -#ifndef MQTT_MAX_PACKET_SIZE -#define MQTT_MAX_PACKET_SIZE 1024 -#endif - -// MQTT_KEEPALIVE : keepAlive interval in Seconds. Override with setKeepAlive() -#ifndef MQTT_KEEPALIVE -#define MQTT_KEEPALIVE 15 -#endif - -// MQTT_SOCKET_TIMEOUT: socket timeout interval in Seconds. Override with setSocketTimeout() -#ifndef MQTT_SOCKET_TIMEOUT -#define MQTT_SOCKET_TIMEOUT 15 -#endif - -// MQTT_MAX_TRANSFER_SIZE : limit how much data is passed to the network client -// in each write call. Needed for the Arduino Wifi Shield. Leave undefined to -// pass the entire MQTT packet in each write call. -//#define MQTT_MAX_TRANSFER_SIZE 80 - -// Possible values for client.state() -#define MQTT_CONNECTION_TIMEOUT -4 -#define MQTT_CONNECTION_LOST -3 -#define MQTT_CONNECT_FAILED -2 -#define MQTT_DISCONNECTED -1 -#define MQTT_CONNECTED 0 -#define MQTT_CONNECT_BAD_PROTOCOL 1 -#define MQTT_CONNECT_BAD_CLIENT_ID 2 -#define MQTT_CONNECT_UNAVAILABLE 3 -#define MQTT_CONNECT_BAD_CREDENTIALS 4 -#define MQTT_CONNECT_UNAUTHORIZED 5 - -#define MQTTCONNECT 1 << 4 // Client request to connect to Server -#define MQTTCONNACK 2 << 4 // Connect Acknowledgment -#define MQTTPUBLISH 3 << 4 // Publish message -#define MQTTPUBACK 4 << 4 // Publish Acknowledgment -#define MQTTPUBREC 5 << 4 // Publish Received (assured delivery part 1) -#define MQTTPUBREL 6 << 4 // Publish Release (assured delivery part 2) -#define MQTTPUBCOMP 7 << 4 // Publish Complete (assured delivery part 3) -#define MQTTSUBSCRIBE 8 << 4 // Client Subscribe request -#define MQTTSUBACK 9 << 4 // Subscribe Acknowledgment -#define MQTTUNSUBSCRIBE 10 << 4 // Client Unsubscribe request -#define MQTTUNSUBACK 11 << 4 // Unsubscribe Acknowledgment -#define MQTTPINGREQ 12 << 4 // PING Request -#define MQTTPINGRESP 13 << 4 // PING Response -#define MQTTDISCONNECT 14 << 4 // Client is Disconnecting -#define MQTTReserved 15 << 4 // Reserved - -#define MQTTQOS0 (0 << 1) -#define MQTTQOS1 (1 << 1) -#define MQTTQOS2 (2 << 1) - -// Maximum size of fixed header and variable length size header -#define MQTT_MAX_HEADER_SIZE 5 - -#if defined(ESP8266) || defined(ESP32) -#include -#define MQTT_CALLBACK_SIGNATURE std::function callback -#else -#define MQTT_CALLBACK_SIGNATURE void (*callback)(char*, uint8_t*, unsigned int) -#endif - -#define CHECK_STRING_LENGTH(l,s) if (l+2+strnlen(s, this->bufferSize) > this->bufferSize) {_client->stop();return false;} - -class PubSubClient : public Print { -private: - Client* _client; - uint8_t* buffer; - uint16_t bufferSize; - uint16_t keepAlive; - uint16_t socketTimeout; - uint16_t nextMsgId; - unsigned long lastOutActivity; - unsigned long lastInActivity; - bool pingOutstanding; - MQTT_CALLBACK_SIGNATURE; - uint32_t readPacket(uint8_t*); - boolean readByte(uint8_t * result); - boolean readByte(uint8_t * result, uint16_t * index); - boolean write(uint8_t header, uint8_t* buf, uint16_t length); - uint16_t writeString(const char* string, uint8_t* buf, uint16_t pos); - // Build up the header ready to send - // Returns the size of the header - // Note: the header is built at the end of the first MQTT_MAX_HEADER_SIZE bytes, so will start - // (MQTT_MAX_HEADER_SIZE - ) bytes into the buffer - size_t buildHeader(uint8_t header, uint8_t* buf, uint16_t length); - IPAddress ip; - const char* domain; - uint16_t port; - Stream* stream; - int _state; -public: - PubSubClient(); - PubSubClient(Client& client); - PubSubClient(IPAddress, uint16_t, Client& client); - PubSubClient(IPAddress, uint16_t, Client& client, Stream&); - PubSubClient(IPAddress, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client); - PubSubClient(IPAddress, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&); - PubSubClient(uint8_t *, uint16_t, Client& client); - PubSubClient(uint8_t *, uint16_t, Client& client, Stream&); - PubSubClient(uint8_t *, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client); - PubSubClient(uint8_t *, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&); - PubSubClient(const char*, uint16_t, Client& client); - PubSubClient(const char*, uint16_t, Client& client, Stream&); - PubSubClient(const char*, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client); - PubSubClient(const char*, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&); - - ~PubSubClient(); - - PubSubClient& setServer(IPAddress ip, uint16_t port); - PubSubClient& setServer(uint8_t * ip, uint16_t port); - PubSubClient& setServer(const char * domain, uint16_t port); - PubSubClient& setCallback(MQTT_CALLBACK_SIGNATURE); - PubSubClient& setClient(Client& client); - PubSubClient& setStream(Stream& stream); - PubSubClient& setKeepAlive(uint16_t keepAlive); - PubSubClient& setSocketTimeout(uint16_t timeout); - - boolean setBufferSize(uint16_t size); - uint16_t getBufferSize(); - - boolean connect(const char* id); - boolean connect(const char* id, const char* user, const char* pass); - boolean connect(const char* id, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage); - boolean connect(const char* id, const char* user, const char* pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage); - boolean connect(const char* id, const char* user, const char* pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage, boolean cleanSession); - void disconnect(); - boolean publish(const char* topic, const char* payload); - boolean publish(const char* topic, const char* payload, boolean retained); - boolean publish(const char* topic, const uint8_t * payload, unsigned int plength); - boolean publish(const char* topic, const uint8_t * payload, unsigned int plength, boolean retained); - boolean publish_P(const char* topic, const char* payload, boolean retained); - boolean publish_P(const char* topic, const uint8_t * payload, unsigned int plength, boolean retained); - // Start to publish a message. - // This API: - // beginPublish(...) - // one or more calls to write(...) - // endPublish() - // Allows for arbitrarily large payloads to be sent without them having to be copied into - // a new buffer and held in memory at one time - // Returns 1 if the message was started successfully, 0 if there was an error - boolean beginPublish(const char* topic, unsigned int plength, boolean retained); - // Finish off this publish message (started with beginPublish) - // Returns 1 if the packet was sent successfully, 0 if there was an error - int endPublish(); - // Write a single byte of payload (only to be used with beginPublish/endPublish) - virtual size_t write(uint8_t); - // Write size bytes from buffer into the payload (only to be used with beginPublish/endPublish) - // Returns the number of bytes written - virtual size_t write(const uint8_t *buffer, size_t size); - boolean subscribe(const char* topic); - boolean subscribe(const char* topic, uint8_t qos); - boolean unsubscribe(const char* topic); - boolean loop(); - boolean connected(); - int state(); - -}; - - -#endif diff --git a/min_spiffs.csv b/min_spiffs.csv new file mode 100644 index 00000000..87f2c475 --- /dev/null +++ b/min_spiffs.csv @@ -0,0 +1,7 @@ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x5000, +otadata, data, ota, 0xe000, 0x2000, +app0, app, ota_0, 0x10000, 0x1E0000, +app1, app, ota_1, 0x1F0000,0x1E0000, +spiffs, data, spiffs, 0x3D0000,0x20000, +coredump, data, coredump,0x3F0000,0x10000, \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index f7cca324..3d35964e 100644 --- a/platformio.ini +++ b/platformio.ini @@ -19,6 +19,7 @@ platform_packages= board = esp32dev monitor_speed = 115200 monitor_filters = default, time, log2file +board_build.partitions = min_spiffs.csv framework = arduino build_flags = -I include lib_deps = From 4c90898117315ca190d92d80f20bfffb73216f75 Mon Sep 17 00:00:00 2001 From: No-Signal Date: Thu, 9 Jan 2025 20:57:19 +0000 Subject: [PATCH 03/11] Restructuring code to remove duplicates and reduce flash size --- Software/src/devboard/mqtt/mqtt.cpp | 222 +++++++++++----------------- 1 file changed, 89 insertions(+), 133 deletions(-) diff --git a/Software/src/devboard/mqtt/mqtt.cpp b/Software/src/devboard/mqtt/mqtt.cpp index 603a01e3..fa51ff3c 100644 --- a/Software/src/devboard/mqtt/mqtt.cpp +++ b/Software/src/devboard/mqtt/mqtt.cpp @@ -44,48 +44,44 @@ struct SensorConfig { const char* device_class; }; -SensorConfig sensorConfigs[] = { - {"SOC", "SOC (scaled)", "{{ value_json.SOC }}", "%", "battery"}, - {"SOC_real", "SOC (real)", "{{ value_json.SOC_real }}", "%", "battery"}, - {"state_of_health", "State Of Health", "{{ value_json.state_of_health }}", "%", "battery"}, - {"temperature_min", "Temperature Min", "{{ value_json.temperature_min }}", "°C", "temperature"}, - {"temperature_max", "Temperature Max", "{{ value_json.temperature_max }}", "°C", "temperature"}, - {"stat_batt_power", "Stat Batt Power", "{{ value_json.stat_batt_power }}", "W", "power"}, - {"battery_current", "Battery Current", "{{ value_json.battery_current }}", "A", "current"}, - {"cell_max_voltage", "Cell Max Voltage", "{{ value_json.cell_max_voltage }}", "V", "voltage"}, - {"cell_min_voltage", "Cell Min Voltage", "{{ value_json.cell_min_voltage }}", "V", "voltage"}, - {"battery_voltage", "Battery Voltage", "{{ value_json.battery_voltage }}", "V", "voltage"}, - {"total_capacity", "Battery Total Capacity", "{{ value_json.total_capacity }}", "Wh", "energy"}, - {"remaining_capacity", "Battery Remaining Capacity (scaled)", "{{ value_json.remaining_capacity }}", "Wh", - "energy"}, - {"remaining_capacity_real", "Battery Remaining Capacity (real)", "{{ value_json.remaining_capacity_real }}", "Wh", - "energy"}, - {"max_discharge_power", "Battery Max Discharge Power", "{{ value_json.max_discharge_power }}", "W", "power"}, - {"max_charge_power", "Battery Max Charge Power", "{{ value_json.max_charge_power }}", "W", "power"}, - {"bms_status", "BMS Status", "{{ value_json.bms_status }}", "", ""}, - {"pause_status", "Pause Status", "{{ value_json.pause_status }}", "", ""}, +SensorConfig sensorConfigTemplate[] = { + {"SOC", "SOC (Scaled)", "", "%", "battery"}, + {"SOC_real", "SOC (real)", "", "%", "battery"}, + {"state_of_health", "State Of Health", "", "%", "battery"}, + {"temperature_min", "Temperature Min", "", "°C", "temperature"}, + {"temperature_max", "Temperature Max", "", "°C", "temperature"}, + {"stat_batt_power", "Stat Batt Power", "", "W", "power"}, + {"battery_current", "Battery Current", "", "A", "current"}, + {"cell_max_voltage", "Cell Max Voltage", "", "V", "voltage"}, + {"cell_min_voltage", "Cell Min Voltage", "", "V", "voltage"}, + {"battery_voltage", "Battery Voltage", "", "V", "voltage"}, + {"total_capacity", "Battery Total Capacity", "", "Wh", "energy"}, + {"remaining_capacity", "Battery Remaining Capacity (scaled)", "", "Wh", "energy"}, + {"remaining_capacity_real", "Battery Remaining Capacity (real)", "", "Wh", "energy"}, + {"max_discharge_power", "Battery Max Discharge Power", "", "W", "power"}, + {"max_charge_power", "Battery Max Charge Power", "", "W", "power"}, + {"bms_status", "BMS Status", "", "", ""}, + {"pause_status", "Pause Status", "", "", ""}}; + #ifdef DOUBLE_BATTERY - {"SOC_2", "SOC 2 (scaled)", "{{ value_json.SOC_2 }}", "%", "battery"}, - {"SOC_real_2", "SOC 2 (real)", "{{ value_json.SOC_real_2 }}", "%", "battery"}, - {"state_of_health_2", "State Of Health 2", "{{ value_json.state_of_health_2 }}", "%", "battery"}, - {"temperature_min_2", "Temperature Min 2", "{{ value_json.temperature_min_2 }}", "°C", "temperature"}, - {"temperature_max_2", "Temperature Max 2", "{{ value_json.temperature_max_2 }}", "°C", "temperature"}, - {"stat_batt_power_2", "Stat Batt Power 2", "{{ value_json.stat_batt_power_2 }}", "W", "power"}, - {"battery_current_2", "Battery 2 Current", "{{ value_json.battery_current_2 }}", "A", "current"}, - {"cell_max_voltage_2", "Cell Max Voltage 2", "{{ value_json.cell_max_voltage_2 }}", "V", "voltage"}, - {"cell_min_voltage_2", "Cell Min Voltage 2", "{{ value_json.cell_min_voltage_2 }}", "V", "voltage"}, - {"battery_voltage_2", "Battery 2 Voltage", "{{ value_json.battery_voltage_2 }}", "V", "voltage"}, - {"total_capacity_2", "Battery 2 Total Capacity", "{{ value_json.total_capacity_2 }}", "Wh", "energy"}, - {"remaining_capacity_2", "Battery 2 Remaining Capacity (scaled)", "{{ value_json.remaining_capacity_2 }}", "Wh", - "energy"}, - {"remaining_capacity_real_2", "Battery 2 Remaining Capacity (real)", "{{ value_json.remaining_capacity_real_2 }}", - "Wh", "energy"}, - {"max_discharge_power_2", "Battery 2 Max Discharge Power", "{{ value_json.max_discharge_power_2 }}", "W", "power"}, - {"max_charge_power_2", "Battery 2 Max Charge Power", "{{ value_json.max_charge_power_2 }}", "W", "power"}, - {"bms_status_2", "BMS 2 Status", "{{ value_json.bms_status_2 }}", "", ""}, - {"pause_status_2", "Pause Status 2", "{{ value_json.pause_status_2 }}", "", ""}, +SensorConfig sensorConfigs[34]; +#else +SensorConfig sensorConfigs[17]; #endif // DOUBLE_BATTERY -}; + +void create_sensor_configs() { + for (int i = 0; i < sizeof(sensorConfigTemplate) / sizeof(sensorConfigTemplate[0]); i++) { + SensorConfig& config = sensorConfigTemplate[i]; + config.value_template = ("{{ value_json." + String(config.object_id) + " }}").c_str(); + sensorConfigs[i] = config; +#ifdef DOUBLE_BATTERY + sensorConfigs[i + 17] = config; + sensorConfigs[i + 17].object_id = String(config.object_id + String("_2")).c_str(); + String temp_value_template = "{{ value_json." + String(sensorConfigs[i + 17].object_id) + " }}"; + sensorConfigs[i + 17].value_template = temp_value_template.c_str(); +#endif // DOUBLE_BATTERY + } +} SensorConfig buttonConfigs[] = {{"BMSRESET", "Reset BMS", "", "", ""}}; @@ -109,6 +105,49 @@ static String generateButtonAutoConfigTopic(const char* subtype) { return generateButtonTopic(subtype) + "/config"; } +void set_common_discovery_attributes(JsonDocument& doc) { + doc["device"]["identifiers"][0] = ha_device_id; + doc["device"]["manufacturer"] = "DalaTech"; + doc["device"]["model"] = "BatteryEmulator"; + doc["origin"]["name"] = "BatteryEmulator"; + doc["origin"]["sw"] = String(version_number) + "-mqtt"; + doc["origin"]["url"] = "https://github.com/dalathegreat/Battery-Emulator"; +} + +void set_battery_attributes(JsonDocument& doc, const DATALAYER_BATTERY_TYPE& battery, const String& suffix) { + doc["SOC" + suffix] = ((float)battery.status.reported_soc) / 100.0; + doc["SOC_real" + suffix] = ((float)battery.status.real_soc) / 100.0; + doc["state_of_health" + suffix] = ((float)battery.status.soh_pptt) / 100.0; + doc["temperature_min" + suffix] = ((float)((int16_t)battery.status.temperature_min_dC)) / 10.0; + doc["temperature_max" + suffix] = ((float)((int16_t)battery.status.temperature_max_dC)) / 10.0; + doc["stat_batt_power" + suffix] = ((float)((int32_t)battery.status.active_power_W)); + doc["battery_current" + suffix] = ((float)((int16_t)battery.status.current_dA)) / 10.0; + doc["battery_voltage" + suffix] = ((float)battery.status.voltage_dV) / 10.0; + if (battery.info.number_of_cells != 0u && battery.status.cell_voltages_mV[battery.info.number_of_cells - 1] != 0u) { + doc["cell_max_voltage" + suffix] = ((float)battery.status.cell_max_voltage_mV) / 1000.0; + doc["cell_min_voltage" + suffix] = ((float)battery.status.cell_min_voltage_mV) / 1000.0; + } + doc["total_capacity" + suffix] = ((float)battery.info.total_capacity_Wh); + doc["remaining_capacity_real" + suffix] = ((float)battery.status.remaining_capacity_Wh); + doc["remaining_capacity" + suffix] = ((float)battery.status.reported_remaining_capacity_Wh); + doc["max_discharge_power" + suffix] = ((float)battery.status.max_discharge_power_W); + doc["max_charge_power" + suffix] = ((float)battery.status.max_charge_power_W); +} + +void set_battery_voltage_attributes(JsonDocument& doc, int i, int cellNumber, const String& state_topic, + const String& object_id_prefix) { + doc["name"] = "Battery Cell Voltage " + String(cellNumber); + doc["object_id"] = object_id_prefix + "battery_voltage_cell" + String(cellNumber); + doc["unique_id"] = topic_name + "_battery_voltage_cell" + String(cellNumber); + doc["device_class"] = "voltage"; + doc["state_class"] = "measurement"; + doc["state_topic"] = state_topic; + doc["unit_of_measurement"] = "V"; + doc["enabled_by_default"] = true; + doc["expire_after"] = 240; + doc["value_template"] = "{{ value_json.cell_voltages[" + String(i) + "] }}"; +} + #endif // HA_AUTODISCOVERY static std::vector order_events; @@ -137,13 +176,7 @@ static void publish_common_info(void) { } doc["enabled_by_default"] = true; doc["expire_after"] = 240; - doc["device"]["identifiers"][0] = ha_device_id; - doc["device"]["manufacturer"] = "DalaTech"; - doc["device"]["model"] = "BatteryEmulator"; - doc["device"]["name"] = device_name; - doc["origin"]["name"] = "BatteryEmulator"; - doc["origin"]["sw"] = String(version_number) + "-mqtt"; - doc["origin"]["url"] = "https://github.com/dalathegreat/Battery-Emulator"; + set_common_discovery_attributes(doc); serializeJson(doc, mqtt_msg); mqtt_publish(generateCommonInfoAutoConfigTopic(config.object_id).c_str(), mqtt_msg, true); doc.clear(); @@ -156,48 +189,12 @@ static void publish_common_info(void) { //only publish these values if BMS is active and we are comunication with the battery (can send CAN messages to the battery) if (datalayer.battery.status.CAN_battery_still_alive && allowed_to_send_CAN && millis() > BOOTUP_TIME) { - doc["SOC"] = ((float)datalayer.battery.status.reported_soc) / 100.0; - doc["SOC_real"] = ((float)datalayer.battery.status.real_soc) / 100.0; - doc["state_of_health"] = ((float)datalayer.battery.status.soh_pptt) / 100.0; - doc["temperature_min"] = ((float)((int16_t)datalayer.battery.status.temperature_min_dC)) / 10.0; - doc["temperature_max"] = ((float)((int16_t)datalayer.battery.status.temperature_max_dC)) / 10.0; - doc["stat_batt_power"] = ((float)((int32_t)datalayer.battery.status.active_power_W)); - doc["battery_current"] = ((float)((int16_t)datalayer.battery.status.current_dA)) / 10.0; - doc["battery_voltage"] = ((float)datalayer.battery.status.voltage_dV) / 10.0; - // publish only if cell voltages have been populated... - if (datalayer.battery.info.number_of_cells != 0u && - datalayer.battery.status.cell_voltages_mV[datalayer.battery.info.number_of_cells - 1] != 0u) { - doc["cell_max_voltage"] = ((float)datalayer.battery.status.cell_max_voltage_mV) / 1000.0; - doc["cell_min_voltage"] = ((float)datalayer.battery.status.cell_min_voltage_mV) / 1000.0; - } - doc["total_capacity"] = ((float)datalayer.battery.info.total_capacity_Wh); - doc["remaining_capacity_real"] = ((float)datalayer.battery.status.remaining_capacity_Wh); - doc["remaining_capacity"] = ((float)datalayer.battery.status.reported_remaining_capacity_Wh); - doc["max_discharge_power"] = ((float)datalayer.battery.status.max_discharge_power_W); - doc["max_charge_power"] = ((float)datalayer.battery.status.max_charge_power_W); + set_battery_attributes(doc, datalayer.battery, ""); } #ifdef DOUBLE_BATTERY //only publish these values if BMS is active and we are comunication with the battery (can send CAN messages to the battery) if (datalayer.battery2.status.CAN_battery_still_alive && allowed_to_send_CAN && millis() > BOOTUP_TIME) { - doc["SOC_2"] = ((float)datalayer.battery2.status.reported_soc) / 100.0; - doc["SOC_real_2"] = ((float)datalayer.battery2.status.real_soc) / 100.0; - doc["state_of_health_2"] = ((float)datalayer.battery2.status.soh_pptt) / 100.0; - doc["temperature_min_2"] = ((float)((int16_t)datalayer.battery2.status.temperature_min_dC)) / 10.0; - doc["temperature_max_2"] = ((float)((int16_t)datalayer.battery2.status.temperature_max_dC)) / 10.0; - doc["stat_batt_power_2"] = ((float)((int32_t)datalayer.battery2.status.active_power_W)); - doc["battery_current_2"] = ((float)((int16_t)datalayer.battery2.status.current_dA)) / 10.0; - doc["battery_voltage_2"] = ((float)datalayer.battery2.status.voltage_dV) / 10.0; - // publish only if cell voltages have been populated... - if (datalayer.battery2.info.number_of_cells != 0u && - datalayer.battery2.status.cell_voltages_mV[datalayer.battery2.info.number_of_cells - 1] != 0u) { - doc["cell_max_voltage_2"] = ((float)datalayer.battery2.status.cell_max_voltage_mV) / 1000.0; - doc["cell_min_voltage_2"] = ((float)datalayer.battery2.status.cell_min_voltage_mV) / 1000.0; - } - doc["total_capacity_2"] = ((float)datalayer.battery2.info.total_capacity_Wh); - doc["remaining_capacity_real_2"] = ((float)datalayer.battery2.status.remaining_capacity_Wh); - doc["remaining_capacity_2"] = ((float)datalayer.battery2.status.reported_remaining_capacity_Wh); - doc["max_discharge_power_2"] = ((float)datalayer.battery2.status.max_discharge_power_W); - doc["max_charge_power_2"] = ((float)datalayer.battery2.status.max_charge_power_W); + set_battery_attributes(doc, datalayer.battery2, "_2"); } #endif // DOUBLE_BATTERY serializeJson(doc, mqtt_msg); @@ -232,23 +229,8 @@ static void publish_cell_voltages(void) { for (int i = 0; i < datalayer.battery.info.number_of_cells; i++) { int cellNumber = i + 1; - doc["name"] = "Battery Cell Voltage " + String(cellNumber); - doc["object_id"] = object_id_prefix + "battery_voltage_cell" + String(cellNumber); - doc["unique_id"] = topic_name + "_battery_voltage_cell" + String(cellNumber); - doc["device_class"] = "voltage"; - doc["state_class"] = "measurement"; - doc["state_topic"] = state_topic; - 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] = ha_device_id; - doc["device"]["manufacturer"] = "DalaTech"; - doc["device"]["model"] = "BatteryEmulator"; - doc["device"]["name"] = device_name; - doc["origin"]["name"] = "BatteryEmulator"; - doc["origin"]["sw"] = String(version_number) + "-mqtt"; - doc["origin"]["url"] = "https://github.com/dalathegreat/Battery-Emulator"; + set_battery_voltage_attributes(doc, i, cellNumber, state_topic, object_id_prefix); + set_common_discovery_attributes(doc); serializeJson(doc, mqtt_msg, sizeof(mqtt_msg)); mqtt_publish(generateCellVoltageAutoConfigTopic(cellNumber, "").c_str(), mqtt_msg, true); @@ -261,23 +243,8 @@ static void publish_cell_voltages(void) { for (int i = 0; i < datalayer.battery.info.number_of_cells; i++) { int cellNumber = i + 1; - doc["name"] = "Battery 2 Cell Voltage " + String(cellNumber); - doc["object_id"] = object_id_prefix + "battery_2_voltage_cell" + String(cellNumber); - doc["unique_id"] = topic_name + "_battery_2_voltage_cell" + String(cellNumber); - doc["device_class"] = "voltage"; - doc["state_class"] = "measurement"; - doc["state_topic"] = state_topic_2; - 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] = ha_device_id; - doc["device"]["manufacturer"] = "DalaTech"; - doc["device"]["model"] = "BatteryEmulator"; - doc["device"]["name"] = device_name; - doc["origin"]["name"] = "BatteryEmulator"; - doc["origin"]["sw"] = String(version_number) + "-mqtt"; - doc["origin"]["url"] = "https://github.com/dalathegreat/Battery-Emulator"; + set_battery_voltage_attributes(doc, i, cellNumber, state_topic_2, object_id_prefix + "2_"); + set_common_discovery_attributes(doc); serializeJson(doc, mqtt_msg, sizeof(mqtt_msg)); mqtt_publish(generateCellVoltageAutoConfigTopic(cellNumber, "_2_").c_str(), mqtt_msg, true); @@ -350,13 +317,7 @@ void publish_events() { doc["json_attributes_topic"] = state_topic; doc["json_attributes_template"] = "{{ value_json | tojson }}"; doc["enabled_by_default"] = true; - doc["device"]["identifiers"][0] = ha_device_id; - doc["device"]["manufacturer"] = "DalaTech"; - doc["device"]["model"] = "BatteryEmulator"; - doc["device"]["name"] = device_name; - doc["origin"]["name"] = "BatteryEmulator"; - doc["origin"]["sw"] = String(version_number) + "-mqtt"; - doc["origin"]["url"] = "https://github.com/dalathegreat/Battery-Emulator"; + set_common_discovery_attributes(doc); serializeJson(doc, mqtt_msg); mqtt_publish(generateEventsAutoConfigTopic("event").c_str(), mqtt_msg, true); @@ -425,13 +386,7 @@ static void publish_buttons_discovery(void) { doc["command_topic"] = generateButtonTopic(config.object_id); doc["enabled_by_default"] = true; doc["expire_after"] = 240; - doc["device"]["identifiers"][0] = ha_device_id; - doc["device"]["manufacturer"] = "DalaTech"; - doc["device"]["model"] = "BatteryEmulator"; - doc["device"]["name"] = device_name; - doc["origin"]["name"] = "BatteryEmulator"; - doc["origin"]["sw"] = String(version_number) + "-mqtt"; - doc["origin"]["url"] = "https://github.com/dalathegreat/Battery-Emulator"; + set_common_discovery_attributes(doc); serializeJson(doc, mqtt_msg); mqtt_publish(generateButtonAutoConfigTopic(config.object_id).c_str(), mqtt_msg, true); doc.clear(); @@ -495,6 +450,7 @@ static void mqtt_event_handler(void* handler_args, esp_event_base_t base, int32_ void init_mqtt(void) { #ifdef MQTT + create_sensor_configs(); #ifdef MQTT_MANUAL_TOPIC_OBJECT_NAME // Use custom topic name, object ID prefix, and device name from user settings topic_name = mqtt_topic_name; From aa7d34a5559fe4439923cf4024fe0288e3f31e68 Mon Sep 17 00:00:00 2001 From: No-Signal Date: Sun, 12 Jan 2025 11:59:41 +0000 Subject: [PATCH 04/11] Fixes to HA auto discovery and adding additional commands --- Software/src/devboard/mqtt/mqtt.cpp | 131 ++++++++++++++++++---------- 1 file changed, 85 insertions(+), 46 deletions(-) diff --git a/Software/src/devboard/mqtt/mqtt.cpp b/Software/src/devboard/mqtt/mqtt.cpp index fa51ff3c..8ef27292 100644 --- a/Software/src/devboard/mqtt/mqtt.cpp +++ b/Software/src/devboard/mqtt/mqtt.cpp @@ -6,7 +6,6 @@ #include "../../../USER_SETTINGS.h" #include "../../battery/BATTERIES.h" #include "../../communication/contactorcontrol/comm_contactorcontrol.h" -#include "../../communication/contactorcontrol/comm_contactorcontrol.h" #include "../../datalayer/datalayer.h" #include "../../lib/bblanchon-ArduinoJson/ArduinoJson.h" #include "../utils/events.h" @@ -18,6 +17,7 @@ esp_mqtt_client_handle_t client; char mqtt_msg[MQTT_MSG_BUFFER_SIZE]; MyTimer publish_global_timer(5000); //publish timer MyTimer check_global_timer(800); // check timmer - low-priority MQTT checks, where responsiveness is not critical. +bool client_started = false; static String topic_name = ""; static String object_id_prefix = ""; @@ -36,6 +36,11 @@ static void publish_values(void) { } #ifdef HA_AUTODISCOVERY + +static bool ha_common_info_published = false; +static bool ha_cell_voltages_published = false; +static bool ha_events_published = false; +static bool ha_buttons_published = false; struct SensorConfig { const char* object_id; const char* name; @@ -64,26 +69,32 @@ SensorConfig sensorConfigTemplate[] = { {"pause_status", "Pause Status", "", "", ""}}; #ifdef DOUBLE_BATTERY -SensorConfig sensorConfigs[34]; +SensorConfig sensorConfigs[(sizeof(sensorConfigTemplate) / sizeof(sensorConfigTemplate[0])) * 2]; #else -SensorConfig sensorConfigs[17]; +SensorConfig sensorConfigs[sizeof(sensorConfigTemplate) / sizeof(sensorConfigTemplate[0]]; #endif // DOUBLE_BATTERY void create_sensor_configs() { - for (int i = 0; i < sizeof(sensorConfigTemplate) / sizeof(sensorConfigTemplate[0]); i++) { - SensorConfig& config = sensorConfigTemplate[i]; - config.value_template = ("{{ value_json." + String(config.object_id) + " }}").c_str(); + int number_of_templates = sizeof(sensorConfigTemplate) / sizeof(sensorConfigTemplate[0]); + for (int i = 0; i < number_of_templates; i++) { + SensorConfig config = sensorConfigTemplate[i]; + config.value_template = strdup(("{{ value_json." + std::string(config.object_id) + " }}").c_str()); sensorConfigs[i] = config; #ifdef DOUBLE_BATTERY - sensorConfigs[i + 17] = config; - sensorConfigs[i + 17].object_id = String(config.object_id + String("_2")).c_str(); - String temp_value_template = "{{ value_json." + String(sensorConfigs[i + 17].object_id) + " }}"; - sensorConfigs[i + 17].value_template = temp_value_template.c_str(); + sensorConfigs[i + number_of_templates] = config; + sensorConfigs[i + number_of_templates].name = strdup(String(config.name + String(" 2")).c_str()); + sensorConfigs[i + number_of_templates].object_id = strdup(String(config.object_id + String("_2")).c_str()); + sensorConfigs[i + number_of_templates].value_template = + strdup(("{{ value_json." + std::string(config.object_id) + "_2 }}").c_str()); #endif // DOUBLE_BATTERY } } -SensorConfig buttonConfigs[] = {{"BMSRESET", "Reset BMS", "", "", ""}}; +SensorConfig buttonConfigs[] = {{"BMSRESET", "Reset BMS"}, + {"PAUSE", "Pause charge/discharge"}, + {"RESUME", "Resume charge/discharge"}, + {"RESTART", "Restart Battery Emulator"}, + {"STOP", "Open Contactors"}}; static String generateCommonInfoAutoConfigTopic(const char* object_id) { return "homeassistant/sensor/" + topic_name + "/" + String(object_id) + "/config"; @@ -106,12 +117,10 @@ static String generateButtonAutoConfigTopic(const char* subtype) { } void set_common_discovery_attributes(JsonDocument& doc) { - doc["device"]["identifiers"][0] = ha_device_id; + doc["device"]["identifiers"][0] = device_id; doc["device"]["manufacturer"] = "DalaTech"; doc["device"]["model"] = "BatteryEmulator"; - doc["origin"]["name"] = "BatteryEmulator"; - doc["origin"]["sw"] = String(version_number) + "-mqtt"; - doc["origin"]["url"] = "https://github.com/dalathegreat/Battery-Emulator"; + doc["device"]["name"] = device_name; } void set_battery_attributes(JsonDocument& doc, const DATALAYER_BATTERY_TYPE& battery, const String& suffix) { @@ -154,13 +163,9 @@ static std::vector order_events; static void publish_common_info(void) { static JsonDocument doc; -#ifdef HA_AUTODISCOVERY - static bool mqtt_first_transmission = true; -#endif // HA_AUTODISCOVERY static String state_topic = topic_name + "/info"; #ifdef HA_AUTODISCOVERY - if (mqtt_first_transmission == true) { - mqtt_first_transmission = false; + if (ha_common_info_published == false) { for (int i = 0; i < sizeof(sensorConfigs) / sizeof(sensorConfigs[0]); i++) { SensorConfig& config = sensorConfigs[i]; doc["name"] = config.name; @@ -178,7 +183,9 @@ static void publish_common_info(void) { doc["expire_after"] = 240; set_common_discovery_attributes(doc); serializeJson(doc, mqtt_msg); - mqtt_publish(generateCommonInfoAutoConfigTopic(config.object_id).c_str(), mqtt_msg, true); + if (mqtt_publish(generateCommonInfoAutoConfigTopic(config.object_id).c_str(), mqtt_msg, true)) { + ha_common_info_published = true; + } doc.clear(); } @@ -210,19 +217,15 @@ static void publish_common_info(void) { } static void publish_cell_voltages(void) { -#ifdef HA_AUTODISCOVERY - static bool mqtt_first_transmission = true; -#endif // HA_AUTODISCOVERY static JsonDocument doc; static String state_topic = topic_name + "/spec_data"; #ifdef DOUBLE_BATTERY static String state_topic_2 = topic_name + "/spec_data_2"; - #endif // DOUBLE_BATTERY #ifdef HA_AUTODISCOVERY - if (mqtt_first_transmission == true) { - mqtt_first_transmission = false; + bool failed_to_publish = false; + if (ha_cell_voltages_published == false) { // If the cell voltage number isn't initialized... if (datalayer.battery.info.number_of_cells != 0u) { @@ -233,7 +236,9 @@ static void publish_cell_voltages(void) { set_common_discovery_attributes(doc); serializeJson(doc, mqtt_msg, sizeof(mqtt_msg)); - mqtt_publish(generateCellVoltageAutoConfigTopic(cellNumber, "").c_str(), mqtt_msg, true); + if (mqtt_publish(generateCellVoltageAutoConfigTopic(cellNumber, "").c_str(), mqtt_msg, true) == false) { + failed_to_publish = true; + } } doc.clear(); // clear after sending autoconfig } @@ -247,12 +252,17 @@ static void publish_cell_voltages(void) { set_common_discovery_attributes(doc); serializeJson(doc, mqtt_msg, sizeof(mqtt_msg)); - mqtt_publish(generateCellVoltageAutoConfigTopic(cellNumber, "_2_").c_str(), mqtt_msg, true); + if (mqtt_publish(generateCellVoltageAutoConfigTopic(cellNumber, "_2_").c_str(), mqtt_msg, true) == false) { + failed_to_publish = true; + } } doc.clear(); // clear after sending autoconfig } #endif // DOUBLE_BATTERY } + if (failed_to_publish == false) { + ha_cell_voltages_published = true; + } #endif // HA_AUTODISCOVERY // If cell voltages have been populated... @@ -297,15 +307,10 @@ static void publish_cell_voltages(void) { } void publish_events() { - static JsonDocument doc; -#ifdef HA_AUTODISCOVERY - static bool mqtt_first_transmission = true; -#endif // HA_AUTODISCOVERY static String state_topic = topic_name + "/events"; #ifdef HA_AUTODISCOVERY - if (mqtt_first_transmission == true) { - mqtt_first_transmission = false; + if (ha_events_published == false) { doc["name"] = "Event"; doc["state_topic"] = state_topic; @@ -319,7 +324,9 @@ void publish_events() { doc["enabled_by_default"] = true; set_common_discovery_attributes(doc); serializeJson(doc, mqtt_msg); - mqtt_publish(generateEventsAutoConfigTopic("event").c_str(), mqtt_msg, true); + if (mqtt_publish(generateEventsAutoConfigTopic("event").c_str(), mqtt_msg, true)) { + ha_events_published = true; + } doc.clear(); } else { @@ -370,10 +377,7 @@ void publish_events() { static void publish_buttons_discovery(void) { #ifdef HA_AUTODISCOVERY - static bool mqtt_first_transmission = true; - if (mqtt_first_transmission == true) { - mqtt_first_transmission = false; - + if (ha_buttons_published == false) { #ifdef DEBUG_LOG logging.println("Publishing buttons discovery"); #endif // DEBUG_LOG @@ -388,7 +392,9 @@ static void publish_buttons_discovery(void) { doc["expire_after"] = 240; set_common_discovery_attributes(doc); serializeJson(doc, mqtt_msg); - mqtt_publish(generateButtonAutoConfigTopic(config.object_id).c_str(), mqtt_msg, true); + if (mqtt_publish(generateButtonAutoConfigTopic(config.object_id).c_str(), mqtt_msg, true)) { + ha_buttons_published = true; + } doc.clear(); } } @@ -420,6 +426,24 @@ void mqtt_message_received(char* topic, int topic_len, char* data, int data_len) start_bms_reset(); } #endif // REMOTE_BMS_RESET + + if (strncmp(topic, generateButtonTopic("PAUSE").c_str(), topic_len) == 0) { + setBatteryPause(true, false); + } + + if (strncmp(topic, generateButtonTopic("RESUME").c_str(), topic_len) == 0) { + setBatteryPause(false, false, false); + } + + if (strncmp(topic, generateButtonTopic("RESTART").c_str(), topic_len) == 0) { + setBatteryPause(true, true, true, false); + delay(1000); + ESP.restart(); + } + + if (strncmp(topic, generateButtonTopic("STOP").c_str(), topic_len) == 0) { + setBatteryPause(true, false, true); + } } static void mqtt_event_handler(void* handler_args, esp_event_base_t base, int32_t event_id, void* event_data) { @@ -444,6 +468,16 @@ static void mqtt_event_handler(void* handler_args, esp_event_base_t base, int32_ case MQTT_EVENT_DATA: mqtt_message_received(event->topic, event->topic_len, event->data, event->data_len); break; + case MQTT_EVENT_ERROR: +#ifdef DEBUG_LOG + logging.print("reported from esp-tls"); + logging.println(event->error_handle->esp_tls_last_esp_err); + logging.print("reported from tls stack"); + logging.println(event->error_handle->esp_tls_stack_err); + logging.print("captured as transport's socket errno"); + logging.println(strerror(event->error_handle->esp_transport_sock_errno)); +#endif // DEBUG_LOG + break; } } @@ -476,16 +510,21 @@ void init_mqtt(void) { mqtt_cfg.credentials.authentication.password = MQTT_PASSWORD; client = esp_mqtt_client_init(&mqtt_cfg); esp_mqtt_client_register_event(client, MQTT_EVENT_ANY, mqtt_event_handler, client); - esp_mqtt_client_start(client); - -#ifdef DEBUG_LOG - logging.println("MQTT initialized"); -#endif // DEBUG_LOG } void mqtt_loop(void) { // Only attempt to publish/reconnect MQTT if Wi-Fi is connectedand checkTimmer is elapsed if (check_global_timer.elapsed() && WiFi.status() == WL_CONNECTED) { + + if (client_started == false) { + esp_mqtt_client_start(client); + client_started = true; +#ifdef DEBUG_LOG + logging.println("MQTT initialized"); +#endif // DEBUG_LOG + return; + } + if (publish_global_timer.elapsed()) // Every 5s { publish_values(); From 602242a661aa11f28616ce8fe92327907da099eb Mon Sep 17 00:00:00 2001 From: No-Signal Date: Tue, 14 Jan 2025 22:03:03 +0000 Subject: [PATCH 05/11] Improvements for dual battery configurations --- Software/src/devboard/mqtt/mqtt.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Software/src/devboard/mqtt/mqtt.cpp b/Software/src/devboard/mqtt/mqtt.cpp index 8ef27292..b9f1fe4a 100644 --- a/Software/src/devboard/mqtt/mqtt.cpp +++ b/Software/src/devboard/mqtt/mqtt.cpp @@ -69,9 +69,9 @@ SensorConfig sensorConfigTemplate[] = { {"pause_status", "Pause Status", "", "", ""}}; #ifdef DOUBLE_BATTERY -SensorConfig sensorConfigs[(sizeof(sensorConfigTemplate) / sizeof(sensorConfigTemplate[0])) * 2]; +SensorConfig sensorConfigs[((sizeof(sensorConfigTemplate) / sizeof(sensorConfigTemplate[0])) * 2) - 2]; #else -SensorConfig sensorConfigs[sizeof(sensorConfigTemplate) / sizeof(sensorConfigTemplate[0]]; +SensorConfig sensorConfigs[sizeof(sensorConfigTemplate) / sizeof(sensorConfigTemplate[0])]; #endif // DOUBLE_BATTERY void create_sensor_configs() { @@ -81,6 +81,9 @@ void create_sensor_configs() { config.value_template = strdup(("{{ value_json." + std::string(config.object_id) + " }}").c_str()); sensorConfigs[i] = config; #ifdef DOUBLE_BATTERY + if (config.object_id == "pause_status" || config.object_id == "bms_status") { + continue; + } sensorConfigs[i + number_of_templates] = config; sensorConfigs[i + number_of_templates].name = strdup(String(config.name + String(" 2")).c_str()); sensorConfigs[i + number_of_templates].object_id = strdup(String(config.object_id + String("_2")).c_str()); @@ -144,8 +147,8 @@ void set_battery_attributes(JsonDocument& doc, const DATALAYER_BATTERY_TYPE& bat } void set_battery_voltage_attributes(JsonDocument& doc, int i, int cellNumber, const String& state_topic, - const String& object_id_prefix) { - doc["name"] = "Battery Cell Voltage " + String(cellNumber); + const String& object_id_prefix, const String& battery_name_suffix) { + doc["name"] = "Battery" + battery_name_suffix + " Cell Voltage " + String(cellNumber); doc["object_id"] = object_id_prefix + "battery_voltage_cell" + String(cellNumber); doc["unique_id"] = topic_name + "_battery_voltage_cell" + String(cellNumber); doc["device_class"] = "voltage"; @@ -232,7 +235,7 @@ static void publish_cell_voltages(void) { for (int i = 0; i < datalayer.battery.info.number_of_cells; i++) { int cellNumber = i + 1; - set_battery_voltage_attributes(doc, i, cellNumber, state_topic, object_id_prefix); + set_battery_voltage_attributes(doc, i, cellNumber, state_topic, object_id_prefix, ""); set_common_discovery_attributes(doc); serializeJson(doc, mqtt_msg, sizeof(mqtt_msg)); @@ -248,7 +251,7 @@ static void publish_cell_voltages(void) { for (int i = 0; i < datalayer.battery.info.number_of_cells; i++) { int cellNumber = i + 1; - set_battery_voltage_attributes(doc, i, cellNumber, state_topic_2, object_id_prefix + "2_"); + set_battery_voltage_attributes(doc, i, cellNumber, state_topic_2, object_id_prefix + "2_", " 2"); set_common_discovery_attributes(doc); serializeJson(doc, mqtt_msg, sizeof(mqtt_msg)); From 3124656668f4c348214297048dbe16e69c57d60b Mon Sep 17 00:00:00 2001 From: No-Signal Date: Wed, 15 Jan 2025 08:24:37 +0000 Subject: [PATCH 06/11] Fixing battery 2 unique_id --- Software/src/devboard/mqtt/mqtt.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/src/devboard/mqtt/mqtt.cpp b/Software/src/devboard/mqtt/mqtt.cpp index b9f1fe4a..127eb24c 100644 --- a/Software/src/devboard/mqtt/mqtt.cpp +++ b/Software/src/devboard/mqtt/mqtt.cpp @@ -150,7 +150,7 @@ void set_battery_voltage_attributes(JsonDocument& doc, int i, int cellNumber, co const String& object_id_prefix, const String& battery_name_suffix) { doc["name"] = "Battery" + battery_name_suffix + " Cell Voltage " + String(cellNumber); doc["object_id"] = object_id_prefix + "battery_voltage_cell" + String(cellNumber); - doc["unique_id"] = topic_name + "_battery_voltage_cell" + String(cellNumber); + doc["unique_id"] = topic_name + object_id_prefix + "_battery_voltage_cell" + String(cellNumber); doc["device_class"] = "voltage"; doc["state_class"] = "measurement"; doc["state_topic"] = state_topic; From 89ef40832f970af2d8c04e50212006c305f46ddb Mon Sep 17 00:00:00 2001 From: No-Signal Date: Wed, 15 Jan 2025 08:45:03 +0000 Subject: [PATCH 07/11] Updating README file with instructions to update Partition Scheme --- README.md | 12 +++++++----- img/ArduinoSettings.png | Bin 0 -> 7421 bytes 2 files changed, 7 insertions(+), 5 deletions(-) create mode 100644 img/ArduinoSettings.png diff --git a/README.md b/README.md index cffdf89b..24089414 100644 --- a/README.md +++ b/README.md @@ -52,17 +52,19 @@ For more examples showing wiring, see each battery types own Wiki page. For inst ![bild](https://github.com/dalathegreat/Battery-Emulator/assets/26695010/6a2414b1-f2ca-4746-8e8d-9afd78bd9252) -5. The Arduino board should be set to `ESP32 Dev Module` (under `Tools` -> `Board` -> `ESP32 Arduino`) with the following settings: -![alt text](https://github.com/Xinyuan-LilyGO/T-CAN485/blob/main/img/arduino_setting.png) -6. Select which battery type you will use, along with other optional settings. This is done in the `USER_SETTINGS.h` file. -7. Copy the `USER_SECRETS.TEMPLATE.h` file to `USER_SECRETS.h` and update connectivity settings inside this file. -8. Press `Verify` and `Upload` to send the sketch to the board. +5. The Arduino board should be set to `ESP32 Dev Module` and `Partition Scheme` to `Minimal SPIFFS (1.9MB APP with OTA/190KB SPIFFS)` (under `Tools` -> `Board` -> `ESP32 Arduino`) with the following settings: +![alt text](https://github.com/dalathegreat/Battery-Emulator/blob/img/ArduinoSettings.png) +7. Select which battery type you will use, along with other optional settings. This is done in the `USER_SETTINGS.h` file. +8. Copy the `USER_SECRETS.TEMPLATE.h` file to `USER_SECRETS.h` and update connectivity settings inside this file. +9. Press `Verify` and `Upload` to send the sketch to the board. NOTE: In some cases, the LilyGo must be powered through the main power connector instead of USB-C when performing the initial firmware upload. NOTE: On Mac, the following USB driver may need to be installed: https://github.com/WCHSoftGroup/ch34xser_macos NOTE: If you see garbled messages on the serial console, change the serial console to match the baud rate to the code, currently 115200. +NOTE: If you see an error XXXX then check you set the Partition Scheme above correctly + This video explains all the above mentioned steps: diff --git a/img/ArduinoSettings.png b/img/ArduinoSettings.png new file mode 100644 index 0000000000000000000000000000000000000000..5c3894f73b734ba0e916a0877358a0ab62d819b2 GIT binary patch literal 7421 zcmZX31z1$y);Ay^jSSu8z|bj;Ffi0mLn9zvN;iUlgi-@YcZ0-GBM3-Hm(r+ohcI+0 z%{Ttvd%x%2_ubD~d;j)YYp=7<+PzOSOiP8BfQ|qI1A|yi6{3rQff zU&0)4E4M{6X}pz;z4hGez5T2_?J!_gFI>I(wUqQ(p70Ct3tzWAXhiEY+8L|atEq8g z;Gr`(7<8C87?@}TMHBtMFuE}?K$s8y;bWn(5GEGJBXnwlrWcXe|L|X;@!w#HroZL? zo&K+vVxXJz{X-0Fw1%Lth#+Rc|5*K70;k}AETy3F-++a-gY}Q5uA~GS{i~Fapx|HW z&X3;`U+0_yFfiz<)gWMffAjrZ!Vr3K-##uKCT=P_-4Y9{MMs-g$*E49tv)$M^CKLK z_VbJT>(?P!d0q!NxIK{@4s7bb?nNX;FmN+IKlB3Qh-V}U_0!nEg_s2cuh);es%4DJ zvNuLoRhkk$46GmL7EfqwwjTS-Obmg9<*ab6JP#W8_5~WA_j6<}@$HWuE{^3stOqA% z_b%-h>on~iL_!DMPa~U@PZKihn>w=YdK|B|9nTU}6@*vtPm?MAOnE8Y+{-5c93K^!DEQCee#9d^&O_`?jRkXPuCQ!*NXI$=L@pt| zA)@z-i41Xjs!oUd+K%Uj_;7L>R(A!Oj6Mg+{!YCL4V^Hm1CERM(_`XK4jLJ&*v-PO zE#b=e8{#gD5dZ52B27H$qJem$y{=_c1mjn^Ri_41W%klCgSh48{MPy$>Dt4bt-7k%0+w5AAJoE0c+0 zJUo}@dpJ@zhQ>%tI(9K}3srnWzzHq77yQ!|{uO!EgS>p_w?!9+ z0kIkR((4Pp!&NVW^;fY~6|T;1zDZ@fEJfrDjIvMmK7`@FJ-fYAU8RqSlPo=_{Lv)i zAAfr}B$8tVT%zKh&@c>t0u+{F$I5CMDlcACIhB(s4M-dksp_3if|d}P1P7dq5#bUM zCc~?Thqf%5=i`ylNEER&&v@;9Al7xHiedNPVeuah$?(HwCqTV? zDn!BUw z>qNMsP}>)lKyz|gazQ%kgI4WiVb20yymEtgOw>Tz`BE7e@78osaN7zu*o>(h6squ5 z{|Q`gW^;0wPE=Bpu%0K!xsJt>A9v6bd6#VXI!;N`1NXTO|2FmJF=K+lgv)@9eIv&8 z#L@PwKb~ddYS^1w>o+G_$+G>xV*Txb9J6tOm)(DEz%e{P*nZ7Du(Jw$jST#@&<;Sd zSQ^)J3z?PxmiKCBer5I`iw&?|DshbUO_5h@c{*~<@KYUxt34#dnk?^=-pN!wEGYx! zMWYQmSx0%<>*D9Y(nmzoS+2nkTR8bJXdWo=;tj60!e`FRK}b)+cOTok>!h|&or$oUXNY>zOPSJ-Y9t9Z`1tP~fIlh$T zI2^)8+vwn9+m^v}Ouq%jAiRGdW0b4k2CbWkZR{Ji3_)NViUoZ`l(3tY40*1CG41G_ ze_8$YWy=iSrXC%9F_5zY@72Mg0AgCGr;T=Evk(HOjsskz7=d_F5?(a4r}}HVbG-y5 z3dFjkbc!S{aVcD8C@<0->ZjKE1yFaMAGt+dkcTA(s;*;(EPmxX7KnH2NCs#)D(_W7 zXpR5fzcT0tIx-Mol-2)d_^I~ZZdQU#yb}|#$PDV3KHq$02&Oos9?gRB(8=F^_TAcJ zu_pRtYf@@j_o{uy>+Z;cxgzYCd;JYZTX%H#$Nq=+?*e|2vKj8qvCQ8bKRtX&TRYDS z6--;#ECie;^i&p;Kzs6JwwJxWRAAF>7z}HVx50A~4IZB~$g|dJk;zSKL|eNpQ9eD? z#dm$Se0K9t_0iV;U+rFAEMI-d^aLnW#+)_T$7klVD>S8bl`5YY*Z|;S=*djuxMh-M z^j%YVG1Z#SRYJ>jup)x1DGS8`%z8dEmra(B}lf+T2xstb($S$m7brU^LqCB1CD0b3sao}cI}TXFJ~U}C@U1q2AHoZ^X@ zu)Q?IrS zs8q7_{?W@*H3l(1IL8Xc4G~K*EuM~kI>(b3ePQn{EOz5l+@K%!C2k%6X&iu;%<5Z% zQ2ZqPIPbIw`#vXHcY;E3dRE#5=#C4|-_+;LFV0rC`Nh{}r$W5b^rp}zE?3Eoh?<)$ zq`w^p>3xG(xi?s61KJ-Oh5qMUN8|N2nx?bb{bx1lgM}xu^}BMZ(7a!im zhf0X&!ZC-7S#!#B@+s)sO95p$)Sm#-pWT^s_4=Q{*Z5LlmlirT^?B}ClnxcRARPl+MIsU%ea(gvpq2`pzx9|6kCuLeXYl&H z90wI}-0FDbO~+0#9d=X_*(D>i_t{0i)m|-6;g!)RAfLxqFM-#*ZX}{~pjwu)eMv2G z4}n1K4xJTS-uzijUHq-L#EQ`pu|Gq`59^a2i=qs})7S{^3Hi{DE#wzO^jF%6~G(Ty-HrrAC zala<$uXOo%j9}_zNG#w+ZJ{B8bdfxMcz%Lr?xs?L;*h)ZUbSeo=f9SoOW1hCypk2a z5WaYapWKJH5(9=730Y?|o+@U$x%UP}FP!=2s9B`2<~ zk2z_~o-Qd-M=}{eXYbE|+X)ij$Du#Pz4k~_b^P|XZOIN@;x#aE$kz}$LPJ-OkRA(` z3|7z&ZQA;$aof zNYW!dkTPj!knb>OuifH-SiB2Xf5yu4Lkm%Nu<1>xa6+1Y;ZQi^~5;_&#aJnvCkwfeHEev9{yNr#zrNuKs3 zQ^vwalm+9SrL9r)y0o^b{yhmamua$V&Jzu*MqAoSU9 zgpsn#MM>z-6vsoRw*}ZScHCk*iWcc6-RX_h0Qjp|?k129)kW{~4dQd?_T;&2a~h9A zE%v%Fyt)8c&VZkLe{!J(pYwc zh7ajC=!Nj7xA?;ehsG7@~V((oW8(<4p~( zv*5jW)LQ^@LVa*Q7Sx(419jO!^LMT_B>MvB3PhORbJ}RLi+S$o+Y-K_{t4j(R7}~B z7^)uZPM+fNq>U@0=s2y*gKJg;U3#}pzu^jMZS#9879Y&wBf5&oDA%r`@iRvG<4ZSJ z#?gqdRKfq;7M#|M=j3v1@rQZ2o0ff+Ag0nQm7O{j@QUH@wrebCdJtGxk2$ipPFI~N zHG1nRl+A0xS)L42aN4+0;iyJ#?3Ja`oP33D*K0uvYLuX=(K)e8I6O^&2PF|#(!9ix zy32>Hy?5lz{Is!S*aVZF7rY%(Bo?W{-6&+G1-sKk+!Co>dVZe9RcCkMurfpxH{1+$ z5_BAFL5J}D9W`DKWmvGDAq)u7aJHVRMWvTiu0kiBUZjgNZKzEWpCiGW-M z-z`!%p%0bPr8|FIL#%;fua==J%$4h+;_UJlOl=c4q}tWJ90vGWw@$0M(kiq##$KPG zmQ`y58DRsom1L|M+dH?WK^@lebDrIM5n~aCayImfgg={ZP_71H)jvBsvx)bOeuj*U z5_+jnSPpG+hw=)U3Svj76k?AIc~RU`Bef-%x#9rDKm%^*(etjU({!_0e1eFDiN=aC z2*m8=bGZP{;46nPA@P+xJN_@!&~nA)2@@|n7jb=2chMBQaEJj`>6t+ue#%PA>km3u zu8?c>fHFSl;1^U!JAk{#=5@*wIz}eOlaw&sbRH@&@r<`Xo~K=hdGzng&lCE<6|=RF zh^T}18psARc1l`F(2~E38oJT7=OrW7?n5g)Xor2;L_R0cRS^e}nSEc0t-RsZv5smF z*uQ%R@YEJ9QFbO-82iM=91{tUvYf2TAFyuq4$>hY)kI*|H{CBow>ct{ z^0+2H82x+dt~0=|z8+vaGo0MhWy{CU zwK7kw?5&}ZmneT2vrGuw_6)H39j#891Kj z3RbC(wc{`0MvCCR|7LnZ_Wn<|4Ha`U{^LF#u+50v zXgKSOp4p6ANOfYE%##PM_OTNochpW9ca+u|%Jq7s%z0Em8d7T@H#HBEtb2l7Y$5O} z1NvPRoR`JfC#5as3L3=Qg;yL1*+%@m3=Wy87F)5Fgr$5lCS_J~R{wB|90t`>SDl0g8V zbn5>W78@6?&&7`O%7l2B;Z|HZK}F>h@6zGGqs=I93~w&BQ4^Lr!}K6Z2itd_BStlE zo7MPwojr9>6&z1a_PQUES8A2_i6y=3lkOU!i{X}fk;8}l>H(}|x~~5| z2L01`8T5mmlv5K#$O(8@HQ8Ar5*zwjo}P$nO*?DcV~zAi6N_xH zHPw^hYN8DuTvv;*jR0gmo*$(e9*X<`VlC^?#7|4z*QT0M3Gfd9>dWlH^@tMHE`asj z`yhZ|3P=q;J)f>7Ekiyiu%rgIm&sI#CfV_G3}m>yWL*HHSjM#JwTVigHuA_~aLUfR z(@=l>0cZF3-ycg*#MihhU+75RZ-k^Q_F2mObR6dfA1c}mAcl{f;R6=((NWjo%^H0Y z%syG-DoaCVm{q+7UC{p;ZI|vtv%@9%ujd;hFv{kwB|EC!32Lk15P3c9&Am4QRp)CC zfN6XEgB5bO6;AU$C^PuG~?8mmdI zJhXw&x%)FGkI-CTrIEwv;jMdTQ)Y08srtlfafsXyE#IfcN8=)){N_-QY3Q3l+b3@@ z9ZXaN6Tp-^Q==h%b2Nk|6H$EGPT-yl4; zEBtITl{%xaO|>sbJr~vr3ec=LgA~bYTiOhO?hl<_jsY|5`BM zLR;^;FD0~GNTO(WKGIJ9kbeUh_FzQ{7Z3=~2y$2rxu?n}dihN#4Bva0rv(z4|1wth zv)d(j^G#5^2GcX*a85&nHRD^GtxvX}>A|N=akI1!-dfKHbo6>(K9Za%;Mebq9->_y zG^YO!v{#&JZCdM||Mn}_?HMZWoRl{&y}?O285h*`y0)s^^|$bR)H`sTLd1#Z*OXI~GR+JWjHF24mzfB#W;;(-`=_4#l?zS9ybn7mW% zEO4Z#sH$@l9*2VTC29%cb9W*7VFgq-58FO297)#dB-qD!<7*5~pnH(Hi=caAp7A zTtixqQPW3aW8x?Q?`F&Vdmn~gkgKGJs1c`&gq~GVPpjy15FI~;O&W@VV zZ8yYZ*s%}+`9Q{EOBn0Z&qEm52ctUC&$5`N?swGS`aj=L(LQRHX@1L)N9UHM`N8iJ zYsIyX>PP@p1b`1g!uYE?s6TZkmB)T6NO$LIbZ`6mU;=YJ&zRBmo0s&93l zhcuP7`HFu1ey31m?^=RQfNC*T6CET-GlvD)g3pIH?** z^AyPib{LLQ$2?s=uI^ybrEH}V%2DkE9#x*;6X=7#1MV*h0Uq_`aHEMf|LiVULr7b8 zGN)tNR;VFk&(kKw7vv_afe z&Zj{I-7xDyaGTqs6+rB>$T7%n@HACVsiV$W%Ds zh(YO{gi}F;lurY#E%J-{pQa?*2f#OTG{HWtt8{I2hV^YZSM81pJTI-El! zv;<+XaE zBM3>ym@UZyE5rIBSWyI^8Ql5fyKoO(_Yo~;e%wOk zoFPbW(+r}fO3xs!=1NRw4*JzHK692XkNk0n_d8=3(tJvqcx3%|&bhRtiMwN7n0-q; UB`rvef@RqM17kehvH$=8 literal 0 HcmV?d00001 From afb6cb622e90943d436ab41803e14abbe1a982ed Mon Sep 17 00:00:00 2001 From: No-Signal Date: Wed, 15 Jan 2025 08:49:59 +0000 Subject: [PATCH 08/11] Fixing README file --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 24089414..134847e8 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ NOTE: On Mac, the following USB driver may need to be installed: https://github. NOTE: If you see garbled messages on the serial console, change the serial console to match the baud rate to the code, currently 115200. -NOTE: If you see an error XXXX then check you set the Partition Scheme above correctly +NOTE: If you see the error `Sketch too big` then check you set the Partition Scheme above correctly. This video explains all the above mentioned steps: From 4085b356f6db411a2ee16359d900fb4844afcd0b Mon Sep 17 00:00:00 2001 From: No-Signal Date: Wed, 15 Jan 2025 21:30:48 +0000 Subject: [PATCH 09/11] Removing old library from readme file --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 134847e8..eef24cb4 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,6 @@ This code uses the following excellent libraries: - [ayushsharma82/ElegantOTA](https://github.com/ayushsharma82/ElegantOTA) AGPL-3.0 license - [bblanchon/ArduinoJson](https://github.com/bblanchon/ArduinoJson) MIT-License - [eModbus/eModbus](https://github.com/eModbus/eModbus) MIT-License -- [knolleary/pubsubclient](https://github.com/knolleary/pubsubclient) MIT-License - [mackelec/SerialDataLink](https://github.com/mackelec/SerialDataLink) - [mathieucarbou/AsyncTCPsock](https://github.com/mathieucarbou/AsyncTCPSock) LGPL-3.0 license - [mathieucarbou/ESPAsyncWebServer](https://github.com/mathieucarbou/ESPAsyncWebServer) LGPL-3.0 license From ad8087447581b34bbe4485f244c063395f4514e0 Mon Sep 17 00:00:00 2001 From: No-Signal Date: Fri, 17 Jan 2025 18:48:48 +0000 Subject: [PATCH 10/11] Adding LWT and configuring HA availability to use it and adjusting command structure to make it easier to use --- Software/src/devboard/mqtt/mqtt.cpp | 37 ++++++++++++++--------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/Software/src/devboard/mqtt/mqtt.cpp b/Software/src/devboard/mqtt/mqtt.cpp index 127eb24c..fcfdd14a 100644 --- a/Software/src/devboard/mqtt/mqtt.cpp +++ b/Software/src/devboard/mqtt/mqtt.cpp @@ -18,6 +18,7 @@ char mqtt_msg[MQTT_MSG_BUFFER_SIZE]; MyTimer publish_global_timer(5000); //publish timer MyTimer check_global_timer(800); // check timmer - low-priority MQTT checks, where responsiveness is not critical. bool client_started = false; +static String lwt_topic = ""; static String topic_name = ""; static String object_id_prefix = ""; @@ -30,6 +31,7 @@ static void publish_events(void); /** Publish global values and call callbacks for specific modules */ static void publish_values(void) { + mqtt_publish((topic_name + "/status").c_str(), "online", false); publish_events(); publish_common_info(); publish_cell_voltages(); @@ -112,11 +114,11 @@ static String generateEventsAutoConfigTopic(const char* object_id) { } static String generateButtonTopic(const char* subtype) { - return "homeassistant/button/" + topic_name + "/" + String(subtype); + return topic_name + "/command/" + String(subtype); } static String generateButtonAutoConfigTopic(const char* subtype) { - return generateButtonTopic(subtype) + "/config"; + return "homeassistant/button/" + topic_name + "/" + String(subtype) + "/config"; } void set_common_discovery_attributes(JsonDocument& doc) { @@ -124,6 +126,10 @@ void set_common_discovery_attributes(JsonDocument& doc) { doc["device"]["manufacturer"] = "DalaTech"; doc["device"]["model"] = "BatteryEmulator"; doc["device"]["name"] = device_name; + doc["availability"][0]["topic"] = lwt_topic; + doc["payload_available"] = "online"; + doc["payload_not_available"] = "offline"; + doc["enabled_by_default"] = true; } void set_battery_attributes(JsonDocument& doc, const DATALAYER_BATTERY_TYPE& battery, const String& suffix) { @@ -155,8 +161,6 @@ void set_battery_voltage_attributes(JsonDocument& doc, int i, int cellNumber, co doc["state_class"] = "measurement"; doc["state_topic"] = state_topic; doc["unit_of_measurement"] = "V"; - doc["enabled_by_default"] = true; - doc["expire_after"] = 240; doc["value_template"] = "{{ value_json.cell_voltages[" + String(i) + "] }}"; } @@ -182,8 +186,6 @@ static void publish_common_info(void) { doc["device_class"] = config.device_class; doc["state_class"] = "measurement"; } - doc["enabled_by_default"] = true; - doc["expire_after"] = 240; set_common_discovery_attributes(doc); serializeJson(doc, mqtt_msg); if (mqtt_publish(generateCommonInfoAutoConfigTopic(config.object_id).c_str(), mqtt_msg, true)) { @@ -324,7 +326,6 @@ void publish_events() { "}}"; doc["json_attributes_topic"] = state_topic; doc["json_attributes_template"] = "{{ value_json | tojson }}"; - doc["enabled_by_default"] = true; set_common_discovery_attributes(doc); serializeJson(doc, mqtt_msg); if (mqtt_publish(generateEventsAutoConfigTopic("event").c_str(), mqtt_msg, true)) { @@ -389,10 +390,8 @@ static void publish_buttons_discovery(void) { for (int i = 0; i < sizeof(buttonConfigs) / sizeof(buttonConfigs[0]); i++) { SensorConfig& config = buttonConfigs[i]; doc["name"] = config.name; - doc["unique_id"] = config.object_id; + doc["unique_id"] = object_id_prefix + config.object_id; doc["command_topic"] = generateButtonTopic(config.object_id); - doc["enabled_by_default"] = true; - doc["expire_after"] = 240; set_common_discovery_attributes(doc); serializeJson(doc, mqtt_msg); if (mqtt_publish(generateButtonAutoConfigTopic(config.object_id).c_str(), mqtt_msg, true)) { @@ -405,14 +404,7 @@ static void publish_buttons_discovery(void) { } static void subscribe() { - for (int i = 0; i < sizeof(buttonConfigs) / sizeof(buttonConfigs[0]); i++) { - SensorConfig& config = buttonConfigs[i]; - const char* topic = generateButtonTopic(config.object_id).c_str(); -#ifdef DEBUG_LOG - logging.printf("Subscribing to topic: [%s]\n", topic); -#endif // DEBUG_LOG - esp_mqtt_client_subscribe(client, topic, 0); - } + esp_mqtt_client_subscribe(client, (topic_name + "/command/+").c_str(), 1); } void mqtt_message_received(char* topic, int topic_len, char* data, int data_len) { @@ -473,6 +465,7 @@ static void mqtt_event_handler(void* handler_args, esp_event_base_t base, int32_ break; case MQTT_EVENT_ERROR: #ifdef DEBUG_LOG + logging.println("MQTT_ERROR"); logging.print("reported from esp-tls"); logging.println(event->error_handle->esp_tls_last_esp_err); logging.print("reported from tls stack"); @@ -511,6 +504,12 @@ void init_mqtt(void) { mqtt_cfg.credentials.client_id = clientId; mqtt_cfg.credentials.username = MQTT_USER; mqtt_cfg.credentials.authentication.password = MQTT_PASSWORD; + lwt_topic = topic_name + "/status"; + mqtt_cfg.session.last_will.topic = lwt_topic.c_str(); + mqtt_cfg.session.last_will.qos = 1; + mqtt_cfg.session.last_will.retain = true; + mqtt_cfg.session.last_will.msg = "offline"; + mqtt_cfg.session.last_will.msg_len = strlen(mqtt_cfg.session.last_will.msg); client = esp_mqtt_client_init(&mqtt_cfg); esp_mqtt_client_register_event(client, MQTT_EVENT_ANY, mqtt_event_handler, client); } @@ -536,6 +535,6 @@ void mqtt_loop(void) { } bool mqtt_publish(const char* topic, const char* mqtt_msg, bool retain) { - int msg_id = esp_mqtt_client_publish(client, topic, mqtt_msg, strlen(mqtt_msg), 0, retain); + int msg_id = esp_mqtt_client_publish(client, topic, mqtt_msg, strlen(mqtt_msg), 1, retain); return msg_id > -1; } From b1242bc3cf1dc8c244261b21704757a61b0031dc Mon Sep 17 00:00:00 2001 From: No-Signal Date: Sun, 19 Jan 2025 10:15:39 +0000 Subject: [PATCH 11/11] Updating template MQTT username and password to empty strings to avoid confusion when setting a username and password --- Software/USER_SECRETS.TEMPLATE.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Software/USER_SECRETS.TEMPLATE.h b/Software/USER_SECRETS.TEMPLATE.h index 1cd013b9..b482d035 100644 --- a/Software/USER_SECRETS.TEMPLATE.h +++ b/Software/USER_SECRETS.TEMPLATE.h @@ -16,5 +16,5 @@ It contains all the credentials that should never be made public */ //MQTT credentials #define MQTT_SERVER "192.168.xxx.yyy" // MQTT server address #define MQTT_PORT 1883 // MQTT server port -#define MQTT_USER NULL // MQTT username, leave blank for no authentication -#define MQTT_PASSWORD NULL // MQTT password, leave blank for no authentication +#define MQTT_USER "" // MQTT username, leave blank for no authentication +#define MQTT_PASSWORD "" // MQTT password, leave blank for no authentication