diff --git a/include/Configuration.h b/include/Configuration.h index 4a802e4e1..1d4f98e0c 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -76,6 +76,16 @@ struct CONFIG_T { bool Enabled; } Mdns; + struct { + bool modbus_tcp_enabled; + bool modbus_delaystart; + char mfrname[32]; + char modelname[32]; + char options[16]; + char version[16]; + char serial[16]; + } modbus; + struct { char Server[NTP_MAX_SERVER_STRLEN + 1]; char Timezone[NTP_MAX_TIMEZONE_STRLEN + 1]; diff --git a/include/ModbusDtu.h b/include/ModbusDtu.h new file mode 100644 index 000000000..9cf2892c4 --- /dev/null +++ b/include/ModbusDtu.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include "Configuration.h" +#include +#include + +class ModbusDtuClass { +public: + ModbusDtuClass(); + void init(Scheduler& scheduler); + +private: + void loop(); + void setup(); + void modbus(); + bool _isstarted = false; + float _lasttotal = 0; + + Task _loopTask; + Task _modbusTask; +}; + +extern ModbusDtuClass ModbusDtu; diff --git a/include/defaults.h b/include/defaults.h index ee3f7b2fe..9917d7f58 100644 --- a/include/defaults.h +++ b/include/defaults.h @@ -22,6 +22,13 @@ #define MDNS_ENABLED false +#define MODBUS_ENABLED false +#define MODBUS_DELAY_START false +#define MODBUS_MFRNAME "OpenDTU" +#define MODBUS_MODELNAME "OpenDTU-SunSpec" +#define MODBUS_OPTIONS "" +#define MODBUS_VERSION "1.0" + #define NTP_SERVER_OLD "pool.ntp.org" #define NTP_SERVER "opendtu.pool.ntp.org" #define NTP_TIMEZONE "CET-1CEST,M3.5.0,M10.5.0/3" diff --git a/platformio.ini b/platformio.ini index 2d86e9de8..f9e867dce 100644 --- a/platformio.ini +++ b/platformio.ini @@ -45,6 +45,7 @@ lib_deps = nrf24/RF24 @ 1.4.9 olikraus/U8g2 @ 2.35.19 buelowp/sunset @ 1.1.7 + https://github.com/emelianov/modbus-esp8266#master https://github.com/arkhipenko/TaskScheduler#testing extra_scripts = diff --git a/src/Configuration.cpp b/src/Configuration.cpp index db47d9c8a..1be7a55b0 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -47,6 +47,15 @@ bool ConfigurationClass::write() JsonObject mdns = doc["mdns"].to(); mdns["enabled"] = config.Mdns.Enabled; + JsonObject modbus = doc["modbus"].to(); + modbus["modbus_tcp_enabled"] = config.modbus.modbus_tcp_enabled; + modbus["modbus_delaystart"] = config.modbus.modbus_delaystart; + modbus["mfrname"] = config.modbus.mfrname; + modbus["modelname"] = config.modbus.modelname; + modbus["options"] = config.modbus.options; + modbus["version"] = config.modbus.version; + modbus["serial"] = config.modbus.serial; + JsonObject ntp = doc["ntp"].to(); ntp["server"] = config.Ntp.Server; ntp["timezone"] = config.Ntp.Timezone; @@ -222,6 +231,15 @@ bool ConfigurationClass::read() JsonObject mdns = doc["mdns"]; config.Mdns.Enabled = mdns["enabled"] | MDNS_ENABLED; + JsonObject modbus = doc["modbus"]; + config.modbus.modbus_tcp_enabled = modbus["modbus_tcp_enabled"] | MODBUS_ENABLED; + config.modbus.modbus_delaystart = modbus["modbus_delaystart"] | MODBUS_DELAY_START; + strlcpy(config.modbus.mfrname, modbus["mfrname"] | MODBUS_MFRNAME, sizeof(config.modbus.mfrname)); + strlcpy(config.modbus.modelname, modbus["modelname"] | MODBUS_MODELNAME, sizeof(config.modbus.modelname)); + strlcpy(config.modbus.options, modbus["options"] | MODBUS_OPTIONS, sizeof(config.modbus.options)); + strlcpy(config.modbus.version, modbus["version"] | MODBUS_VERSION, sizeof(config.modbus.version)); + strlcpy(config.modbus.serial, modbus["serial"] | "", sizeof(config.modbus.serial)); + JsonObject ntp = doc["ntp"]; strlcpy(config.Ntp.Server, ntp["server"] | NTP_SERVER, sizeof(config.Ntp.Server)); strlcpy(config.Ntp.Timezone, ntp["timezone"] | NTP_TIMEZONE, sizeof(config.Ntp.Timezone)); diff --git a/src/ModbusDtu.cpp b/src/ModbusDtu.cpp new file mode 100644 index 000000000..999702840 --- /dev/null +++ b/src/ModbusDtu.cpp @@ -0,0 +1,230 @@ +#include "ModbusDtu.h" +#include "Datastore.h" +#include "MessageOutput.h" + +ModbusIP mb; + +ModbusDtuClass ModbusDtu; + +ModbusDtuClass::ModbusDtuClass() + : _loopTask(Configuration.get().Dtu.PollInterval * TASK_SECOND, TASK_FOREVER, std::bind(&ModbusDtuClass::loop, this)) + , _modbusTask(TASK_IMMEDIATE, TASK_FOREVER, std::bind(&ModbusDtuClass::modbus, this)) +{ +} + +void ModbusDtuClass::init(Scheduler& scheduler) +{ + scheduler.addTask(_loopTask); + _loopTask.enable(); + scheduler.addTask(_modbusTask); +} + +void ModbusDtuClass::modbus() +{ + mb.task(); +} + +void ModbusDtuClass::setup() +{ + if ((Configuration.get().Dtu.Serial) < 0x100000000000 || (Configuration.get().Dtu.Serial) > 0x199999999999) { + MessageOutput.printf("Modbus: need a DTU Serial between 100000000000 and 199999999999 (currently configured: %llx)\r\n", Configuration.get().Dtu.Serial); + _isstarted = false; + return; + } + mb.server(); + mb.addHreg(0x9c40, 21365); //40000 Sunspec Id start + mb.addHreg(0x9c41, 28243); //40001 Sunspec Id end + mb.addHreg(0x9c42, 1); //40002 SunSpec_DID + mb.addHreg(0x9c43, 65); //40003 SunSpec_Length + const char *mfrname = Configuration.get().modbus.mfrname; //Manufacturer Name max. 32 chars + for (uint8_t i = 0; i < 32; i += 2) { + uint16_t value = 0; + if (strlen(mfrname) > i) value = (mfrname[i] << 8) | (i + 1 < strlen(mfrname) ? mfrname[i + 1] : 0); + mb.addHreg(0x9c44 + (i / 2), value); //40004 - 40019 Manufacturer name + // MessageOutput.printf("Vendor: write %d to register %d\r\n", value, (0x9c44 + (i / 2))); + } + const char *modelname = Configuration.get().modbus.modelname; + for (uint8_t i = 0; i < 32; i += 2) { + uint16_t value = 0; + if (strlen(modelname) > i) value = (modelname[i] << 8) | (i + 1 < strlen(modelname) ? modelname[i + 1] : 0); + mb.addHreg(0x9c54 + (i / 2), value); //40020 - 40035 Device Model Name + // MessageOutput.printf("Modelname: write %d to register %d\r\n", value, (0x9c54 + (i / 2))); + } + const char *options = Configuration.get().modbus.options; + for (uint8_t i = 0; i < 16; i += 2) { + uint16_t value = 0; + if (strlen(options) > i) value = (options[i] << 8) | (i + 1 < strlen(options) ? options[i + 1] : 0); + mb.addHreg(0x9c64 + (i / 2), value); //40036 - 40043 Options + // MessageOutput.printf("Options: write %d to register %d\r\n", value, (0x9c64 + (i / 2))); + } + const char *version = Configuration.get().modbus.version; + for (uint8_t i = 0; i < 16; i += 2) { + uint16_t value = 0; + if (strlen(version) > i) value = (version[i] << 8) | (i + 1 < strlen(version) ? version[i + 1] : 0); + mb.addHreg(0x9c6c + (i / 2), value); //40044 - 40051 Version + // MessageOutput.printf("Version: write %d to register %d\r\n", value, (0x9c6c + (i / 2))); + } + const char *serialconfig = Configuration.get().modbus.serial; + if (!strlen(serialconfig)) { + char serial[24]; + uint16_t *hexbytes = reinterpret_cast(serial); + snprintf(serial,sizeof(serial),"%llx",(Configuration.get().Dtu.Serial)); + MessageOutput.printf("Modbus: init uses DTU Serial: %llx\r\n", Configuration.get().Dtu.Serial); + MessageOutput.printf("Modbus: writing to init modbus registers %d %d %d %d %d %d\r\n", ntohs(hexbytes[0]), ntohs(hexbytes[1]), ntohs(hexbytes[2]), ntohs(hexbytes[3]), ntohs(hexbytes[4]), ntohs(hexbytes[5])); + for (uint8_t i = 0; i < 6; i++) { + mb.addHreg(0x9c74 + i, ntohs(hexbytes[i])); //40052 Serial Number start + } + mb.addHreg(0x9c7a, 0, 10); //40067 Serial Number end + } else { + for (uint8_t i = 0; i < 32; i += 2) { + uint16_t value = 0; + if (strlen(serialconfig) > i) value = (serialconfig[i] << 8) | (i + 1 < strlen(serialconfig) ? serialconfig[i + 1] : 0); + mb.addHreg(0x9c74 + (i / 2), value); //40052 - 40067 Serial Number + // MessageOutput.printf("Modbus: write %d to register %d\r\n", value, (0x9c74 + (i / 2))); + } + } + mb.addHreg(0x9c84, 202); //40068 DeviceAddress Modbus TCP Address: 202 + mb.addHreg(0x9c85, 213); //40069 SunSpec_DID + mb.addHreg(0x9c86, 124); //40070 SunSpec_Length + mb.addHreg(0x9c87, 0, 123);//40071 - 40194 smartmeter data + mb.addHreg(0x9d03, 65535); //40195 end block identifier + mb.addHreg(0x9d04, 0); //40196 + _isstarted = true; +} + +void ModbusDtuClass::loop() +{ + _loopTask.setInterval(Configuration.get().Dtu.PollInterval * TASK_SECOND); + + if (!(Configuration.get().modbus.modbus_tcp_enabled)) return; + + if (!Hoymiles.isAllRadioIdle()) { + _loopTask.forceNextIteration(); + return; + } + + if (!_isstarted) { + if (!(Configuration.get().modbus.modbus_delaystart) || (Datastore.getIsAllEnabledReachable() && Datastore.getTotalAcYieldTotalEnabled() != 0)) { + MessageOutput.printf("Modbus: starting server ... \r\n"); + ModbusDtu.setup(); + _modbusTask.enable(); + } else { + MessageOutput.printf("Modbus: not initializing yet! (Total Yield = 0 or not all configured inverters reachable)\r\n"); + return; + } + } + + if (!(Datastore.getIsAllEnabledReachable()) || !(Datastore.getTotalAcYieldTotalEnabled() != 0) || (!_isstarted) || !(Configuration.get().modbus.modbus_delaystart)) { + MessageOutput.printf("Modbus: not updating registers! (Total Yield = 0 or not all configured inverters reachable)\r\n"); + return; + } else { + float value; + uint16_t *hexbytes = reinterpret_cast(&value); + value = (Datastore.getTotalAcPowerEnabled()*-1); + // MessageOutput.printf("Modbus: write %.2f to 40097 and 40098\r\n", value); + mb.Hreg(0x9ca1, hexbytes[1]); + mb.Hreg(0x9ca2, hexbytes[0]); + value = (Datastore.getTotalAcYieldTotalEnabled()*1000); + if (value > _lasttotal) { + _lasttotal = value; + // MessageOutput.printf("Modbus: write %.2f to 40129 and 40130\r\n", value); + mb.Hreg(0x9cc1, hexbytes[1]); + mb.Hreg(0x9cc2, hexbytes[0]); + } + + if (Hoymiles.getNumInverters() == 1) { + // MessageOutput.printf("Modbus: Start additional SM Information\r\n"); + auto inv = Hoymiles.getInverterByPos(0); + if (inv != nullptr) { + for (auto& t : inv->Statistics()->getChannelTypes()) { + if (t == TYPE_DC) { + value = (inv->Statistics()->getChannelFieldValue(TYPE_AC, CH0, FLD_IAC)); + mb.Hreg(0x9c87, hexbytes[1]); + mb.Hreg(0x9c88, hexbytes[0]); + value = ((inv->Statistics()->getChannelFieldValue(TYPE_AC, CH0, FLD_IAC_1) != 0 ? inv->Statistics()->getChannelFieldValue(TYPE_AC, CH0, FLD_IAC_1) : inv->Statistics()->getChannelFieldValue(TYPE_AC, CH0, FLD_IAC))); + mb.Hreg(0x9c89, hexbytes[1]); + mb.Hreg(0x9c8a, hexbytes[0]); + value = (inv->Statistics()->getChannelFieldValue(TYPE_AC, CH0, FLD_IAC_2)); + mb.Hreg(0x9c8b, hexbytes[1]); + mb.Hreg(0x9c8c, hexbytes[0]); + value = (inv->Statistics()->getChannelFieldValue(TYPE_AC, CH0, FLD_IAC_3)); + mb.Hreg(0x9c8d, hexbytes[1]); + mb.Hreg(0x9c8e, hexbytes[0]); + value = (inv->Statistics()->getChannelFieldValue(TYPE_AC, CH0, FLD_UAC_1N)); + mb.Hreg(0x9c8f, hexbytes[1]); + mb.Hreg(0x9c90, hexbytes[0]); + value = ((inv->Statistics()->getChannelFieldValue(TYPE_AC, CH0, FLD_UAC_1N) != 0 ? inv->Statistics()->getChannelFieldValue(TYPE_AC, CH0, FLD_UAC_1N) : inv->Statistics()->getChannelFieldValue(TYPE_AC, CH0, FLD_UAC))); + mb.Hreg(0x9c91, hexbytes[1]); + mb.Hreg(0x9c92, hexbytes[0]); + value = (inv->Statistics()->getChannelFieldValue(TYPE_AC, CH0, FLD_UAC_2N)); + mb.Hreg(0x9c93, hexbytes[1]); + mb.Hreg(0x9c94, hexbytes[0]); + value = (inv->Statistics()->getChannelFieldValue(TYPE_AC, CH0, FLD_UAC_3N)); + mb.Hreg(0x9c95, hexbytes[1]); + mb.Hreg(0x9c96, hexbytes[0]); + value = (inv->Statistics()->getChannelFieldValue(TYPE_AC, CH0, FLD_UAC)); + mb.Hreg(0x9c97, hexbytes[1]); + mb.Hreg(0x9c98, hexbytes[0]); + value = (inv->Statistics()->getChannelFieldValue(TYPE_AC, CH0, FLD_UAC_12)); + mb.Hreg(0x9c99, hexbytes[1]); + mb.Hreg(0x9c9a, hexbytes[0]); + value = (inv->Statistics()->getChannelFieldValue(TYPE_AC, CH0, FLD_UAC_23)); + mb.Hreg(0x9c9b, hexbytes[1]); + mb.Hreg(0x9c9c, hexbytes[0]); + value = (inv->Statistics()->getChannelFieldValue(TYPE_AC, CH0, FLD_UAC_31)); + mb.Hreg(0x9c9d, hexbytes[1]); + mb.Hreg(0x9c9e, hexbytes[0]); + value = (inv->Statistics()->getChannelFieldValue(TYPE_AC, CH0, FLD_F)); + mb.Hreg(0x9c9f, hexbytes[1]); + mb.Hreg(0x9ca0, hexbytes[0]); + // value = (inv->Statistics()->getChannelFieldValue(TYPE_AC, CH0, FLD_PAC)*-1); //done above already! + // mb.Hreg(0x9ca1, hexbytes[1]); //done above already! + // mb.Hreg(0x9ca2, hexbytes[0]); //done above already! + value = ((inv->Statistics()->getChannelFieldValue(TYPE_AC, CH0, FLD_IAC_1) != 0) ? ((inv->Statistics()->getChannelFieldValue(TYPE_AC, CH0, FLD_IAC_1)) * (inv->Statistics()->getChannelFieldValue(TYPE_AC, CH0, FLD_UAC_1N)) *-1) : ((inv->Statistics()->getChannelFieldValue(TYPE_AC, CH0, FLD_IAC)) * (inv->Statistics()->getChannelFieldValue(TYPE_AC, CH0, FLD_UAC)) *-1)); + mb.Hreg(0x9ca3, hexbytes[1]); + mb.Hreg(0x9ca4, hexbytes[0]); + value = ((inv->Statistics()->getChannelFieldValue(TYPE_AC, CH0, FLD_IAC_2)) * (inv->Statistics()->getChannelFieldValue(TYPE_AC, CH0, FLD_UAC_2N)) *-1); + mb.Hreg(0x9ca5, hexbytes[1]); + mb.Hreg(0x9ca6, hexbytes[0]); + value = ((inv->Statistics()->getChannelFieldValue(TYPE_AC, CH0, FLD_IAC_3)) * (inv->Statistics()->getChannelFieldValue(TYPE_AC, CH0, FLD_UAC_3N)) *-1); + mb.Hreg(0x9ca7, hexbytes[1]); + mb.Hreg(0x9ca8, hexbytes[0]); + // mb.Hreg(0x9ca9, 0); + // mb.Hreg(0x9caa, 0); + // mb.Hreg(0x9cab, 0); + // mb.Hreg(0x9cac, 0); + // mb.Hreg(0x9cad, 0); + // mb.Hreg(0x9cae, 0); + // mb.Hreg(0x9caf, 0); + // mb.Hreg(0x9cb0, 0); + value = (inv->Statistics()->getChannelFieldValue(TYPE_AC, CH0, FLD_Q)); + mb.Hreg(0x9cb1, hexbytes[1]); + mb.Hreg(0x9cb2, hexbytes[0]); + // mb.Hreg(0x9cb3, 0); + // mb.Hreg(0x9cb4, 0); + // mb.Hreg(0x9cb5, 0); + // mb.Hreg(0x9cb6, 0); + // mb.Hreg(0x9cb7, 0); + // mb.Hreg(0x9cb8, 0); + value = (inv->Statistics()->getChannelFieldValue(TYPE_AC, CH0, FLD_PF)); + mb.Hreg(0x9cb9, hexbytes[1]); + mb.Hreg(0x9cba, hexbytes[0]); + // mb.Hreg(0x9cbb, 0); + // mb.Hreg(0x9cbc, 0); + // mb.Hreg(0x9cbd, 0); + // mb.Hreg(0x9cbe, 0); + // mb.Hreg(0x9cbf, 0); + // mb.Hreg(0x9cc0, 0); + // value = (inv->Statistics()->getChannelFieldValue(TYPE_AC, CH0, FLD_YT)*1000); //done above already! + // if (value > _lasttotal) { + // _lasttotal = value; + // mb.Hreg(0x9cc1, hexbytes[1]); //done above already! + // mb.Hreg(0x9cc2, hexbytes[0]); //done above already! + // } + } + } + } + // MessageOutput.printf("Modbus: End additional SM Information\r\n"); + } + } +} diff --git a/src/WebApi_network.cpp b/src/WebApi_network.cpp index 7fec44b2a..f18796a13 100644 --- a/src/WebApi_network.cpp +++ b/src/WebApi_network.cpp @@ -70,6 +70,13 @@ void WebApiNetworkClass::onNetworkAdminGet(AsyncWebServerRequest* request) root["password"] = config.WiFi.Password; root["aptimeout"] = config.WiFi.ApTimeout; root["mdnsenabled"] = config.Mdns.Enabled; + root["modbus_tcp_enabled"] = config.modbus.modbus_tcp_enabled; + root["modbus_delaystart"] = config.modbus.modbus_delaystart; + root["mfrname"] = config.modbus.mfrname; + root["modelname"] = config.modbus.modelname; + root["options"] = config.modbus.options; + root["version"] = config.modbus.version; + root["serial"] = config.modbus.serial; WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); } @@ -195,6 +202,13 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request) } config.WiFi.ApTimeout = root["aptimeout"].as(); config.Mdns.Enabled = root["mdnsenabled"].as(); + config.modbus.modbus_tcp_enabled = root["modbus_tcp_enabled"].as(); + config.modbus.modbus_delaystart = root["modbus_delaystart"].as(); + strlcpy(config.modbus.mfrname, root["mfrname"].as().c_str(), sizeof(config.modbus.mfrname)); + strlcpy(config.modbus.modelname, root["modelname"].as().c_str(), sizeof(config.modbus.modelname)); + strlcpy(config.modbus.options, root["options"].as().c_str(), sizeof(config.modbus.options)); + strlcpy(config.modbus.version, root["version"].as().c_str(), sizeof(config.modbus.version)); + strlcpy(config.modbus.serial, root["serial"].as().c_str(), sizeof(config.modbus.serial)); WebApi.writeConfig(retMsg); diff --git a/src/main.cpp b/src/main.cpp index 433619e1f..b0613c727 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -18,6 +18,7 @@ #include "PinMapping.h" #include "Scheduler.h" #include "SunPosition.h" +#include "ModbusDtu.h" #include "Utils.h" #include "WebApi.h" #include "defaults.h" @@ -153,6 +154,8 @@ void setup() InverterSettings.init(scheduler); + ModbusDtu.init(scheduler); + Datastore.init(scheduler); } diff --git a/webapp/src/locales/de.json b/webapp/src/locales/de.json index 834849d22..2adbd7e6f 100644 --- a/webapp/src/locales/de.json +++ b/webapp/src/locales/de.json @@ -432,7 +432,22 @@ "ApTimeoutHint": "Zeit die der AccessPoint offen gehalten wird. Ein Wert von 0 bedeutet unendlich.", "Minutes": "Minuten", "EnableMdns": "mDNS aktivieren", - "MdnsSettings": "mDNS-Einstellungen" + "MdnsSettings": "mDNS-Einstellungen", + "ModbusSettings": "Modbus-Einstellungen", + "EnableModbusTCP": "Modbus TCP Server aktivieren", + "EnableModbusTCPHint": "SunSpec-kompatiblen Modbus TCP Server auf port 502 aktivieren", + "DelayModbusStart": "Modbus TCP Serverstart verzögern", + "DelayModbusStartHint": "Modbus Server startet erst, wenn alle Wechselrichter erreichbar sind und YieldTotal != 0 ist", + "ModbusMfrName": "SunSpec Modbus Manufacturer Name", + "ModbusMfrNameHint": "Wenn leer wird automatisch 'OpenDTU' verwendet", + "ModbusModelName": "SunSpec Modbus Model Name", + "ModbusModelNameHint": "Wenn leer wird automatisch 'OpenDTU-Sunspec' verwendet", + "ModbusOptions": "SunSpec Modbus Options", + "ModbusOptionsHint": "Sunspec Modbus Options String", + "ModbusVersion": "SunSpec Modbus Version", + "ModbusVersionHint": "Wenn leer wird '1.0' verwendet", + "Modbusserial": "SunSpec Modbus Serial Number", + "ModbusserialHint": "Wenn leer wird automatisch die OpenDTU Serial verwendet" }, "mqttadmin": { "MqttSettings": "MQTT-Einstellungen", diff --git a/webapp/src/locales/en.json b/webapp/src/locales/en.json index af2a1b440..de37ffbfe 100644 --- a/webapp/src/locales/en.json +++ b/webapp/src/locales/en.json @@ -432,7 +432,22 @@ "ApTimeoutHint": "Time which the AccessPoint is kept open. A value of 0 means infinite.", "Minutes": "minutes", "EnableMdns": "Enable mDNS", - "MdnsSettings": "mDNS Settings" + "MdnsSettings": "mDNS Settings", + "ModbusSettings": "Modbus Settings", + "EnableModbusTCP": "Enable Modbus TCP server", + "EnableModbusTCPHint": "Activate SunSpec-compatible Modbus TCP server on port 502", + "DelayModbusStart": "Delay Modbus TCP serverstart", + "DelayModbusStartHint": "Modbus server start is delayed until all inverters are reachable and YieldTotal != 0", + "ModbusMfrName": "SunSpec Modbus manufacturer name", + "ModbusMfrNameHint": "If empty, 'OpenDTU' is used", + "ModbusModelName": "SunSpec Modbus model name", + "ModbusModelNameHint": "If empty, 'OpenDTU-Sunspec' is used", + "ModbusOptions": "SunSpec Modbus options", + "ModbusOptionsHint": "Sunspec Modbus options string", + "ModbusVersion": "SunSpec Modbus version", + "ModbusVersionHint": "If empty, '1.0' is used", + "Modbusserial": "SunSpec Modbus serial number", + "ModbusserialHint": "If empty, OpenDTU serial is used" }, "mqttadmin": { "MqttSettings": "MQTT Settings", diff --git a/webapp/src/locales/fr.json b/webapp/src/locales/fr.json index 96f259d08..56176bdd5 100644 --- a/webapp/src/locales/fr.json +++ b/webapp/src/locales/fr.json @@ -432,7 +432,22 @@ "ApTimeoutHint": "Durée pendant laquelle le point d'accès reste ouvert. Une valeur de 0 signifie infini.", "Minutes": "minutes", "EnableMdns": "Activer mDNS", - "MdnsSettings": "mDNS Settings" + "MdnsSettings": "mDNS Settings", + "ModbusSettings": "Modbus-Einstellungen", + "EnableModbusTCP": "Activer Modbus TCP server", + "EnableModbusTCPHint": "Activer le serveur Modbus TCP compatible SunSpec sur le port 502", + "DelayModbusStart": "Retarder le démarrage du serveur Modbus TCP", + "DelayModbusStartHint": "Le démarrage du serveur Modbus est retardé jusqu'à ce que tous les onduleurs soient accessibles et que YieldTotal != 0", + "ModbusMfrName": "Nom du fabricant SunSpec Modbus", + "ModbusMfrNameHint": "Si vide, « OpenDTU » est utilisé", + "ModbusModelName": "Nom du modèle SunSpec Modbus", + "ModbusModelNameHint": "Si vide, « OpenDTU-Sunspec » est utilisé", + "ModbusOptions": "Options Modbus SunSpec", + "ModbusOptionsHint": "Chaîne d'options Sunspec Modbus", + "ModbusVersion": "Version Modbus de SunSpec", + "ModbusVersionHint": "Si vide, « 1.0 » est utilisé", + "Modbusserial": "Numéro de série SunSpec Modbus", + "ModbusserialHint": "Si vide, le numéro de série OpenDTU est utilisé" }, "mqttadmin": { "MqttSettings": "Paramètres MQTT", diff --git a/webapp/src/types/NetworkConfig.ts b/webapp/src/types/NetworkConfig.ts index da5ddd44b..173f6a352 100644 --- a/webapp/src/types/NetworkConfig.ts +++ b/webapp/src/types/NetworkConfig.ts @@ -10,4 +10,11 @@ export interface NetworkConfig { dns2: string; aptimeout: number; mdnsenabled: boolean; + modbus_tcp_enabled: boolean; + modbus_delaystart: boolean; + mfrname: string; + modelname: string; + options: string; + version: string; + serial: string; } diff --git a/webapp/src/views/NetworkAdminView.vue b/webapp/src/views/NetworkAdminView.vue index aeabec085..a775f5651 100644 --- a/webapp/src/views/NetworkAdminView.vue +++ b/webapp/src/views/NetworkAdminView.vue @@ -93,6 +93,62 @@ :tooltip="$t('networkadmin.ApTimeoutHint')" /> + + + + + + + + + + + + + + + + diff --git a/webapp_dist/index.html.gz b/webapp_dist/index.html.gz index 84ebd66c2..a3c1d78c5 100644 Binary files a/webapp_dist/index.html.gz and b/webapp_dist/index.html.gz differ diff --git a/webapp_dist/js/app.js.gz b/webapp_dist/js/app.js.gz index a8d427d0d..e573a040e 100644 Binary files a/webapp_dist/js/app.js.gz and b/webapp_dist/js/app.js.gz differ diff --git a/webapp_dist/zones.json.gz b/webapp_dist/zones.json.gz index feeaebf73..24ed0f5a3 100644 Binary files a/webapp_dist/zones.json.gz and b/webapp_dist/zones.json.gz differ