Fixed Modbus register access

This commit is contained in:
Matthias Heisig
2024-01-17 11:26:11 +01:00
parent 82826c258a
commit 57987cb19f
24 changed files with 1310 additions and 572 deletions

View File

@@ -3,6 +3,7 @@
<DUT Name="E_BATTERY_STATUS" Id="{99b02815-2b47-4159-a889-c86170baa34d}">
<Declaration><![CDATA[{attribute 'qualified_only'}
{attribute 'strict'}
{attribute 'to_string'}
TYPE E_BATTERY_STATUS :
(
ERROR := 1,

View File

@@ -3,6 +3,7 @@
<DUT Name="E_CHARGE_STATUS" Id="{7a281462-bf73-4949-96d8-aca55bee6c60}">
<Declaration><![CDATA[{attribute 'qualified_only'}
{attribute 'strict'}
{attribute 'to_string'}
TYPE E_CHARGE_STATUS :
(
UNDEFINED := 1,

View File

@@ -3,6 +3,7 @@
<DUT Name="E_REACTIVE_POWER_TYPE" Id="{e2c788a7-c536-4b02-910e-6cbe600afdab}">
<Declaration><![CDATA[{attribute 'qualified_only'}
{attribute 'strict'}
{attribute 'to_string'}
TYPE E_REACTIVE_POWER_TYPE :
(
INDUCTIVE := 1,

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<TcPlcObject Version="1.1.0.1" ProductVersion="3.1.4024.12">
<DUT Name="ST_MODBUS_REG_10" Id="{0c25ea41-552d-4ad4-8369-cc4ac1877b46}">
<Declaration><![CDATA[{attribute 'pack_mode' := '1'}
TYPE ST_MODBUS_REG_10 :
STRUCT
// Addr: 10.000
sManufacturer : STRING(32); // 1 char = 1 byte
// Addr: 10.016
sModelname : STRING(32);
// Addr: 10.032
sSerialnumber : STRING(32);
// Addr: 10.048
sBMSVersion : STRING(64);
// Addr: 10.080
uiTotalParallelMembers : UINT; //uint 16bit = 1 Word
// Addr: 10.081
uiActiveParallelMembers : UINT;
END_STRUCT
END_TYPE
]]></Declaration>
</DUT>
</TcPlcObject>

View File

@@ -1,27 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<TcPlcObject Version="1.1.0.1" ProductVersion="3.1.4024.12">
<DUT Name="ST_EMS_MODBUS_INTERFACE" Id="{c26c9ee9-feee-46cf-86e6-45d1a1e6819c}">
<Declaration><![CDATA[TYPE ST_EMS_MODBUS_INTERFACE :
<DUT Name="ST_MODBUS_REG_11" Id="{7e772293-d4e3-4809-9da8-3d4e0412ede6}">
<Declaration><![CDATA[{attribute 'pack_mode' := '1'}
TYPE ST_MODBUS_REG_11 :
STRUCT
// Size = 16 * 2 byte = 32 byte
// Addr: 10.000
sManufacturer : STRING(32); // 1 char = 1 byte
// Addr: 10.016
sModelname : STRING(32);
// Addr: 10.032
sSerialnumber : STRING(32);
// Addr: 10.048
sBMSVersion : STRING(64);
// Addr: 10.080
uiTotalParallelMembers : UINT; //uint 16bit = 1 Word
// Addr: 10.081
uiActiveParallelMembers : UINT;
// Addr: 11.000
eBatteryStatus : E_BATTERY_STATUS;
@@ -38,19 +20,19 @@ STRUCT
// Addr: 11.006
// Unit: W
udiMaxChargingActivePower : UDINT;
udiMaxChargingActivePower : UDINT := 30_000;
// Addr: 11.008
// Unit: W
udiMaxDischargingActivePower : UDINT;
udiMaxDischargingActivePower : UDINT := 30_000;
// Addr: 11.010
// Unit: var
udiMaxAvailableInductiveReactivePower : UDINT;
udiMaxAvailableInductiveReactivePower : UDINT := 0;
// Addr: 11.012
// Unit: var
udiMaxAvailableCapacitiveReactivePower : UDINT;
udiMaxAvailableCapacitiveReactivePower : UDINT := 0;
// Addr: 11.014
// Unit: %
@@ -58,11 +40,11 @@ STRUCT
// Addr: 11.015
// Unit: %
uiMaxAllowedSOC : UINT;
uiMaxAllowedSOC : UINT := 100;
// Addr: 11.016
// Unit: %
uiMinAllowedSOC : UINT;
uiMinAllowedSOC : UINT := 0;
// Addr: 11.017
// Unit: %
@@ -82,53 +64,43 @@ STRUCT
// Addr: 11.023
// Unit: %
uiCurrentSOH : UINT;
uiCurrentSOH : UINT := 100;
// Addr: 11.024
// Unit: A
diTotalACCurrentPhase1 : DINT;
// Addr: 11.025
// Addr: 11.026
// Unit: A
diTotalACCurrentPhase2 : DINT;
// Addr: 11.026
// Addr: 11.028
// Unit: A
diTotalACCurrentPhase3 : DINT;
// Addr: 11.027
// Addr: 11.030
// Unit: W
diSetpointActivePowerMirror : DINT;
// Addr: 11.028
// Addr: 11.032
rSetpointCosPhiMirror : REAL; // 32 bit
// Addr: 11.030
// Addr: 11.034
// Unit: W
diCurrentActivePower : DINT;
// Addr: 11.031
// Addr: 11.036
// Unit: var
diCurrentReactivePower : DINT;
// Addr: 11.032
// Addr: 11.038
udiLifeMessage : UDINT;
// Addr: 11.033
// Addr: 11.040
lwWarningBitmap : LWORD;
// Addr: 11.035
// Addr: 11.044
lwErrorBitmap : LWORD;
// Addr: 12.000
// Unit: W
diSetpointActivePower : DINT;
// Addr: 12.001
rSetpointCosPhi : REAL;
// Addr: 12.003
eReactivePowerType : E_REACTIVE_POWER_TYPE;
END_STRUCT
END_TYPE
]]></Declaration>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<TcPlcObject Version="1.1.0.1" ProductVersion="3.1.4024.12">
<DUT Name="ST_MODBUS_REG_12" Id="{7c428918-2d8d-43ab-8096-edbee8bd4171}">
<Declaration><![CDATA[{attribute 'pack_mode' := '1'}
TYPE ST_MODBUS_REG_12 :
STRUCT
// Unit: W
diSetpointActivePower : DINT;
// Addr: 12.002
rSetpointCosPhi : REAL;
// Addr: 12.004
eReactivePowerType : E_REACTIVE_POWER_TYPE;
END_STRUCT
END_TYPE
]]></Declaration>
</DUT>
</TcPlcObject>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<TcPlcObject Version="1.1.0.1" ProductVersion="3.1.4024.12">
<DUT Name="ST_EMS_MODBUS_INTERFACE" Id="{c26c9ee9-feee-46cf-86e6-45d1a1e6819c}">
<Declaration><![CDATA[// Speicherbereiche, welche über einen kontinuierlichen Registerbereich
// gelesen werden sollen, müssen in einer Struktur mit dem attribut {attribute 'pack_mode' := '1'}
// angelegt werden.
// Der Grund ist, dass der TwinCAT Modbus Server beim zugriff über den ADS
// Server nur die Symbolische erste Adresse anfragt und dann davon ausgeht,
// dass die anderen Register im Speicherbereich hintereinander liegen.
// Bei rein symbolisch angelegten Variablen müss dies nicht der Fall sein und es kommt zu einem
// Fehler beim lesen über die symbolischen grenzen der variablen hinaus.
{attribute 'pack_mode' := '1'}
TYPE ST_EMS_MODBUS_INTERFACE :
STRUCT
// Modbus Registers 10.000 to 10.999
stModbusReg10 : ST_MODBUS_REG_10;
// Modbus Registers 11.000 to 11.999
stModbusReg11 : ST_MODBUS_REG_11;
// Modbus Registers 12.000 to 12.999
stModbusReg12 : ST_MODBUS_REG_12;
END_STRUCT
END_TYPE
]]></Declaration>
</DUT>
</TcPlcObject>

View File

@@ -200,7 +200,7 @@ VAR_GLOBAL
diMaxStringDischargePower : DINT := 30_000;
// Inverter ip address
sInverterIp : STRING := '192.168.0.1';
sInverterIp : STRING := '192.168.42.11';
// Absolute shutdown discharge power (Watt)
rAbsShutdownDischargePower : REAL := 5_000;
@@ -214,6 +214,9 @@ VAR_GLOBAL
// String empty voltage (Volt)
rStringEmptyVoltage : REAL := 672.0;
// Unit wait startup time
timUnitStartupTime : TIME := T#5S;
END_VAR]]></Declaration>
</GVL>
</TcPlcObject>

View File

@@ -5,19 +5,16 @@
VAR_GLOBAL
// Modbus interface to customer EMS
stModbusEMSComm : ST_EMS_MODBUS_INTERFACE := (
sManufacturer := 'cmblu energy ag',
sModelname := 'all liquid',
sSerialnumber := '202312151643',
sBMSVersion := '0.8.0',
uiTotalParallelMembers := 2,
eChargeStatus := E_CHARGE_STATUS.UNDEFINED,
eBatteryStatus := E_BATTERY_STATUS.OFF);
// Modbus default config
//mb_Input_Coils : ARRAY [0..255] OF BOOL;
//mb_Output_Coils : ARRAY [0..255] OF BOOL;
//mb_Input_Registers : ARRAY [0..255] OF WORD;
//mb_Output_Registers : ARRAY [0..255] OF WORD;
stModbusReg10 := (
sManufacturer := 'cmblu energy ag',
sModelname := 'all liquid',
sSerialnumber := '202312151643',
sBMSVersion := '0.8.0',
uiTotalParallelMembers := 1),
stModbusReg11 := (
eChargeStatus := E_CHARGE_STATUS.UNDEFINED,
eBatteryStatus := E_BATTERY_STATUS.OFF)
);
END_VAR]]></Declaration>
</GVL>
</TcPlcObject>

19
PLC/GVLs/GVL_MQTT.TcGVL Normal file
View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<TcPlcObject Version="1.1.0.1" ProductVersion="3.1.4024.12">
<GVL Name="GVL_MQTT" Id="{b8376b04-66fc-43a7-b673-600aa359c597}">
<Declaration><![CDATA[{attribute 'qualified_only'}
VAR_GLOBAL
// Mqtt brocker ip address
sMQTTBrokerAddr : STRING := '127.0.0.1';
// Mqtt keep alive (s)
uiMQTTKeepAlive : UINT := 60;
// MQTT prefix
sMQTTPrefix : STRING := 'cmblu/uniper/1/';
// MQTT QOS (Default 0 -> At most once; 1 -> At least once; 2 -> Exactly once
eMQTTQoS : TcIoTMqttQos := TcIoTMqttQos.AtMostOnceDelivery;
END_VAR]]></Declaration>
</GVL>
</TcPlcObject>

View File

@@ -27,7 +27,16 @@
<Compile Include="DUTs\E_REACTIVE_POWER_TYPE.TcDUT">
<SubType>Code</SubType>
</Compile>
<Compile Include="DUTs\ST_EMS_MODBUS_INTERFACE.TcDUT">
<Compile Include="DUTs\Modbus\HoldingRegisters\ST_MODBUS_REG_10.TcDUT">
<SubType>Code</SubType>
</Compile>
<Compile Include="DUTs\Modbus\HoldingRegisters\ST_MODBUS_REG_11.TcDUT">
<SubType>Code</SubType>
</Compile>
<Compile Include="DUTs\Modbus\HoldingRegisters\ST_MODBUS_REG_12.TcDUT">
<SubType>Code</SubType>
</Compile>
<Compile Include="DUTs\Modbus\ST_EMS_MODBUS_INTERFACE.TcDUT">
<SubType>Code</SubType>
</Compile>
<Compile Include="DUTs\ST_MODULE_HMI_INTERFACE.TcDUT">
@@ -50,6 +59,10 @@
<SubType>Code</SubType>
<LinkAlways>true</LinkAlways>
</Compile>
<Compile Include="GVLs\GVL_MQTT.TcGVL">
<SubType>Code</SubType>
<LinkAlways>true</LinkAlways>
</Compile>
<Compile Include="GVLs\GVL_SCADA.TcGVL">
<SubType>Code</SubType>
<LinkAlways>true</LinkAlways>
@@ -72,12 +85,17 @@
<Compile Include="POUs\Sunspec\FB_PowerSupplySunspec.TcPOU">
<SubType>Code</SubType>
</Compile>
<Compile Include="POUs\Sunspec\ST_SUNSPEC_CURRENT_VALUES.TcDUT">
<SubType>Code</SubType>
</Compile>
<Compile Include="POUs\Sunspec\U_SUNSPEC_ERROR.TcDUT">
<SubType>Code</SubType>
</Compile>
</ItemGroup>
<ItemGroup>
<Folder Include="DUTs" />
<Folder Include="DUTs\Modbus" />
<Folder Include="DUTs\Modbus\HoldingRegisters" />
<Folder Include="GVLs" />
<Folder Include="POUs\Sunspec" />
<Folder Include="VISUs" />
@@ -100,8 +118,16 @@
<DefaultResolution>Tc2_System, * (Beckhoff Automation GmbH)</DefaultResolution>
<Namespace>Tc2_System</Namespace>
</PlaceholderReference>
<PlaceholderReference Include="Tc2_Utilities">
<DefaultResolution>Tc2_Utilities, * (Beckhoff Automation GmbH)</DefaultResolution>
</PlaceholderReference>
<PlaceholderReference Include="Tc3_EventLogger">
<DefaultResolution>Tc3_EventLogger, * (Beckhoff Automation GmbH)</DefaultResolution>
<Namespace>Tc3_EventLogger</Namespace>
</PlaceholderReference>
<PlaceholderReference Include="Tc3_IotBase">
<DefaultResolution>Tc3_IotBase, * (Beckhoff Automation GmbH)</DefaultResolution>
<Namespace>Tc3_IotBase</Namespace>
</PlaceholderReference>
<PlaceholderReference Include="Tc3_Module">
<DefaultResolution>Tc3_Module, * (Beckhoff Automation GmbH)</DefaultResolution>

File diff suppressed because one or more lines are too long

View File

@@ -12,6 +12,9 @@ VAR_INPUT
// Module completely off
xOff : BOOL := TRUE;
// All safetyinterlocks are ok
xSafetyIntlksOk : BOOL;
// HMI Interface
stHMIInterface : REFERENCE TO ST_MODULE_HMI_INTERFACE;
@@ -44,10 +47,10 @@ VAR_OUTPUT
xWarning : BOOL;
END_VAR
VAR
_fbUnit1 : FB_Unit(CONCAT(_sName, ' Unit 1'));
_fbUnit2 : FB_Unit(CONCAT(_sName, ' Unit 2'));
_fbUnit3 : FB_Unit(CONCAT(_sName, ' Unit 3'));
_fbUnit4 : FB_Unit(CONCAT(_sName, ' Unit 4'));
_fbUnit1 : FB_Unit('Unit 1');
_fbUnit2 : FB_Unit('Unit 2');
_fbUnit3 : FB_Unit('Unit 3');
_fbUnit4 : FB_Unit('Unit 4');
// Flag for unit balance checking
_xBalanceOk : BOOL;
@@ -55,6 +58,9 @@ VAR
// All units are ready
_xAllUnitsReady : BOOL;
// Units out of balance alarm
_fbUnitsOutOfBalanceAlarm : FB_TcAlarm;
// Module name
_sName : STRING;
END_VAR
@@ -64,6 +70,10 @@ END_VAR
xError := FALSE;
xWarning := FALSE;
// Reset safety interlocks flag
xSafetyIntlksOk := TRUE;
// ===============================
// Unit 1
// ===============================
@@ -87,6 +97,10 @@ IF _fbUnit1.xError THEN
xError := TRUE;
END_IF
IF NOT _fbUnit1.xSafetyIntlksOk THEN
xSafetyIntlksOk := FALSE;
END_IF
// ===============================
// Unit 2
@@ -111,6 +125,10 @@ IF _fbUnit2.xError THEN
xError := TRUE;
END_IF
IF NOT _fbUnit2.xSafetyIntlksOk THEN
xSafetyIntlksOk := FALSE;
END_IF
// ===============================
// Unit 3
@@ -135,6 +153,10 @@ IF _fbUnit3.xError THEN
xError := TRUE;
END_IF
IF NOT _fbUnit3.xSafetyIntlksOk THEN
xSafetyIntlksOk := FALSE;
END_IF
// ===============================
// Unit 4
@@ -159,6 +181,10 @@ IF _fbUnit4.xError THEN
xError := TRUE;
END_IF
IF NOT _fbUnit4.xSafetyIntlksOk THEN
xSafetyIntlksOk := FALSE;
END_IF
// ===============================
// Units ready check
@@ -230,6 +256,21 @@ IF _xAllUnitsReady AND (NOT _xBalanceOk) THEN
xError := TRUE;
END_IF
// Raise error
IF (NOT _xBalanceOk) AND (NOT _fbUnitsOutOfBalanceAlarm.bRaised) THEN
_fbUnitsOutOfBalanceAlarm.Raise(0);
END_IF
// Clear error
IF _xBalanceOk AND _fbUnitsOutOfBalanceAlarm.bRaised THEN
_fbUnitsOutOfBalanceAlarm.Clear(0, FALSE);
END_IF
// Confirm error
IF _fbUnitsOutOfBalanceAlarm.eConfirmationState = TcEventConfirmationState.WaitForConfirmation AND xConfirmAlarms THEN
_fbUnitsOutOfBalanceAlarm.Confirm(0);
END_IF
// ===============================
// Module ready validation check
// ===============================
@@ -249,21 +290,79 @@ VAR_INPUT
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[_sName := sName;]]></ST>
<ST><![CDATA[_sName := sName;
// Set unit names
_fbUnit1.Name := CONCAT(_sName, 'Unit 1');
_fbUnit2.Name := CONCAT(_sName, 'Unit 2');
_fbUnit3.Name := CONCAT(_sName, 'Unit 3');
_fbUnit4.Name := CONCAT(_sName, 'Unit 4');
// Create out of balance alarm
_fbUnitsOutOfBalanceAlarm.CreateEx(stEventEntry := TC_EVENTS.BMSEvents.ModuleImbalance, bWithConfirmation := TRUE, 0);
// Create alarm message
_fbUnitsOutOfBalanceAlarm.ipArguments.Clear().AddString(_sName);]]></ST>
</Implementation>
</Method>
<Property Name="Name" Id="{bc8bc990-5071-47c9-a928-a129c60c6f41}">
<Declaration><![CDATA[PROPERTY Name : String]]></Declaration>
<Get Name="Get" Id="{c00a6d41-9156-49b5-aa08-e4cc4c913fca}">
<Declaration><![CDATA[VAR
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[Name := _sName;]]></ST>
</Implementation>
</Get>
<Set Name="Set" Id="{ade5f4d9-ff42-4236-b5fc-e43a53c9ca28}">
<Declaration><![CDATA[VAR
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[_sName := Name;
// Set unit names
_fbUnit1.Name := CONCAT(_sName, ' - Unit 1');
_fbUnit2.Name := CONCAT(_sName, ' - Unit 2');
_fbUnit3.Name := CONCAT(_sName, ' - Unit 3');
_fbUnit4.Name := CONCAT(_sName, ' - Unit 4');
// Create alarm message
_fbUnitsOutOfBalanceAlarm.ipArguments.Clear().AddString(_sName);]]></ST>
</Implementation>
</Set>
</Property>
<LineIds Name="FB_Module">
<LineId Id="248" Count="7" />
<LineId Id="248" Count="2" />
<LineId Id="557" Count="1" />
<LineId Id="556" Count="0" />
<LineId Id="575" Count="0" />
<LineId Id="251" Count="4" />
<LineId Id="456" Count="0" />
<LineId Id="256" Count="22" />
<LineId Id="256" Count="16" />
<LineId Id="560" Count="0" />
<LineId Id="559" Count="0" />
<LineId Id="561" Count="1" />
<LineId Id="273" Count="5" />
<LineId Id="457" Count="0" />
<LineId Id="279" Count="22" />
<LineId Id="279" Count="16" />
<LineId Id="563" Count="2" />
<LineId Id="296" Count="0" />
<LineId Id="574" Count="0" />
<LineId Id="297" Count="4" />
<LineId Id="458" Count="0" />
<LineId Id="302" Count="22" />
<LineId Id="302" Count="16" />
<LineId Id="566" Count="2" />
<LineId Id="319" Count="0" />
<LineId Id="573" Count="0" />
<LineId Id="320" Count="4" />
<LineId Id="459" Count="0" />
<LineId Id="325" Count="15" />
<LineId Id="188" Count="0" />
<LineId Id="569" Count="2" />
<LineId Id="441" Count="0" />
<LineId Id="572" Count="0" />
<LineId Id="436" Count="0" />
<LineId Id="438" Count="1" />
<LineId Id="437" Count="0" />
@@ -317,6 +416,17 @@ END_VAR
<LineId Id="422" Count="0" />
<LineId Id="377" Count="0" />
<LineId Id="449" Count="1" />
<LineId Id="580" Count="0" />
<LineId Id="579" Count="0" />
<LineId Id="581" Count="0" />
<LineId Id="584" Count="0" />
<LineId Id="583" Count="0" />
<LineId Id="586" Count="0" />
<LineId Id="585" Count="0" />
<LineId Id="587" Count="2" />
<LineId Id="592" Count="0" />
<LineId Id="591" Count="0" />
<LineId Id="593" Count="2" />
<LineId Id="452" Count="0" />
<LineId Id="460" Count="1" />
<LineId Id="451" Count="0" />
@@ -327,6 +437,27 @@ END_VAR
</LineIds>
<LineIds Name="FB_Module.FB_init">
<LineId Id="7" Count="0" />
<LineId Id="29" Count="0" />
<LineId Id="28" Count="0" />
<LineId Id="31" Count="2" />
<LineId Id="30" Count="0" />
<LineId Id="16" Count="0" />
<LineId Id="15" Count="0" />
<LineId Id="17" Count="0" />
<LineId Id="19" Count="0" />
<LineId Id="18" Count="0" />
<LineId Id="20" Count="0" />
</LineIds>
<LineIds Name="FB_Module.Name.Get">
<LineId Id="2" Count="0" />
</LineIds>
<LineIds Name="FB_Module.Name.Set">
<LineId Id="2" Count="0" />
<LineId Id="8" Count="0" />
<LineId Id="12" Count="3" />
<LineId Id="11" Count="0" />
<LineId Id="6" Count="1" />
<LineId Id="5" Count="0" />
</LineIds>
</POU>
</TcPlcObject>

View File

@@ -37,13 +37,16 @@ VAR_OUTPUT
// String completely off
xOff : BOOL;
// All safetyinterlocks are ok
xSafetyIntlksOk : BOOL;
xError : BOOL;
xWarning : BOOL;
END_VAR
VAR
_fbModule1 : FB_Module(CONCAT(_sName, 'Module 1'));
_fbModule2 : FB_Module(CONCAT(_sName, 'Module 2'));
_fbModule3 : FB_Module(CONCAT(_sName, 'Module 3'));
_fbModule1 : FB_Module('Module 1');
_fbModule2 : FB_Module('Module 2');
_fbModule3 : FB_Module('Module 3');
// All modules are ready
_xAllModulesReady : BOOL;
@@ -54,6 +57,12 @@ VAR
// Flag for module balance checking
_xBalanceOk : BOOL;
// Modules out of balance alarm message
_fbModulesOutOfBalanceAlarm : Fb_TcAlarm;
// Safetyinterlocks pending alarm
_fbSafetyInterlocksNotOkAlarm : FB_TcAlarm;
// String name
_sName : STRING;
END_VAR
@@ -62,6 +71,9 @@ END_VAR
<ST><![CDATA[// Reset error flag
xError := FALSE;
// Reset safety interlocks flag
xSafetyIntlksOk := TRUE;
// ===============================
// Module 1
// ===============================
@@ -81,6 +93,10 @@ IF _fbModule1.xError THEN
xError := TRUE;
END_IF
IF NOT _fbModule1.xSafetyIntlksOk THEN
xSafetyIntlksOk := FALSE;
END_IF
// ===============================
// Module 2
@@ -101,6 +117,10 @@ IF _fbModule2.xError THEN
xError := TRUE;
END_IF
IF NOT _fbModule2.xSafetyIntlksOk THEN
xSafetyIntlksOk := FALSE;
END_IF
// ===============================
// Module 3
@@ -121,6 +141,22 @@ IF _fbModule3.xError THEN
xError := TRUE;
END_IF
IF NOT _fbModule3.xSafetyIntlksOk THEN
xSafetyIntlksOk := FALSE;
END_IF
// ===============================
// Handle safety interlock alarm
// ===============================
IF NOT xSafetyIntlksOk AND NOT _fbSafetyInterlocksNotOkAlarm.bRaised THEN
_fbSafetyInterlocksNotOkAlarm.Raise(0);
END_IF
IF xSafetyIntlksOk AND _fbSafetyInterlocksNotOkAlarm.bRaised THEN
_fbSafetyInterlocksNotOkAlarm.Clear(0, TRUE);
END_IF
// ===============================
// Modules ready check
@@ -179,6 +215,21 @@ IF _xAllModulesReady AND (NOT _xBalanceOk) THEN
xError := TRUE;
END_IF
// Raise error
IF (NOT _xBalanceOk) AND (NOT _fbModulesOutOfBalanceAlarm.bRaised) THEN
_fbModulesOutOfBalanceAlarm.Raise(0);
END_IF
// Clear error
IF _xBalanceOk AND _fbModulesOutOfBalanceAlarm.bRaised THEN
_fbModulesOutOfBalanceAlarm.Clear(0, FALSE);
END_IF
// Confirm error
IF _fbModulesOutOfBalanceAlarm.eConfirmationState = TcEventConfirmationState.WaitForConfirmation AND xConfirmAlarms THEN
_fbModulesOutOfBalanceAlarm.Confirm(0);
END_IF
// ===============================
// String ready validation check
// ===============================
@@ -198,12 +249,56 @@ VAR_INPUT
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[_sName := sName;]]></ST>
<ST><![CDATA[_sName := sName;
// Set names for modules
_fbModule1.Name := Concat(_sName, ' - Module 1');
_fbModule2.Name := Concat(_sName, ' - Module 2');
_fbModule3.Name := Concat(_sName, ' - Module 3');
// Create out of balance alarm
_fbModulesOutOfBalanceAlarm.CreateEx(stEventEntry := TC_EVENTS.BMSEvents.StringImbalance, bWithConfirmation := TRUE, 0);
_fbModulesOutOfBalanceAlarm.ipArguments.Clear().AddString(_sName);
// Create safetyinterlocks active alarm
_fbSafetyInterlocksNotOkAlarm.CreateEx(stEventEntry := TC_EVENTS.BMSEvents.SafetyIntlksActive, bWithConfirmation := FALSE, 0);
_fbSafetyInterlocksNotOkAlarm.ipArguments.Clear().AddString(_sName);]]></ST>
</Implementation>
</Method>
<Property Name="Name" Id="{19fcb6d4-fd4b-49f9-9791-1e4c931b9e69}">
<Declaration><![CDATA[PROPERTY Name : String]]></Declaration>
<Get Name="Get" Id="{a4b6ba34-8ad9-46b1-939c-45cef957fd9a}">
<Declaration><![CDATA[VAR
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[Name := _sName;]]></ST>
</Implementation>
</Get>
<Set Name="Set" Id="{c32997c5-bac4-4ef0-bc87-edcbbb2e542f}">
<Declaration><![CDATA[VAR
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[_sName := Name;
// Set modules names
_fbModule1.Name := CONCAT(_sName, ' - Module 1');
_fbModule2.Name := CONCAT(_sName, ' - Module 2');
_fbModule3.Name := CONCAT(_sName, ' - Module 3');
// Create alarm messages
_fbModulesOutOfBalanceAlarm.ipArguments.Clear().AddString(_sName);
_fbSafetyInterlocksNotOkAlarm.ipArguments.Clear().AddString(_sName);]]></ST>
</Implementation>
</Set>
</Property>
<LineIds Name="FB_String">
<LineId Id="266" Count="0" />
<LineId Id="268" Count="0" />
<LineId Id="335" Count="0" />
<LineId Id="334" Count="0" />
<LineId Id="336" Count="0" />
<LineId Id="267" Count="0" />
<LineId Id="69" Count="0" />
<LineId Id="67" Count="1" />
@@ -217,14 +312,26 @@ END_VAR
<LineId Id="66" Count="0" />
<LineId Id="63" Count="0" />
<LineId Id="59" Count="2" />
<LineId Id="338" Count="0" />
<LineId Id="337" Count="0" />
<LineId Id="339" Count="1" />
<LineId Id="71" Count="5" />
<LineId Id="141" Count="0" />
<LineId Id="77" Count="11" />
<LineId Id="70" Count="0" />
<LineId Id="342" Count="2" />
<LineId Id="341" Count="0" />
<LineId Id="90" Count="5" />
<LineId Id="142" Count="0" />
<LineId Id="96" Count="11" />
<LineId Id="89" Count="0" />
<LineId Id="346" Count="2" />
<LineId Id="345" Count="0" />
<LineId Id="433" Count="1" />
<LineId Id="432" Count="0" />
<LineId Id="435" Count="4" />
<LineId Id="441" Count="2" />
<LineId Id="440" Count="0" />
<LineId Id="183" Count="5" />
<LineId Id="245" Count="4" />
<LineId Id="244" Count="0" />
@@ -249,12 +356,38 @@ END_VAR
<LineId Id="212" Count="0" />
<LineId Id="237" Count="1" />
<LineId Id="236" Count="0" />
<LineId Id="215" Count="11" />
<LineId Id="215" Count="5" />
<LineId Id="353" Count="13" />
<LineId Id="352" Count="0" />
<LineId Id="221" Count="5" />
<LineId Id="239" Count="1" />
<LineId Id="214" Count="0" />
</LineIds>
<LineIds Name="FB_String.FB_init">
<LineId Id="7" Count="0" />
<LineId Id="33" Count="0" />
<LineId Id="32" Count="0" />
<LineId Id="34" Count="2" />
<LineId Id="16" Count="0" />
<LineId Id="15" Count="0" />
<LineId Id="17" Count="0" />
<LineId Id="20" Count="0" />
<LineId Id="31" Count="0" />
<LineId Id="29" Count="1" />
<LineId Id="28" Count="0" />
</LineIds>
<LineIds Name="FB_String.Name.Get">
<LineId Id="2" Count="0" />
</LineIds>
<LineIds Name="FB_String.Name.Set">
<LineId Id="2" Count="0" />
<LineId Id="9" Count="0" />
<LineId Id="6" Count="2" />
<LineId Id="5" Count="0" />
<LineId Id="13" Count="0" />
<LineId Id="11" Count="0" />
<LineId Id="10" Count="0" />
<LineId Id="12" Count="0" />
</LineIds>
</POU>
</TcPlcObject>

View File

@@ -37,6 +37,9 @@ VAR_OUTPUT
// Unit completely off
xOff : BOOL := TRUE;
// All safetyinterlocks are ok
xSafetyIntlksOk : BOOL;
// Unit can be discharged during shutdown sequence
xShutdownDischargeAllowed : BOOL;
@@ -57,7 +60,7 @@ VAR_OUTPUT
END_VAR
VAR
// Check unit condition after some time during startup
_timUnitStartupWaitTime : TIME := T#20S;
// _timUnitStartupWaitTime : TIME := T#10S;
// Valves posolyt
_fbPosolytValveTankOutlet : FB_Valve('Posolyt tank outlet');
@@ -152,6 +155,9 @@ _xErrorActive := FALSE;
// Reset warning active
_xWarningActive := FALSE;
// Reset safety interlocks flag
xSafetyIntlksOk := TRUE;
// ===============================
// Valve posolyt tank outlet
// ===============================
@@ -177,6 +183,11 @@ IF _fbPosolytValveTankOutlet.xError THEN
_xErrorActive := TRUE;
END_IF
// Set safety interlock flag if fb has safety interlocks active
IF NOT stHMIInterface.stPosolytValve.stInterlock.xSafetyINTLKOk THEN
xSafetyIntlksOk := FALSE;
END_IF
// ===============================
// Valve negolyt tank outlet
@@ -203,6 +214,11 @@ IF _fbNegolytValveTankOutlet.xError THEN
_xErrorActive := TRUE;
END_IF
// Set safety interlock flag if fb has safety interlocks active
IF NOT stHMIInterface.stNegolytValve.stInterlock.xSafetyINTLKOk THEN
xSafetyIntlksOk := FALSE;
END_IF
// ===============================
// Pump posolyt segment inlet
@@ -242,6 +258,11 @@ IF _fbPosolytPumpInlet.xWarning THEN
_xWarningActive := TRUE;
END_IF
// Set safety interlock flag if fb has safety interlocks active
IF NOT stHMIInterface.stPosolytPump.stInterlock.xSafetyINTLKOk THEN
xSafetyIntlksOk := FALSE;
END_IF
// ===============================
// Pump negolyt segment inlet
@@ -281,6 +302,11 @@ IF _fbNegolytPumpInlet.xWarning THEN
_xWarningActive := TRUE;
END_IF
// Set safety interlock flag if fb has safety interlocks active
IF NOT stHMIInterface.stNegolytPump.stInterlock.xSafetyINTLKOk THEN
xSafetyIntlksOk := FALSE;
END_IF
// ===============================
// Pressure sensor posolyt segment inlet
@@ -510,7 +536,7 @@ CASE _iState OF
END_IF
30: // Wait some time
_tonStartupCheck(In := TRUE, PT := _timUnitStartupWaitTime);
_tonStartupCheck(In := TRUE, PT := GVL_CONFIG.timUnitStartupTime);
// After some time, check if all values are ok
IF _tonStartupCheck.Q THEN
@@ -534,7 +560,6 @@ CASE _iState OF
35: // Unit in enabled state
IF (NOT xEnable) AND NOT _xErrorActive THEN
_xEnableVoltageLimitChecks := FALSE;
_iState := 40;
END_IF
@@ -557,6 +582,7 @@ CASE _iState OF
// When there is an error trying to close the valves,
// dont try to discharge the segment
IF _fbNegolytValveTankOutlet.xError OR _fbPosolytValveTankOutlet.xError THEN
_xEnableVoltageLimitChecks := FALSE;
_iState := 60;
END_IF
@@ -570,6 +596,7 @@ CASE _iState OF
xShutdownDischargeAllowed := TRUE;
ELSE
xShutdownDischargeAllowed := FALSE;
_xEnableVoltageLimitChecks := FALSE;
END_IF
IF (_fbVoltageSegment.rScaledValue <= GVL_CONFIG.rPumpshutoffThreshold) THEN
@@ -648,14 +675,53 @@ END_VAR
<Implementation>
<ST><![CDATA[_sName := sName;
// Create not all components in automatic mode alarm
_fbNotAllAutomaticAlarm.CreateEx(TC_EVENTS.General.NotAllCompInAutomatic, bWithConfirmation := FALSE, 0);
_fbNotAllAutomaticAlarm.ipArguments.Clear().AddString(_sName);]]></ST>
</Implementation>
</Method>
<Property Name="Name" Id="{a82783d4-f1f5-481b-bbb0-7ea8279de793}">
<Declaration><![CDATA[PROPERTY Name : String]]></Declaration>
<Get Name="Get" Id="{62ca6bce-5aa4-40f1-aa68-f3e5d22ad4a4}">
<Declaration><![CDATA[VAR
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[Name := _sName;]]></ST>
</Implementation>
</Get>
<Set Name="Set" Id="{b2e0f6da-5412-45b8-aacd-c140a5beab95}">
<Declaration><![CDATA[VAR
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[_sName := Name;
// Create alarm message
_fbNotAllAutomaticAlarm.ipArguments.Clear().AddString(_sName);]]></ST>
</Implementation>
</Set>
</Property>
<LineIds Name="FB_Unit">
<LineId Id="4232" Count="29" />
<LineId Id="4232" Count="4" />
<LineId Id="5406" Count="1" />
<LineId Id="5405" Count="0" />
<LineId Id="4237" Count="24" />
<LineId Id="5408" Count="0" />
<LineId Id="4584" Count="0" />
<LineId Id="4263" Count="102" />
<LineId Id="5409" Count="2" />
<LineId Id="5418" Count="0" />
<LineId Id="4263" Count="25" />
<LineId Id="5412" Count="2" />
<LineId Id="4289" Count="0" />
<LineId Id="5416" Count="0" />
<LineId Id="5415" Count="0" />
<LineId Id="4290" Count="36" />
<LineId Id="5420" Count="3" />
<LineId Id="5419" Count="0" />
<LineId Id="4327" Count="38" />
<LineId Id="5425" Count="3" />
<LineId Id="5424" Count="0" />
<LineId Id="4619" Count="0" />
<LineId Id="4367" Count="46" />
<LineId Id="5099" Count="0" />
@@ -708,7 +774,6 @@ _fbNotAllAutomaticAlarm.ipArguments.Clear().AddString(_sName);]]></ST>
<LineId Id="4496" Count="0" />
<LineId Id="4794" Count="0" />
<LineId Id="4497" Count="12" />
<LineId Id="4795" Count="0" />
<LineId Id="4511" Count="1" />
<LineId Id="5078" Count="0" />
<LineId Id="5083" Count="0" />
@@ -717,7 +782,9 @@ _fbNotAllAutomaticAlarm.ipArguments.Clear().AddString(_sName);]]></ST>
<LineId Id="5081" Count="0" />
<LineId Id="4513" Count="7" />
<LineId Id="5239" Count="0" />
<LineId Id="4521" Count="7" />
<LineId Id="4521" Count="5" />
<LineId Id="5441" Count="0" />
<LineId Id="4527" Count="1" />
<LineId Id="4653" Count="0" />
<LineId Id="4655" Count="2" />
<LineId Id="4654" Count="0" />
@@ -725,6 +792,7 @@ _fbNotAllAutomaticAlarm.ipArguments.Clear().AddString(_sName);]]></ST>
<LineId Id="5220" Count="0" />
<LineId Id="5223" Count="0" />
<LineId Id="5225" Count="1" />
<LineId Id="5440" Count="0" />
<LineId Id="5224" Count="0" />
<LineId Id="5221" Count="0" />
<LineId Id="4531" Count="0" />
@@ -751,7 +819,7 @@ _fbNotAllAutomaticAlarm.ipArguments.Clear().AddString(_sName);]]></ST>
<LineId Id="5084" Count="0" />
<LineId Id="5086" Count="1" />
<LineId Id="5089" Count="1" />
<LineId Id="5093" Count="0" />
<LineId Id="5434" Count="0" />
<LineId Id="5227" Count="0" />
<LineId Id="5240" Count="0" />
<LineId Id="5092" Count="0" />
@@ -763,8 +831,19 @@ _fbNotAllAutomaticAlarm.ipArguments.Clear().AddString(_sName);]]></ST>
<LineId Id="2754" Count="0" />
</LineIds>
<LineIds Name="FB_Unit.FB_init">
<LineId Id="11" Count="1" />
<LineId Id="11" Count="0" />
<LineId Id="20" Count="0" />
<LineId Id="12" Count="0" />
<LineId Id="7" Count="1" />
</LineIds>
<LineIds Name="FB_Unit.Name.Get">
<LineId Id="2" Count="0" />
</LineIds>
<LineIds Name="FB_Unit.Name.Set">
<LineId Id="2" Count="0" />
<LineId Id="7" Count="0" />
<LineId Id="6" Count="0" />
<LineId Id="5" Count="0" />
</LineIds>
</POU>
</TcPlcObject>

View File

@@ -11,6 +11,15 @@ VAR
_xEnableInverter : BOOL;
_fbString : FB_String('String 1');
_fbInverter : FB_PowerSupplySunspec;
_stInverterData : ST_SUNSPEC_CURRENT_VALUES;
// Variable to detect charge status change
_eLastChargeStatus : E_CHARGE_STATUS;
// Variable to detect battery status change
_eLastBatteryStatus : E_BATTERY_STATUS;
// Variable to detect any battery status change
_xBatteryStatusChange : BOOL;
_iState : INT;
@@ -25,10 +34,52 @@ VAR
// Small delay for inverter shutdown
_tonBeginShutdown : TON := (PT := T#10S);
// Not all strings in automatic mode
_fbNoAutomaticModeAlarm : Fb_TcAlarm;
// First cycle tag
_xFirstCycle : BOOL := TRUE;
// ADS reader for modbus server data
_fbADSReader : ADSREAD;
// Timer for ADS read
_timADSReadTimer : TON;
// MQTT client
_fbMQTTClient : FB_IotMqttClient;
// connect to mqtt broker
_xConnectToMQTTBrocker : BOOL;
// DEBUG
_xRestart : BOOL;
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[// Dely release of errors during PLC startup phase
<ST><![CDATA[// ===============================
// DEBUG
IF _xRestart AND (_iState = 1010) THEN
GVL_MODBUS.stModbusEMSComm.stModbusReg12.diSetpointActivePower := 0;
_xConfirmAlarms := TRUE;
END_IF
IF _xRestart AND (_iState = 0) THEN
_xRestart := FALSE;
GVL_MODBUS.stModbusEMSComm.stModbusReg12.diSetpointActivePower := 1000;
END_IF
// DEBUG
// ===============================
IF _xFirstCycle THEN
_xFirstCycle := FALSE;
_fbString.Name := 'String 1';
_xConnectToMQTTBrocker := TRUE;
END_IF
// Dely release of errors during PLC startup phase
_tonStartupDelay(IN := TRUE);
// Call string 1
@@ -40,7 +91,7 @@ _fbString(
xReleaseLimitErrors:= _xReleaseLimitsErrors AND _tonStartupDelay.Q,
xConfirmAlarms:= _xConfirmAlarms);
// DEACTIVATED FOR DEBUG REASONS !!!
// Call inverter
//_fbInverter(
// sInverterIPAddr:= GVL_CONFIG.sInverterIp,
@@ -48,12 +99,32 @@ _fbString(
// rPower:= _rPowerInverter,
// xReset:= _xConfirmAlarms,
// rMaxBattPower:= DINT_TO_REAL(GVL_CONFIG.diMaxStringDischargePower),
// xCloseDCRelais=> ,
// rActDCCurrent=> ,
// rActDCVoltage=> ,
// xError=> ,
// xActive=> );
// stCurrentValues => _stInverterData);
// ===============================
// Read modbus request count
// ===============================
_timADSReadTimer(IN := NOT _fbADSReader.BUSY, PT := T#200MS);
_fbADSReader(
NETID:= '',
PORT:= 10500,
IDXGRP:= 16#2000,
IDXOFFS:= 1,
LEN:= 4,
DESTADDR:= ADR(GVL_MODBUS.stModbusEMSComm.stModbusReg11.udiLifeMessage),
READ:= _timADSReadTimer.Q,
TMOUT:= T#1S,
BUSY=> ,
ERR=> ,
ERRID=> );
// ===============================
// MQTT cummunication
// ===============================
// Handle connection to broker
_fbMQTTClient.Execute(_xConnectToMQTTBrocker);
// ===============================
// State machine
@@ -61,18 +132,18 @@ _fbString(
CASE _iState OF
0: // Idle
// Wait for power command
IF ABS(GVL_MODBUS.stModbusEMSComm.diSetpointActivePower) > GVL_CONFIG.diMinimumAbsPowerForEnable THEN
IF (ABS(GVL_MODBUS.stModbusEMSComm.stModbusReg12.diSetpointActivePower) > GVL_CONFIG.diMinimumAbsPowerForEnable) AND _fbString.xSafetyIntlksOk AND (NOT _fbString.xError) THEN
_iState := 5;
END_IF
5: // Check if power command is within limits
IF GVL_MODBUS.stModbusEMSComm.diSetpointActivePower < GVL_CONFIG.diMaxStringDischargePower
AND GVL_MODBUS.stModbusEMSComm.diSetpointActivePower > GVL_CONFIG.diMaxStringChargingPower THEN
IF GVL_MODBUS.stModbusEMSComm.stModbusReg12.diSetpointActivePower < GVL_CONFIG.diMaxStringDischargePower
AND GVL_MODBUS.stModbusEMSComm.stModbusReg12.diSetpointActivePower > GVL_CONFIG.diMaxStringChargingPower THEN
_xEnableString := TRUE;
_iState := 10;
ELSE
// Set error bitmap flag
GVL_MODBUS.stModbusEMSComm.lwErrorBitmap.0 := 1;
GVL_MODBUS.stModbusEMSComm.stModbusReg11.lwErrorBitmap.0 := 1;
// Goto error state
_iState := 1000;
@@ -90,7 +161,9 @@ CASE _iState OF
25: // Wait for inverter to be ready
IF _fbInverter.xActive AND (NOT _fbInverter.xError) THEN
GVL_MODBUS.stModbusEMSComm.eBatteryStatus := E_BATTERY_STATUS.ACTIVE;
// Set battery status for modbus
GVL_MODBUS.stModbusEMSComm.stModbusReg11.eBatteryStatus := E_BATTERY_STATUS.ACTIVE;
GVL_MODBUS.stModbusEMSComm.stModbusReg10.uiActiveParallelMembers := 1;
_iState := 30;
END_IF
@@ -101,22 +174,23 @@ CASE _iState OF
30: // String and inverter enabled
// Set inverter power to modbus requested power
_rPowerInverter := DINT_TO_REAL(GVL_MODBUS.stModbusEMSComm.diSetpointActivePower);
_rPowerInverter := DINT_TO_REAL(GVL_MODBUS.stModbusEMSComm.stModbusReg12.diSetpointActivePower);
// Check if the battery should still be active
IF (GVL_MODBUS.stModbusEMSComm.diSetpointActivePower = 0) THEN
IF (GVL_MODBUS.stModbusEMSComm.stModbusReg12.diSetpointActivePower = 0) THEN
_xNoPowerRequested := TRUE;
ELSE
_xNoPowerRequested := FALSE;
END_IF
// Set battery status
IF GVL_MODBUS.stModbusEMSComm.diSetpointActivePower > 0 THEN
GVL_MODBUS.stModbusEMSComm.eChargeStatus := E_CHARGE_STATUS.DISCHARGING;
ELSIF GVL_MODBUS.stModbusEMSComm.diSetpointActivePower < 0 THEN
GVL_MODBUS.stModbusEMSComm.eChargeStatus := E_CHARGE_STATUS.CHARGING;
IF GVL_MODBUS.stModbusEMSComm.stModbusReg12.diSetpointActivePower > 0 THEN
GVL_MODBUS.stModbusEMSComm.stModbusReg11.eChargeStatus := E_CHARGE_STATUS.DISCHARGING;
ELSIF GVL_MODBUS.stModbusEMSComm.stModbusReg12.diSetpointActivePower < 0 THEN
GVL_MODBUS.stModbusEMSComm.stModbusReg11.eChargeStatus := E_CHARGE_STATUS.CHARGING;
ELSE
GVL_MODBUS.stModbusEMSComm.eChargeStatus := E_CHARGE_STATUS.UNDEFINED;
GVL_MODBUS.stModbusEMSComm.stModbusReg11.eChargeStatus := E_CHARGE_STATUS.UNDEFINED;
END_IF
// Add small delay before shutdown by EMS is detected
@@ -131,11 +205,12 @@ CASE _iState OF
// Start string shutdown
_xEnableString := FALSE;
GVL_MODBUS.stModbusEMSComm.stModbusReg10.uiActiveParallelMembers := 0;
_iState := 35;
END_IF
// Shutdown triggered by battery fully charged
IF GVL_MODBUS.stModbusEMSComm.eChargeStatus = E_CHARGE_STATUS.CHARGING AND (_fbString.rCurrentVoltage >= GVL_CONFIG.rStringFullyChargedVoltage) THEN
IF GVL_MODBUS.stModbusEMSComm.stModbusReg11.eChargeStatus = E_CHARGE_STATUS.CHARGING AND (_fbString.rCurrentVoltage >= GVL_CONFIG.rStringFullyChargedVoltage) THEN
_tonBeginShutdown(In := FALSE);
// Set inverter to zero power
@@ -145,13 +220,13 @@ CASE _iState OF
_xEnableString := FALSE;
// Change battery status
GVL_MODBUS.stModbusEMSComm.eChargeStatus := E_CHARGE_STATUS.FULL;
GVL_MODBUS.stModbusEMSComm.stModbusReg11.eChargeStatus := E_CHARGE_STATUS.FULL;
GVL_MODBUS.stModbusEMSComm.stModbusReg10.uiActiveParallelMembers := 0;
_iState := 35;
END_IF
// Shutdown triggered by battery empty
IF GVL_MODBUS.stModbusEMSComm.eChargeStatus = E_CHARGE_STATUS.DISCHARGING AND (_fbString.rCurrentVoltage <= GVL_CONFIG.rStringEmptyVoltage) THEN
IF GVL_MODBUS.stModbusEMSComm.stModbusReg11.eChargeStatus = E_CHARGE_STATUS.DISCHARGING AND (_fbString.rCurrentVoltage <= GVL_CONFIG.rStringEmptyVoltage) THEN
_tonBeginShutdown(In := FALSE);
// Set inverter to zero power
@@ -161,8 +236,8 @@ CASE _iState OF
_xEnableString := FALSE;
// Change battery status
GVL_MODBUS.stModbusEMSComm.eChargeStatus := E_CHARGE_STATUS.FULL;
GVL_MODBUS.stModbusEMSComm.stModbusReg11.eChargeStatus := E_CHARGE_STATUS.FULL;
GVL_MODBUS.stModbusEMSComm.stModbusReg10.uiActiveParallelMembers := 0;
_iState := 35;
END_IF
@@ -205,6 +280,7 @@ CASE _iState OF
45: // Wait for shutdown of string to be done
IF (NOT _fbString.xInShutdownDischargeMode) AND _fbString.xOff THEN
GVL_MODBUS.stModbusEMSComm.stModbusReg11.eBatteryStatus := E_BATTERY_STATUS.OFF;
_iState := 0;
END_IF
@@ -217,36 +293,130 @@ CASE _iState OF
_xEnableString := FALSE;
_xEnableInverter := FALSE;
_rPowerInverter := 0.0;
GVL_MODBUS.stModbusEMSComm.eBatteryStatus := E_BATTERY_STATUS.ERROR;
GVL_MODBUS.stModbusEMSComm.stModbusReg11.eBatteryStatus := E_BATTERY_STATUS.ERROR;
_iState := 1010;
1010: // Wait for reset from error state
IF (GVL_MODBUS.stModbusEMSComm.diSetpointActivePower = 0) AND (NOT _fbString.xError) AND (NOT _fbInverter.xError) THEN
IF (GVL_MODBUS.stModbusEMSComm.stModbusReg12.diSetpointActivePower = 0) AND (NOT _fbString.xError) AND (NOT _fbInverter.xError) THEN
// Reset modbus error register
GVL_MODBUS.stModbusEMSComm.lwErrorBitmap := 0;
GVL_MODBUS.stModbusEMSComm.stModbusReg11.lwErrorBitmap := 0;
// Reset modbus error flag
GVL_MODBUS.stModbusEMSComm.eBatteryStatus := E_BATTERY_STATUS.OFF;
GVL_MODBUS.stModbusEMSComm.stModbusReg11.eBatteryStatus := E_BATTERY_STATUS.OFF;
// Goto init state
_iState := 0;
END_IF
END_CASE
// Send MQTT battery status
_xBatteryStatusChange := FALSE;
IF GVL_MODBUS.stModbusEMSComm.stModbusReg11.eBatteryStatus <> _eLastBatteryStatus THEN
_xBatteryStatusChange := TRUE;
END_IF
_eLastBatteryStatus := GVL_MODBUS.stModbusEMSComm.stModbusReg11.eBatteryStatus;
IF GVL_MODBUS.stModbusEMSComm.stModbusReg11.eChargeStatus <> _eLastChargeStatus THEN
_xBatteryStatusChange := TRUE;
END_IF
_eLastChargeStatus := GVL_MODBUS.stModbusEMSComm.stModbusReg11.eChargeStatus;
IF _xBatteryStatusChange THEN
SendBatteryStatus();
END_IF
// Reset alarm confirmation
IF _xConfirmAlarms THEN
_xConfirmAlarms := FALSE;
END_IF]]></ST>
</Implementation>
<Method Name="SendBatteryStatus" Id="{d5a8daa9-6942-4419-a57d-7958e56f7e71}">
<Declaration><![CDATA[METHOD SendBatteryStatus
VAR
_sJSONString : T_MaxString;
_fbFormatString : FB_FormatString;
_sBatteryStatus : T_MaxString;
_sChargeStatus : T_MaxString;
_sBMSVersion : T_MaxString;
END_VAR
VAR CONSTANT
_sTemplateString : STRING := '{"batteryStatus": "%s", "chargeStatus": "%s", "bmsVersion": "%s"}';
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[// Temp strings because string format needs write access to these variables
_sBatteryStatus := TO_STRING(GVL_MODBUS.stModbusEMSComm.stModbusReg11.eBatteryStatus);
_sChargeStatus := TO_STRING(GVL_MODBUS.stModbusEMSComm.stModbusReg11.eChargeStatus);
_sBMSVersion := GVL_MODBUS.stModbusEMSComm.stModbusReg10.sBMSVersion;
// Build JSON string
_fbFormatString(
sFormat:= _sTemplateString,
arg1:= F_STRING(_sBatteryStatus),
arg2:= F_STRING(_sChargeStatus),
arg3:= F_STRINg(_sBMSVersion),
sOut=> _sJSONString);
// Send message if connected and no formatting error occured
IF NOT _fbFormatString.bError AND _fbMQTTClient.bConnected THEN
_fbMQTTClient.Publish('status', ADR(_sJSONString), INT_TO_UDINT(LEN(_sJSONString)), GVL_MQTT.eMQTTQoS, FALSE, FALSE);
END_IF]]></ST>
</Implementation>
</Method>
<Action Name="SetMQTTParameter" Id="{f5ef2661-7fa1-4c9b-aa13-fba0d7bff32a}">
<Implementation>
<ST><![CDATA[//_fbMQTTClient.sClientId:= '';
_fbMQTTClient.sHostName:= GVL_MQTT.sMQTTBrokerAddr;
_fbMQTTClient.nHostPort:= 1883;
//_fbMQTTClient.sTopicPrefix := '';
_fbMQTTClient.nKeepAlive:= GVL_MQTT.uiMQTTKeepAlive;
// _fbMQTTClient.ipMessageQueue := ;]]></ST>
</Implementation>
</Action>
<LineIds Name="MAIN">
<LineId Id="126" Count="55" />
<LineId Id="405" Count="0" />
<LineId Id="395" Count="0" />
<LineId Id="400" Count="0" />
<LineId Id="402" Count="1" />
<LineId Id="399" Count="0" />
<LineId Id="407" Count="0" />
<LineId Id="406" Count="0" />
<LineId Id="408" Count="0" />
<LineId Id="410" Count="0" />
<LineId Id="409" Count="0" />
<LineId Id="534" Count="0" />
<LineId Id="398" Count="0" />
<LineId Id="404" Count="0" />
<LineId Id="475" Count="0" />
<LineId Id="396" Count="0" />
<LineId Id="476" Count="0" />
<LineId Id="478" Count="0" />
<LineId Id="624" Count="0" />
<LineId Id="477" Count="0" />
<LineId Id="397" Count="0" />
<LineId Id="126" Count="19" />
<LineId Id="147" Count="0" />
<LineId Id="784" Count="3" />
<LineId Id="598" Count="0" />
<LineId Id="808" Count="0" />
<LineId Id="793" Count="10" />
<LineId Id="789" Count="0" />
<LineId Id="599" Count="0" />
<LineId Id="463" Count="0" />
<LineId Id="601" Count="0" />
<LineId Id="600" Count="0" />
<LineId Id="626" Count="0" />
<LineId Id="617" Count="0" />
<LineId Id="724" Count="0" />
<LineId Id="152" Count="29" />
<LineId Id="218" Count="0" />
<LineId Id="182" Count="0" />
<LineId Id="304" Count="0" />
<LineId Id="207" Count="2" />
<LineId Id="628" Count="0" />
<LineId Id="314" Count="0" />
<LineId Id="782" Count="0" />
<LineId Id="210" Count="2" />
<LineId Id="305" Count="1" />
<LineId Id="308" Count="0" />
@@ -260,6 +430,7 @@ END_IF]]></ST>
<LineId Id="236" Count="0" />
<LineId Id="309" Count="3" />
<LineId Id="315" Count="1" />
<LineId Id="632" Count="0" />
<LineId Id="360" Count="1" />
<LineId Id="313" Count="0" />
<LineId Id="317" Count="0" />
@@ -274,6 +445,7 @@ END_IF]]></ST>
<LineId Id="263" Count="0" />
<LineId Id="265" Count="0" />
<LineId Id="260" Count="0" />
<LineId Id="783" Count="0" />
<LineId Id="245" Count="0" />
<LineId Id="244" Count="0" />
<LineId Id="328" Count="0" />
@@ -311,6 +483,7 @@ END_IF]]></ST>
<LineId Id="286" Count="0" />
<LineId Id="282" Count="1" />
<LineId Id="300" Count="0" />
<LineId Id="666" Count="0" />
<LineId Id="302" Count="1" />
<LineId Id="301" Count="0" />
<LineId Id="297" Count="2" />
@@ -321,8 +494,38 @@ END_IF]]></ST>
<LineId Id="252" Count="1" />
<LineId Id="255" Count="0" />
<LineId Id="254" Count="0" />
<LineId Id="193" Count="9" />
<LineId Id="193" Count="2" />
<LineId Id="197" Count="0" />
<LineId Id="647" Count="0" />
<LineId Id="646" Count="0" />
<LineId Id="654" Count="0" />
<LineId Id="659" Count="0" />
<LineId Id="648" Count="2" />
<LineId Id="664" Count="0" />
<LineId Id="656" Count="0" />
<LineId Id="655" Count="0" />
<LineId Id="657" Count="1" />
<LineId Id="665" Count="0" />
<LineId Id="661" Count="0" />
<LineId Id="660" Count="0" />
<LineId Id="662" Count="1" />
<LineId Id="199" Count="3" />
<LineId Id="25" Count="0" />
</LineIds>
<LineIds Name="MAIN.SendBatteryStatus">
<LineId Id="27" Count="0" />
<LineId Id="29" Count="1" />
<LineId Id="28" Count="0" />
<LineId Id="32" Count="0" />
<LineId Id="31" Count="0" />
<LineId Id="14" Count="4" />
<LineId Id="5" Count="0" />
<LineId Id="23" Count="0" />
<LineId Id="19" Count="3" />
</LineIds>
<LineIds Name="MAIN.SetMQTTParameter">
<LineId Id="2" Count="4" />
<LineId Id="1" Count="0" />
</LineIds>
</POU>
</TcPlcObject>

View File

@@ -10,11 +10,17 @@ VAR_INPUT
rMaxBattPower : REAL := 24_000; // 24kW
END_VAR
VAR_OUTPUT
// Output for SCS DC-Relais
xCloseDCRelais AT %Q*: BOOL;
rActDCCurrent : REAL;
rActDCVoltage : REAL;
xError : BOOL;
// Inverter active
xActive : BOOL;
// FB error
xError : BOOL;
// Current inverter values
stCurrentValues : ST_SUNSPEC_CURRENT_VALUES;
END_VAR
VAR
// Current state
@@ -41,6 +47,10 @@ VAR
// Unscaled limit for converter power
_iWMaxLimPct : INT;
// Reread set power limit
_iWMaxLimPctRead : INT;
_iWMaxLimPctReadScaled : REAL;
// Scaling factor for power limiting
_iWMaxLimPctSF : INT;
@@ -71,8 +81,11 @@ VAR
// Timer for polling of current values
_tonPollingTimer : TON;
// Current DC values (DCA, DCA_SF, DCV, DCV_SF) in word array for efficient modbus reading
_awCurrentDCValues : ARRAY[0..3] OF WORD;
// Current DC values (DCA, DCA_SF, DCV, DCV_SF, DCW, DCW_SF) in word array for efficient modbus reading
_awCurrentDCValues : ARRAY[0..5] OF WORD;
// Current AC values (W, W_SF, Hz, Hz_SF, VA, VA_SF, VAr, VAr_SF, PF, PF_SF) in word array for efficient modbus reading
_awCurrentACValues : ARRAY[0..9] OF WORD;
// Inverter error bits
_dwErrorBits : DWORD;
@@ -88,7 +101,7 @@ VAR CONSTANT
// Throttled power register
// Size 1, int16 (Range = -32767 .. 32767, Not implemented 0x8000)
W_MAX_LIM_PCT_REGISTER : WORD := 40187;
W_MAX_LIM_PCT_REGISTER : WORD := 40187;
// Throttled power register scaling factor
// Size 1, sunssf (int16) (Range = -10 .. 10, Not implemented 0x8000)
@@ -126,6 +139,10 @@ VAR CONSTANT
// Size 4
DC_VALUES_START_REGISTER : WORD := 40097;
// Start of register with the current ac values
// SIZE 10
AC_VALUES_START_REGISTER : WORD := 40084;
// Error bits register
// Size 2
EVT_1_REGISTER : WORD := 40110;
@@ -143,6 +160,7 @@ END_IF
// State machine
CASE _iState OF
0: // Off
// If enable and INTLK Ok
IF xEnable THEN
@@ -172,8 +190,34 @@ CASE _iState OF
IF NOT _fbReadRegister.bError AND _uiInverterState = 8 THEN
_iState := 20;
END_IF
// If the inverter is not ready wait some time before polling again
IF NOT _fbReadRegister.bError AND _uiInverterState <> 8 THEN
_iState := 15;
END_IF
_fbReadRegister(bExecute := FALSE);
END_IF
// If not enable, go back to idle
IF NOT xEnable THEN
_fbReadRegister(bExecute := FALSE);
_iState := 0;
END_IF
15: // Delay polling inverter ready
_tonPollingTimer(IN := TRUE, PT := _timPollingDelay);
IF _tonPollingTimer.Q THEN
_tonPollingTimer(IN := FALSE);
_iState := 10;
END_IF
// If not enable, go back to idle
IF NOT xEnable THEN
_tonPollingTimer(IN := FALSE);
_iState := 0;
END_IF
20: // Read inverter max power scaling
_iErrorInState := _iState;
@@ -271,7 +315,7 @@ CASE _iState OF
_rWMax := LREAL_TO_REAL(_iWMax * EXPT(10,_iWMaxSF));
// Calculate power to write to register
_iWMaxLimPct := LREAL_TO_INT(rPower * EXPT(10,_iWMaxLimPctSF) / _rWMax);
_iWMaxLimPct := LREAL_TO_INT((rPower*100)/(_rWMax * EXPT(10,_iWMaxLimPctSF)));
ELSE
xError := TRUE;
// Goto error state
@@ -311,6 +355,32 @@ CASE _iState OF
_fbWriteRegister(bExecute := FALSE);
END_IF
// 45: // Read set power
// _fbReadRegister(
// sIPAddr:= sInverterIPAddr,
// nTCPPort:= 502,
// nUnitID:= 16#FF, // 16#FF for Modbus TCP
// nQuantity:= 1,
// nMBAddr:= W_MAX_LIM_PCT_REGISTER,
// cbLength:= SIZEOF(_iWMaxLimPctRead),
// pDestAddr:= ADR(_iWMaxLimPctRead),
// bExecute:= TRUE,
// tTimeout:= T#5S,
// bBusy=> ,
// bError=> ,
// nErrId=> ,
// cbRead=> );
// // Check if reading mudbus register is done
// IF NOT _fbReadRegister.bBusy THEN
// IF NOT _fbReadRegister.bError THEN
// _iWMaxLimPctReadScaled := LREAL_TO_INT(_rWMax * _iWMaxLimPctRead * EXPT(10,_iWMaxLimPctSF)*0.01);
// _iState := 50;
// END_IF
// _fbReadRegister(bExecute := FALSE);
// END_IF
50: // Enable Power limiting (THROTTLED)
_iErrorInState := _iState;
_fbWriteRegister(
@@ -369,7 +439,7 @@ CASE _iState OF
_fbWriteRegister(bExecute := FALSE);
END_IF
65: // Wait for error polling timer
65: // Wait for polling timer
_tonPollingTimer(IN := TRUE, PT := _timPollingDelay);
IF _tonPollingTimer.Q THEN
_tonPollingTimer(IN := FALSE);
@@ -378,6 +448,8 @@ CASE _iState OF
_tonPollingTimer(IN := FALSE);
// If power has ben changed, goto set power limit mode
_iState := 40;
// Calculate power to write to register
_iWMaxLimPct := LREAL_TO_INT((rPower*100)/(_rWMax * EXPT(10,_iWMaxLimPctSF)));
END_IF
// check if inverter should shut down
@@ -428,7 +500,7 @@ CASE _iState OF
sIPAddr:= sInverterIPAddr,
nTCPPort:= 502,
nUnitID:= 16#FF, // 16#FF for Modbus TCP
nQuantity:= 4,
nQuantity:= 6,
nMBAddr:= DC_VALUES_START_REGISTER,
cbLength:= SIZEOF(_awCurrentDCValues),
pDestAddr:= ADR(_awCurrentDCValues),
@@ -443,9 +515,45 @@ CASE _iState OF
IF NOT _fbReadRegister.bBusy THEN
// If there was no error and the converter has no error continue
IF NOT _fbReadRegister.bError THEN
_iState := 85;
stCurrentValues.rActDCCurrent := LREAL_TO_REAL(WORD_TO_UINT(_awCurrentDCValues[0]) * EXPT(10,WORD_TO_INT(_awCurrentDCValues[1])));
stCurrentValues.rActDCVoltage := LREAL_TO_REAL(WORD_TO_UINT(_awCurrentDCValues[2]) * EXPT(10,WORD_TO_INT(_awCurrentDCValues[3])));
stCurrentValues.rActDCPower := LREAL_TO_REAL(WORD_TO_INT(_awCurrentDCValues[4]) * EXPT(10,WORD_TO_INT(_awCurrentDCValues[5])));
ELSE
// Read error register
_iState := 1000;
END_IF
_fbReadRegister(bExecute := FALSE);
END_IF
85: // Read current ac values
_iErrorInState := _iState;
_fbReadRegister(
sIPAddr:= sInverterIPAddr,
nTCPPort:= 502,
nUnitID:= 16#FF, // 16#FF for Modbus TCP
nQuantity:= 10,
nMBAddr:= AC_VALUES_START_REGISTER,
cbLength:= SIZEOF(_awCurrentACValues),
pDestAddr:= ADR(_awCurrentACValues),
bExecute:= TRUE,
tTimeout:= T#5S,
bBusy=> ,
bError=> ,
nErrId=> ,
cbRead=> );
// Check if reading mudbus register is done
IF NOT _fbReadRegister.bBusy THEN
// If there was no error and the converter has no error continue
IF NOT _fbReadRegister.bError THEN
// Go back to polling state
_iState := 65;
rActDCCurrent := LREAL_TO_REAL(WORD_TO_UINT(_awCurrentDCValues[0]) * EXPT(10,WORD_TO_INT(_awCurrentDCValues[1])));
rActDCVoltage := LREAL_TO_REAL(WORD_TO_UINT(_awCurrentDCValues[2]) * EXPT(10,WORD_TO_INT(_awCurrentDCValues[3])));
stCurrentValues.rActACPower := LREAL_TO_REAL(WORD_TO_INT(_awCurrentACValues[0]) * EXPT(10,WORD_TO_INT(_awCurrentACValues[1])));
stCurrentValues.rActACFreq := LREAL_TO_REAL(WORD_TO_UINT(_awCurrentACValues[2]) * EXPT(10,WORD_TO_INT(_awCurrentACValues[3])));
stCurrentValues.rActApparentPower := LREAL_TO_REAL(WORD_TO_INT(_awCurrentACValues[4]) * EXPT(10,WORD_TO_INT(_awCurrentACValues[5])));
stCurrentValues.rActReactivePower := LREAL_TO_REAL(WORD_TO_INT(_awCurrentACValues[6]) * EXPT(10,WORD_TO_INT(_awCurrentACValues[7])));
stCurrentValues.rActPowerFactor := LREAL_TO_REAL(WORD_TO_INT(_awCurrentACValues[8]) * EXPT(10,WORD_TO_INT(_awCurrentACValues[9])));
ELSE
// Read error register
_iState := 1000;
@@ -474,8 +582,8 @@ CASE _iState OF
// And there is no error, then continue
IF NOT _fbWriteRegister.bError THEN
_iState := 210;
rActDCCurrent := 0.0;
rActDCVoltage := 0.0;
stCurrentValues.rActDCCurrent := 0.0;
stCurrentValues.rActDCVoltage := 0.0;
ELSE
// Goto error state
_iState := 1000;
@@ -572,7 +680,27 @@ CASE _iState OF
END_CASE]]></ST>
</Implementation>
<LineIds Name="FB_PowerSupplySunspec">
<LineId Id="774" Count="70" />
<LineId Id="774" Count="10" />
<LineId Id="1550" Count="0" />
<LineId Id="785" Count="28" />
<LineId Id="1701" Count="5" />
<LineId Id="814" Count="1" />
<LineId Id="1714" Count="0" />
<LineId Id="1719" Count="0" />
<LineId Id="1715" Count="0" />
<LineId Id="1718" Count="0" />
<LineId Id="1716" Count="1" />
<LineId Id="1707" Count="1" />
<LineId Id="1710" Count="2" />
<LineId Id="1709" Count="0" />
<LineId Id="1713" Count="0" />
<LineId Id="1720" Count="0" />
<LineId Id="1722" Count="1" />
<LineId Id="1726" Count="0" />
<LineId Id="1725" Count="0" />
<LineId Id="1721" Count="0" />
<LineId Id="1433" Count="0" />
<LineId Id="816" Count="28" />
<LineId Id="1171" Count="0" />
<LineId Id="845" Count="33" />
<LineId Id="1172" Count="0" />
@@ -580,9 +708,19 @@ END_CASE]]></ST>
<LineId Id="1173" Count="0" />
<LineId Id="913" Count="29" />
<LineId Id="1174" Count="0" />
<LineId Id="943" Count="28" />
<LineId Id="943" Count="4" />
<LineId Id="1520" Count="0" />
<LineId Id="1522" Count="17" />
<LineId Id="1541" Count="0" />
<LineId Id="1548" Count="0" />
<LineId Id="1542" Count="2" />
<LineId Id="1521" Count="0" />
<LineId Id="1493" Count="0" />
<LineId Id="948" Count="23" />
<LineId Id="1175" Count="0" />
<LineId Id="972" Count="46" />
<LineId Id="972" Count="42" />
<LineId Id="1560" Count="1" />
<LineId Id="1015" Count="3" />
<LineId Id="1127" Count="0" />
<LineId Id="1019" Count="25" />
<LineId Id="1097" Count="1" />
@@ -594,8 +732,17 @@ END_CASE]]></ST>
<LineId Id="1063" Count="20" />
<LineId Id="1090" Count="0" />
<LineId Id="1092" Count="0" />
<LineId Id="1775" Count="0" />
<LineId Id="1084" Count="4" />
<LineId Id="1062" Count="0" />
<LineId Id="1736" Count="1" />
<LineId Id="1739" Count="19" />
<LineId Id="1781" Count="0" />
<LineId Id="1759" Count="2" />
<LineId Id="1774" Count="0" />
<LineId Id="1776" Count="1" />
<LineId Id="1762" Count="4" />
<LineId Id="1738" Count="0" />
<LineId Id="1093" Count="1" />
<LineId Id="1102" Count="19" />
<LineId Id="1166" Count="1" />

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<TcPlcObject Version="1.1.0.1" ProductVersion="3.1.4024.12">
<DUT Name="ST_SUNSPEC_CURRENT_VALUES" Id="{429ce2f6-f580-460c-8eb4-3ddb289effd4}">
<Declaration><![CDATA[TYPE ST_SUNSPEC_CURRENT_VALUES :
STRUCT
// Current DC current (A)
rActDCCurrent : REAL;
// Current DC voltage (V)
rActDCVoltage : REAL;
// Current DC power (W)
rActDCPower : REAL;
// Current AC power (W)
rActACPower : REAL;
// Current AC frequency (Hz)
rActACFreq : REAL;
// Current AC apparent power (VA)
rActApparentPower : REAL;
// Current AC reactive power (VAr)
rActReactivePower : REAL;
// Current AC power factor
rActPowerFactor : REAL;
END_STRUCT
END_TYPE
]]></Declaration>
</DUT>
</TcPlcObject>