diff --git a/README.md b/README.md index f50151297..28d3fbb28 100644 --- a/README.md +++ b/README.md @@ -57,17 +57,23 @@ Start by watching this [quickstart guide](https://www.youtube.com/watch?v=hcl2Gd ![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 the error `Sketch too big` then check you set the Partition Scheme above correctly. + +This video explains all the above mentioned steps: + + + ### Linux Development Environment Setup In addition to the steps above, ESP32 requires a dependency for a Python module, pyserial install using the cli.\ ```python3 -m pip install pyserial``` @@ -84,7 +90,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 diff --git a/Software/USER_SECRETS.TEMPLATE.h b/Software/USER_SECRETS.TEMPLATE.h index 975a75f67..b482d0356 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, NULL for no authentication, username within "", example "Emulator" -#define MQTT_PASSWORD NULL // MQTT password, NULL for no authentication, password within "", example "Password" +#define MQTT_USER "" // MQTT username, leave blank for no authentication +#define MQTT_PASSWORD "" // MQTT password, leave blank for no authentication diff --git a/Software/src/devboard/mqtt/mqtt.cpp b/Software/src/devboard/mqtt/mqtt.cpp index 15e973315..e833167f0 100644 --- a/Software/src/devboard/mqtt/mqtt.cpp +++ b/Software/src/devboard/mqtt/mqtt.cpp @@ -8,39 +8,41 @@ #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. +bool client_started = false; +static String lwt_topic = ""; static String topic_name = ""; 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); /** 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(); } #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; @@ -49,52 +51,56 @@ 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"}, - {"cell_voltage_delta", "Cell Voltage Delta", "{{ value_json.cell_voltage_delta }}", "mV", "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"}, + {"cell_voltage_delta", "Cell Voltage Delta", "", "mV", "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"}, - {"cell_voltage_delta_2", "Cell Voltage Delta 2", "{{ value_json.cell_voltage_delta_2 }}", "mV", "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[((sizeof(sensorConfigTemplate) / sizeof(sensorConfigTemplate[0])) * 2) - 2]; +#else +SensorConfig sensorConfigs[sizeof(sensorConfigTemplate) / sizeof(sensorConfigTemplate[0])]; #endif // DOUBLE_BATTERY -}; -SensorConfig buttonConfigs[] = {{"BMSRESET", "Reset BMS", "", "", ""}}; +void create_sensor_configs() { + 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 + 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()); + 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"}, + {"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"; @@ -109,11 +115,56 @@ 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) { + doc["device"]["identifiers"][0] = device_id; + 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) { + 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["cell_voltage_delta" + suffix] = + ((float)battery.status.cell_max_voltage_mV) - ((float)battery.status.cell_min_voltage_mV); + } + 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, 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 + object_id_prefix + "_battery_voltage_cell" + String(cellNumber); + doc["device_class"] = "voltage"; + doc["state_class"] = "measurement"; + doc["state_topic"] = state_topic; + doc["unit_of_measurement"] = "V"; + doc["value_template"] = "{{ value_json.cell_voltages[" + String(i) + "] }}"; } #endif // HA_AUTODISCOVERY @@ -122,13 +173,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; @@ -142,17 +189,11 @@ 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; - 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); + if (mqtt_publish(generateCommonInfoAutoConfigTopic(config.object_id).c_str(), mqtt_msg, true)) { + ha_common_info_published = true; + } doc.clear(); } @@ -163,52 +204,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["cell_voltage_delta"] = ((float)datalayer.battery.status.cell_max_voltage_mV) - - ((float)datalayer.battery.status.cell_min_voltage_mV); - } - 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["cell_voltage_delta_2"] = ((float)datalayer.battery2.status.cell_max_voltage_mV) - - ((float)datalayer.battery2.status.cell_min_voltage_mV); - } - 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); @@ -224,45 +225,28 @@ 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) { 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); + if (mqtt_publish(generateCellVoltageAutoConfigTopic(cellNumber, "").c_str(), mqtt_msg, true) == false) { + failed_to_publish = true; + } } doc.clear(); // clear after sending autoconfig } @@ -272,31 +256,21 @@ 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_", " 2"); + 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... @@ -341,15 +315,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; @@ -360,16 +329,11 @@ 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); + if (mqtt_publish(generateEventsAutoConfigTopic("event").c_str(), mqtt_msg, true)) { + ha_events_published = true; + } doc.clear(); } else { @@ -420,10 +384,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 @@ -432,19 +393,13 @@ 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; - 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); + if (mqtt_publish(generateButtonAutoConfigTopic(config.object_id).c_str(), mqtt_msg, true)) { + ha_buttons_published = true; + } doc.clear(); } } @@ -452,71 +407,83 @@ 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 - client.subscribe(topic); - } + esp_mqtt_client_subscribe(client, (topic_name + "/command/+").c_str(), 1); } -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 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); + } } -/* 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; + 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"); + 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; } - return client.connected(); } 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; @@ -532,54 +499,45 @@ void init_mqtt(void) { #endif #endif - client.setServer(MQTT_SERVER, MQTT_PORT); - client.setCallback(mqtt_message_received); -#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(); + 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; + 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); } 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) { + + if (client_started == false) { + esp_mqtt_client_start(client); + client_started = true; #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 - } - } + logging.println("MQTT initialized"); +#endif // DEBUG_LOG + return; + } + + 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), 1, 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 2b48d2b6b..000000000 --- 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 0a950acf9..000000000 --- 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/img/ArduinoSettings.png b/img/ArduinoSettings.png new file mode 100644 index 000000000..5c3894f73 Binary files /dev/null and b/img/ArduinoSettings.png differ diff --git a/min_spiffs.csv b/min_spiffs.csv new file mode 100644 index 000000000..87f2c4756 --- /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 f7cca3245..3d35964ed 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 =