diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 9337d695..398e901d 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -18,6 +18,7 @@ add_subdirectory(iec61850_client_example4) add_subdirectory(iec61850_client_example5) add_subdirectory(iec61850_client_example_files) add_subdirectory(iec61850_client_example_reporting) +add_subdirectory(iec61850_client_example_log) add_subdirectory(mms_client_example1) add_subdirectory(mms_client_example2) add_subdirectory(mms_client_example3) diff --git a/examples/iec61850_client_example_log/CMakeLists.txt b/examples/iec61850_client_example_log/CMakeLists.txt new file mode 100644 index 00000000..0322bd61 --- /dev/null +++ b/examples/iec61850_client_example_log/CMakeLists.txt @@ -0,0 +1,17 @@ + +set(iec61850_client_example_log_SRCS + client_example_log.c +) + +IF(MSVC) +set_source_files_properties(${iec61850_client_example_log_SRCS} + PROPERTIES LANGUAGE CXX) +ENDIF(MSVC) + +add_executable(iec61850_client_example_log + ${iec61850_client_example_log_SRCS} +) + +target_link_libraries(iec61850_client_example_log + iec61850 +) diff --git a/examples/iec61850_client_example_log/Makefile b/examples/iec61850_client_example_log/Makefile new file mode 100644 index 00000000..4c379413 --- /dev/null +++ b/examples/iec61850_client_example_log/Makefile @@ -0,0 +1,17 @@ +LIBIEC_HOME=../.. + +PROJECT_BINARY_NAME = client_example_log +PROJECT_SOURCES = client_example_log.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_client_example_log/client_example_log.c b/examples/iec61850_client_example_log/client_example_log.c new file mode 100644 index 00000000..83f8e958 --- /dev/null +++ b/examples/iec61850_client_example_log/client_example_log.c @@ -0,0 +1,133 @@ +/* + * client_example_log.c + * + * This example is intended to be used with server_example_logging + */ + +#include "iec61850_client.h" + +#include +#include +#include + +//#include "hal_thread.h" + +static void +printJournalEntries(LinkedList journalEntries) +{ + char buf[1024]; + + LinkedList journalEntriesElem = LinkedList_getNext(journalEntries); + + while (journalEntriesElem != NULL) { + + MmsJournalEntry journalEntry = (MmsJournalEntry) LinkedList_getData(journalEntriesElem); + + MmsValue_printToBuffer(MmsJournalEntry_getEntryID(journalEntry), buf, 1024); + printf("EntryID: %s\n", buf); + MmsValue_printToBuffer(MmsJournalEntry_getOccurenceTime(journalEntry), buf, 1024); + printf(" occurence time: %s\n", buf); + + LinkedList journalVariableElem = LinkedList_getNext(journalEntry->journalVariables); + + while (journalVariableElem != NULL) { + + MmsJournalVariable journalVariable = (MmsJournalVariable) LinkedList_getData(journalVariableElem); + + printf(" variable-tag: %s\n", MmsJournalVariable_getTag(journalVariable)); + MmsValue_printToBuffer(MmsJournalVariable_getValue(journalVariable), buf, 1024); + printf(" variable-value: %s\n", buf); + + journalVariableElem = LinkedList_getNext(journalVariableElem); + } + + journalEntriesElem = LinkedList_getNext(journalEntriesElem); + } +} + +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]); + + char* logRef = "simpleIOGenericIO/LLN0$EventLog"; + + IedClientError error; + + IedConnection con = IedConnection_create(); + + IedConnection_connect(con, &error, hostname, tcpPort); + + if (error == IED_ERROR_OK) { + + /* read list of logs in LN (optional - if you don't know the existing logs) */ + LinkedList logs = IedConnection_getLogicalNodeDirectory(con, &error, "simpleIOGenericIO/LLN0", ACSI_CLASS_LOG); + + if (error == IED_ERROR_OK) { + printf("Found log in LN simpleIOGenericIO/LLN0:\n"); + + LinkedList log = LinkedList_getNext(logs); + + while (log != NULL) { + char* logName = (char*) LinkedList_getData(log); + + printf(" %s\n", logName); + + log = LinkedList_getNext(log); + } + + LinkedList_destroy(logs); + } + + /* read log control block (using the generic read function) */ + MmsValue* lcbValue = IedConnection_readObject(con, &error, "simpleIOGenericIO/LLN0.EventLog", IEC61850_FC_LG); + + if (error == IED_ERROR_OK) { + + MmsValue* oldEntryTm = MmsValue_getElement(lcbValue, 3); + MmsValue* oldEntry = MmsValue_getElement(lcbValue, 5); + + uint64_t timestamp = MmsValue_getUtcTimeInMs(oldEntryTm); + + bool moreFollows; + + /* + * read the log contents. Be aware that the logRef uses the '$' sign as separator between the LN and + * the log name! This is in contrast to the LCB object reference above. + */ + LinkedList logEntries = IedConnection_queryLogAfter(con, &error, "simpleIOGenericIO/LLN0$EventLog", oldEntry, timestamp, &moreFollows); + + if (error == IED_ERROR_OK) { + printJournalEntries(logEntries); + + LinkedList_destroyDeep(logEntries, (LinkedListValueDeleteFunction) MmsJournalEntry_destroy); + } + else + printf("QueryLog failed (error code: %i)!\n", error); + + //TODO handle moreFollows + + MmsValue_delete(lcbValue); + } + else + printf("Read LCB failed!\n"); + + + IedConnection_abort(con, &error); + } + else { + printf("Failed to connect to %s:%i\n", hostname, tcpPort); + } + + IedConnection_destroy(con); +} + + diff --git a/src/iec61850/client/ied_connection.c b/src/iec61850/client/ied_connection.c index bf1782f4..da745847 100644 --- a/src/iec61850/client/ied_connection.c +++ b/src/iec61850/client/ied_connection.c @@ -2250,6 +2250,91 @@ exit_function: return dataSet; } +LinkedList /* */ +IedConnection_queryLogByTime(IedConnection self, IedClientError* error, const char* logReference, + uint64_t startTime, uint64_t endTime, bool* moreFollows) +{ + MmsError mmsError; + + char logRef[130]; + + strncpy(logRef, logReference, 129); + + char* logDomain = logRef; + char* logName = strchr(logRef, '/'); + + if (logName != NULL) { + + logName[0] = 0; + logName++; + + MmsValue* startTimeMms = MmsValue_newBinaryTime(false); + MmsValue_setBinaryTime(startTimeMms, startTime); + + MmsValue* endTimeMms = MmsValue_newBinaryTime(false); + MmsValue_setBinaryTime(endTimeMms, endTime); + + LinkedList journalEntries = MmsConnection_readJournalTimeRange(self->connection, &mmsError, logDomain, logName, + startTimeMms, endTimeMms, moreFollows); + + MmsValue_delete(startTimeMms); + MmsValue_delete(endTimeMms); + + if (mmsError != MMS_ERROR_NONE) { + *error = iedConnection_mapMmsErrorToIedError(mmsError); + return NULL; + } + else + return journalEntries; + } + else { + *error = IED_ERROR_OBJECT_REFERENCE_INVALID; + return NULL; + } + +} + + +LinkedList /* */ +IedConnection_queryLogAfter(IedConnection self, IedClientError* error, const char* logReference, + MmsValue* entryID, uint64_t timeStamp, bool* moreFollows) +{ + MmsError mmsError; + + char logRef[130]; + + strncpy(logRef, logReference, 129); + + char* logDomain = logRef; + char* logName = strchr(logRef, '/'); + + if (logName != NULL) { + + logName[0] = 0; + logName++; + + MmsValue* timeStampMms = MmsValue_newBinaryTime(false); + MmsValue_setBinaryTime(timeStampMms, timeStamp); + + LinkedList journalEntries = MmsConnection_readJournalStartAfter(self->connection, &mmsError, logDomain, logName, + timeStampMms, entryID, moreFollows); + + MmsValue_delete(timeStampMms); + + if (mmsError != MMS_ERROR_NONE) { + *error = iedConnection_mapMmsErrorToIedError(mmsError); + return NULL; + } + else + return journalEntries; + } + else { + *error = IED_ERROR_OBJECT_REFERENCE_INVALID; + return NULL; + } +} + + MmsConnection IedConnection_getMmsConnection(IedConnection self) diff --git a/src/iec61850/inc/iec61850_client.h b/src/iec61850/inc/iec61850_client.h index 76ec2c26..47d46180 100644 --- a/src/iec61850/inc/iec61850_client.h +++ b/src/iec61850/inc/iec61850_client.h @@ -1747,6 +1747,58 @@ MmsVariableSpecification* IedConnection_getVariableSpecification(IedConnection self, IedClientError* error, const char* dataAttributeReference, FunctionalConstraint fc); +/** @} */ + +/** + * @defgroup IEC61850_CLIENT_LOG_SERVICE Log service related functions, data types, and definitions + * + * @{ + */ + +/** + * \brief Implementation of the QueryLogByTime ACSI service + * + * Read log entries from a log at the server. The log entries to read are specified by + * a starting time and an end time. If the complete range does not fit in a single MMS message + * the moreFollows flag will be set to true, to indicate that more entries are available for the + * specified time range. + * + * \param self the connection object + * \param error the error code if an error occurs + * \param logReference log object reference in the form /$ + * \param startTime as millisecond UTC timestamp + * \param endTime as millisecond UTC timestamp + * \param moreFollows (output value) indicates that more entries are available that match the specification. + * + * \return list of MmsJournalEntry objects matching the specification + */ +LinkedList /* */ +IedConnection_queryLogByTime(IedConnection self, IedClientError* error, const char* logReference, + uint64_t startTime, uint64_t endTime, bool* moreFollows); + +/** + * \brief Implementation of the QueryLogAfter ACSI service + * + * Read log entries from a log at the server following the entry with the specified entryID and timestamp. + * If the complete range does not fit in a single MMS message + * the moreFollows flag will be set to true, to indicate that more entries are available for the + * specified time range. + * + * \param self the connection object + * \param error the error code if an error occurs + * \param logReference log object reference in the form /$ + * \param entryID usually the entryID of the last received entry + * \param timeStamp as millisecond UTC timestamp + * \param moreFollows (output value) indicates that more entries are available that match the specification. + * + * \return list of MmsJournalEntry objects matching the specification + */ +LinkedList /* */ +IedConnection_queryLogAfter(IedConnection self, IedClientError* error, const char* logReference, + MmsValue* entryID, uint64_t timeStamp, bool* moreFollows); + + + /** @} */ /** diff --git a/src/mms/iso_mms/client/mms_client_journals.c b/src/mms/iso_mms/client/mms_client_journals.c index f09535a3..919e2c57 100644 --- a/src/mms/iso_mms/client/mms_client_journals.c +++ b/src/mms/iso_mms/client/mms_client_journals.c @@ -320,7 +320,8 @@ mmsClient_parseReadJournalResponse(MmsConnection self, bool* moreFollows, Linked tag = buffer[bufPos++]; - *moreFollows = false; + if (moreFollows) + *moreFollows = false; if (tag != 0x41) { if (DEBUG_MMS_CLIENT) @@ -355,7 +356,8 @@ mmsClient_parseReadJournalResponse(MmsConnection self, bool* moreFollows, Linked break; case 0x81: /* moreFollows */ - *moreFollows = BerDecoder_decodeBoolean(buffer, bufPos); + if (moreFollows) + *moreFollows = BerDecoder_decodeBoolean(buffer, bufPos); break; default: @@ -373,51 +375,6 @@ mmsClient_parseReadJournalResponse(MmsConnection self, bool* moreFollows, Linked return true; } -#if 0 -void -mmsClient_createReadJournalRequest(uint32_t invokeId, ByteBuffer* request, const char* domainId, const char* itemId) -{ - /* calculate sizes */ - uint32_t invokeIdSize = BerEncoder_UInt32determineEncodedSize(invokeId); - - uint32_t domainIdStringSize = strlen(domainId); - uint32_t domainIdSize = 1 + BerEncoder_determineLengthSize(domainIdStringSize) + domainIdStringSize; - - uint32_t itemIdStringSize = strlen(itemId); - uint32_t itemIdSize = 1 + BerEncoder_determineLengthSize(itemIdStringSize) + itemIdStringSize; - - uint32_t objectIdSize = domainIdSize + itemIdSize; - - uint32_t journalNameSize = 1 + BerEncoder_determineLengthSize(objectIdSize) + (objectIdSize); - - uint32_t journalReadSize = 1 + BerEncoder_determineLengthSize(journalNameSize) + (journalNameSize); - - uint32_t confirmedRequestPduSize = 1 + 2 + 2 + invokeIdSize + journalReadSize; - - /* encode to buffer */ - int bufPos = 0; - uint8_t* buffer = request->buffer; - - bufPos = BerEncoder_encodeTL(0xa0, confirmedRequestPduSize, buffer, bufPos); - bufPos = BerEncoder_encodeTL(0x02, invokeIdSize, buffer, bufPos); - bufPos = BerEncoder_encodeUInt32(invokeId, buffer, bufPos); - - /* Encode read journal tag (context | structured ) [65 = 41h] */ - buffer[bufPos++] = 0xbf; - buffer[bufPos++] = 0x41; - - bufPos = BerEncoder_encodeLength(journalReadSize, buffer, bufPos); - bufPos = BerEncoder_encodeTL(0xa0, journalNameSize, buffer, bufPos); - - bufPos = BerEncoder_encodeTL(0xa1, objectIdSize, buffer, bufPos); - - bufPos = BerEncoder_encodeOctetString(0x1a, (uint8_t*) domainId, domainIdStringSize, buffer, bufPos); - bufPos = BerEncoder_encodeOctetString(0x1a, (uint8_t*) itemId, itemIdStringSize, buffer, bufPos); - - request->size = bufPos; -} -#endif - void mmsClient_createReadJournalRequestWithTimeRange(uint32_t invokeId, ByteBuffer* request, const char* domainId, const char* itemId, MmsValue* startingTime, MmsValue* endingTime) diff --git a/src/mms/iso_mms/server/mms_journal_service.c b/src/mms/iso_mms/server/mms_journal_service.c index c055f5e4..74d69a77 100644 --- a/src/mms/iso_mms/server/mms_journal_service.c +++ b/src/mms/iso_mms/server/mms_journal_service.c @@ -69,10 +69,8 @@ entryCallback(void* parameter, uint64_t timestamp, uint64_t entryID, bool moreFo if (moreFollow) { - if (encoder->moreFollows) { - printf("entryCallback return false\n"); + if (encoder->moreFollows) return false; - } encoder->currentEntryBufPos = encoder->bufPos; @@ -80,10 +78,6 @@ entryCallback(void* parameter, uint64_t timestamp, uint64_t entryID, bool moreFo encoder->currentEntryId = entryID; encoder->currentTimestamp = timestamp; - - } - else { - printf("Encoded last entry: FINISHED\n"); } return true; @@ -319,9 +313,6 @@ mmsServer_handleReadJournalRequest( memcpy(rangeStart.value.binaryTime.buf, requestBuffer + bufPos, length); - char stringBuf[100]; - MmsValue_printToBuffer(&rangeStart, stringBuf, 100); - hasRangeStartSpec = true; } else { @@ -350,9 +341,6 @@ mmsServer_handleReadJournalRequest( memcpy(rangeStop.value.binaryTime.buf, requestBuffer + bufPos, length); - char stringBuf[100]; - MmsValue_printToBuffer(&rangeStop, stringBuf, 100); - hasRangeStopSpec = true; } else { @@ -382,9 +370,6 @@ mmsServer_handleReadJournalRequest( memcpy(rangeStart.value.binaryTime.buf, requestBuffer + bufPos, length); - char stringBuf[100]; - MmsValue_printToBuffer(&rangeStart, stringBuf, 100); - hasTimeSpec = true; } else { @@ -428,7 +413,9 @@ mmsServer_handleReadJournalRequest( //TODO check if required fields are present if (hasNames == false) { - printf("MMS_SERVER: readJournal missing journal name\n"); + if (DEBUG_MMS_SERVER) + printf("MMS_SERVER: readJournal missing journal name\n"); + mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response); return; } @@ -441,7 +428,9 @@ mmsServer_handleReadJournalRequest( MmsDomain* mmsDomain = MmsDevice_getDomain(mmsDevice, domainId); if (mmsDomain == NULL) { - printf("Domain %s not found\n", domainId); + if (DEBUG_MMS_SERVER) + printf("MMS_SERVER: readJournal domain %s not found\n", domainId); + mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response); return; } @@ -449,12 +438,15 @@ mmsServer_handleReadJournalRequest( MmsJournal mmsJournal = MmsDomain_getJournal(mmsDomain, logName); if (mmsJournal == NULL) { - printf("Journal %s not found\n", logName); + if (DEBUG_MMS_SERVER) + printf("MMS_SERVER: readJournal journal %s not found\n", logName); + mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response); return; } - printf("Read journal %s ...\n", mmsJournal->name); + if (DEBUG_MMS_SERVER) + printf("MMS_SERVER: readJournal - read journal %s ...\n", mmsJournal->name); struct sJournalEncoder encoder; @@ -476,7 +468,9 @@ mmsServer_handleReadJournalRequest( entryCallback, entryDataCallback, &encoder); } else { - printf("MMS_SERVER: readJournal missing valid argument combination\n"); + if (DEBUG_MMS_SERVER) + printf("MMS_SERVER: readJournal missing valid argument combination\n"); + mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response); return; } diff --git a/src/vs/libiec61850-wo-goose.def b/src/vs/libiec61850-wo-goose.def index 72f07795..b1b511f5 100644 --- a/src/vs/libiec61850-wo-goose.def +++ b/src/vs/libiec61850-wo-goose.def @@ -545,3 +545,5 @@ EXPORTS MmsConnection_readJournalStartAfter LogControlBlock_create Log_create + IedConnection_queryLogByTime + IedConnection_queryLogAfter diff --git a/src/vs/libiec61850.def b/src/vs/libiec61850.def index 94415198..26594733 100644 --- a/src/vs/libiec61850.def +++ b/src/vs/libiec61850.def @@ -621,3 +621,6 @@ EXPORTS MmsConnection_readJournalStartAfter LogControlBlock_create Log_create + IedConnection_queryLogByTime + IedConnection_queryLogAfter +