diff --git a/examples/iec61850_9_2_LE_example/sv.icd b/examples/iec61850_9_2_LE_example/sv.icd index b52f8d92..af5fe962 100644 --- a/examples/iec61850_9_2_LE_example/sv.icd +++ b/examples/iec61850_9_2_LE_example/sv.icd @@ -93,7 +93,7 @@ SCL.xsd"> + smvID="xxxxMUnn01" smpRate="80" nofASDU="1" confRev="1" smpMod="SmpPerPeriod"> diff --git a/src/goose/goose_publisher.c b/src/goose/goose_publisher.c index 1abf067d..60bacd43 100644 --- a/src/goose/goose_publisher.c +++ b/src/goose/goose_publisher.c @@ -65,8 +65,8 @@ GoosePublisher_createEx(CommParameters* parameters, const char* interfaceID, boo { GoosePublisher self = (GoosePublisher) GLOBAL_CALLOC(1, sizeof(struct sGoosePublisher)); - if (self) { - + if (self) + { if (prepareGooseBuffer(self, parameters, interfaceID, useVlanTag)) { self->timestamp = MmsValue_newUtcTimeByMsTime(Hal_getTimeInMs()); @@ -76,7 +76,6 @@ GoosePublisher_createEx(CommParameters* parameters, const char* interfaceID, boo GoosePublisher_destroy(self); self = NULL; } - } return self; @@ -91,20 +90,21 @@ GoosePublisher_create(CommParameters* parameters, const char* interfaceID) void GoosePublisher_destroy(GoosePublisher self) { - if (self) { + if (self) + { if (self->ethernetSocket) { Ethernet_destroySocket(self->ethernetSocket); } MmsValue_delete(self->timestamp); - if (self->goID != NULL) + if (self->goID) GLOBAL_FREEMEM(self->goID); - if (self->goCBRef != NULL) + if (self->goCBRef) GLOBAL_FREEMEM(self->goCBRef); - if (self->dataSetRef != NULL) + if (self->dataSetRef) GLOBAL_FREEMEM(self->dataSetRef); if (self->buffer) @@ -117,18 +117,27 @@ GoosePublisher_destroy(GoosePublisher self) void GoosePublisher_setGoID(GoosePublisher self, char* goID) { + if (self->goID) + GLOBAL_FREEMEM(self->goID); + self->goID = StringUtils_copyString(goID); } void GoosePublisher_setGoCbRef(GoosePublisher self, char* goCbRef) { + if (self->goCBRef) + GLOBAL_FREEMEM(self->goCBRef); + self->goCBRef = StringUtils_copyString(goCbRef); } void GoosePublisher_setDataSetRef(GoosePublisher self, char* dataSetRef) { + if (self->dataSetRef) + GLOBAL_FREEMEM(self->dataSetRef); + self->dataSetRef = StringUtils_copyString(dataSetRef); } @@ -197,7 +206,7 @@ prepareGooseBuffer(GoosePublisher self, CommParameters* parameters, const char* { uint8_t srcAddr[6]; - if (interfaceID != NULL) + if (interfaceID) Ethernet_getInterfaceMACAddress(interfaceID, srcAddr); else Ethernet_getInterfaceMACAddress(CONFIG_ETHERNET_INTERFACE_ID, srcAddr); @@ -209,7 +218,7 @@ prepareGooseBuffer(GoosePublisher self, CommParameters* parameters, const char* uint16_t vlanId; uint16_t appId; - if (parameters == NULL) { + if (parameters) { dstAddr = defaultDstAddr; priority = CONFIG_GOOSE_DEFAULT_PRIORITY; vlanId = CONFIG_GOOSE_DEFAULT_VLAN_ID; @@ -222,7 +231,7 @@ prepareGooseBuffer(GoosePublisher self, CommParameters* parameters, const char* appId = parameters->appId; } - if (interfaceID != NULL) + if (interfaceID) self->ethernetSocket = Ethernet_createSocket(interfaceID, dstAddr); else self->ethernetSocket = Ethernet_createSocket(CONFIG_ETHERNET_INTERFACE_ID, dstAddr); @@ -294,7 +303,7 @@ createGoosePayload(GoosePublisher self, LinkedList dataSetValues, uint8_t* buffe goosePduLength += BerEncoder_determineEncodedStringSize(self->dataSetRef); - if (self->goID != NULL) + if (self->goID) goosePduLength += BerEncoder_determineEncodedStringSize(self->goID); else goosePduLength += BerEncoder_determineEncodedStringSize(self->goCBRef); @@ -317,7 +326,7 @@ createGoosePayload(GoosePublisher self, LinkedList dataSetValues, uint8_t* buffe LinkedList element = LinkedList_getNext(dataSetValues); - while (element != NULL) { + while (element) { MmsValue* dataSetEntry = (MmsValue*) element->data; if (dataSetEntry) { @@ -358,7 +367,7 @@ createGoosePayload(GoosePublisher self, LinkedList dataSetValues, uint8_t* buffe bufPos = BerEncoder_encodeStringWithTag(0x82, self->dataSetRef, buffer, bufPos); /* Encode goID */ - if (self->goID != NULL) + if (self->goID) bufPos = BerEncoder_encodeStringWithTag(0x83, self->goID, buffer, bufPos); else bufPos = BerEncoder_encodeStringWithTag(0x83, self->goCBRef, buffer, bufPos); @@ -390,7 +399,7 @@ createGoosePayload(GoosePublisher self, LinkedList dataSetValues, uint8_t* buffe /* Encode data set entries */ element = LinkedList_getNext(dataSetValues); - while (element != NULL) { + while (element) { MmsValue* dataSetEntry = (MmsValue*) element->data; if (dataSetEntry) { diff --git a/src/goose/goose_publisher.h b/src/goose/goose_publisher.h index 77eb2766..0d5e6417 100644 --- a/src/goose/goose_publisher.h +++ b/src/goose/goose_publisher.h @@ -170,7 +170,7 @@ GoosePublisher_setStNum(GoosePublisher self, uint32_t stNum); * NOTE: Only for testing! The sequence number is increase manually whenever \ref GoosePublisher_publish is called. * * \param self GoosePublisher instance - * \param stNum the state number of the next GOOSE message to send + * \param sqNum the sequence number of the next GOOSE message to send */ LIB61850_API void GoosePublisher_setSqNum(GoosePublisher self, uint32_t sqNum); diff --git a/src/iec61850/server/model/dynamic_model.c b/src/iec61850/server/model/dynamic_model.c index 790c9be3..68710320 100644 --- a/src/iec61850/server/model/dynamic_model.c +++ b/src/iec61850/server/model/dynamic_model.c @@ -166,7 +166,6 @@ IedModel_addSettingGroupControlBlock(IedModel* self, SettingGroupControlBlock* s } #endif /* (CONFIG_IEC61850_SETTING_GROUPS == 1) */ - static void IedModel_addGSEControlBlock(IedModel* self, GSEControlBlock* gcb) { @@ -175,13 +174,29 @@ IedModel_addGSEControlBlock(IedModel* self, GSEControlBlock* gcb) else { GSEControlBlock* lastGcb = self->gseCBs; - while (lastGcb->sibling != NULL) + while (lastGcb->sibling) lastGcb = lastGcb->sibling; lastGcb->sibling = gcb; } } +static void +IedModel_addSMVControlBlock(IedModel* self, SVControlBlock* smvcb) +{ + if (self->svCBs == NULL) { + self->svCBs = smvcb; + } + else { + SVControlBlock* lastSvCB = self->svCBs; + + while (lastSvCB->sibling) + lastSvCB = lastSvCB->sibling; + + lastSvCB->sibling = smvcb; + } +} + LogicalDevice* LogicalDevice_createEx(const char* inst, IedModel* parent, const char* ldName) { @@ -512,6 +527,14 @@ GSEControlBlock_create(const char* name, LogicalNode* parent, const char* appId, return self; } +static void +LogicalNode_addSMVControlBlock(LogicalNode* self, SVControlBlock* smvcb) +{ + IedModel* model = (IedModel*) self->parent->parent; + + IedModel_addSMVControlBlock(model, smvcb); +} + SVControlBlock* SVControlBlock_create(const char* name, LogicalNode* parent, const char* svID, const char* dataSet, uint32_t confRev, uint8_t smpMod, uint16_t smpRate, uint8_t optFlds, bool isUnicast) @@ -536,6 +559,12 @@ SVControlBlock_create(const char* name, LogicalNode* parent, const char* svID, c self->optFlds = optFlds; self->isUnicast = isUnicast; + + self->dstAddress = NULL; + self->sibling = NULL; + + if (parent) + LogicalNode_addSMVControlBlock(parent, self); } return self; diff --git a/src/mms/iso_mms/client/mms_client_initiate.c b/src/mms/iso_mms/client/mms_client_initiate.c index 62ca427d..fdd9b94e 100644 --- a/src/mms/iso_mms/client/mms_client_initiate.c +++ b/src/mms/iso_mms/client/mms_client_initiate.c @@ -147,7 +147,7 @@ parseInitResponseDetail(MmsConnection self, uint8_t* buffer, int bufPos, int max int i; for (i = 0; i < 11; i++) - self->parameters.servicesSupported[i] = buffer[bufPos + i]; + self->parameters.servicesSupported[i] = buffer[bufPos + i + 1]; /* add 1 to skip padding */ } break; diff --git a/tools/model_generator/genconfig.jar b/tools/model_generator/genconfig.jar index 2c9ed1fc..6f42a2ba 100644 Binary files a/tools/model_generator/genconfig.jar and b/tools/model_generator/genconfig.jar differ diff --git a/tools/model_generator/genmodel.jar b/tools/model_generator/genmodel.jar index 752df205..00bc30b2 100644 Binary files a/tools/model_generator/genmodel.jar and b/tools/model_generator/genmodel.jar differ diff --git a/tools/model_generator/src/com/libiec61850/scl/communication/ConnectedAP.java b/tools/model_generator/src/com/libiec61850/scl/communication/ConnectedAP.java index d9765b04..b3cf608d 100644 --- a/tools/model_generator/src/com/libiec61850/scl/communication/ConnectedAP.java +++ b/tools/model_generator/src/com/libiec61850/scl/communication/ConnectedAP.java @@ -85,7 +85,7 @@ public class ConnectedAP { public List getSmvs() { return smvs; } - + public GSE lookupGSE(String logicalDeviceName, String name) { for (GSE gse : this.getGses()) { @@ -97,6 +97,18 @@ public class ConnectedAP { return null; } + + public SMV lookupSMV(String logicalDeviceName, String name) { + + for (SMV smv : this.getSmvs()) { + if (smv.getLdInst().equals(logicalDeviceName)) { + if (smv.getCbName().equals(name)) + return smv; + } + } + + return null; + } public PhyComAddress lookupSMVAddress(String logicalDeviceName, String name) { diff --git a/tools/model_generator/src/com/libiec61850/scl/model/SampledValueControl.java b/tools/model_generator/src/com/libiec61850/scl/model/SampledValueControl.java index f2f03b66..6ef8c29b 100644 --- a/tools/model_generator/src/com/libiec61850/scl/model/SampledValueControl.java +++ b/tools/model_generator/src/com/libiec61850/scl/model/SampledValueControl.java @@ -16,9 +16,8 @@ public class SampledValueControl { private int nofASDU; private boolean multicast = false; private SmvOpts smvOpts; - - - + private SmpMod smpMod = SmpMod.SMP_PER_PERIOD; + public SampledValueControl(Node smvControlNode) throws SclParserException { this.name = ParserUtils.parseAttribute(smvControlNode, "name"); this.desc = ParserUtils.parseAttribute(smvControlNode, "desc"); @@ -49,10 +48,25 @@ public class SampledValueControl { Node smvOptsNode = ParserUtils.getChildNodeWithTag(smvControlNode, "SmvOpts"); this.smvOpts = new SmvOpts(smvOptsNode); + + String smpModString = ParserUtils.parseAttribute(smvControlNode, "smpMod"); + + if (smpModString != null) { + if (smpModString.equals("SmpPerPeriod")) { + smpMod = SmpMod.SMP_PER_PERIOD; + } + else if (smpModString.equals("SmpPerSec")) { + smpMod = SmpMod.SMP_PER_SECOND; + } + else if (smpModString.equals("SecPerSmp")) { + smpMod = SmpMod.SEC_PER_SMP; + } + else { + throw new SclParserException(smvControlNode, "Invalid smpMod value " + smpModString); + } + } } - - - + public String getName() { return name; } @@ -88,5 +102,8 @@ public class SampledValueControl { public SmvOpts getSmvOpts() { return smvOpts; } - + + public SmpMod getSmpMod() { + return smpMod; + } } diff --git a/tools/model_generator/src/com/libiec61850/scl/model/SmpMod.java b/tools/model_generator/src/com/libiec61850/scl/model/SmpMod.java new file mode 100644 index 00000000..4de9f6eb --- /dev/null +++ b/tools/model_generator/src/com/libiec61850/scl/model/SmpMod.java @@ -0,0 +1,56 @@ +package com.libiec61850.scl.model; + +/* + * Copyright 2023 Michael Zillgith + * + * This file is part of libIEC61850. + * + * libIEC61850 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * libIEC61850 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with libIEC61850. If not, see . + * + * See COPYING file for the complete license text. + */ + +public enum SmpMod +{ + SMP_PER_PERIOD(0), + SMP_PER_SECOND(1), + SEC_PER_SMP(2); + + private int value; + + private SmpMod(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public boolean compare(int i) + { + return (value == i); + } + + public static SmpMod fromValue(int val) + { + SmpMod[] errors = SmpMod.values(); + + for (int i = 0; i < errors.length; i++) { + if (errors[i].compare(val)) + return errors[i]; + } + + return SmpMod.SMP_PER_PERIOD; + } +} diff --git a/tools/model_generator/src/com/libiec61850/tools/DynamicModelGenerator.java b/tools/model_generator/src/com/libiec61850/tools/DynamicModelGenerator.java index cd1d4593..0c05fd57 100644 --- a/tools/model_generator/src/com/libiec61850/tools/DynamicModelGenerator.java +++ b/tools/model_generator/src/com/libiec61850/tools/DynamicModelGenerator.java @@ -37,6 +37,7 @@ import com.libiec61850.scl.SclParserException; import com.libiec61850.scl.communication.ConnectedAP; import com.libiec61850.scl.communication.GSE; import com.libiec61850.scl.communication.PhyComAddress; +import com.libiec61850.scl.communication.SMV; import com.libiec61850.scl.model.AccessPoint; import com.libiec61850.scl.model.DataAttribute; import com.libiec61850.scl.model.DataModelValue; @@ -51,6 +52,7 @@ import com.libiec61850.scl.model.LogicalDevice; import com.libiec61850.scl.model.LogicalNode; import com.libiec61850.scl.model.ReportControlBlock; import com.libiec61850.scl.model.ReportSettings; +import com.libiec61850.scl.model.SampledValueControl; import com.libiec61850.scl.model.Services; import com.libiec61850.scl.model.SettingControl; @@ -166,6 +168,52 @@ public class DynamicModelGenerator { for (Log log : logicalNode.getLogs()) output.println("LOG(" + log.getName() + ");"); + + for (SampledValueControl svcb : logicalNode.getSampledValueControlBlocks()) { + LogicalDevice ld = logicalNode.getParentLogicalDevice(); + + SMV smv = null; + PhyComAddress smvAddress = null; + + if (connectedAP != null) { + smv = connectedAP.lookupSMV(ld.getInst(), svcb.getName()); + + if (smv != null) + smvAddress = smv.getAddress(); + } + else + System.out.println("WARNING: IED \"" + ied.getName() + "\" has no connected access point!"); + + output.print("SMVC("); + output.print(svcb.getName() + " "); + output.print(svcb.getSmvID() + " "); + output.print(svcb.getDatSet() + " "); + output.print(svcb.getConfRev() + " "); + output.print(svcb.getSmpMod().getValue() + " "); + output.print(svcb.getSmpRate() + " "); + output.print(svcb.getSmvOpts().getIntValue() + " "); + output.print(svcb.isMulticast() ? "0" : "1"); + output.print(")"); + + if (smvAddress != null) { + output.println("{"); + + output.print("PA("); + output.print(smvAddress.getVlanPriority() + " "); + output.print(smvAddress.getVlanId() + " "); + output.print(smvAddress.getAppId() + " "); + + for (int i = 0; i < 6; i++) + output.printf("%02x", smvAddress.getMacAddress()[i]); + + output.println(");"); + + output.println("}"); + } + else { + output.println(";"); + } + } for (GSEControl gcb : logicalNode.getGSEControlBlocks()) { LogicalDevice ld = logicalNode.getParentLogicalDevice(); diff --git a/tools/model_generator/src/com/libiec61850/tools/StaticModelGenerator.java b/tools/model_generator/src/com/libiec61850/tools/StaticModelGenerator.java index 4381aff0..a06313a0 100644 --- a/tools/model_generator/src/com/libiec61850/tools/StaticModelGenerator.java +++ b/tools/model_generator/src/com/libiec61850/tools/StaticModelGenerator.java @@ -1247,8 +1247,10 @@ public class StaticModelGenerator { svString += "NULL, "; svString += svCB.getSmvOpts().getIntValue() + ", "; + + svString += svCB.getSmpMod().getValue() + ", "; - svString += "0, " + svCB.getSmpRate() + ", "; + svString += svCB.getSmpRate() + ", "; svString += svCB.getConfRev() + ", ";