diff --git a/examples/iec61850_sv_client_example/CMakeLists.txt b/examples/iec61850_sv_client_example/CMakeLists.txt new file mode 100644 index 00000000..1b9b1334 --- /dev/null +++ b/examples/iec61850_sv_client_example/CMakeLists.txt @@ -0,0 +1,17 @@ + +set(iec61850_sv_client_example_SRCS + sv_client_example.c +) + +IF(WIN32) +set_source_files_properties(${iec61850_sv_client_example_SRCS} + PROPERTIES LANGUAGE CXX) +ENDIF(WIN32) + +add_executable(iec61850_sv_client_example + ${iec61850_sv_client_example_SRCS} +) + +target_link_libraries(iec61850_sv_client_example + iec61850 +) diff --git a/examples/iec61850_sv_client_example/Makefile b/examples/iec61850_sv_client_example/Makefile new file mode 100644 index 00000000..39f73461 --- /dev/null +++ b/examples/iec61850_sv_client_example/Makefile @@ -0,0 +1,17 @@ +LIBIEC_HOME=../.. + +PROJECT_BINARY_NAME = sv_client_example +PROJECT_SOURCES = sv_client_example.c + +include $(LIBIEC_HOME)/make/target_system.mk +include $(LIBIEC_HOME)/make/stack_includes.mk + +all: $(PROJECT_BINARY_NAME) + +include $(LIBIEC_HOME)/make/common_targets.mk + +$(PROJECT_BINARY_NAME): $(PROJECT_SOURCES) $(LIB_NAME) + $(CC) $(CFLAGS) $(LDFLAGS) -o $(PROJECT_BINARY_NAME) $(PROJECT_SOURCES) $(INCLUDES) $(LIB_NAME) $(LDLIBS) + +clean: + rm -f $(PROJECT_BINARY_NAME) diff --git a/examples/iec61850_sv_client_example/sv_client_example.c b/examples/iec61850_sv_client_example/sv_client_example.c new file mode 100644 index 00000000..7da1e81c --- /dev/null +++ b/examples/iec61850_sv_client_example/sv_client_example.c @@ -0,0 +1,74 @@ +/* + * sv_client_example.c + * + * This example is intended to show how SV control blocks are accessed + */ + +#include "iec61850_client.h" + +#include +#include + +#include "hal_thread.h" + + +int main(int argc, char** argv) { + + char* hostname; + int tcpPort = 102; + + if (argc > 1) + hostname = argv[1]; + else + hostname = "localhost"; + + if (argc > 2) + tcpPort = atoi(argv[2]); + + IedClientError error; + + IedConnection con = IedConnection_create(); + + IedConnection_connect(con, &error, hostname, tcpPort); + + if (error == IED_ERROR_OK) { + + char* svcbRef = "simpleIOGenericIO/LLN0.Volt"; + + ClientSVControlBlock svcb = ClientSVControlBlock_create(con, svcbRef); + + if (svcb != NULL) { + if (ClientSVControlBlock_isMulticast(svcb)) + printf("SVCB is multicast\n"); + else + printf("SVCB is unicast\n"); + + if (ClientSVControlBlock_setSvEna(svcb, true)) + printf("SVCB enabled\n"); + else + printf("Failed to enable SVCB\n"); + + printf("SvEna state: %i\n", ClientSVControlBlock_getSvEna(svcb)); + + char* msvID = ClientSVControlBlock_getMsvID(svcb); + + if (msvID != NULL) { + printf("MsvID: %s\n", msvID); + free(msvID); + } + + } + else { + printf("SVCB %s does not exist on server!\n", svcbRef); + } + + IedConnection_close(con); + } + else { + printf("Failed to connect to %s:%i\n", hostname, tcpPort); + } + + IedConnection_destroy(con); +} + + diff --git a/src/iec61850/common/iec61850_common.c b/src/iec61850/common/iec61850_common.c index 53f8194b..4a35256c 100644 --- a/src/iec61850/common/iec61850_common.c +++ b/src/iec61850/common/iec61850_common.c @@ -125,6 +125,10 @@ FunctionalConstraint_toString(FunctionalConstraint fc) { return "EX"; case IEC61850_FC_CO: return "CO"; + case IEC61850_FC_US: + return "US"; + case IEC61850_FC_MS: + return "MS"; default: return NULL; } diff --git a/src/iec61850/inc/iec61850_client.h b/src/iec61850/inc/iec61850_client.h index f0b86f81..4b9e397c 100644 --- a/src/iec61850/inc/iec61850_client.h +++ b/src/iec61850/inc/iec61850_client.h @@ -1,7 +1,7 @@ /* * iec61850_client.h * - * Copyright 2013, 2014 Michael Zillgith + * Copyright 2013, 2014, 2015 Michael Zillgith * * This file is part of libIEC61850. * @@ -306,6 +306,51 @@ IedConnection_getMmsConnection(IedConnection self); /** @} */ +/** + * @defgroup IEC61850_CLIENT_SV Client side SV control block handling functions + * + * @{ + */ + +/** an opaque handle to the instance data of a ClientSVControlBlock object */ +typedef struct sClientSVControlBlock* ClientSVControlBlock; + +/** + * \brief Create a new ClientSVControlBlock instance + * + * This function simplifies client side access to server MSV/USV control blocks + * NOTE: Do not use the functions after the IedConnection object is invalidated! + * + * \param connection the IedConnection object with a valid connection to the server. + * \param reference the object reference of the control block + * + * \return the new instance + */ +ClientSVControlBlock +ClientSVControlBlock_create(IedConnection connection, const char* reference); + +/** + * \brief Free all resources related to the ClientSVControlBlock instance. + * + * \param self the ClientSVControlBlock instance to operate on + */ +void +ClientSVControlBlock_destroy(ClientSVControlBlock self); + +bool +ClientSVControlBlock_isMulticast(ClientSVControlBlock self); + +bool +ClientSVControlBlock_setSvEna(ClientSVControlBlock self, bool svEna); + +bool +ClientSVControlBlock_getSvEna(ClientSVControlBlock self); + +char* +ClientSVControlBlock_getMsvID(ClientSVControlBlock self); + +/** @} */ + /** * @defgroup IEC61850_CLIENT_GOOSE Client side GOOSE control block handling functions * diff --git a/src/iec61850/inc/iec61850_common.h b/src/iec61850/inc/iec61850_common.h index c3d3bc65..0ff91892 100644 --- a/src/iec61850/inc/iec61850_common.h +++ b/src/iec61850/inc/iec61850_common.h @@ -217,6 +217,11 @@ typedef enum eFunctionalConstraint { IEC61850_FC_EX = 11, /** Control */ IEC61850_FC_CO = 12, + /** Unicast SV */ + IEC61850_FC_US = 13, + /** Multicast SV */ + IEC61850_FC_MS = 14, + IEC61850_FC_ALL = 99, IEC61850_FC_NONE = -1 } FunctionalConstraint; diff --git a/src/iec61850/inc_private/mms_sv.h b/src/iec61850/inc_private/mms_sv.h index 6f59a374..f868d75f 100644 --- a/src/iec61850/inc_private/mms_sv.h +++ b/src/iec61850/inc_private/mms_sv.h @@ -40,4 +40,8 @@ LIBIEC61850_SV_createSVControlBlocks(MmsMapping* self, MmsDomain* domain, MmsValue* LIBIEC61850_SV_readAccessSampledValueControlBlock(MmsMapping* self, MmsDomain* domain, char* variableIdOrig); +MmsDataAccessError +LIBIEC61850_SV_writeAccessSVControlBlock(MmsMapping* self, MmsDomain* domain, char* variableIdOrig, + MmsValue* value, MmsServerConnection connection); + #endif /* LIBIEC61850_SRC_IEC61850_INC_PRIVATE_MMS_SV_H_ */ diff --git a/src/iec61850/server/mms_mapping/mms_mapping.c b/src/iec61850/server/mms_mapping/mms_mapping.c index 7e873698..5acf1e79 100644 --- a/src/iec61850/server/mms_mapping/mms_mapping.c +++ b/src/iec61850/server/mms_mapping/mms_mapping.c @@ -1691,17 +1691,19 @@ mmsWriteHandler(void* parameter, MmsDomain* domain, #endif /* (CONFIG_IEC61850_CONTROL_SERVICE == 1) */ #if (CONFIG_INCLUDE_GOOSE_SUPPORT == 1) + /* Goose control block - GO */ - if (isGooseControlBlock(separator)) { + if (isGooseControlBlock(separator)) return writeAccessGooseControlBlock(self, domain, variableId, value); - } + #endif /* (CONFIG_INCLUDE_GOOSE_SUPPORT == 1) */ #if (CONFIG_IEC61850_SAMPLED_VALUES_SUPPORT == 1) + /* Sampled Value control block - MS/US */ - if (isSampledValueControlBlock(separator)) { - //TODO handle write access to SVCB - } + if (isSampledValueControlBlock(separator)) + return LIBIEC61850_SV_writeAccessSVControlBlock(self, domain, variableId, value, connection); + #endif /* (CONFIG_IEC61850_SAMPLED_VALUES_SUPPORT == 1) */ diff --git a/src/iec61850/server/mms_mapping/mms_sv.c b/src/iec61850/server/mms_mapping/mms_sv.c index c4f1c1ce..29ddefd3 100644 --- a/src/iec61850/server/mms_mapping/mms_sv.c +++ b/src/iec61850/server/mms_mapping/mms_sv.c @@ -36,7 +36,9 @@ struct sMmsSampledValueControlBlock { char* name; + bool svEna; + MmsServerConnection reservedByClient; char* dstAddress; @@ -46,7 +48,8 @@ struct sMmsSampledValueControlBlock { MmsVariableSpecification* mmsType; MmsValue* mmsValue; - //MmsMapping* mmsMapping; + MmsValue* svEnaValue; + MmsValue* resvValue; }; MmsSampledValueControlBlock @@ -88,6 +91,109 @@ lookupSVCB(MmsMapping* self, MmsDomain* domain, char* lnName, char* objectName) return NULL; } +static void +MmsSampledValueControlBlock_enable(MmsSampledValueControlBlock self) +{ + //TODO call application callback handler + self->svEna = true; + MmsValue_setBoolean(self->svEnaValue, true); +} + +static void +MmsSampledValueControlBlock_disable(MmsSampledValueControlBlock self) +{ + //TODO call application callback handler + self->svEna = false; + MmsValue_setBoolean(self->svEnaValue, false); +} + +static bool +MmsSampledValueControlBlock_isEnabled(MmsSampledValueControlBlock self) +{ + return self->svEna; +} + +MmsDataAccessError +LIBIEC61850_SV_writeAccessSVControlBlock(MmsMapping* self, MmsDomain* domain, char* variableIdOrig, + MmsValue* value, MmsServerConnection connection) +{ + char variableId[130]; + + strncpy(variableId, variableIdOrig, 129); + + char* separator = strchr(variableId, '$'); + + *separator = 0; + + char* lnName = variableId; + + if (lnName == NULL) + return DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; + + char* objectName = MmsMapping_getNextNameElement(separator + 1); + + if (objectName == NULL) + return DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; + + char* varName = MmsMapping_getNextNameElement(objectName); + + if (varName != NULL) + *(varName - 1) = 0; + else + return DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; + + MmsSampledValueControlBlock mmsSVCB = lookupSVCB(self, domain, lnName, objectName); + + if (mmsSVCB == NULL) + return DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; + + if (mmsSVCB->reservedByClient != NULL) { + if (mmsSVCB->reservedByClient != connection) + return DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE; + } + + if (strcmp(varName, "Resv") == 0) { + if (MmsValue_getType(value) != MMS_BOOLEAN) + return DATA_ACCESS_ERROR_TYPE_INCONSISTENT; + + if (MmsValue_getBoolean(value)) { + mmsSVCB->reservedByClient = connection; + MmsValue_setBoolean(mmsSVCB->resvValue, true); + } + else { + mmsSVCB->reservedByClient = NULL; + MmsValue_setBoolean(mmsSVCB->resvValue, false); + } + + return DATA_ACCESS_ERROR_SUCCESS; + } + else if (strcmp(varName, "SvEna") == 0) { + if (MmsValue_getType(value) != MMS_BOOLEAN) + return DATA_ACCESS_ERROR_TYPE_INCONSISTENT; + + if (MmsValue_getBoolean(value)) + MmsSampledValueControlBlock_enable(mmsSVCB); + else + MmsSampledValueControlBlock_disable(mmsSVCB); + + return DATA_ACCESS_ERROR_SUCCESS; + } + else { + if (MmsSampledValueControlBlock_isEnabled(mmsSVCB)) + return DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE; + else { + bool allowAccess = false; + + // In 61850-9-2 mapping only Resv and SvEna are writable! + + if (allowAccess) + return DATA_ACCESS_ERROR_SUCCESS; + else + return DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; + + } + } +} MmsValue* LIBIEC61850_SV_readAccessSampledValueControlBlock(MmsMapping* self, MmsDomain* domain, char* variableIdOrig) @@ -284,12 +390,18 @@ LIBIEC61850_SV_createSVControlBlocks(MmsMapping* self, MmsDomain* domain, namedVariable->typeSpec.structure.elements[currentSVCB] = svTypeSpec; - int currentIndex; + int currentIndex = 0; + + /* SvEna */ + MmsValue* svEna = MmsValue_getElement(svValues, currentIndex++); + + MmsValue* resv = NULL; + + if (unicast) { + /* Resv */ + resv = MmsValue_getElement(svValues, currentIndex++); + } - if (unicast) - currentIndex = 2; - else - currentIndex = 1; /* SvID */ MmsValue* svID = MmsValue_getElement(svValues, currentIndex++); @@ -353,6 +465,8 @@ LIBIEC61850_SV_createSVControlBlocks(MmsMapping* self, MmsDomain* domain, MmsSampledValueControlBlock mmsSvCb = MmsSampledValueControlBlock_create(); mmsSvCb->mmsValue = svValues; + mmsSvCb->svEnaValue = svEna; + mmsSvCb->resvValue = resv; mmsSvCb->mmsType = svTypeSpec; mmsSvCb->domain = domain; mmsSvCb->logicalNode = logicalNode;