- added IEC 61850 client API to query logs

- added client example to read logs
This commit is contained in:
Michael Zillgith 2016-06-09 14:46:14 +02:00
parent 922c5eec50
commit 97fd7524f8
10 changed files with 329 additions and 68 deletions

View file

@ -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)

View file

@ -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
)

View file

@ -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)

View file

@ -0,0 +1,133 @@
/*
* client_example_log.c
*
* This example is intended to be used with server_example_logging
*/
#include "iec61850_client.h"
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
//#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);
}

View file

@ -2250,6 +2250,91 @@ exit_function:
return dataSet;
}
LinkedList /* <MmsJournalEntry> */
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 /* <MmsJournalEntry> */
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)

View file

@ -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 <LD name>/<LN name>$<log name>
* \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 /* <MmsJournalEntry> */
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 <LD name>/<LN name>$<log name>
* \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 /* <MmsJournalEntry> */
IedConnection_queryLogAfter(IedConnection self, IedClientError* error, const char* logReference,
MmsValue* entryID, uint64_t timeStamp, bool* moreFollows);
/** @} */
/**

View file

@ -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)

View file

@ -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;
}

View file

@ -545,3 +545,5 @@ EXPORTS
MmsConnection_readJournalStartAfter
LogControlBlock_create
Log_create
IedConnection_queryLogByTime
IedConnection_queryLogAfter

View file

@ -621,3 +621,6 @@ EXPORTS
MmsConnection_readJournalStartAfter
LogControlBlock_create
Log_create
IedConnection_queryLogByTime
IedConnection_queryLogAfter