Skip to content

Commit

Permalink
from Fronius-Simulation to SunSpec-OpenDTU compatibility.
Browse files Browse the repository at this point in the history
add configuration for elements to equip with custom vendor strings.
Alois Klingler committed Aug 6, 2024
1 parent 389f982 commit ad04999
Showing 11 changed files with 190 additions and 46 deletions.
8 changes: 7 additions & 1 deletion include/Configuration.h
Original file line number Diff line number Diff line change
@@ -77,7 +77,13 @@ struct CONFIG_T {
} Mdns;

struct {
bool Fronius_SM_Simulation_Enabled;
bool modbus_tcp_enabled;
bool modbus_delaystart;
char mfrname[32];
char modelname[32];
char options[16];
char version[16];
char serial[16];
} modbus;

struct {
7 changes: 6 additions & 1 deletion include/defaults.h
Original file line number Diff line number Diff line change
@@ -22,7 +22,12 @@

#define MDNS_ENABLED false

#define FRONIUS_SM_SIMULATION_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"
16 changes: 14 additions & 2 deletions src/Configuration.cpp
Original file line number Diff line number Diff line change
@@ -48,7 +48,13 @@ bool ConfigurationClass::write()
mdns["enabled"] = config.Mdns.Enabled;

JsonObject modbus = doc["modbus"].to<JsonObject>();
modbus["enabled"] = config.modbus.Fronius_SM_Simulation_Enabled;
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<JsonObject>();
ntp["server"] = config.Ntp.Server;
@@ -226,7 +232,13 @@ bool ConfigurationClass::read()
config.Mdns.Enabled = mdns["enabled"] | MDNS_ENABLED;

JsonObject modbus = doc["modbus"];
config.modbus.Fronius_SM_Simulation_Enabled = modbus["enabled"] | FRONIUS_SM_SIMULATION_ENABLED;
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));
70 changes: 42 additions & 28 deletions src/ModbusDtu.cpp
Original file line number Diff line number Diff line change
@@ -27,52 +27,66 @@ void ModbusDtuClass::modbus()
void ModbusDtuClass::setup()
{
if ((Configuration.get().Dtu.Serial) < 0x100000000000 || (Configuration.get().Dtu.Serial) > 0x199999999999) {
MessageOutput.printf("Fronius SM Simulation: need a DTU Serial between 100000000000 and 199999999999 (currently configured: %llx)\r\n", Configuration.get().Dtu.Serial);
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 5375 == Su
mb.addHreg(0x9c41, 28243); //40001 Sunspec Id end 6e53 == nS
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 = "Fronius"; //Manufacturer Name max. 32 chars
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 = "Smart Meter TS 65A-3"; //Device Model max. 32 chars
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 = ""; //Options max. 16 chars
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 = "1.3"; //Version max. 16 chars
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)));
}
char serial[24];
uint16_t *hexbytes = reinterpret_cast<uint16_t *>(serial);
snprintf(serial,sizeof(serial),"%llx",(Configuration.get().Dtu.Serial));
MessageOutput.printf("Fronius SM Simulation: init uses DTU Serial: %llx\r\n", Configuration.get().Dtu.Serial);
MessageOutput.printf("Fronius SM Simulation: 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
const char *serialconfig = Configuration.get().modbus.serial;
if (!strlen(serialconfig)) {
char serial[24];
uint16_t *hexbytes = reinterpret_cast<uint16_t *>(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(0x9c7a, 0, 10); //40067 Serial Number end
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(40071, 0, 123); //40071 - 40194 smartmeter data
mb.addHreg(0x9c87, 0, 123);//40071 - 40194 smartmeter data
mb.addHreg(0x9d03, 65535); //40195 end block identifier
mb.addHreg(0x9d04, 0); //40196
_isstarted = true;
@@ -82,43 +96,43 @@ void ModbusDtuClass::loop()
{
_loopTask.setInterval(Configuration.get().Dtu.PollInterval * TASK_SECOND);

if (!(Configuration.get().modbus.Fronius_SM_Simulation_Enabled)) return;
if (!(Configuration.get().modbus.modbus_tcp_enabled)) return;

if (!Hoymiles.isAllRadioIdle()) {
_loopTask.forceNextIteration();
return;
}

if (!_isstarted) {
if (Datastore.getIsAllEnabledReachable() && Datastore.getTotalAcYieldTotalEnabled() != 0) {
if (Configuration.get().modbus.modbus_delaystart || (Datastore.getIsAllEnabledReachable() && Datastore.getTotalAcYieldTotalEnabled() != 0)) {
ModbusDtu.setup();
_modbusTask.enable();
} else {
MessageOutput.printf("Fronius SM Simulation: not initializing yet! (Total Yield = 0 or not all configured inverters reachable).\r\n");
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)) {
MessageOutput.printf("Fronius SM Simulation: not updating registers! (Total Yield = 0 or not all configured inverters reachable).\r\n");
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<uint16_t *>(&value);
value = (Datastore.getTotalAcPowerEnabled()*-1);
// MessageOutput.printf("Fronius SM Simulation: modbus write %.2f to 40097 and 40098\r\n", value);
// 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("Fronius SM Simulation: modbus write %.2f to 40129 and 40130\r\n", 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("Fronius SM Simulation: Start additional SM Information\r\n");
// MessageOutput.printf("Modbus: Start additional SM Information\r\n");
auto inv = Hoymiles.getInverterByPos(0);
if (inv != nullptr) {
for (auto& t : inv->Statistics()->getChannelTypes()) {
@@ -162,9 +176,9 @@ void ModbusDtuClass::loop()
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);
// mb.Hreg(0x9ca1, hexbytes[1]);
// mb.Hreg(0x9ca2, 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]);
@@ -209,7 +223,7 @@ void ModbusDtuClass::loop()
}
}
}
// MessageOutput.printf("Fronius SM Simulation: End additional SM Information\r\n");
// MessageOutput.printf("Modbus: End additional SM Information\r\n");
}
}
}
16 changes: 14 additions & 2 deletions src/WebApi_network.cpp
Original file line number Diff line number Diff line change
@@ -70,7 +70,13 @@ void WebApiNetworkClass::onNetworkAdminGet(AsyncWebServerRequest* request)
root["password"] = config.WiFi.Password;
root["aptimeout"] = config.WiFi.ApTimeout;
root["mdnsenabled"] = config.Mdns.Enabled;
root["froniussmmodbusenabled"] = config.modbus.Fronius_SM_Simulation_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__);
}
@@ -196,7 +202,13 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request)
}
config.WiFi.ApTimeout = root["aptimeout"].as<uint>();
config.Mdns.Enabled = root["mdnsenabled"].as<bool>();
config.modbus.Fronius_SM_Simulation_Enabled = root["froniussmmodbusenabled"].as<bool>();
config.modbus.modbus_tcp_enabled = root["modbus_tcp_enabled"].as<bool>();
config.modbus.modbus_delaystart = root["modbus_delaystart"].as<bool>();
strlcpy(config.modbus.mfrname, root["mfrname"].as<String>().c_str(), sizeof(config.modbus.mfrname));
strlcpy(config.modbus.modelname, root["modelname"].as<String>().c_str(), sizeof(config.modbus.modelname));
strlcpy(config.modbus.options, root["options"].as<String>().c_str(), sizeof(config.modbus.options));
strlcpy(config.modbus.version, root["version"].as<String>().c_str(), sizeof(config.modbus.version));
strlcpy(config.modbus.serial, root["serial"].as<String>().c_str(), sizeof(config.modbus.serial));

