diff --git a/config/stack_config.h b/config/stack_config.h index 26207ee5..e6a12ed1 100644 --- a/config/stack_config.h +++ b/config/stack_config.h @@ -67,12 +67,14 @@ /* Ethernet interface ID for GOOSE and SV */ #define CONFIG_ETHERNET_INTERFACE_ID "eth0" //#define CONFIG_ETHERNET_INTERFACE_ID "vboxnet0" -//#define CONFIG_ETHERNET_INTERFACE_ID "eth-f" //#define CONFIG_ETHERNET_INTERFACE_ID "en0" // OS X uses enX in place of ethX as ethernet NIC names. /* Set to 1 to include GOOSE support in the build. Otherwise set to 0 */ #define CONFIG_INCLUDE_GOOSE_SUPPORT 1 +/* Set to 1 to include Sampled Values support in the build. Otherwise set to 0 */ +#define CONFIG_IEC61850_SAMPLED_VALUES_SUPPORT 1 + #ifdef _WIN32 /* GOOSE will be disabled for Windows if ethernet support (winpcap) is not available */ diff --git a/config/stack_config.h.cmake b/config/stack_config.h.cmake index 011cbf82..36b7ff3e 100644 --- a/config/stack_config.h.cmake +++ b/config/stack_config.h.cmake @@ -71,6 +71,9 @@ /* Set to 1 to include GOOSE support in the build. Otherwise set to 0 */ #cmakedefine01 CONFIG_INCLUDE_GOOSE_SUPPORT +/* Set to 1 to include Sampled Values support in the build. Otherwise set to 0 */ +#cmakedefine01 CONFIG_IEC61850_SAMPLED_VALUES_SUPPORT + #ifdef _WIN32 /* GOOSE will be disabled for Windows if ethernet support (winpcap) is not available */ diff --git a/dotnet/reporting/Settings.cs b/dotnet/reporting/Settings.cs new file mode 100644 index 00000000..99fcfeb5 --- /dev/null +++ b/dotnet/reporting/Settings.cs @@ -0,0 +1,28 @@ +namespace reporting.Properties { + + + // This class allows you to handle specific events on the settings class: + // The SettingChanging event is raised before a setting's value is changed. + // The PropertyChanged event is raised after a setting's value is changed. + // The SettingsLoaded event is raised after the setting values are loaded. + // The SettingsSaving event is raised before the setting values are saved. + internal sealed partial class Settings { + + public Settings() { + // // To add event handlers for saving and changing settings, uncomment the lines below: + // + // this.SettingChanging += this.SettingChangingEventHandler; + // + // this.SettingsSaving += this.SettingsSavingEventHandler; + // + } + + private void SettingChangingEventHandler(object sender, System.Configuration.SettingChangingEventArgs e) { + // Add code to handle the SettingChangingEvent event here. + } + + private void SettingsSavingEventHandler(object sender, System.ComponentModel.CancelEventArgs e) { + // Add code to handle the SettingsSaving event here. + } + } +} diff --git a/src/common/inc/libiec61850_platform_includes.h b/src/common/inc/libiec61850_platform_includes.h index 9a9f9097..f4de6b94 100644 --- a/src/common/inc/libiec61850_platform_includes.h +++ b/src/common/inc/libiec61850_platform_includes.h @@ -15,7 +15,7 @@ #include "platform_endian.h" -#define LIBIEC61850_VERSION "0.8.7" +#define LIBIEC61850_VERSION "0.9.0" #ifndef CONFIG_DEFAULT_MMS_VENDOR_NAME #define CONFIG_DEFAULT_MMS_VENDOR_NAME "libiec61850.com" @@ -29,6 +29,10 @@ #define CONFIG_DEFAULT_MMS_REVISION LIBIEC61850_VERSION #endif +#ifndef CONFIG_IEC61850_SAMPLED_VALUES_SUPPORT +#define CONFIG_IEC61850_SAMPLED_VALUES_SUPPORT 0 +#endif + #if (DEBUG != 1) #define NDEBUG 1 #endif diff --git a/src/iec61850/inc/iec61850_dynamic_model.h b/src/iec61850/inc/iec61850_dynamic_model.h index 77725334..967c764f 100644 --- a/src/iec61850/inc/iec61850_dynamic_model.h +++ b/src/iec61850/inc/iec61850_dynamic_model.h @@ -183,7 +183,7 @@ SettingGroupControlBlock_create(LogicalNode* parent, uint8_t actSG, uint8_t numO * \param parent the parent LN * \param appId the application ID of the GoCB * \param dataSet the data set reference to be used by the GoCB - * \param confRef the configuration revision + * \param confRev the configuration revision * \param fixedOffs indicates if GOOSE publisher shall use fixed offsets (NOT YET SUPPORTED) * \param minTime minimum GOOSE retransmission time (-1 if not specified - uses stack default then) * \param maxTime GOOSE retransmission time in stable state (-1 if not specified - uses stack default then) @@ -191,15 +191,40 @@ SettingGroupControlBlock_create(LogicalNode* parent, uint8_t actSG, uint8_t numO * \return the new GoCB instance */ GSEControlBlock* -GSEControlBlock_create(const char* name, LogicalNode* parent, char* appId, char* dataSet, uint32_t confRef, +GSEControlBlock_create(const char* name, LogicalNode* parent, char* appId, char* dataSet, uint32_t confRev, bool fixedOffs, int minTime, int maxTime); /** - * \brief create a PhyComAddress object and add it to a GoCB + * \brief create a new Multicast/Unicast Sampled Value (SV) control block (SvCB) + * + * Create a new Sampled Value control block (SvCB) and add it to the given logical node (LN) + * + * \param name name of the SvCB relative to the parent LN + * \param parent the parent LN + * \param svID the application ID of the SvCB + * \param dataSet the data set reference to be used by the SVCB + * \param confRev the configuration revision + * \param smpMod the sampling mode used + * \param smpRate the sampling rate used + * \param optFlds the optional element configuration + * + * \return the new SvCB instance + */ +SVControlBlock* +SVControlBlock_create(const char* name, LogicalNode* parent, char* svID, char* dataSet, uint32_t confRev, uint8_t smpMod, + uint16_t smpRate, uint8_t optFlds, bool isUnicast); + +void +SVControlBlock_addPhyComAddress(SVControlBlock* self, PhyComAddress* phyComAddress); + +void +GSEControlBlock_addPhyComAddress(GSEControlBlock* self, PhyComAddress* phyComAddress); + +/** + * \brief create a PhyComAddress object * - * A PhyComAddress object contains all required addressing informations for a GOOSE publisher. + * A PhyComAddress object contains all required addressing information for a GOOSE publisher. * - * \param parent the parent GSEControlBlock object * \param vlanPriority the priority field of the VLAN tag * \param vlanId the ID field of the VLAN tag * \param appId the application identifier @@ -208,7 +233,7 @@ GSEControlBlock_create(const char* name, LogicalNode* parent, char* appId, char* * \return the new PhyComAddress object */ PhyComAddress* -PhyComAddress_create(GSEControlBlock* parent, uint8_t vlanPriority, uint16_t vlanId, uint16_t appId, uint8_t dstAddress[]); +PhyComAddress_create(uint8_t vlanPriority, uint16_t vlanId, uint16_t appId, uint8_t dstAddress[]); /** * \brief create a new data set diff --git a/src/iec61850/inc/iec61850_model.h b/src/iec61850/inc/iec61850_model.h index a94204d7..258c9b10 100644 --- a/src/iec61850/inc/iec61850_model.h +++ b/src/iec61850/inc/iec61850_model.h @@ -80,6 +80,8 @@ typedef struct sSettingGroupControlBlock SettingGroupControlBlock; typedef struct sGSEControlBlock GSEControlBlock; +typedef struct sSVControlBlock SVControlBlock; + typedef enum { IEC61850_BOOLEAN = 0,/* int */ @@ -162,6 +164,7 @@ struct sIedModel { DataSet* dataSets; ReportControlBlock* rcbs; GSEControlBlock* gseCBs; + SVControlBlock* svCBs; SettingGroupControlBlock* sgcbs; void (*initializer) (void); }; @@ -279,7 +282,7 @@ struct sGSEControlBlock { char* name; char* appId; char* dataSetName; /* pre loaded with relative name in logical node */ - uint32_t confRef; /* ConfRef - configuration revision */ + uint32_t confRev; /* ConfRev - configuration revision */ bool fixedOffs; /* fixed offsets */ PhyComAddress* address; /* GSE communication parameters */ int minTime; /* optional minTime parameter --> -1 if not present */ @@ -287,6 +290,29 @@ struct sGSEControlBlock { GSEControlBlock* sibling; /* next control block in list or NULL if this is the last entry */ }; +struct sSVControlBlock { + LogicalNode* parent; + char* name; + + char* svId; /* MsvUD/UsvID */ + char* dataSetName; /* pre loaded with relative name in logical node */ + + uint8_t optFlds; + + uint8_t smpMod; + uint16_t smpRate; + + uint32_t confRev; /* ConfRev - configuration revision */ + + PhyComAddress* dstAddress; /* SV communication parameters */ + + bool isUnicast; + + int noASDU; + + SVControlBlock* sibling; /* next control block in list or NULL if this is the last entry */ +}; + /** * \brief get the number of direct children of a model node * diff --git a/src/iec61850/inc_private/mms_mapping_internal.h b/src/iec61850/inc_private/mms_mapping_internal.h index a23fe8e9..ef09bb24 100644 --- a/src/iec61850/inc_private/mms_mapping_internal.h +++ b/src/iec61850/inc_private/mms_mapping_internal.h @@ -40,6 +40,11 @@ struct sMmsMapping { const char* gooseInterfaceId; #endif +#if (CONFIG_IEC61850_SAMPLED_VALUES_SUPPORT == 1) + LinkedList svControls; + const char* svInterfaceId; +#endif + LinkedList controlObjects; LinkedList observedObjects; LinkedList attributeAccessHandlers; diff --git a/src/iec61850/inc_private/mms_sv.h b/src/iec61850/inc_private/mms_sv.h new file mode 100644 index 00000000..4af77fe5 --- /dev/null +++ b/src/iec61850/inc_private/mms_sv.h @@ -0,0 +1,31 @@ +/* + * mms_sv.h + * + * Copyright 2015 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. + */ + +#ifndef LIBIEC61850_SRC_IEC61850_INC_PRIVATE_MMS_SV_H_ +#define LIBIEC61850_SRC_IEC61850_INC_PRIVATE_MMS_SV_H_ + +MmsVariableSpecification* +LIBIEC61850_SV_createSVControlBlocks(MmsMapping* self, MmsDomain* domain, + LogicalNode* logicalNode, int svCount, bool unicast); + +#endif /* LIBIEC61850_SRC_IEC61850_INC_PRIVATE_MMS_SV_H_ */ diff --git a/src/iec61850/server/mms_mapping/mms_goose.c b/src/iec61850/server/mms_mapping/mms_goose.c index 85428f30..4df30eba 100644 --- a/src/iec61850/server/mms_mapping/mms_goose.c +++ b/src/iec61850/server/mms_mapping/mms_goose.c @@ -550,7 +550,7 @@ GOOSE_createGOOSEControlBlocks(MmsMapping* self, MmsDomain* domain, MmsValue* confRef = MmsValue_getElement(gseValues, 3); - MmsValue_setUint32(confRef, gooseControlBlock->confRef); + MmsValue_setUint32(confRef, gooseControlBlock->confRev); mmsGCB->dataSet = NULL; @@ -587,5 +587,5 @@ GOOSE_createGOOSEControlBlocks(MmsMapping* self, MmsDomain* domain, return namedVariable; } -#endif +#endif /* (CONFIG_INCLUDE_GOOSE_SUPPORT == 1) */ diff --git a/src/iec61850/server/mms_mapping/mms_mapping.c b/src/iec61850/server/mms_mapping/mms_mapping.c index 40064194..0a488866 100644 --- a/src/iec61850/server/mms_mapping/mms_mapping.c +++ b/src/iec61850/server/mms_mapping/mms_mapping.c @@ -766,9 +766,8 @@ countGSEControlBlocksForLogicalNode(MmsMapping* self, LogicalNode* logicalNode) GSEControlBlock* gcb = self->model->gseCBs; while (gcb != NULL) { - if (gcb->parent == logicalNode) { + if (gcb->parent == logicalNode) gseCount++; - } gcb = gcb->sibling; } @@ -778,6 +777,27 @@ countGSEControlBlocksForLogicalNode(MmsMapping* self, LogicalNode* logicalNode) #endif /* (CONFIG_INCLUDE_GOOSE_SUPPORT == 1) */ +#if (CONFIG_IEC61850_SAMPLED_VALUES_SUPPORT == 1) + +static int +countSVControlBlocksForLogicalNode(MmsMapping* self, LogicalNode* logicalNode, bool unicast) +{ + int svCount = 0; + + SVControlBlock* svCb = self->model->svCBs; + + while (svCb != NULL) { + if ((svCb->parent == logicalNode) && (svCb->isUnicast == unicast)) + svCount++; + + svCb = svCb->sibling; + } + + return svCount; +} + +#endif /* (CONFIG_IEC61850_SAMPLED_VALUES_SUPPORT == 1) */ + static SettingGroupControlBlock* checkForSgcb(MmsMapping* self, LogicalNode* logicalNode) { @@ -862,6 +882,28 @@ createNamedVariableFromLogicalNode(MmsMapping* self, MmsDomain* domain, #endif /* (CONFIG_INCLUDE_GOOSE_SUPPORT == 1) */ +#if (CONFIG_IEC61850_SAMPLED_VALUES_SUPPORT == 1) + + int msvcbCount = countSVControlBlocksForLogicalNode(self, logicalNode, false); + + if (msvcbCount > 0) { + if (DEBUG_IED_SERVER) + printf(" and %i MSV control blocks\n", msvcbCount); + + componentCount++; + } + + int usvcbCount = countSVControlBlocksForLogicalNode(self, logicalNode, true); + + if (usvcbCount > 0) { + if (DEBUG_IED_SERVER) + printf(" and %i USV control blocks\n", usvcbCount); + + componentCount++; + } + +#endif /* (CONFIG_IEC61850_SAMPLED_VALUES_SUPPORT == 1) */ + namedVariable->typeSpec.structure.elements = (MmsVariableSpecification**) GLOBAL_CALLOC(componentCount, sizeof(MmsVariableSpecification*)); @@ -961,6 +1003,24 @@ createNamedVariableFromLogicalNode(MmsMapping* self, MmsDomain* domain, currentComponent++; } +#if (CONFIG_IEC61850_SAMPLED_VALUES_SUPPORT == 1) + + /* Add MS and US named variables */ + if (msvcbCount > 0) { + namedVariable->typeSpec.structure.elements[currentComponent] = + LIBIEC61850_SV_creatSVControlBlocks(self, domain, logicalNode, msvcbCount, false); + + currentComponent++; + } + + if (usvcbCount > 0) { + namedVariable->typeSpec.structure.elements[currentComponent] = + LIBIEC61850_SV_creatSVControlBlocks(self, domain, logicalNode, msvcbCount, true); + + currentComponent++; + } +#endif + if (LogicalNode_hasFCData(logicalNode, IEC61850_FC_EX)) { namedVariable->typeSpec.structure.elements[currentComponent] = createFCNamedVariable(logicalNode, IEC61850_FC_EX); @@ -1138,6 +1198,11 @@ MmsMapping_create(IedModel* model) self->gooseInterfaceId = NULL; #endif +#if (CONFIG_IEC61850_SAMPLED_VALUES_SUPPORT == 1) + self->svControls = LinkedList_create(); + self->svInterfaceId = NULL; +#endif + #if (CONFIG_IEC61850_CONTROL_SERVICE == 1) self->controlObjects = LinkedList_create(); #endif @@ -1177,6 +1242,10 @@ MmsMapping_destroy(MmsMapping* self) LinkedList_destroyDeep(self->gseControls, (LinkedListValueDeleteFunction) MmsGooseControlBlock_destroy); #endif +#if (CONFIG_IEC61850_SAMPLED_VALUES_SUPPORT == 1) + LinkedList_destroyDeep(self->svControls, (LinkedListValueDeleteFunction) MmsSvControlBlock_destroy); +#endif + #if (CONFIG_IEC61850_CONTROL_SERVICE == 1) LinkedList_destroyDeep(self->controlObjects, (LinkedListValueDeleteFunction) ControlObject_destroy); #endif diff --git a/src/iec61850/server/mms_mapping/mms_sv.c b/src/iec61850/server/mms_mapping/mms_sv.c new file mode 100644 index 00000000..56e8674b --- /dev/null +++ b/src/iec61850/server/mms_mapping/mms_sv.c @@ -0,0 +1,251 @@ +/* + * mms_sv.c + * + * Copyright 2015 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. + */ + +#include "stack_config.h" + +#if (CONFIG_IEC61850_SAMPLED_VALUES_SUPPORT == 1) + +#include "libiec61850_platform_includes.h" +#include "mms_mapping.h" +#include "linked_list.h" +#include "array_list.h" + +#include "mms_mapping_internal.h" + + +static GSEControlBlock* +getSVCBForLogicalNodeWithIndex(MmsMapping* self, LogicalNode* logicalNode, int index, bool isUnicast) +{ + int svCount = 0; + + SVControlBlock* svcb = self->model->svCBs; + + /* Iterate list of SvCBs */ + while (svcb != NULL ) { + if ((svcb->parent == logicalNode) && (svcb->isUnicast == isUnicast)) { + if (svCount == index) + return svcb; + + svCount++; + } + + svcb = svcb->sibling; + } + + return NULL ; +} + + +static MmsVariableSpecification* +createSVControlBlockMmsStructure(char* gcbName, bool isUnicast) +{ + MmsVariableSpecification* gcb = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); + gcb->name = copyString(gcbName); + gcb->type = MMS_STRUCTURE; + + int elementCount; + + if (isUnicast) + elementCount = 10; + else + elementCount = 9; + + gcb->typeSpec.structure.elementCount = elementCount; + gcb->typeSpec.structure.elements = (MmsVariableSpecification**) + GLOBAL_CALLOC(elementCount, sizeof(MmsVariableSpecification*)); + + int currentElement = 0; + + MmsVariableSpecification* namedVariable; + + namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); + namedVariable->name = copyString("SvEna"); + namedVariable->type = MMS_BOOLEAN; + + gcb->typeSpec.structure.elements[currentElement++] = namedVariable; + + if (isUnicast) { + namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); + namedVariable->name = copyString("Resv"); + namedVariable->type = MMS_BOOLEAN; + } + + namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); + if (isUnicast) + namedVariable->name = copyString("UsvID"); + else + namedVariable->name = copyString("MsvID"); + namedVariable->typeSpec.visibleString = -129; + namedVariable->type = MMS_VISIBLE_STRING; + + gcb->typeSpec.structure.elements[currentElement++] = namedVariable; + + namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); + namedVariable->name = copyString("DatSet"); + namedVariable->typeSpec.visibleString = -129; + namedVariable->type = MMS_VISIBLE_STRING; + + gcb->typeSpec.structure.elements[currentElement++] = namedVariable; + + namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); + namedVariable->name = copyString("ConfRev"); + namedVariable->type = MMS_INTEGER; + namedVariable->typeSpec.integer = 32; + + gcb->typeSpec.structure.elements[currentElement++] = namedVariable; + + namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); + namedVariable->name = copyString("SmpRate"); + namedVariable->type = MMS_INTEGER; + namedVariable->typeSpec.unsignedInteger = 32; + + gcb->typeSpec.structure.elements[currentElement++] = namedVariable; + + namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); + namedVariable->name = copyString("OptFlds"); + namedVariable->type = MMS_BIT_STRING; + namedVariable->typeSpec.bitString = 5; + + gcb->typeSpec.structure.elements[currentElement++] = namedVariable; + + namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); + namedVariable->name = copyString("DstAddress"); + MmsMapping_createPhyComAddrStructure(namedVariable); + + gcb->typeSpec.structure.elements[currentElement++] = namedVariable; + + namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); + namedVariable->name = copyString("noASDU"); + namedVariable->type = MMS_INTEGER; + namedVariable->typeSpec.integer = 32; + + gcb->typeSpec.structure.elements[currentElement++] = namedVariable; + + return gcb; +} + + + +MmsVariableSpecification* +LIBIEC61850_SV_createSVControlBlocks(MmsMapping* self, MmsDomain* domain, + LogicalNode* logicalNode, int svCount, bool unicast) +{ + MmsVariableSpecification* namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, + sizeof(MmsVariableSpecification)); + + if (unicast) + namedVariable->name = copyString("US"); + else + namedVariable->name = copyString("MS"); + + namedVariable->type = MMS_STRUCTURE; + + namedVariable->typeSpec.structure.elementCount = svCount; + namedVariable->typeSpec.structure.elements = (MmsVariableSpecification**) GLOBAL_CALLOC(svCount, + sizeof(MmsVariableSpecification*)); + + int currentSVCB = 0; + + while (currentSVCB < svCount) { + SVControlBlock* svControlBlock = getSVCBForLogicalNodeWithIndex( + self, logicalNode, currentSVCB, unicast); + + MmsVariableSpecification* svTypeSpec = createSVControlBlockMmsStructure(svControlBlock->name, unicast); + + MmsValue* svValues = MmsValue_newStructure(svTypeSpec); + + namedVariable->typeSpec.structure.elements[currentSVCB] = svTypeSpec; + + int currentIndex; + + if (unicast) + currentIndex = 2; + else + currentIndex = 1; + + /* SvID */ + MmsValue* svID = MmsValue_getElement(svValues, currentIndex++); + MmsValue_setVisibleString(svID, svControlBlock->svId); + + /* DatSet */ + MmsValue* dataSetRef = MmsValue_getElement(svValues, currentIndex++); + MmsValue_setVisibleString(dataSetRef, svControlBlock->dataSetName); + + /* ConfRev */ + MmsValue* confRev = MmsValue_getElement(svValues, currentIndex++); + MmsValue_setInt32(confRev, svControlBlock->confRev); + + /* SmpRate */ + MmsValue* smpRate = MmsValue_getElement(svValues, currentIndex++); + MmsValue_setInt32(smpRate, svControlBlock->smpRate); + + /* OptFlds */ + MmsValue* optFlds = MmsValue_getElement(svValues, currentIndex++); + MmsValue_setBitStringFromInteger(optFlds, svControlBlock->optFlds); + + /* SmpMod */ + MmsValue* smpMod = MmsValue_getElement(svValues, currentIndex++); + MmsValue_setInt32(smpRate, svControlBlock->smpMod); + + /* Set communication parameters - DstAddress */ + uint8_t priority = CONFIG_GOOSE_DEFAULT_PRIORITY; + uint8_t dstAddr[] = CONFIG_GOOSE_DEFAULT_DST_ADDRESS; + uint16_t vid = CONFIG_GOOSE_DEFAULT_VLAN_ID; + uint16_t appId = CONFIG_GOOSE_DEFAULT_APPID; + + if (svControlBlock->dstAddress != NULL) { + priority = svControlBlock->dstAddress->vlanPriority; + vid = svControlBlock->dstAddress->vlanId; + appId = svControlBlock->dstAddress->appId; + + int i; + for (i = 0; i < 6; i++) { + dstAddr[i] = svControlBlock->dstAddress->dstAddress[i]; + } + } + + MmsValue* dstAddress = MmsValue_getElement(svValues, currentIndex++); + + MmsValue* addr = MmsValue_getElement(dstAddress, 0); + MmsValue_setOctetString(addr, dstAddr, 6); + + MmsValue* prio = MmsValue_getElement(dstAddress, 1); + MmsValue_setUint8(prio, priority); + + MmsValue* vlanId = MmsValue_getElement(dstAddress, 2); + MmsValue_setUint16(vlanId, vid); + + MmsValue* appIdVal = MmsValue_getElement(dstAddress, 3); + MmsValue_setUint16(appIdVal, appId); + + /* noASDU */ + MmsValue* noASDU = MmsValue_getElement(svValues, currentIndex++); + MmsValue_setInt32(noASDU, svControlBlock->noASDU); + + currentSVCB++; + } + + return namedVariable; +} + +#endif /* (CONFIG_IEC61850_SAMPLED_VALUES_SUPPORT == 1) */ diff --git a/src/iec61850/server/model/config_file_parser.c b/src/iec61850/server/model/config_file_parser.c index 1157ec6a..e34258dc 100644 --- a/src/iec61850/server/model/config_file_parser.c +++ b/src/iec61850/server/model/config_file_parser.c @@ -395,8 +395,12 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) if (StringUtils_createBufferFromHexString(nameString, (uint8_t*) nameString2) != 6) goto exit_error; - PhyComAddress_create(currentGoCB, (uint8_t) vlanPrio, (uint16_t) vlanId, (uint16_t) appId, - (uint8_t*) nameString2); + + PhyComAddress* dstAddress = + PhyComAddress_create((uint8_t) vlanPrio, (uint16_t) vlanId, (uint16_t) appId, + (uint8_t*) nameString2); + + GSEControlBlock_addPhyComAddress(currentGoCB, dstAddress); } else diff --git a/src/iec61850/server/model/dynamic_model.c b/src/iec61850/server/model/dynamic_model.c index 09a719e9..206e7b87 100644 --- a/src/iec61850/server/model/dynamic_model.c +++ b/src/iec61850/server/model/dynamic_model.c @@ -327,7 +327,7 @@ GSEControlBlock_create(const char* name, LogicalNode* parent, char* appId, char* else self->dataSetName = NULL; - self->confRef = confRef; + self->confRev = confRef; self->fixedOffs = fixedOffs; self->minTime = minTime; self->maxTime = maxTime; @@ -342,14 +342,47 @@ GSEControlBlock_create(const char* name, LogicalNode* parent, char* appId, char* return self; } -static void +SVControlBlock* +SVControlBlock_create(const char* name, LogicalNode* parent, char* svID, char* dataSet, uint32_t confRev, uint8_t smpMod, + uint16_t smpRate, uint8_t optFlds, bool isUnicast) +{ + SVControlBlock* self = (SVControlBlock*) GLOBAL_MALLOC(sizeof(SVControlBlock)); + + self->name = copyString(name); + self->parent = parent; + + self->svId = copyString(svID); /* Is there a default value? */ + + if (dataSet) + self->dataSetName = copyString(dataSet); + else + self->dataSetName = NULL; + + self->confRev = confRev; + + self->smpMod = smpMod; + self->smpRate = smpRate; + + self->optFlds = optFlds; + self->isUnicast = isUnicast; + + return self; +} + +void +SVControlBlock_addPhyComAddress(SVControlBlock* self, PhyComAddress* phyComAddress) +{ + self->dstAddress = phyComAddress; +} + +void GSEControlBlock_addPhyComAddress(GSEControlBlock* self, PhyComAddress* phyComAddress) { self->address = phyComAddress; } PhyComAddress* -PhyComAddress_create(GSEControlBlock* parent, uint8_t vlanPriority, uint16_t vlanId, uint16_t appId, uint8_t dstAddress[]) +PhyComAddress_create(uint8_t vlanPriority, uint16_t vlanId, uint16_t appId, uint8_t dstAddress[]) { PhyComAddress* self = (PhyComAddress*) GLOBAL_MALLOC(sizeof(PhyComAddress)); @@ -359,9 +392,6 @@ PhyComAddress_create(GSEControlBlock* parent, uint8_t vlanPriority, uint16_t vla memcpy(self->dstAddress, dstAddress, 6); - if (parent != NULL) - GSEControlBlock_addPhyComAddress(parent, self); - return self; } diff --git a/src/vs/libiec61850-wo-goose.def b/src/vs/libiec61850-wo-goose.def index 9b30b7d0..f1cdcb39 100644 --- a/src/vs/libiec61850-wo-goose.def +++ b/src/vs/libiec61850-wo-goose.def @@ -494,4 +494,7 @@ EXPORTS LogicalDevice_getChildByMmsVariableName LogicalNode_getDataSet ClientReport_getDataSetName - MmsValue_getStringSize \ No newline at end of file + MmsValue_getStringSize + SVControlBlock_create + SVControlBlock_addPhyComAddress + GSEControlBlock_addPhyComAddress diff --git a/src/vs/libiec61850.def b/src/vs/libiec61850.def index a88a7fc5..aca168eb 100644 --- a/src/vs/libiec61850.def +++ b/src/vs/libiec61850.def @@ -519,3 +519,6 @@ EXPORTS LogicalNode_getDataSet ClientReport_getDataSetName MmsValue_getStringSize + SVControlBlock_create + SVControlBlock_addPhyComAddress + GSEControlBlock_addPhyComAddress 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 46579aaf..2aceca49 100644 --- a/tools/model_generator/src/com/libiec61850/scl/communication/ConnectedAP.java +++ b/tools/model_generator/src/com/libiec61850/scl/communication/ConnectedAP.java @@ -35,6 +35,7 @@ public class ConnectedAP { private String apName; private List gses; + private List smvs; public ConnectedAP(Node node) throws SclParserException { iedName = ParserUtils.parseAttribute(node, "iedName"); @@ -50,6 +51,14 @@ public class ConnectedAP { for (Node gseNode : gseNodes) { gses.add(new GSE(gseNode)); } + + smvs = new LinkedList(); + + List smvNodes = ParserUtils.getChildNodesWithTag(node, "SMV"); + + for (Node smvNode : smvNodes) { + smvs.add(new SMV(smvNode)); + } } public String getIedName() { @@ -64,7 +73,11 @@ public class ConnectedAP { return gses; } - public GSEAddress lookupGSEAddress(String logicalDeviceName, String name) { + public List getSmvs() { + return smvs; + } + + public PhyComAddress lookupGSEAddress(String logicalDeviceName, String name) { for (GSE gse : this.getGses()) { if (gse.getLdInst().equals(logicalDeviceName)) { @@ -75,5 +88,17 @@ public class ConnectedAP { return null; } + + public PhyComAddress lookupSMVAddress(String logicalDeviceName, String name) { + + for (SMV smv : this.getSmvs()) { + if (smv.getLdInst().equals(logicalDeviceName)) { + if (smv.getCbName().equals(name)) + return smv.getAddress(); + } + } + + return null; + } } diff --git a/tools/model_generator/src/com/libiec61850/scl/communication/GSE.java b/tools/model_generator/src/com/libiec61850/scl/communication/GSE.java index 1bb0427b..eb56e1bf 100644 --- a/tools/model_generator/src/com/libiec61850/scl/communication/GSE.java +++ b/tools/model_generator/src/com/libiec61850/scl/communication/GSE.java @@ -31,7 +31,7 @@ public class GSE { private String ldInst; private String cbName; - private GSEAddress address; + private PhyComAddress address; public GSE(Node gseNode) throws SclParserException { ldInst = ParserUtils.parseAttribute(gseNode, "ldInst"); @@ -45,7 +45,7 @@ public class GSE { if (addressNode == null) throw new SclParserException(gseNode, "GSE is missing address definition!"); - address = new GSEAddress(addressNode); + address = new PhyComAddress(addressNode); } public String getLdInst() { @@ -56,7 +56,7 @@ public class GSE { return cbName; } - public GSEAddress getAddress() { + public PhyComAddress getAddress() { return address; } diff --git a/tools/model_generator/src/com/libiec61850/scl/communication/GSEAddress.java b/tools/model_generator/src/com/libiec61850/scl/communication/PhyComAddress.java similarity index 96% rename from tools/model_generator/src/com/libiec61850/scl/communication/GSEAddress.java rename to tools/model_generator/src/com/libiec61850/scl/communication/PhyComAddress.java index 151033ea..c4ff7e10 100644 --- a/tools/model_generator/src/com/libiec61850/scl/communication/GSEAddress.java +++ b/tools/model_generator/src/com/libiec61850/scl/communication/PhyComAddress.java @@ -29,14 +29,14 @@ import org.w3c.dom.Node; import com.libiec61850.scl.ParserUtils; import com.libiec61850.scl.SclParserException; -public class GSEAddress { +public class PhyComAddress { private Integer vlanId = null; private Integer vlanPriority = null; private Integer appId = null; private int[] macAddress = null; - public GSEAddress(Node addressNode) throws DOMException, SclParserException { + public PhyComAddress(Node addressNode) throws DOMException, SclParserException { List pNodes = ParserUtils.getChildNodesWithTag(addressNode, "P"); diff --git a/tools/model_generator/src/com/libiec61850/scl/communication/SMV.java b/tools/model_generator/src/com/libiec61850/scl/communication/SMV.java new file mode 100644 index 00000000..71012cf8 --- /dev/null +++ b/tools/model_generator/src/com/libiec61850/scl/communication/SMV.java @@ -0,0 +1,61 @@ +package com.libiec61850.scl.communication; + +import org.w3c.dom.Node; + +import com.libiec61850.scl.ParserUtils; +import com.libiec61850.scl.SclParserException; + +/* + * Copyright 2015 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 class SMV { + private String ldInst; + private String cbName; + + private PhyComAddress address; + + public SMV(Node gseNode) throws SclParserException { + ldInst = ParserUtils.parseAttribute(gseNode, "ldInst"); + cbName = ParserUtils.parseAttribute(gseNode, "cbName"); + + if ((ldInst == null) || (cbName == null)) + throw new SclParserException(gseNode, "SMV is missing required attribute"); + + Node addressNode = ParserUtils.getChildNodeWithTag(gseNode, "Address"); + + if (addressNode == null) + throw new SclParserException(gseNode, "SMV is missing address definition!"); + + address = new PhyComAddress(addressNode); + } + + public String getLdInst() { + return ldInst; + } + + public String getCbName() { + return cbName; + } + + public PhyComAddress getAddress() { + return address; + } +} diff --git a/tools/model_generator/src/com/libiec61850/scl/model/GSEControl.java b/tools/model_generator/src/com/libiec61850/scl/model/GSEControl.java index 40ee837e..73309921 100644 --- a/tools/model_generator/src/com/libiec61850/scl/model/GSEControl.java +++ b/tools/model_generator/src/com/libiec61850/scl/model/GSEControl.java @@ -36,40 +36,45 @@ public class GSEControl { private boolean fixedOffs = false; private int minTime = -1; private int maxTime = -1; - + public GSEControl(Node gseControlNode) throws SclParserException { this.name = ParserUtils.parseAttribute(gseControlNode, "name"); this.desc = ParserUtils.parseAttribute(gseControlNode, "desc"); this.dataSet = ParserUtils.parseAttribute(gseControlNode, "datSet"); - - String confRevString = ParserUtils.parseAttribute(gseControlNode, "confRev"); - + + String confRevString = ParserUtils.parseAttribute(gseControlNode, + "confRev"); + if (confRevString != null) this.confRev = new Integer(confRevString); - + this.appID = ParserUtils.parseAttribute(gseControlNode, "appID"); - - Boolean fixedOffs = ParserUtils.parseBooleanAttribute(gseControlNode, "fixedOffs"); - - if (fixedOffs != null) - this.fixedOffs = fixedOffs; - - String minTimeStr = ParserUtils.parseAttribute(gseControlNode, "minTime"); - String maxTimeStr = ParserUtils.parseAttribute(gseControlNode, "maxTime"); - - if (minTimeStr != null) - minTime = new Integer(minTimeStr); - - if (maxTimeStr != null) - maxTime = new Integer(maxTimeStr); - - String typeString = ParserUtils.parseAttribute(gseControlNode, "type"); - - if (typeString != null) - if (!typeString.equals("GOOSE")) - throw new SclParserException(gseControlNode, "GSEControl of type " + typeString + " not supported!"); - + + Boolean fixedOffs = ParserUtils.parseBooleanAttribute(gseControlNode, + "fixedOffs"); + + if (fixedOffs != null) + this.fixedOffs = fixedOffs; + + String minTimeStr = ParserUtils.parseAttribute(gseControlNode, + "minTime"); + String maxTimeStr = ParserUtils.parseAttribute(gseControlNode, + "maxTime"); + + if (minTimeStr != null) + minTime = new Integer(minTimeStr); + + if (maxTimeStr != null) + maxTime = new Integer(maxTimeStr); + + String typeString = ParserUtils.parseAttribute(gseControlNode, "type"); + + if (typeString != null) + if (!typeString.equals("GOOSE")) + throw new SclParserException(gseControlNode, + "GSEControl of type " + typeString + " not supported!"); + } public String getName() { diff --git a/tools/model_generator/src/com/libiec61850/scl/model/LogicalNode.java b/tools/model_generator/src/com/libiec61850/scl/model/LogicalNode.java index 8fa7c58c..5d34675f 100644 --- a/tools/model_generator/src/com/libiec61850/scl/model/LogicalNode.java +++ b/tools/model_generator/src/com/libiec61850/scl/model/LogicalNode.java @@ -47,6 +47,7 @@ public class LogicalNode implements DataModelNode { private List dataSets; private List reportControlBlocks; private List gseControlBlocks; + private List smvControlBlocks; private List logControlBlocks; private List logs; private List settingGroupControlBlocks; @@ -113,6 +114,10 @@ public class LogicalNode implements DataModelNode { for (Node gseControlNode : gseControlNodes) gseControlBlocks.add(new GSEControl(gseControlNode)); + /* Parse Sampled Values (SV) control block definitions */ + smvControlBlocks = new LinkedList(); + + /* Parse log control block definitions */ logControlBlocks = new LinkedList(); @@ -261,6 +266,10 @@ public class LogicalNode implements DataModelNode { return gseControlBlocks; } + public List getSampledValueControlBlocks() { + return smvControlBlocks; + } + public List getSettingGroupControlBlocks() { return settingGroupControlBlocks; } diff --git a/tools/model_generator/src/com/libiec61850/scl/model/SampledValueControl.java b/tools/model_generator/src/com/libiec61850/scl/model/SampledValueControl.java new file mode 100644 index 00000000..77993e77 --- /dev/null +++ b/tools/model_generator/src/com/libiec61850/scl/model/SampledValueControl.java @@ -0,0 +1,89 @@ +package com.libiec61850.scl.model; + +import org.w3c.dom.Node; + +import com.libiec61850.scl.ParserUtils; +import com.libiec61850.scl.SclParserException; + +public class SampledValueControl { + + private String name; + private String desc = null; + private String datSet; + private int confRev = 1; + private String smvID; + private int smpRate; + private int nofASDU; + private boolean multicast; + private SmvOpts smvOpts; + + + + public SampledValueControl(Node smvControlNode) throws SclParserException { + this.name = ParserUtils.parseAttribute(smvControlNode, "name"); + this.desc = ParserUtils.parseAttribute(smvControlNode, "desc"); + this.datSet = ParserUtils.parseAttribute(smvControlNode, "datSet"); + + String confRevString = ParserUtils.parseAttribute(smvControlNode, "confRev"); + + if (confRevString != null) + this.confRev = new Integer(confRevString); + + this.smvID = ParserUtils.parseAttribute(smvControlNode, "smvID"); + + this.multicast = ParserUtils.parseBooleanAttribute(smvControlNode, "multicast"); + + String smpRateString = ParserUtils.parseAttribute(smvControlNode, "smpRate"); + + if (smpRateString != null) + this.smpRate = new Integer(smpRateString); + + String nofASDUString = ParserUtils.parseAttribute(smvControlNode, "nofASDU"); + + if (nofASDUString != null) + this.nofASDU = new Integer(nofASDUString); + + Node smvOptsNode = ParserUtils.getChildNodeWithTag(smvControlNode, "SmvOpts"); + + this.smvOpts = new SmvOpts(smvOptsNode); + } + + + + public String getName() { + return name; + } + + public String getDesc() { + return desc; + } + + public String getDatSet() { + return datSet; + } + + public int getConfRev() { + return confRev; + } + + public String getSmvID() { + return smvID; + } + + public int getSmpRate() { + return smpRate; + } + + public int getNofASDI() { + return nofASDU; + } + + public boolean isMulticast() { + return multicast; + } + + public SmvOpts getSmvOpts() { + return smvOpts; + } + +} diff --git a/tools/model_generator/src/com/libiec61850/scl/model/SmvOpts.java b/tools/model_generator/src/com/libiec61850/scl/model/SmvOpts.java new file mode 100644 index 00000000..1dcd59ad --- /dev/null +++ b/tools/model_generator/src/com/libiec61850/scl/model/SmvOpts.java @@ -0,0 +1,41 @@ +package com.libiec61850.scl.model; + +import org.w3c.dom.Node; + +import com.libiec61850.scl.ParserUtils; +import com.libiec61850.scl.SclParserException; + +public class SmvOpts { + + + private boolean refreshTime = false; + private boolean sampleRate = false; + private boolean dataSet = false; + private boolean security = false; + private boolean sampleSynchronized = false; + + public SmvOpts(Node smvOptsNode) throws SclParserException { + + Boolean refreshTime = ParserUtils.parseBooleanAttribute(smvOptsNode, "refreshTime"); + if (refreshTime != null) + this.refreshTime = refreshTime; + + Boolean sampleRate = ParserUtils.parseBooleanAttribute(smvOptsNode, "sampleRate"); + if (sampleRate != null) + this.sampleRate = sampleRate; + + Boolean dataSet = ParserUtils.parseBooleanAttribute(smvOptsNode, "dataSet"); + if (dataSet != null) + this.dataSet = dataSet; + + Boolean security = ParserUtils.parseBooleanAttribute(smvOptsNode, "security"); + if (security != null) + this.security = security; + + Boolean sampleSynchronized = ParserUtils.parseBooleanAttribute(smvOptsNode, "sampleSynchronized"); + if (sampleSynchronized != null) + this.sampleSynchronized = sampleSynchronized; + + } + +} diff --git a/tools/model_generator/src/com/libiec61850/tools/DynamicModelGenerator.java b/tools/model_generator/src/com/libiec61850/tools/DynamicModelGenerator.java index ac47251c..1297c623 100644 --- a/tools/model_generator/src/com/libiec61850/tools/DynamicModelGenerator.java +++ b/tools/model_generator/src/com/libiec61850/tools/DynamicModelGenerator.java @@ -35,7 +35,7 @@ import com.libiec61850.scl.DataAttributeDefinition; import com.libiec61850.scl.SclParser; import com.libiec61850.scl.SclParserException; import com.libiec61850.scl.communication.ConnectedAP; -import com.libiec61850.scl.communication.GSEAddress; +import com.libiec61850.scl.communication.PhyComAddress; import com.libiec61850.scl.model.AccessPoint; import com.libiec61850.scl.model.DataAttribute; import com.libiec61850.scl.model.DataModelValue; @@ -145,7 +145,7 @@ public class DynamicModelGenerator { for (GSEControl gcb : logicalNode.getGSEControlBlocks()) { LogicalDevice ld = logicalNode.getParentLogicalDevice(); - GSEAddress gseAddress = null; + PhyComAddress gseAddress = null; if (connectedAP != null) gseAddress = connectedAP.lookupGSEAddress(ld.getInst(), gcb.getName()); diff --git a/tools/model_generator/src/com/libiec61850/tools/StaticModelGenerator.java b/tools/model_generator/src/com/libiec61850/tools/StaticModelGenerator.java index b1ed816e..a7e3ff42 100644 --- a/tools/model_generator/src/com/libiec61850/tools/StaticModelGenerator.java +++ b/tools/model_generator/src/com/libiec61850/tools/StaticModelGenerator.java @@ -37,7 +37,7 @@ import com.libiec61850.scl.SclParserException; import com.libiec61850.scl.communication.Communication; import com.libiec61850.scl.communication.ConnectedAP; import com.libiec61850.scl.communication.GSE; -import com.libiec61850.scl.communication.GSEAddress; +import com.libiec61850.scl.communication.PhyComAddress; import com.libiec61850.scl.communication.SubNetwork; import com.libiec61850.scl.model.AccessPoint; import com.libiec61850.scl.model.DataAttribute; @@ -753,7 +753,7 @@ public class StaticModelGenerator { for (GSEControl gseControlBlock : gseControlBlocks) { - GSEAddress gseAddress = connectedAP.lookupGSEAddress(logicalDeviceName, gseControlBlock.getName()); + PhyComAddress gseAddress = connectedAP.lookupGSEAddress(logicalDeviceName, gseControlBlock.getName()); String gseString = "";