- added SV subscriber and example
parent
c3dace4150
commit
c0174f9f38
@ -0,0 +1,34 @@
|
|||||||
|
|
||||||
|
set(sv_subscriber_example_SRCS
|
||||||
|
sv_subscriber_example.c
|
||||||
|
)
|
||||||
|
|
||||||
|
IF(WIN32)
|
||||||
|
|
||||||
|
IF(WITH_WPCAP)
|
||||||
|
|
||||||
|
set_source_files_properties(${sv_subscriber_example_SRCS}
|
||||||
|
PROPERTIES LANGUAGE CXX)
|
||||||
|
add_executable(sv_subscriber_example
|
||||||
|
${sv_subscriber_example_SRCS}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(svsubscriber_example
|
||||||
|
iec61850
|
||||||
|
)
|
||||||
|
|
||||||
|
ENDIF(WITH_WPCAP)
|
||||||
|
|
||||||
|
ELSE(WIN32)
|
||||||
|
|
||||||
|
add_executable(sv_subscriber_example
|
||||||
|
${sv_subscriber_example_SRCS}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(sv_subscriber_example
|
||||||
|
iec61850
|
||||||
|
)
|
||||||
|
|
||||||
|
ENDIF(WIN32)
|
||||||
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
|||||||
|
LIBIEC_HOME=../../../libiec61850
|
||||||
|
|
||||||
|
PROJECT_BINARY_NAME = sv_subscriber
|
||||||
|
PROJECT_SOURCES += sv_subscriber_example.c
|
||||||
|
|
||||||
|
INCLUDES += -I.
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
* subscriber_test.c
|
||||||
|
*
|
||||||
|
* Example program for Sampled Values (SV) subscriber
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "hal_thread.h"
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include "sv_subscriber.h"
|
||||||
|
|
||||||
|
|
||||||
|
static bool running = true;
|
||||||
|
|
||||||
|
void sigint_handler(int signalId)
|
||||||
|
{
|
||||||
|
running = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Callback handler for received SV messages */
|
||||||
|
static void
|
||||||
|
svUpdateListener (SVSubscriber subscriber, void* parameter, SVClientASDU asdu)
|
||||||
|
{
|
||||||
|
printf("svUpdateListener called\n");
|
||||||
|
|
||||||
|
const char* svID = SVClientASDU_getSvId(asdu);
|
||||||
|
|
||||||
|
if (svID != NULL)
|
||||||
|
printf(" svID=(%s)\n", svID);
|
||||||
|
|
||||||
|
printf(" smpCnt: %i\n", SVClientASDU_getSmpCnt(asdu));
|
||||||
|
printf(" confRev: %u\n", SVClientASDU_getConfRev(asdu));
|
||||||
|
|
||||||
|
if (SVClientASDU_getDataSize(asdu) >= 8) {
|
||||||
|
printf(" DATA[0]: %f\n", SVClientASDU_getFLOAT32(asdu, 0));
|
||||||
|
printf(" DATA[1]: %f\n", SVClientASDU_getFLOAT32(asdu, 4));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
SVReceiver receiver = SVReceiver_create();
|
||||||
|
|
||||||
|
if (argc > 1) {
|
||||||
|
printf("Set interface id: %s\n", argv[1]);
|
||||||
|
SVReceiver_setInterfaceId(receiver, argv[1]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
printf("Using interface eth0\n");
|
||||||
|
SVReceiver_setInterfaceId(receiver, "eth0");
|
||||||
|
}
|
||||||
|
|
||||||
|
SVSubscriber subscriber = SVSubscriber_create(NULL, 0x4000);
|
||||||
|
|
||||||
|
SVSubscriber_setListener(subscriber, svUpdateListener, NULL);
|
||||||
|
|
||||||
|
SVReceiver_addSubscriber(receiver, subscriber);
|
||||||
|
|
||||||
|
SVReceiver_start(receiver);
|
||||||
|
|
||||||
|
signal(SIGINT, sigint_handler);
|
||||||
|
|
||||||
|
while (running) {
|
||||||
|
Thread_sleep(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
SVReceiver_stop(receiver);
|
||||||
|
|
||||||
|
SVReceiver_destroy(receiver);
|
||||||
|
}
|
@ -0,0 +1,621 @@
|
|||||||
|
/*
|
||||||
|
* sv_receiver.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 <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* See COPYING file for the complete license text.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "stack_config.h"
|
||||||
|
|
||||||
|
#include "libiec61850_platform_includes.h"
|
||||||
|
|
||||||
|
#include "hal_ethernet.h"
|
||||||
|
#include "hal_thread.h"
|
||||||
|
#include "ber_decode.h"
|
||||||
|
#include "ber_encoder.h"
|
||||||
|
|
||||||
|
#include "sv_subscriber.h"
|
||||||
|
|
||||||
|
#ifndef DEBUG_SV_SUBSCRIBER
|
||||||
|
#define DEBUG_SV_SUBSCRIBER 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define ETH_BUFFER_LENGTH 1518
|
||||||
|
|
||||||
|
#define ETH_P_SV 0x88ba
|
||||||
|
|
||||||
|
struct sSVReceiver {
|
||||||
|
bool running;
|
||||||
|
bool stopped;
|
||||||
|
|
||||||
|
bool checkDestAddr; /* option: check destination address (additionally to AppID) to identify application */
|
||||||
|
|
||||||
|
char* interfaceId;
|
||||||
|
|
||||||
|
uint8_t* buffer;
|
||||||
|
EthernetSocket ethSocket;
|
||||||
|
LinkedList subscriberList;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct sSVSubscriber {
|
||||||
|
uint8_t ethAddr[6];
|
||||||
|
uint16_t appId;
|
||||||
|
|
||||||
|
SVUpdateListener listener;
|
||||||
|
void* listenerParameter;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct sSVClientASDU {
|
||||||
|
|
||||||
|
char* svId;
|
||||||
|
|
||||||
|
uint8_t* smpCnt;
|
||||||
|
uint8_t* confRev;
|
||||||
|
uint8_t* smpSynch;
|
||||||
|
|
||||||
|
|
||||||
|
int dataBufferLength;
|
||||||
|
uint8_t* dataBuffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
SVReceiver
|
||||||
|
SVReceiver_create(void)
|
||||||
|
{
|
||||||
|
SVReceiver self = (SVReceiver) GLOBAL_MALLOC(sizeof(struct sSVReceiver));
|
||||||
|
|
||||||
|
if (self != NULL) {
|
||||||
|
self->subscriberList = LinkedList_create();
|
||||||
|
self->buffer = (uint8_t*) GLOBAL_MALLOC(ETH_BUFFER_LENGTH);
|
||||||
|
|
||||||
|
self->checkDestAddr = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
SVReceiver_setInterfaceId(SVReceiver self, const char* interfaceId)
|
||||||
|
{
|
||||||
|
if (self->interfaceId != NULL)
|
||||||
|
GLOBAL_FREEMEM(self->interfaceId);
|
||||||
|
|
||||||
|
self->interfaceId = copyString(interfaceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
SVReceiver_disableDestAddrCheck(SVReceiver self)
|
||||||
|
{
|
||||||
|
self->checkDestAddr = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
SVReceiver_addSubscriber(SVReceiver self, SVSubscriber subscriber)
|
||||||
|
{
|
||||||
|
LinkedList_add(self->subscriberList, (void*) subscriber);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
SVReceiver_removeSubscriber(SVReceiver self, SVSubscriber subscriber)
|
||||||
|
{
|
||||||
|
LinkedList_remove(self->subscriberList, (void*) subscriber);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
svReceiverLoop(void* threadParameter)
|
||||||
|
{
|
||||||
|
SVReceiver self = (SVReceiver) threadParameter;
|
||||||
|
|
||||||
|
self->running = true;
|
||||||
|
self->stopped = false;
|
||||||
|
|
||||||
|
SVReceiver_startThreadless(self);
|
||||||
|
|
||||||
|
while (self->running) {
|
||||||
|
|
||||||
|
if (SVReceiver_tick(self) == false)
|
||||||
|
Thread_sleep(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
SVReceiver_stopThreadless(self);
|
||||||
|
|
||||||
|
self->stopped = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
SVReceiver_start(SVReceiver self)
|
||||||
|
{
|
||||||
|
Thread thread = Thread_create((ThreadExecutionFunction) svReceiverLoop, (void*) self, true);
|
||||||
|
|
||||||
|
if (thread != NULL) {
|
||||||
|
if (DEBUG_SV_SUBSCRIBER)
|
||||||
|
printf("SV_SUBSCRIBER: SV receiver started for interface %s\n", self->interfaceId);
|
||||||
|
|
||||||
|
Thread_start(thread);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (DEBUG_SV_SUBSCRIBER)
|
||||||
|
printf("SV_SUBSCRIBER: Starting SV receiver failed for interface %s\n", self->interfaceId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
SVReceiver_stop(SVReceiver self)
|
||||||
|
{
|
||||||
|
self->running = false;
|
||||||
|
|
||||||
|
while (self->stopped == false)
|
||||||
|
Thread_sleep(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
SVReceiver_destroy(SVReceiver self)
|
||||||
|
{
|
||||||
|
LinkedList_destroyDeep(self->subscriberList,
|
||||||
|
(LinkedListValueDeleteFunction) SVSubscriber_destroy);
|
||||||
|
|
||||||
|
GLOBAL_FREEMEM(self->buffer);
|
||||||
|
GLOBAL_FREEMEM(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
SVReceiver_startThreadless(SVReceiver self)
|
||||||
|
{
|
||||||
|
if (self->interfaceId == NULL)
|
||||||
|
self->ethSocket = Ethernet_createSocket(CONFIG_ETHERNET_INTERFACE_ID, NULL);
|
||||||
|
else
|
||||||
|
self->ethSocket = Ethernet_createSocket(self->interfaceId, NULL);
|
||||||
|
|
||||||
|
Ethernet_setProtocolFilter(self->ethSocket, ETH_P_SV);
|
||||||
|
|
||||||
|
self->running = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
SVReceiver_stopThreadless(SVReceiver self)
|
||||||
|
{
|
||||||
|
Ethernet_destroySocket(self->ethSocket);
|
||||||
|
|
||||||
|
self->running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
parseASDU(SVReceiver self, SVSubscriber subscriber, uint8_t* buffer, int length)
|
||||||
|
{
|
||||||
|
int bufPos = 0;
|
||||||
|
|
||||||
|
struct sSVClientASDU asdu;
|
||||||
|
memset(&asdu, 0, sizeof(struct sSVClientASDU));
|
||||||
|
|
||||||
|
int svIdLength = 0;
|
||||||
|
|
||||||
|
|
||||||
|
while (bufPos < length) {
|
||||||
|
int elementLength;
|
||||||
|
|
||||||
|
uint8_t tag = buffer[bufPos++];
|
||||||
|
|
||||||
|
bufPos = BerDecoder_decodeLength(buffer, &elementLength, bufPos, length);
|
||||||
|
|
||||||
|
switch (tag) {
|
||||||
|
|
||||||
|
case 0x80:
|
||||||
|
asdu.svId = (char*) (buffer + bufPos);
|
||||||
|
svIdLength = elementLength;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x82:
|
||||||
|
asdu.smpCnt = buffer + bufPos;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x83:
|
||||||
|
asdu.confRev = buffer + bufPos;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x85:
|
||||||
|
asdu.smpSynch = buffer + bufPos;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x87:
|
||||||
|
asdu.dataBuffer = buffer + bufPos;
|
||||||
|
asdu.dataBufferLength = elementLength;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: /* ignore unknown tag */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
bufPos += elementLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (asdu.svId != NULL)
|
||||||
|
asdu.svId[svIdLength] = 0;
|
||||||
|
|
||||||
|
/* Call callback handler */
|
||||||
|
if (subscriber->listener != NULL)
|
||||||
|
subscriber->listener(subscriber, subscriber->listenerParameter, &asdu);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
parseSequenceOfASDU(SVReceiver self, SVSubscriber subscriber, uint8_t* buffer, int length)
|
||||||
|
{
|
||||||
|
int bufPos = 0;
|
||||||
|
|
||||||
|
while (bufPos < length) {
|
||||||
|
int elementLength;
|
||||||
|
|
||||||
|
uint8_t tag = buffer[bufPos++];
|
||||||
|
|
||||||
|
bufPos = BerDecoder_decodeLength(buffer, &elementLength, bufPos, length);
|
||||||
|
|
||||||
|
switch (tag) {
|
||||||
|
case 0x30:
|
||||||
|
parseASDU(self, subscriber, buffer + bufPos, elementLength);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: /* ignore unknown tag */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
bufPos += elementLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
parseSVPayload(SVReceiver self, SVSubscriber subscriber, uint8_t* buffer, int apduLength)
|
||||||
|
{
|
||||||
|
int bufPos = 0;
|
||||||
|
|
||||||
|
if (buffer[bufPos++] == 0x60) {
|
||||||
|
int elementLength;
|
||||||
|
|
||||||
|
bufPos = BerDecoder_decodeLength(buffer, &elementLength, bufPos, apduLength);
|
||||||
|
|
||||||
|
int svEnd = bufPos + elementLength;
|
||||||
|
|
||||||
|
while (bufPos < svEnd) {
|
||||||
|
uint8_t tag = buffer[bufPos++];
|
||||||
|
|
||||||
|
bufPos = BerDecoder_decodeLength(buffer, &elementLength, bufPos, svEnd);
|
||||||
|
|
||||||
|
if (bufPos + elementLength > apduLength) {
|
||||||
|
if (DEBUG_SV_SUBSCRIBER)
|
||||||
|
printf("SV_SUBSCRIBER: Malformed message: sub element is too large!\n");
|
||||||
|
|
||||||
|
goto exit_error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bufPos == -1)
|
||||||
|
goto exit_error;
|
||||||
|
|
||||||
|
switch(tag) {
|
||||||
|
case 0x80: /* noASDU (INTEGER) */
|
||||||
|
/* ignore */
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0xa2: /* asdu (SEQUENCE) */
|
||||||
|
parseSequenceOfASDU(self, subscriber, buffer + bufPos, elementLength);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: /* ignore unknown tag */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bufPos += elementLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
exit_error:
|
||||||
|
if (DEBUG_SV_SUBSCRIBER)
|
||||||
|
printf("SV_SUBSCRIBER: Invalid SV message!\n");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
parseSVMessage(SVReceiver self, int numbytes)
|
||||||
|
{
|
||||||
|
int bufPos;
|
||||||
|
bool subscriberFound = false;
|
||||||
|
uint8_t* buffer = self->buffer;
|
||||||
|
|
||||||
|
if (numbytes < 22) return;
|
||||||
|
|
||||||
|
/* Ethernet source address */
|
||||||
|
uint8_t* srcAddr = buffer;
|
||||||
|
|
||||||
|
/* skip ethernet addresses */
|
||||||
|
bufPos = 12;
|
||||||
|
int headerLength = 14;
|
||||||
|
|
||||||
|
/* check for VLAN tag */
|
||||||
|
if ((buffer[bufPos] == 0x81) && (buffer[bufPos + 1] == 0x00)) {
|
||||||
|
bufPos += 4; /* skip VLAN tag */
|
||||||
|
headerLength += 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check for SV Ethertype */
|
||||||
|
if (buffer[bufPos++] != 0x88)
|
||||||
|
return;
|
||||||
|
if (buffer[bufPos++] != 0xba)
|
||||||
|
return;
|
||||||
|
|
||||||
|
uint16_t appId;
|
||||||
|
|
||||||
|
appId = buffer[bufPos++] * 0x100;
|
||||||
|
appId += buffer[bufPos++];
|
||||||
|
|
||||||
|
uint16_t length;
|
||||||
|
|
||||||
|
length = buffer[bufPos++] * 0x100;
|
||||||
|
length += buffer[bufPos++];
|
||||||
|
|
||||||
|
/* skip reserved fields */
|
||||||
|
bufPos += 4;
|
||||||
|
|
||||||
|
int apduLength = length - 8;
|
||||||
|
|
||||||
|
if (numbytes < length + headerLength) {
|
||||||
|
if (DEBUG_SV_SUBSCRIBER)
|
||||||
|
printf("SV_SUBSCRIBER: Invalid PDU size\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DEBUG_SV_SUBSCRIBER) {
|
||||||
|
printf("SV_SUBSCRIBER: SV message: ----------------\n");
|
||||||
|
printf("SV_SUBSCRIBER: APPID: %u\n", appId);
|
||||||
|
printf("SV_SUBSCRIBER: LENGTH: %u\n", length);
|
||||||
|
printf("SV_SUBSCRIBER: APDU length: %i\n", apduLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* check if there is a matching subscriber */
|
||||||
|
LinkedList element = LinkedList_getNext(self->subscriberList);
|
||||||
|
|
||||||
|
SVSubscriber subscriber;
|
||||||
|
|
||||||
|
while (element != NULL) {
|
||||||
|
subscriber = (SVSubscriber) LinkedList_getData(element);
|
||||||
|
|
||||||
|
if (subscriber->appId == appId) {
|
||||||
|
|
||||||
|
if (self->checkDestAddr) {
|
||||||
|
if (memcmp(srcAddr, subscriber->ethAddr, 6) == 0) {
|
||||||
|
subscriberFound = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if (DEBUG_SV_SUBSCRIBER)
|
||||||
|
printf("SV_SUBSCRIBER: Checking ethernet src address failed!\n");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
subscriberFound = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
element = LinkedList_getNext(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (subscriberFound)
|
||||||
|
parseSVPayload(self, subscriber, buffer + bufPos, apduLength);
|
||||||
|
else {
|
||||||
|
if (DEBUG_SV_SUBSCRIBER)
|
||||||
|
printf("SV_SUBSCRIBER: SV message ignored due to unknown APPID value\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
SVReceiver_tick(SVReceiver self)
|
||||||
|
{
|
||||||
|
int packetSize = Ethernet_receivePacket(self->ethSocket, self->buffer, ETH_BUFFER_LENGTH);
|
||||||
|
|
||||||
|
if (packetSize > 0) {
|
||||||
|
parseSVMessage(self, packetSize);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SVSubscriber
|
||||||
|
SVSubscriber_create(const uint8_t* ethAddr, uint16_t appID)
|
||||||
|
{
|
||||||
|
SVSubscriber self = (SVSubscriber) GLOBAL_CALLOC(1, sizeof(struct sSVSubscriber));
|
||||||
|
|
||||||
|
if (self != NULL) {
|
||||||
|
self->appId = appID;
|
||||||
|
|
||||||
|
if (ethAddr != NULL)
|
||||||
|
memcpy(self->ethAddr, ethAddr, 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
SVSubscriber_destroy(SVSubscriber self)
|
||||||
|
{
|
||||||
|
if (self != NULL)
|
||||||
|
GLOBAL_FREEMEM(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
SVSubscriber_setListener(SVSubscriber self, SVUpdateListener listener, void* parameter)
|
||||||
|
{
|
||||||
|
self->listener = listener;
|
||||||
|
self->listenerParameter = parameter;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t
|
||||||
|
SVClientASDU_getSmpCnt(SVClientASDU self)
|
||||||
|
{
|
||||||
|
uint16_t retVal;
|
||||||
|
uint8_t* valBytes = (uint8_t*) &retVal;
|
||||||
|
|
||||||
|
#if (ORDER_LITTLE_ENDIAN == 1)
|
||||||
|
valBytes[0] = self->smpCnt[1];
|
||||||
|
valBytes[1] = self->smpCnt[0];
|
||||||
|
#else
|
||||||
|
valBytes[0] = self->smpCnt[0];
|
||||||
|
valBytes[1] = self->smpCnt[1];
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char*
|
||||||
|
SVClientASDU_getSvId(SVClientASDU self)
|
||||||
|
{
|
||||||
|
return self->svId;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t
|
||||||
|
SVClientASDU_getConfRev(SVClientASDU self)
|
||||||
|
{
|
||||||
|
uint32_t retVal = *((uint32_t*) (self->confRev));
|
||||||
|
|
||||||
|
#if (ORDER_LITTLE_ENDIAN == 1)
|
||||||
|
uint8_t* buf = (uint8_t*) (&retVal);
|
||||||
|
|
||||||
|
BerEncoder_revertByteOrder(buf, 4);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
int8_t
|
||||||
|
SVClientASDU_getINT8(SVClientASDU self, int index)
|
||||||
|
{
|
||||||
|
int8_t retVal = *((int8_t*) (self->dataBuffer + index));
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
int16_t
|
||||||
|
SVClientASDU_getINT16(SVClientASDU self, int index)
|
||||||
|
{
|
||||||
|
int16_t retVal = *((int16_t*) (self->dataBuffer + index));
|
||||||
|
|
||||||
|
#if (ORDER_LITTLE_ENDIAN == 1)
|
||||||
|
uint8_t* buf = (uint8_t*) (&retVal);
|
||||||
|
|
||||||
|
BerEncoder_revertByteOrder(buf, 2);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t
|
||||||
|
SVClientASDU_getINT32(SVClientASDU self, int index)
|
||||||
|
{
|
||||||
|
int32_t retVal = *((int32_t*) (self->dataBuffer + index));
|
||||||
|
|
||||||
|
#if (ORDER_LITTLE_ENDIAN == 1)
|
||||||
|
uint8_t* buf = (uint8_t*) (&retVal);
|
||||||
|
|
||||||
|
BerEncoder_revertByteOrder(buf, 4);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t
|
||||||
|
SVClientASDU_getINT8U(SVClientASDU self, int index)
|
||||||
|
{
|
||||||
|
uint8_t retVal = *((uint8_t*) (self->dataBuffer + index));
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t
|
||||||
|
SVClientASDU_getINT16U(SVClientASDU self, int index)
|
||||||
|
{
|
||||||
|
uint16_t retVal = *((uint16_t*) (self->dataBuffer + index));
|
||||||
|
|
||||||
|
#if (ORDER_LITTLE_ENDIAN == 1)
|
||||||
|
uint8_t* buf = (uint8_t*) (&retVal);
|
||||||
|
|
||||||
|
BerEncoder_revertByteOrder(buf, 2);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t
|
||||||
|
SVClientASDU_getINT32U(SVClientASDU self, int index)
|
||||||
|
{
|
||||||
|
uint32_t retVal = *((uint32_t*) (self->dataBuffer + index));
|
||||||
|
|
||||||
|
#if (ORDER_LITTLE_ENDIAN == 1)
|
||||||
|
uint8_t* buf = (uint8_t*) (&retVal);
|
||||||
|
|
||||||
|
BerEncoder_revertByteOrder(buf, 4);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
float
|
||||||
|
SVClientASDU_getFLOAT32(SVClientASDU self, int index)
|
||||||
|
{
|
||||||
|
float retVal = *((float*) (self->dataBuffer + index));
|
||||||
|
|
||||||
|
#if (ORDER_LITTLE_ENDIAN == 1)
|
||||||
|
uint8_t* buf = (uint8_t*) (&retVal);
|
||||||
|
|
||||||
|
BerEncoder_revertByteOrder(buf, 4);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
double
|
||||||
|
SVClientASDU_getFLOAT64(SVClientASDU self, int index)
|
||||||
|
{
|
||||||
|
double retVal = *((double*) (self->dataBuffer + index));
|
||||||
|
|
||||||
|
#if (ORDER_LITTLE_ENDIAN == 1)
|
||||||
|
uint8_t* buf = (uint8_t*) (&retVal);
|
||||||
|
|
||||||
|
BerEncoder_revertByteOrder(buf, 8);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int
|
||||||
|
SVClientASDU_getDataSize(SVClientASDU self)
|
||||||
|
{
|
||||||
|
return self->dataBufferLength;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,326 @@
|
|||||||
|
/*
|
||||||
|
* sv_subscriber_api.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 <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* See COPYING file for the complete license text.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SAMPLED_VALUES_SV_SUBSCRIBER_H_
|
||||||
|
#define SAMPLED_VALUES_SV_SUBSCRIBER_H_
|
||||||
|
|
||||||
|
#include "libiec61850_common_api.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief opaque handle to a SV ASDU (Application service data unit) instance.
|
||||||
|
*
|
||||||
|
* Sampled Values (SV) ASDUs (application service data units) are the basic units for
|
||||||
|
* sampled value data. Each ASDU represents a single sample consisting of multiple measurement
|
||||||
|
* values with a single dedicated timestamp.
|
||||||
|
*
|
||||||
|
* NOTE: SVClientASDU are statically allocated and are only valid inside of the SVUpdateListener
|
||||||
|
* function when called by the library. If you need the data contained in the ASDU elsewhere
|
||||||
|
* you have to copy and store the data by yourself!
|
||||||
|
*/
|
||||||
|
typedef struct sSVClientASDU* SVClientASDU;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief opaque handle to a SV subscriber instance
|
||||||
|
*
|
||||||
|
* A subscriber is an instance associated with a single stream of measurement data. It is identified
|
||||||
|
* by the Ethernet destination address, the appID value (both are on SV message level) and the svID value
|
||||||
|
* that is part of each ASDU (SVClientASDU object).
|
||||||
|
*/
|
||||||
|
typedef struct sSVSubscriber* SVSubscriber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Callback function for received SV messages
|
||||||
|
*
|
||||||
|
* Will be called for each ASDU contained in a SV message!
|
||||||
|
*
|
||||||
|
* \param subscriber the subscriber that was associated with the received SV message
|
||||||
|
* \param parameter a user provided parameter that is simply passed to the callback
|
||||||
|
* \param asdu SV ASDU data structure. This structure is only valid inside of the callback function
|
||||||
|
*/
|
||||||
|
typedef void (*SVUpdateListener)(SVSubscriber subscriber, void* parameter, SVClientASDU asdu);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief opaque handle to a SV receiver instance
|
||||||
|
*/
|
||||||
|
typedef struct sSVReceiver* SVReceiver;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Create a new SV receiver instance.
|
||||||
|
*
|
||||||
|
* A receiver is responsible for processing all SV message for a single Ethernet interface.
|
||||||
|
* In order to process messages from multiple Ethernet interfaces you have to create multiple
|
||||||
|
* instances.
|
||||||
|
*
|
||||||
|
* \return the newly created receiver instance
|
||||||
|
*/
|
||||||
|
SVReceiver
|
||||||
|
SVReceiver_create(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Disable check for destination address of the received SV messages
|
||||||
|
*
|
||||||
|
* Per default both the appID and the destination address are checked to identify
|
||||||
|
* relevant SV messages. Destination address check can be disabled for performance
|
||||||
|
* reason when the appIDs are unique in the local system.
|
||||||
|
*
|
||||||
|
* \param self the receiver instance reference
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
SVReceiver_disableDestAddrCheck(SVReceiver self);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Set the Ethernet interface ID for the receiver instance
|
||||||
|
*
|
||||||
|
* Use this function if you want to use a different interface than
|
||||||
|
* the default interface set by CONFIG_ETHERNET_INTERFACE_ID (stack_config.h)
|
||||||
|
* NOTE: This function has to be called before calling SVReceiver_start.
|
||||||
|
*
|
||||||
|
* \param self the receiver instance reference
|
||||||
|
* \param interfaceId the Ethernet interface id (platform specific e.g. eth0 for linux).
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
SVReceiver_setInterfaceId(SVReceiver self, const char* interfaceId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Add a subscriber instance to the receiver
|
||||||
|
*
|
||||||
|
* The given subscriber will be connected to the receiver instance.
|
||||||
|
*
|
||||||
|
* \param self the receiver instance reference
|
||||||
|
* \param subscriber the subscriber instance to connect
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
SVReceiver_addSubscriber(SVReceiver self, SVSubscriber subscriber);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Disconnect subscriber and receiver
|
||||||
|
*
|
||||||
|
* \param self the receiver instance reference
|
||||||
|
* \param subscriber the subscriber instance to disconnect
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
SVReceiver_removeSubscriber(SVReceiver self, SVSubscriber subscriber);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Receiver starts listening for SV messages.
|
||||||
|
*
|
||||||
|
* NOTE: This call will start a new background thread.
|
||||||
|
*
|
||||||
|
* \param self the receiver instance reference
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
SVReceiver_start(SVReceiver self);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Receiver stops listening for SV messages
|
||||||
|
*
|
||||||
|
* \param self the receiver instance reference
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
SVReceiver_stop(SVReceiver self);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Destroy receiver instance (cleanup resources)
|
||||||
|
*
|
||||||
|
* \param self the receiver instance reference
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
SVReceiver_destroy(SVReceiver self);
|
||||||
|
|
||||||
|
/***************************************
|
||||||
|
* Functions for non-threaded operation
|
||||||
|
***************************************/
|
||||||
|
|
||||||
|
void
|
||||||
|
SVReceiver_startThreadless(SVReceiver self);
|
||||||
|
|
||||||
|
void
|
||||||
|
SVReceiver_stopThreadless(SVReceiver self);
|
||||||
|
|
||||||
|
bool
|
||||||
|
SVReceiver_tick(SVReceiver self);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Subscriber
|
||||||
|
*/
|
||||||
|
|
||||||
|
SVSubscriber
|
||||||
|
SVSubscriber_create(const uint8_t* ethAddr, uint16_t appID);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Set a callback handler to process received SV messages
|
||||||
|
*
|
||||||
|
* If the received SV message contains multiple ASDUs (application service data units) the callback
|
||||||
|
* function will be called for each ASDU separately. If a callback function has already been installed
|
||||||
|
* for this SVSubscriber object the old callback will be replaced.
|
||||||
|
*
|
||||||
|
* \param self The subscriber object
|
||||||
|
* \param listener the callback function to install
|
||||||
|
* \param a user provided parameter that is provided to the callback function
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
SVSubscriber_setListener(SVSubscriber self, SVUpdateListener listener, void* parameter);
|
||||||
|
|
||||||
|
void
|
||||||
|
SVSubscriber_destroy(SVSubscriber self);
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
* SVClientASDU object methods
|
||||||
|
**************************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief return the SmpCnt value included in the SV ASDU
|
||||||
|
*
|
||||||
|
* The SmpCnt (sample counter) is increased for each ASDU to
|
||||||
|
* identify the sample.
|
||||||
|
*
|
||||||
|
* \param self ASDU object instance
|
||||||
|
*/
|
||||||
|
uint16_t
|
||||||
|
SVClientASDU_getSmpCnt(SVClientASDU self);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief return the SvID value included in the SV ASDU
|
||||||
|
*
|
||||||
|
* \param self ASDU object instance
|
||||||
|
*/
|
||||||
|
const char*
|
||||||
|
SVClientASDU_getSvId(SVClientASDU self);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief return the ConfRev value included in the SV ASDU
|
||||||
|
*
|
||||||
|
* \param self ASDU object instance
|
||||||
|
*/
|
||||||
|
uint32_t
|
||||||
|
SVClientASDU_getConfRev(SVClientASDU self);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Get an INT8 data value in the data part of the ASDU
|
||||||
|
*
|
||||||
|
* \param self ASDU object instance
|
||||||
|
* \param index the index (byte position of the start) of the data in the data part
|
||||||
|
*
|
||||||
|
* \return SV data
|
||||||
|
*/
|
||||||
|
int8_t
|
||||||
|
SVClientASDU_getINT8(SVClientASDU self, int index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Get an INT16 data value in the data part of the ASDU
|
||||||
|
*
|
||||||
|
* \param self ASDU object instance
|
||||||
|
* \param index the index (byte position of the start) of the data in the data part
|
||||||
|
*
|
||||||
|
* \return SV data
|
||||||
|
*/
|
||||||
|
int16_t
|
||||||
|
SVClientASDU_getINT16(SVClientASDU self, int index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Get an INT32 data value in the data part of the ASDU
|
||||||
|
*
|
||||||
|
* \param self ASDU object instance
|
||||||
|
* \param index the index (byte position of the start) of the data in the data part
|
||||||
|
*
|
||||||
|
* \return SV data
|
||||||
|
*/
|
||||||
|
int32_t
|
||||||
|
SVClientASDU_getINT32(SVClientASDU self, int index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Get an INT8U data value in the data part of the ASDU
|
||||||
|
*
|
||||||
|
* \param self ASDU object instance
|
||||||
|
* \param index the index (byte position of the start) of the data in the data part
|
||||||
|
*
|
||||||
|
* \return SV data
|
||||||
|
*/
|
||||||
|
uint8_t
|
||||||
|
SVClientASDU_getINT8U(SVClientASDU self, int index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Get an INT16U data value in the data part of the ASDU
|
||||||
|
*
|
||||||
|
* \param self ASDU object instance
|
||||||
|
* \param index the index (byte position of the start) of the data in the data part
|
||||||
|
*
|
||||||
|
* \return SV data
|
||||||
|
*/
|
||||||
|
uint16_t
|
||||||
|
SVClientASDU_getINT16U(SVClientASDU self, int index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Get an INT32U data value in the data part of the ASDU
|
||||||
|
*
|
||||||
|
* \param self ASDU object instance
|
||||||
|
* \param index the index (byte position of the start) of the data in the data part
|
||||||
|
*
|
||||||
|
* \return SV data
|
||||||
|
*/
|
||||||
|
uint32_t
|
||||||
|
SVClientASDU_getINT32U(SVClientASDU self, int index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Get an FLOAT32 data value in the data part of the ASDU
|
||||||
|
*
|
||||||
|
* \param self ASDU object instance
|
||||||
|
* \param index the index (byte position of the start) of the data in the data part
|
||||||
|
*
|
||||||
|
* \return SV data
|
||||||
|
*/
|
||||||
|
float
|
||||||
|
SVClientASDU_getFLOAT32(SVClientASDU self, int index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Get an FLOAT64 data value in the data part of the ASDU
|
||||||
|
*
|
||||||
|
* \param self ASDU object instance
|
||||||
|
* \param index the index (byte position of the start) of the data in the data part
|
||||||
|
*
|
||||||
|
* \return SV data
|
||||||
|
*/
|
||||||
|
double
|
||||||
|
SVClientASDU_getFLOAT64(SVClientASDU self, int index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Returns the size of the data part of the ASDU
|
||||||
|
*
|
||||||
|
* \param self ASDU object instance
|
||||||
|
*
|
||||||
|
* \return size of the ASDU data part in bytes.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
SVClientASDU_getDataSize(SVClientASDU self);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* SAMPLED_VALUES_SV_SUBSCRIBER_ */
|
Loading…
Reference in New Issue