Merge remote-tracking branch 'tbnobody/OpenDTU/master'

This commit is contained in:
helgeerbe 2022-10-31 14:37:44 +01:00
commit 6b4129c400
19 changed files with 253 additions and 151 deletions

View File

@ -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/)

View File

@ -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

View File

@ -3,6 +3,7 @@
#include "Configuration.h"
#include <Arduino.h>
#include <ArduinoJson.h>
#include <Hoymiles.h>
#include <memory>
@ -59,6 +60,10 @@ public:
private:
void publishField(std::shared_ptr<InverterAbstract> inv, uint8_t channel, byteAssign_fieldDeviceClass_t fieldType, bool clear = false);
void publishInverterButton(std::shared_ptr<InverterAbstract> inv, const char* caption, const char* icon, const char* category, const char* deviceClass, const char* subTopic, const char* payload);
void publishInverterNumber(std::shared_ptr<InverterAbstract> 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<InverterAbstract> inv, const char* caption, const char* subTopic, const char* payload_on, const char* payload_off);
void createDeviceInfo(JsonObject& object, std::shared_ptr<InverterAbstract> inv);
bool _wasConnected = false;
bool _updateForced = false;

View File

@ -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();

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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);

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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<InverterAbstract> 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<InverterAbstract> 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<InverterAbstract> 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<JsonObject>();
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<InverterAbstract> inv
char buffer[512];
serializeJson(root, buffer);
MqttSettings.publishHass(configTopic, buffer);
}
else {
} else {
MqttSettings.publishHass(configTopic, "");
}
}
void MqttHassPublishingClass::publishInverterButton(std::shared_ptr<InverterAbstract> 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<InverterAbstract> 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<InverterAbstract> 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<InverterAbstract> 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;
}

View File

@ -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<InverterAbstract> 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<InverterAbstract> inv, uint
chanName.toLowerCase();
}
return invSerial + "/" + String(channel) + "/" + chanName;
return inv->serialString() + "/" + String(channel) + "/" + chanName;
}

View File

@ -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;

View File

@ -375,7 +375,7 @@ String NetworkSettingsClass::macAddress()
return WiFi.macAddress();
break;
default:
return String("");
return "";
}
}

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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();