diff --git a/README.md b/README.md index a63be279..084b52c1 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,7 @@ A heavily incomplete list of trusted hardware shops in germany is: This list is for your convenience only, the project is not related to any of these shops. ### Power supply -Use a power suppy with 5V and 1A. The USB cable connected to your PC/Notebook may be powerful enough or may be not. +Use a power suppy with 5 V and 1 A. The USB cable connected to your PC/Notebook may be powerful enough or may be not. ## Wiring up @@ -146,11 +146,14 @@ This can be achieved by editing the 'platformio.ini' file and add/change one or * Install git and enable git in vscode - [git download](https://git-scm.com/downloads/) - [Instructions](https://www.jcchouinard.com/install-git-in-vscode/) * Clone this repository (you really have to clone it, don't just download the ZIP file. During the build process the git hash gets embedded into the firmware. If you download the ZIP file a build error will occur): Inside vscode open the command palette by pressing `CTRL` + `SHIFT` + `P`. Enter `git clone`, add the repository-URL `https://github.com/tbnobody/OpenDTU`. Next you have to choose (or create) a target directory. * In vscode, choose File --> Open Folder and select the previously downloaded source code. (You have to select the folder which contains the "platformio.ini" file) -* There is a short [Video](https://youtu.be/9cA_esv3zeA) showing these steps. * Adjust the COM port in the file "platformio.ini" for your USB-serial-converter. It occurs twice: * upload_port * monitor_port * Select the arrow button in the status bar (PlatformIO: Upload) to compile and upload the firmware. During the compilation, all required libraries are downloaded automatically. +* There are two videos showing these steps: + * [Git Clone and compilation](https://youtu.be/9cA_esv3zeA) + * [Full installation and compilation](https://youtu.be/xs6TqHn7QWM) + ### on the commandline with PlatformIO Core * Install [PlatformIO Core](https://platformio.org/install/cli) * Clone this repository (you really have to clone it, don't just download the ZIP file. During the build process the git hash gets embedded into the firmware. If you download the ZIP file a build error will occur) @@ -244,11 +247,11 @@ A documentation of all available MQTT Topics can be found here: [MQTT Documentat * First: When there is no light on the solar panels, the inverter completely turns off and does not answer to OpenDTU! So if you assembled your OpenDTU in the evening, wait until tomorrow. * When there is no data received from the inverter(s) - try to reduce the distance between the openDTU and the inverter (e.g. move it to the window towards the roof) * Under Settings -> DTU Settings you can increase the transmit power "PA level". Default is "minimum". -* The NRF24L01+ needs relatively much current. With bad power supply (and especially bad cables!) a 10uF capacitor soldered directly to the NRF24L01+ board connector brings more stability (pin 1+2 are the power supply). Note the polarity of the capacitor.... -* You can try to use an USB power supply with 1A or more instead of connecting the ESP32 to the computer. +* The NRF24L01+ needs relatively much current. With bad power supply (and especially bad cables!) a 10 µF capacitor soldered directly to the NRF24L01+ board connector brings more stability (pin 1+2 are the power supply). Note the polarity of the capacitor… +* You can try to use an USB power supply with 1 A or more instead of connecting the ESP32 to the computer. * Try a different USB cable. Once again, a stable power source is important. Some USB cables are made of much plastic and very little copper inside. -* Double-Check that you have a radio module NRF24L01+ with a plus sign at the end. NRF24L01 module without the plus are not compatible with this project. -* There is no possibility of auto-discovering the inverters. Double-Check you have entered the serial numbers of the inverters correctly. +* Double check that you have a radio module NRF24L01+ with a plus sign at the end. NRF24L01 module without the plus are not compatible with this project. +* There is no possibility of auto-discovering the inverters. Double check you have entered the serial numbers of the inverters correctly. * OpenDTU needs access to a working NTP server to get the current date & time. * If your problem persists, check the [Issues on Github](https://github.com/tbnobody/OpenDTU/issues). Please inspect not only the open issues, also the closed issues contain useful information. * Another source of information are the [Discussions](https://github.com/tbnobody/OpenDTU/discussions/) diff --git a/auto_firmware_version.py b/auto_firmware_version.py index 33d2e587..2f15cc95 100644 --- a/auto_firmware_version.py +++ b/auto_firmware_version.py @@ -11,7 +11,7 @@ installed_pkgs = {pkg.key for pkg in pkg_resources.working_set} missing_pkgs = required_pkgs - installed_pkgs if missing_pkgs: - env.Execute('"$PYTHONEXE" -m pip install dulwich --global-option="--pure"') + env.Execute('"$PYTHONEXE" -m pip install dulwich') from dulwich import porcelain diff --git a/include/MqttHassPublishing.h b/include/MqttHassPublishing.h index 366f41c7..e5044746 100644 --- a/include/MqttHassPublishing.h +++ b/include/MqttHassPublishing.h @@ -3,6 +3,7 @@ #include "Configuration.h" #include +#include #include #include @@ -59,6 +60,10 @@ public: private: void publishField(std::shared_ptr inv, uint8_t channel, byteAssign_fieldDeviceClass_t fieldType, bool clear = false); + void publishInverterButton(std::shared_ptr inv, const char* caption, const char* icon, const char* category, const char* deviceClass, const char* subTopic, const char* payload); + void publishInverterNumber(std::shared_ptr inv, const char* caption, const char* icon, const char* category, const char* commandTopic, const char* stateTopic, const char* unitOfMeasure, int16_t min = 1, int16_t max = 100); + void publishInverterBinarySensor(std::shared_ptr inv, const char* caption, const char* subTopic, const char* payload_on, const char* payload_off); + void createDeviceInfo(JsonObject& object, std::shared_ptr inv); bool _wasConnected = false; bool _updateForced = false; diff --git a/include/MqttSettings.h b/include/MqttSettings.h index 209a8ad6..afa3283c 100644 --- a/include/MqttSettings.h +++ b/include/MqttSettings.h @@ -13,8 +13,8 @@ public: void init(); void performReconnect(); bool getConnected(); - void publish(String subtopic, String payload); - void publishHass(String subtopic, String payload); + void publish(const String& subtopic, const String& payload); + void publishHass(const String& subtopic, const String& payload); String getPrefix(); diff --git a/lib/Hoymiles/src/inverters/HM_1CH.cpp b/lib/Hoymiles/src/inverters/HM_1CH.cpp index 5de99138..2311330e 100644 --- a/lib/Hoymiles/src/inverters/HM_1CH.cpp +++ b/lib/Hoymiles/src/inverters/HM_1CH.cpp @@ -25,7 +25,7 @@ bool HM_1CH::isValidSerial(uint64_t serial) String HM_1CH::typeName() { - return String(F("HM-300, HM-350, HM-400")); + return F("HM-300, HM-350, HM-400"); } const byteAssign_t* HM_1CH::getByteAssignment() diff --git a/lib/Hoymiles/src/inverters/HM_2CH.cpp b/lib/Hoymiles/src/inverters/HM_2CH.cpp index aea62cb3..cf2c5536 100644 --- a/lib/Hoymiles/src/inverters/HM_2CH.cpp +++ b/lib/Hoymiles/src/inverters/HM_2CH.cpp @@ -25,7 +25,7 @@ bool HM_2CH::isValidSerial(uint64_t serial) String HM_2CH::typeName() { - return String(F("HM-600, HM-700, HM-800")); + return F("HM-600, HM-700, HM-800"); } const byteAssign_t* HM_2CH::getByteAssignment() diff --git a/lib/Hoymiles/src/inverters/HM_4CH.cpp b/lib/Hoymiles/src/inverters/HM_4CH.cpp index 908f163c..bda9592d 100644 --- a/lib/Hoymiles/src/inverters/HM_4CH.cpp +++ b/lib/Hoymiles/src/inverters/HM_4CH.cpp @@ -25,7 +25,7 @@ bool HM_4CH::isValidSerial(uint64_t serial) String HM_4CH::typeName() { - return String(F("HM-1000, HM-1200, HM-1500")); + return F("HM-1000, HM-1200, HM-1500"); } const byteAssign_t* HM_4CH::getByteAssignment() diff --git a/lib/Hoymiles/src/inverters/InverterAbstract.cpp b/lib/Hoymiles/src/inverters/InverterAbstract.cpp index adb3191e..bb7216d9 100644 --- a/lib/Hoymiles/src/inverters/InverterAbstract.cpp +++ b/lib/Hoymiles/src/inverters/InverterAbstract.cpp @@ -5,6 +5,13 @@ InverterAbstract::InverterAbstract(uint64_t serial) { _serial.u64 = serial; + + char serial_buff[sizeof(uint64_t) * 8 + 1]; + snprintf(serial_buff, sizeof(serial_buff), "%0x%08x", + ((uint32_t)((serial >> 32) & 0xFFFFFFFF)), + ((uint32_t)(serial & 0xFFFFFFFF))); + _serialString = serial_buff; + _alarmLogParser.reset(new AlarmLogParser()); _devInfoParser.reset(new DevInfoParser()); _powerCommandParser.reset(new PowerCommandParser()); @@ -26,6 +33,11 @@ uint64_t InverterAbstract::serial() return _serial.u64; } +const String& InverterAbstract::serialString() +{ + return _serialString; +} + void InverterAbstract::setName(const char* name) { uint8_t len = strlen(name); diff --git a/lib/Hoymiles/src/inverters/InverterAbstract.h b/lib/Hoymiles/src/inverters/InverterAbstract.h index 5d962706..5c9088b1 100644 --- a/lib/Hoymiles/src/inverters/InverterAbstract.h +++ b/lib/Hoymiles/src/inverters/InverterAbstract.h @@ -33,6 +33,7 @@ public: explicit InverterAbstract(uint64_t serial); void init(); uint64_t serial(); + const String& serialString(); void setName(const char* name); const char* name(); virtual String typeName() = 0; @@ -64,6 +65,7 @@ public: private: serial_u _serial; + String _serialString; char _name[MAX_NAME_LENGTH] = ""; fragment_t _rxFragmentBuffer[MAX_RF_FRAGMENT_COUNT]; uint8_t _rxFragmentMaxPacketId = 0; diff --git a/lib/Hoymiles/src/parser/AlarmLogParser.cpp b/lib/Hoymiles/src/parser/AlarmLogParser.cpp index a5d09366..4958b6a7 100644 --- a/lib/Hoymiles/src/parser/AlarmLogParser.cpp +++ b/lib/Hoymiles/src/parser/AlarmLogParser.cpp @@ -59,214 +59,214 @@ void AlarmLogParser::getLogEntry(uint8_t entryId, AlarmLogEntry_t* entry) switch (entry->MessageId) { case 1: - entry->Message = String(F("Inverter start")); + entry->Message = F("Inverter start"); break; case 2: - entry->Message = String(F("DTU command failed")); + entry->Message = F("DTU command failed"); break; case 121: - entry->Message = String(F("Over temperature protection")); + entry->Message = F("Over temperature protection"); break; case 124: - entry->Message = String(F("Shut down by remote control")); + entry->Message = F("Shut down by remote control"); break; case 125: - entry->Message = String(F("Grid configuration parameter error")); + entry->Message = F("Grid configuration parameter error"); break; case 126: - entry->Message = String(F("Software error code 126")); + entry->Message = F("Software error code 126"); break; case 127: - entry->Message = String(F("Firmware error")); + entry->Message = F("Firmware error"); break; case 128: - entry->Message = String(F("Software error code 128")); + entry->Message = F("Software error code 128"); break; case 129: - entry->Message = String(F("Abnormal bias")); + entry->Message = F("Abnormal bias"); break; case 130: - entry->Message = String(F("Offline")); + entry->Message = F("Offline"); break; case 141: - entry->Message = String(F("Grid: Grid overvoltage")); + entry->Message = F("Grid: Grid overvoltage"); break; case 142: - entry->Message = String(F("Grid: 10 min value grid overvoltage")); + entry->Message = F("Grid: 10 min value grid overvoltage"); break; case 143: - entry->Message = String(F("Grid: Grid undervoltage")); + entry->Message = F("Grid: Grid undervoltage"); break; case 144: - entry->Message = String(F("Grid: Grid overfrequency")); + entry->Message = F("Grid: Grid overfrequency"); break; case 145: - entry->Message = String(F("Grid: Grid underfrequency")); + entry->Message = F("Grid: Grid underfrequency"); break; case 146: - entry->Message = String(F("Grid: Rapid grid frequency change rate")); + entry->Message = F("Grid: Rapid grid frequency change rate"); break; case 147: - entry->Message = String(F("Grid: Power grid outage")); + entry->Message = F("Grid: Power grid outage"); break; case 148: - entry->Message = String(F("Grid: Grid disconnection")); + entry->Message = F("Grid: Grid disconnection"); break; case 149: - entry->Message = String(F("Grid: Island detected")); + entry->Message = F("Grid: Island detected"); break; case 205: - entry->Message = String(F("MPPT-A: Input overvoltage")); + entry->Message = F("MPPT-A: Input overvoltage"); break; case 206: - entry->Message = String(F("MPPT-B: Input overvoltage")); + entry->Message = F("MPPT-B: Input overvoltage"); break; case 207: - entry->Message = String(F("MPPT-A: Input undervoltage")); + entry->Message = F("MPPT-A: Input undervoltage"); break; case 208: - entry->Message = String(F("MPPT-B: Input undervoltage")); + entry->Message = F("MPPT-B: Input undervoltage"); break; case 209: - entry->Message = String(F("PV-1: No input")); + entry->Message = F("PV-1: No input"); break; case 210: - entry->Message = String(F("PV-2: No input")); + entry->Message = F("PV-2: No input"); break; case 211: - entry->Message = String(F("PV-3: No input")); + entry->Message = F("PV-3: No input"); break; case 212: - entry->Message = String(F("PV-4: No input")); + entry->Message = F("PV-4: No input"); break; case 213: - entry->Message = String(F("MPPT-A: PV-1 & PV-2 abnormal wiring")); + entry->Message = F("MPPT-A: PV-1 & PV-2 abnormal wiring"); break; case 214: - entry->Message = String(F("MPPT-B: PV-3 & PV-4 abnormal wiring")); + entry->Message = F("MPPT-B: PV-3 & PV-4 abnormal wiring"); break; case 215: - entry->Message = String(F("PV-1: Input overvoltage")); + entry->Message = F("PV-1: Input overvoltage"); break; case 216: - entry->Message = String(F("PV-1: Input undervoltage")); + entry->Message = F("PV-1: Input undervoltage"); break; case 217: - entry->Message = String(F("PV-2: Input overvoltage")); + entry->Message = F("PV-2: Input overvoltage"); break; case 218: - entry->Message = String(F("PV-2: Input undervoltage")); + entry->Message = F("PV-2: Input undervoltage"); break; case 219: - entry->Message = String(F("PV-3: Input overvoltage")); + entry->Message = F("PV-3: Input overvoltage"); break; case 220: - entry->Message = String(F("PV-3: Input undervoltage")); + entry->Message = F("PV-3: Input undervoltage"); break; case 221: - entry->Message = String(F("PV-4: Input overvoltage")); + entry->Message = F("PV-4: Input overvoltage"); break; case 222: - entry->Message = String(F("PV-4: Input undervoltage")); + entry->Message = F("PV-4: Input undervoltage"); break; case 301: - entry->Message = String(F("Hardware error code 301")); + entry->Message = F("Hardware error code 301"); break; case 302: - entry->Message = String(F("Hardware error code 302")); + entry->Message = F("Hardware error code 302"); break; case 303: - entry->Message = String(F("Hardware error code 303")); + entry->Message = F("Hardware error code 303"); break; case 304: - entry->Message = String(F("Hardware error code 304")); + entry->Message = F("Hardware error code 304"); break; case 305: - entry->Message = String(F("Hardware error code 305")); + entry->Message = F("Hardware error code 305"); break; case 306: - entry->Message = String(F("Hardware error code 306")); + entry->Message = F("Hardware error code 306"); break; case 307: - entry->Message = String(F("Hardware error code 307")); + entry->Message = F("Hardware error code 307"); break; case 308: - entry->Message = String(F("Hardware error code 308")); + entry->Message = F("Hardware error code 308"); break; case 309: - entry->Message = String(F("Hardware error code 309")); + entry->Message = F("Hardware error code 309"); break; case 310: - entry->Message = String(F("Hardware error code 310")); + entry->Message = F("Hardware error code 310"); break; case 311: - entry->Message = String(F("Hardware error code 311")); + entry->Message = F("Hardware error code 311"); break; case 312: - entry->Message = String(F("Hardware error code 312")); + entry->Message = F("Hardware error code 312"); break; case 313: - entry->Message = String(F("Hardware error code 313")); + entry->Message = F("Hardware error code 313"); break; case 314: - entry->Message = String(F("Hardware error code 314")); + entry->Message = F("Hardware error code 314"); break; case 5041: - entry->Message = String(F("Error code-04 Port 1")); + entry->Message = F("Error code-04 Port 1"); break; case 5042: - entry->Message = String(F("Error code-04 Port 2")); + entry->Message = F("Error code-04 Port 2"); break; case 5043: - entry->Message = String(F("Error code-04 Port 3")); + entry->Message = F("Error code-04 Port 3"); break; case 5044: - entry->Message = String(F("Error code-04 Port 4")); + entry->Message = F("Error code-04 Port 4"); break; case 5051: - entry->Message = String(F("PV Input 1 Overvoltage/Undervoltage")); + entry->Message = F("PV Input 1 Overvoltage/Undervoltage"); break; case 5052: - entry->Message = String(F("PV Input 2 Overvoltage/Undervoltage")); + entry->Message = F("PV Input 2 Overvoltage/Undervoltage"); break; case 5053: - entry->Message = String(F("PV Input 3 Overvoltage/Undervoltage")); + entry->Message = F("PV Input 3 Overvoltage/Undervoltage"); break; case 5054: - entry->Message = String(F("PV Input 4 Overvoltage/Undervoltage")); + entry->Message = F("PV Input 4 Overvoltage/Undervoltage"); break; case 5060: - entry->Message = String(F("Abnormal bias")); + entry->Message = F("Abnormal bias"); break; case 5070: - entry->Message = String(F("Over temperature protection")); + entry->Message = F("Over temperature protection"); break; case 5080: - entry->Message = String(F("Grid Overvoltage/Undervoltage")); + entry->Message = F("Grid Overvoltage/Undervoltage"); break; case 5090: - entry->Message = String(F("Grid Overfrequency/Underfrequency")); + entry->Message = F("Grid Overfrequency/Underfrequency"); break; case 5100: - entry->Message = String(F("Island detected")); + entry->Message = F("Island detected"); break; case 5120: - entry->Message = String(F("EEPROM reading and writing error")); + entry->Message = F("EEPROM reading and writing error"); break; case 5150: - entry->Message = String(F("10 min value grid overvoltage")); + entry->Message = F("10 min value grid overvoltage"); break; case 5200: - entry->Message = String(F("Firmware error")); + entry->Message = F("Firmware error"); break; case 8310: - entry->Message = String(F("Shut down")); + entry->Message = F("Shut down"); break; case 9000: - entry->Message = String(F("Microinverter is suspected of being stolen")); + entry->Message = F("Microinverter is suspected of being stolen"); break; default: - entry->Message = String(F("Unknown")); + entry->Message = F("Unknown"); break; } } diff --git a/src/MqttHassPublishing.cpp b/src/MqttHassPublishing.cpp index aaa50d9d..60705b4a 100644 --- a/src/MqttHassPublishing.cpp +++ b/src/MqttHassPublishing.cpp @@ -3,7 +3,6 @@ * Copyright (C) 2022 Thomas Basler and others */ #include "MqttHassPublishing.h" -#include "ArduinoJson.h" #include "MqttPublishing.h" #include "MqttSettings.h" #include "NetworkSettings.h" @@ -52,6 +51,19 @@ void MqttHassPublishingClass::publishConfig() for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) { auto inv = Hoymiles.getInverterByPos(i); + publishInverterButton(inv, "Turn Inverter Off", "mdi:power-plug-off", "config", "", "cmd/power", "0"); + publishInverterButton(inv, "Turn Inverter On", "mdi:power-plug", "config", "", "cmd/power", "1"); + publishInverterButton(inv, "Restart Inverter", "", "config", "restart", "cmd/restart", "1"); + + publishInverterNumber(inv, "Limit NonPersistent Relative", "mdi:speedometer", "config", "cmd/limit_nonpersistent_relative", "status/limit_relative", "%"); + publishInverterNumber(inv, "Limit Persistent Relative", "mdi:speedometer", "config", "cmd/limit_persistent_relative", "status/limit_relative", "%"); + + publishInverterNumber(inv, "Limit NonPersistent Absolute", "mdi:speedometer", "config", "cmd/limit_nonpersistent_absolute", "status/limit_absolute", "W", 10, 1500); + publishInverterNumber(inv, "Limit Persistent Absolute", "mdi:speedometer", "config", "cmd/limit_persistent_absolute", "status/limit_absolute", "W", 10, 1500); + + publishInverterBinarySensor(inv, "Reachable", "status/reachable", "1", "0"); + publishInverterBinarySensor(inv, "Producing", "status/producing", "1", "0"); + // Loop all channels for (uint8_t c = 0; c <= inv->Statistics()->getChannelCount(); c++) { for (uint8_t f = 0; f < DEVICE_CLS_ASSIGN_LIST_LEN; f++) { @@ -73,10 +85,7 @@ void MqttHassPublishingClass::publishField(std::shared_ptr inv return; } - char serial[sizeof(uint64_t) * 8 + 1]; - snprintf(serial, sizeof(serial), "%0x%08x", - ((uint32_t)((inv->serial() >> 32) & 0xFFFFFFFF)), - ((uint32_t)(inv->serial() & 0xFFFFFFFF))); + String serial = inv->serialString(); String fieldName; if (channel == CH0 && fieldType.fieldId == FLD_PDC) { @@ -85,7 +94,7 @@ void MqttHassPublishingClass::publishField(std::shared_ptr inv fieldName = inv->Statistics()->getChannelFieldName(channel, fieldType.fieldId); } - String configTopic = "sensor/dtu_" + String(serial) + String configTopic = "sensor/dtu_" + serial + "/" + "ch" + String(channel) + "_" + fieldName + "/config"; @@ -101,21 +110,15 @@ void MqttHassPublishingClass::publishField(std::shared_ptr inv name = String(inv->name()) + " CH" + String(channel) + " " + fieldName; } - DynamicJsonDocument deviceDoc(512); - deviceDoc[F("name")] = inv->name(); - deviceDoc[F("ids")] = String(serial); - deviceDoc[F("cu")] = String(F("http://")) + String(WiFi.localIP().toString()); - deviceDoc[F("mf")] = F("OpenDTU"); - deviceDoc[F("mdl")] = inv->typeName(); - deviceDoc[F("sw")] = AUTO_GIT_HASH; - JsonObject deviceObj = deviceDoc.as(); - DynamicJsonDocument root(1024); root[F("name")] = name; root[F("stat_t")] = stateTopic; root[F("unit_of_meas")] = inv->Statistics()->getChannelFieldUnit(channel, fieldType.fieldId); - root[F("uniq_id")] = String(serial) + "_ch" + String(channel) + "_" + fieldName; - root[F("dev")] = deviceObj; + root[F("uniq_id")] = serial + "_ch" + String(channel) + "_" + fieldName; + + JsonObject deviceObj = root.createNestedObject("dev"); + createDeviceInfo(deviceObj, inv); + if (Configuration.get().Mqtt_Hass_Expire) { root[F("exp_aft")] = Hoymiles.getNumInverters() * Configuration.get().Mqtt_PublishInterval * 2; } @@ -129,8 +132,120 @@ void MqttHassPublishingClass::publishField(std::shared_ptr inv char buffer[512]; serializeJson(root, buffer); MqttSettings.publishHass(configTopic, buffer); - } - else { + } else { MqttSettings.publishHass(configTopic, ""); } +} + +void MqttHassPublishingClass::publishInverterButton(std::shared_ptr inv, const char* caption, const char* icon, const char* category, const char* deviceClass, const char* subTopic, const char* payload) +{ + String serial = inv->serialString(); + + String buttonId = caption; + buttonId.replace(" ", "_"); + buttonId.toLowerCase(); + + String configTopic = "button/dtu_" + serial + + "/" + buttonId + + "/config"; + + String cmdTopic = MqttSettings.getPrefix() + serial + "/" + subTopic; + + DynamicJsonDocument root(1024); + root[F("name")] = caption; + root[F("uniq_id")] = serial + "_" + buttonId; + if (strcmp(icon, "")) { + root[F("ic")] = icon; + } + if (strcmp(deviceClass, "")) { + root[F("dev_cla")] = deviceClass; + } + root[F("ent_cat")] = category; + root[F("cmd_t")] = cmdTopic; + root[F("payload_press")] = payload; + + JsonObject deviceObj = root.createNestedObject("dev"); + createDeviceInfo(deviceObj, inv); + + char buffer[512]; + serializeJson(root, buffer); + MqttSettings.publishHass(configTopic, buffer); +} + +void MqttHassPublishingClass::publishInverterNumber( + std::shared_ptr inv, const char* caption, const char* icon, const char* category, + const char* commandTopic, const char* stateTopic, const char* unitOfMeasure, + int16_t min, int16_t max) +{ + String serial = inv->serialString(); + + String buttonId = caption; + buttonId.replace(" ", "_"); + buttonId.toLowerCase(); + + String configTopic = "number/dtu_" + serial + + "/" + buttonId + + "/config"; + + String cmdTopic = MqttSettings.getPrefix() + serial + "/" + commandTopic; + String statTopic = MqttSettings.getPrefix() + serial + "/" + stateTopic; + + DynamicJsonDocument root(1024); + root[F("name")] = caption; + root[F("uniq_id")] = serial + "_" + buttonId; + if (strcmp(icon, "")) { + root[F("ic")] = icon; + } + root[F("ent_cat")] = category; + root[F("cmd_t")] = cmdTopic; + root[F("stat_t")] = statTopic; + root[F("unit_of_meas")] = unitOfMeasure; + root[F("min")] = min; + root[F("max")] = max; + + JsonObject deviceObj = root.createNestedObject("dev"); + createDeviceInfo(deviceObj, inv); + + char buffer[512]; + serializeJson(root, buffer); + MqttSettings.publishHass(configTopic, buffer); +} + +void MqttHassPublishingClass::publishInverterBinarySensor(std::shared_ptr inv, const char* caption, const char* subTopic, const char* payload_on, const char* payload_off) +{ + String serial = inv->serialString(); + + String sensorId = caption; + sensorId.replace(" ", "_"); + sensorId.toLowerCase(); + + String configTopic = "binary_sensor/dtu_" + serial + + "/" + sensorId + + "/config"; + + String statTopic = MqttSettings.getPrefix() + serial + "/" + subTopic; + + DynamicJsonDocument root(1024); + root[F("name")] = caption; + root[F("uniq_id")] = serial + "_" + sensorId; + root[F("stat_t")] = statTopic; + root[F("pl_on")] = payload_on; + root[F("pl_off")] = payload_off; + + JsonObject deviceObj = root.createNestedObject("dev"); + createDeviceInfo(deviceObj, inv); + + char buffer[512]; + serializeJson(root, buffer); + MqttSettings.publishHass(configTopic, buffer); +} + +void MqttHassPublishingClass::createDeviceInfo(JsonObject& object, std::shared_ptr inv) +{ + object[F("name")] = inv->name(); + object[F("ids")] = inv->serialString(); + object[F("cu")] = String(F("http://")) + WiFi.localIP().toString(); + object[F("mf")] = F("OpenDTU"); + object[F("mdl")] = inv->typeName(); + object[F("sw")] = AUTO_GIT_HASH; } \ No newline at end of file diff --git a/src/MqttPublishing.cpp b/src/MqttPublishing.cpp index fc74b785..46819bce 100644 --- a/src/MqttPublishing.cpp +++ b/src/MqttPublishing.cpp @@ -33,11 +33,7 @@ void MqttPublishingClass::loop() for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) { auto inv = Hoymiles.getInverterByPos(i); - char buffer[sizeof(uint64_t) * 8 + 1]; - snprintf(buffer, sizeof(buffer), "%0x%08x", - ((uint32_t)((inv->serial() >> 32) & 0xFFFFFFFF)), - ((uint32_t)(inv->serial() & 0xFFFFFFFF))); - String subtopic = String(buffer); + String subtopic = inv->serialString(); // Name MqttSettings.publish(subtopic + "/name", inv->name()); @@ -110,12 +106,6 @@ String MqttPublishingClass::getTopic(std::shared_ptr inv, uint return String(""); } - char buffer[sizeof(uint64_t) * 8 + 1]; - snprintf(buffer, sizeof(buffer), "%0x%08x", - ((uint32_t)((inv->serial() >> 32) & 0xFFFFFFFF)), - ((uint32_t)(inv->serial() & 0xFFFFFFFF))); - String invSerial = String(buffer); - String chanName; if (channel == 0 && fieldId == FLD_PDC) { chanName = "powerdc"; @@ -124,5 +114,5 @@ String MqttPublishingClass::getTopic(std::shared_ptr inv, uint chanName.toLowerCase(); } - return invSerial + "/" + String(channel) + "/" + chanName; + return inv->serialString() + "/" + String(channel) + "/" + chanName; } \ No newline at end of file diff --git a/src/MqttSettings.cpp b/src/MqttSettings.cpp index d7e7a655..516409bd 100644 --- a/src/MqttSettings.cpp +++ b/src/MqttSettings.cpp @@ -233,14 +233,14 @@ String MqttSettingsClass::getPrefix() return Configuration.get().Mqtt_Topic; } -void MqttSettingsClass::publish(String subtopic, String payload) +void MqttSettingsClass::publish(const String& subtopic, const String& payload) { String topic = getPrefix(); topic += subtopic; mqttClient->publish(topic.c_str(), 0, Configuration.get().Mqtt_Retain, payload.c_str()); } -void MqttSettingsClass::publishHass(String subtopic, String payload) +void MqttSettingsClass::publishHass(const String& subtopic, const String& payload) { String topic = Configuration.get().Mqtt_Hass_Topic; topic += subtopic; diff --git a/src/NetworkSettings.cpp b/src/NetworkSettings.cpp index 732f3ec2..b975043c 100644 --- a/src/NetworkSettings.cpp +++ b/src/NetworkSettings.cpp @@ -375,7 +375,7 @@ String NetworkSettingsClass::macAddress() return WiFi.macAddress(); break; default: - return String(""); + return ""; } } diff --git a/src/WebApi_devinfo.cpp b/src/WebApi_devinfo.cpp index 318b2f02..93f2fa75 100644 --- a/src/WebApi_devinfo.cpp +++ b/src/WebApi_devinfo.cpp @@ -29,13 +29,7 @@ void WebApiDevInfoClass::onDevInfoStatus(AsyncWebServerRequest* request) for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) { auto inv = Hoymiles.getInverterByPos(i); - // Inverter Serial is read as HEX - char buffer[sizeof(uint64_t) * 8 + 1]; - snprintf(buffer, sizeof(buffer), "%0x%08x", - ((uint32_t)((inv->serial() >> 32) & 0xFFFFFFFF)), - ((uint32_t)(inv->serial() & 0xFFFFFFFF))); - - JsonObject devInfoObj = root[buffer].createNestedObject(); + JsonObject devInfoObj = root[inv->serialString()].createNestedObject(); devInfoObj[F("valid_data")] = inv->DevInfo()->getLastUpdate() > 0; devInfoObj[F("fw_bootloader_version")] = inv->DevInfo()->getFwBootloaderVersion(); devInfoObj[F("fw_build_version")] = inv->DevInfo()->getFwBuildVersion(); diff --git a/src/WebApi_eventlog.cpp b/src/WebApi_eventlog.cpp index edff3bb6..9ba3c525 100644 --- a/src/WebApi_eventlog.cpp +++ b/src/WebApi_eventlog.cpp @@ -34,16 +34,12 @@ void WebApiEventlogClass::onEventlogStatus(AsyncWebServerRequest* request) auto inv = Hoymiles.getInverterBySerial(serial); if (inv != nullptr) { - // Inverter Serial is read as HEX - char buffer[sizeof(uint64_t) * 8 + 1]; - snprintf(buffer, sizeof(buffer), "%0x%08x", - ((uint32_t)((inv->serial() >> 32) & 0xFFFFFFFF)), - ((uint32_t)(inv->serial() & 0xFFFFFFFF))); + String serial = inv->serialString(); uint8_t logEntryCount = inv->EventLog()->getEntryCount(); - root[buffer]["count"] = logEntryCount; - JsonArray eventsArray = root[buffer].createNestedArray(F("events")); + root[serial]["count"] = logEntryCount; + JsonArray eventsArray = root[serial].createNestedArray(F("events")); for (uint8_t logEntry = 0; logEntry < logEntryCount; logEntry++) { JsonObject eventsObject = eventsArray.createNestedObject(); diff --git a/src/WebApi_limit.cpp b/src/WebApi_limit.cpp index cfcb21e7..33bcd407 100644 --- a/src/WebApi_limit.cpp +++ b/src/WebApi_limit.cpp @@ -29,14 +29,10 @@ void WebApiLimitClass::onLimitStatus(AsyncWebServerRequest* request) for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) { auto inv = Hoymiles.getInverterByPos(i); - // Inverter Serial is read as HEX - char buffer[sizeof(uint64_t) * 8 + 1]; - snprintf(buffer, sizeof(buffer), "%0x%08x", - ((uint32_t)((inv->serial() >> 32) & 0xFFFFFFFF)), - ((uint32_t)(inv->serial() & 0xFFFFFFFF))); + String serial = inv->serialString(); - root[buffer]["limit_relative"] = inv->SystemConfigPara()->getLimitPercent(); - root[buffer]["max_power"] = inv->DevInfo()->getMaxPower(); + root[serial]["limit_relative"] = inv->SystemConfigPara()->getLimitPercent(); + root[serial]["max_power"] = inv->DevInfo()->getMaxPower(); LastCommandSuccess status = inv->SystemConfigPara()->getLastLimitCommandSuccess(); String limitStatus = "Unknown"; @@ -49,7 +45,7 @@ void WebApiLimitClass::onLimitStatus(AsyncWebServerRequest* request) else if (status == LastCommandSuccess::CMD_PENDING) { limitStatus = "Pending"; } - root[buffer]["limit_set_status"] = limitStatus; + root[serial]["limit_set_status"] = limitStatus; } response->setLength(); diff --git a/src/WebApi_power.cpp b/src/WebApi_power.cpp index 8ecda811..8e3c2bc8 100644 --- a/src/WebApi_power.cpp +++ b/src/WebApi_power.cpp @@ -29,12 +29,6 @@ void WebApiPowerClass::onPowerStatus(AsyncWebServerRequest* request) for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) { auto inv = Hoymiles.getInverterByPos(i); - // Inverter Serial is read as HEX - char buffer[sizeof(uint64_t) * 8 + 1]; - snprintf(buffer, sizeof(buffer), "%0x%08x", - ((uint32_t)((inv->serial() >> 32) & 0xFFFFFFFF)), - ((uint32_t)(inv->serial() & 0xFFFFFFFF))); - LastCommandSuccess status = inv->PowerCommand()->getLastPowerCommandSuccess(); String limitStatus = "Unknown"; if (status == LastCommandSuccess::CMD_OK) { @@ -44,7 +38,7 @@ void WebApiPowerClass::onPowerStatus(AsyncWebServerRequest* request) } else if (status == LastCommandSuccess::CMD_PENDING) { limitStatus = "Pending"; } - root[buffer]["power_set_status"] = limitStatus; + root[inv->serialString()]["power_set_status"] = limitStatus; } response->setLength(); diff --git a/src/WebApi_ws_live.cpp b/src/WebApi_ws_live.cpp index 032c9078..41ce2b5e 100644 --- a/src/WebApi_ws_live.cpp +++ b/src/WebApi_ws_live.cpp @@ -77,12 +77,7 @@ void WebApiWsLiveClass::generateJsonResponse(JsonVariant& root) for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) { auto inv = Hoymiles.getInverterByPos(i); - char buffer[sizeof(uint64_t) * 8 + 1]; - snprintf(buffer, sizeof(buffer), "%0x%08x", - ((uint32_t)((inv->serial() >> 32) & 0xFFFFFFFF)), - ((uint32_t)(inv->serial() & 0xFFFFFFFF))); - - root[i][F("serial")] = String(buffer); + root[i][F("serial")] = inv->serialString(); root[i][F("name")] = inv->name(); root[i][F("data_age")] = (millis() - inv->Statistics()->getLastUpdate()) / 1000; root[i][F("reachable")] = inv->isReachable();