WebApi.writeConfig(retMsg);

15 changes: 14 additions & 1 deletion webapp/src/locales/de.json
Original file line number Diff line number Diff line change
@@ -434,7 +434,20 @@
"EnableMdns": "mDNS aktivieren",
"MdnsSettings": "mDNS-Einstellungen",
"ModbusSettings": "Modbus-Einstellungen",
"EnableFroniusSM": "Fronius SmartMeter Simulation aktivieren"
"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",
17 changes: 15 additions & 2 deletions webapp/src/locales/en.json
Original file line number Diff line number Diff line change
@@ -433,8 +433,21 @@
"Minutes": "minutes",
"EnableMdns": "Enable mDNS",
"MdnsSettings": "mDNS Settings",
"ModbusSettings": "Modbus Settings",
"EnableFroniusSM": "Enable Fronius SmartMeter simulation"
"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",
17 changes: 15 additions & 2 deletions webapp/src/locales/fr.json
Original file line number Diff line number Diff line change
@@ -433,8 +433,21 @@
"Minutes": "minutes",
"EnableMdns": "Activer mDNS",
"MdnsSettings": "mDNS Settings",
"ModbusSettings": "Modbus Settings",
"EnableFroniusSM": "Activer Fronius SmartMeter simulation"
"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",
8 changes: 7 additions & 1 deletion webapp/src/types/NetworkConfig.ts
Original file line number Diff line number Diff line change
@@ -10,5 +10,11 @@ export interface NetworkConfig {
dns2: string;
aptimeout: number;
mdnsenabled: boolean;
froniussmmodbusenabled: boolean;
modbus_tcp_enabled: boolean;
modbus_delaystart: boolean;
mfrname: string;
modelname: string;
options: string;
version: string;
serial: string;
}
Loading

0 comments on commit ad04999

Please sign in to comment.