diff --git a/examples/iec61850_sv_client_example/sv_client_example.c b/examples/iec61850_sv_client_example/sv_client_example.c index 7da1e81c..1a09384b 100644 --- a/examples/iec61850_sv_client_example/sv_client_example.c +++ b/examples/iec61850_sv_client_example/sv_client_example.c @@ -57,6 +57,35 @@ int main(int argc, char** argv) { free(msvID); } + char* datSetName = ClientSVControlBlock_getDatSet(svcb); + + if (datSetName != NULL) { + printf("DatSet: %s\n", datSetName); + free(datSetName); + } + + printf("ConfRev: %i\n", ClientSVControlBlock_getConfRev(svcb)); + printf("SmpRate: %i\n", ClientSVControlBlock_getSmpRate(svcb)); + printf("SmpMod: %i\n", ClientSVControlBlock_getSmpMod(svcb)); + + int optFlds = ClientSVControlBlock_getOptFlds(svcb); + + printf("OptFlds: "); + if (optFlds & IEC61850_SV_OPT_REFRESH_TIME) + printf("refresh-time "); + if (optFlds & IEC61850_SV_OPT_SAMPLE_SYNC) + printf("sample-synch "); + if (optFlds & IEC61850_SV_OPT_SAMPLE_RATE) + printf("sample-rate "); + if (optFlds & IEC61850_SV_OPT_DATA_SET) + printf("date-set "); + if (optFlds & IEC61850_SV_OPT_SECURITY) + printf("security "); + printf("\n"); + + printf("noASDU: %i\n", ClientSVControlBlock_getNoASDU(svcb)); + + } else { printf("SVCB %s does not exist on server!\n", svcbRef); diff --git a/src/iec61850/inc/iec61850_client.h b/src/iec61850/inc/iec61850_client.h index 4b9e397c..027c928d 100644 --- a/src/iec61850/inc/iec61850_client.h +++ b/src/iec61850/inc/iec61850_client.h @@ -312,9 +312,31 @@ IedConnection_getMmsConnection(IedConnection self); * @{ */ +/** SV ASDU contains attribute RefrTm */ +#define IEC61850_SV_OPT_REFRESH_TIME 1 + +/** SV ASDU contains attribute SmpSynch */ +#define IEC61850_SV_OPT_SAMPLE_SYNC 2 + +/** SV ASDU contains attribute SmpRate */ +#define IEC61850_SV_OPT_SAMPLE_RATE 4 + +/** SV ASDU contains attribute DatSet */ +#define IEC61850_SV_OPT_DATA_SET 8 + +/** SV ASDU contains attribute Security */ +#define IEC61850_SV_OPT_SECURITY 16 + /** an opaque handle to the instance data of a ClientSVControlBlock object */ typedef struct sClientSVControlBlock* ClientSVControlBlock; +typedef struct { + uint8_t addr[6]; + uint8_t priority; + uint16_t vid; + uint16_t appId; +} DstAddress; + /** * \brief Create a new ClientSVControlBlock instance * @@ -340,15 +362,83 @@ ClientSVControlBlock_destroy(ClientSVControlBlock self); bool ClientSVControlBlock_isMulticast(ClientSVControlBlock self); +/** + * \brief Return the error code of the last write or write acccess to the SVCB + * + * \param self the ClientSVControlBlock instance to operate on + * + * \return the error code of the last read or write access + */ +IedClientError +ClientSVControlBlock_getLastError(ClientSVControlBlock self); + + bool ClientSVControlBlock_setSvEna(ClientSVControlBlock self, bool svEna); bool ClientSVControlBlock_getSvEna(ClientSVControlBlock self); +bool +ClientSVControlBlock_setResv(ClientSVControlBlock self, bool svEna); + char* ClientSVControlBlock_getMsvID(ClientSVControlBlock self); +/** + * \brief Get the (MMS) reference to the data set + * + * NOTE: the returned string is dynamically allocated with the + * GLOBAL_MALLOC macro. The application is responsible to release + * the memory when the string is no longer needed. + * + * \param self the ClientSVControlBlock instance to operate on + * + * \return the data set reference as a NULL terminated string + */ +char* +ClientSVControlBlock_getDatSet(ClientSVControlBlock self); + +uint32_t +ClientSVControlBlock_getConfRev(ClientSVControlBlock self); + +uint16_t +ClientSVControlBlock_getSmpRate(ClientSVControlBlock self); + + +/** + * \brief returns the destination address of the SV publisher + * + * \param self the ClientSVControlBlock instance to operate on + */ +DstAddress +ClientSVControlBlock_getDstAddress(ClientSVControlBlock self); + +/** + * \brief returns the OptFlds bit string as integer + * + * \param self the ClientSVControlBlock instance to operate on + */ +int +ClientSVControlBlock_getOptFlds(ClientSVControlBlock self); + +/** + * \brief returns number of sample mode of the SV publisher + * + * \param self the ClientSVControlBlock instance to operate on + */ +uint8_t +ClientSVControlBlock_getSmpMod(ClientSVControlBlock self); + +/** + * \brief returns number of ASDUs included in the SV message + * + * \param self the ClientSVControlBlock instance to operate on + */ +int +ClientSVControlBlock_getNoASDU(ClientSVControlBlock self); + + /** @} */ /** diff --git a/src/iec61850/server/mms_mapping/mms_sv.c b/src/iec61850/server/mms_mapping/mms_sv.c index 29ddefd3..49ca4817 100644 --- a/src/iec61850/server/mms_mapping/mms_sv.c +++ b/src/iec61850/server/mms_mapping/mms_sv.c @@ -358,7 +358,11 @@ createSVControlBlockMmsStructure(char* gcbName, bool isUnicast) return gcb; } - +static void +createDataSetReference(char* buffer, char* domainName, char* lnName, char* dataSetName) +{ + StringUtils_createStringInBuffer(buffer, 5, domainName, "/", lnName, "$", dataSetName); +} MmsVariableSpecification* LIBIEC61850_SV_createSVControlBlocks(MmsMapping* self, MmsDomain* domain, @@ -380,6 +384,8 @@ LIBIEC61850_SV_createSVControlBlocks(MmsMapping* self, MmsDomain* domain, int currentSVCB = 0; + char dataRefBuffer[130]; + while (currentSVCB < svCount) { SVControlBlock* svControlBlock = getSVCBForLogicalNodeWithIndex( self, logicalNode, currentSVCB, unicast); @@ -409,7 +415,11 @@ LIBIEC61850_SV_createSVControlBlocks(MmsMapping* self, MmsDomain* domain, /* DatSet */ MmsValue* dataSetRef = MmsValue_getElement(svValues, currentIndex++); - MmsValue_setVisibleString(dataSetRef, svControlBlock->dataSetName); + + createDataSetReference(dataRefBuffer, MmsDomain_getName(domain), + logicalNode->name, svControlBlock->dataSetName); + + MmsValue_setVisibleString(dataSetRef, dataRefBuffer); /* ConfRev */ MmsValue* confRev = MmsValue_getElement(svValues, currentIndex++); diff --git a/src/mms/inc/mms_value.h b/src/mms/inc/mms_value.h index 0a8aeb8a..235cbb62 100644 --- a/src/mms/inc/mms_value.h +++ b/src/mms/inc/mms_value.h @@ -935,6 +935,18 @@ MmsValue_getSubElement(MmsValue* self, MmsVariableSpecification* varSpec, char* char* MmsValue_getTypeString(MmsValue* self); +/** + * \brief create a string representation of the MmsValue object in the provided buffer + * + * NOTE: This function is for debugging purposes only. It may not be aimed to be used + * in embedded systems. It requires a full featured snprintf function. + * + * \param self the MmsValue instance + * \param buffer the buffer where to copy the string representation + * \param bufferSize the size of the provided buffer + * + * \return a pointer to the start of the buffer + */ char* MmsValue_printToBuffer(MmsValue* self, char* buffer, int bufferSize); diff --git a/src/mms/iso_mms/common/mms_value.c b/src/mms/iso_mms/common/mms_value.c index 8c9492a4..9ecc61c2 100644 --- a/src/mms/iso_mms/common/mms_value.c +++ b/src/mms/iso_mms/common/mms_value.c @@ -1,7 +1,7 @@ /* * MmsValue.c * - * Copyright 2013 Michael Zillgith + * Copyright 2013-2015 Michael Zillgith * * This file is part of libIEC61850. * @@ -1968,6 +1968,22 @@ MmsValue_getTypeString(MmsValue* self) } } + +static void +msTimeToGeneralizedTime(uint64_t msTime, uint8_t* buffer, size_t bufferSize) +{ + time_t unixTime = (msTime / 1000); + + struct tm tmTime; + + int msPart = (msTime % 1000); + gmtime_r(&unixTime, &tmTime); + + snprintf((char*) buffer, bufferSize, "%04d%02d%02d%02d%02d%02d.%03dZ", tmTime.tm_year + 1900, tmTime.tm_mon + 1, + tmTime.tm_mday, tmTime.tm_hour, tmTime.tm_min, tmTime.tm_sec, msPart); +} + + char* MmsValue_printToBuffer(MmsValue* self, char* buffer, int bufferSize) { @@ -1995,19 +2011,32 @@ MmsValue_printToBuffer(MmsValue* self, char* buffer, int bufferSize) } } - buffer[bufPos++] = '}'; - buffer[bufPos] = 0; + if (bufPos < (bufferSize - 1)) { + buffer[bufPos++] = '}'; + buffer[bufPos] = 0; + } + else + buffer[bufferSize - 1] = 0; + } break; + case MMS_BINARY_TIME: - Conversions_msTimeToGeneralizedTime(MmsValue_getBinaryTimeAsUtcMs(self), (uint8_t*) buffer); + msTimeToGeneralizedTime(MmsValue_getBinaryTimeAsUtcMs(self), (uint8_t*) buffer, bufferSize); break; + case MMS_BIT_STRING: { int bufPos = 0; int size = MmsValue_getBitStringSize(self); + /* Behave like strncpy and fill buffer with zeros */ + if (size > bufferSize) { + memset(buffer, 0, bufferSize); + break; + } + int i; for (i = 0; i < size; i++) { if (MmsValue_getBitStringBit(self, i)) @@ -2018,24 +2047,40 @@ MmsValue_printToBuffer(MmsValue* self, char* buffer, int bufferSize) buffer[bufPos] = 0; } break; + case MMS_BOOLEAN: if (MmsValue_getBoolean(self)) strncpy(buffer, "true", bufferSize); else strncpy(buffer, "false", bufferSize); + + /* Ensure buffer is always 0 terminated */ + if (bufferSize > 0) + buffer[bufferSize - 1] = 0; + break; + case MMS_DATA_ACCESS_ERROR: snprintf(buffer, bufferSize, "error %i", self->value.dataAccessError); break; + case MMS_FLOAT: snprintf(buffer, bufferSize, "%f", MmsValue_toFloat(self)); break; - case MMS_GENERALIZED_TIME: + + case MMS_GENERALIZED_TIME: /* type not supported */ strncpy(buffer, "generalized time", bufferSize); + + /* Ensure buffer is always 0 terminated */ + if (bufferSize > 0) + buffer[bufferSize - 1] = 0; + break; + case MMS_INTEGER: snprintf(buffer, bufferSize, "%i", MmsValue_toInt32(self)); break; + case MMS_OCTET_STRING: { int size = MmsValue_getOctetStringSize(self); @@ -2049,20 +2094,33 @@ MmsValue_printToBuffer(MmsValue* self, char* buffer, int bufferSize) break; } } - break; + case MMS_UNSIGNED: snprintf(buffer, bufferSize, "%u", MmsValue_toUint32(self)); break; + case MMS_UTC_TIME: - Conversions_msTimeToGeneralizedTime(MmsValue_getUtcTimeInMs(self), (uint8_t*) buffer); + msTimeToGeneralizedTime(MmsValue_getUtcTimeInMs(self), (uint8_t*) buffer, bufferSize); break; + case MMS_STRING: case MMS_VISIBLE_STRING: strncpy(buffer, MmsValue_toString(self), bufferSize); + + /* Ensure buffer is always 0 terminated */ + if (bufferSize > 0) + buffer[bufferSize - 1] = 0; + break; + default: strncpy(buffer, "unknown type", bufferSize); + + /* Ensure buffer is always 0 terminated */ + if (bufferSize > 0) + buffer[bufferSize - 1] = 0; + break; }