- extented SV publisher code / 9-2LE example
This commit is contained in:
parent
57a6fa49e2
commit
da0af0ba0f
14 changed files with 211 additions and 642 deletions
|
@ -20,7 +20,7 @@
|
|||
#define DEBUG_IED_SERVER 1
|
||||
#define DEBUG_IED_CLIENT 0
|
||||
#define DEBUG_MMS_CLIENT 0
|
||||
#define DEBUG_MMS_SERVER 1
|
||||
#define DEBUG_MMS_SERVER 0
|
||||
#define DEBUG_GOOSE_SUBSCRIBER 0
|
||||
#define DEBUG_GOOSE_PUBLISHER 0
|
||||
#define DEBUG_SV_SUBSCRIBER 0
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
extern IedModel iedModel;
|
||||
|
||||
static int running = 0;
|
||||
static int svcbEnabled = 0;
|
||||
|
||||
void sigint_handler(int signalId)
|
||||
{
|
||||
|
@ -75,13 +76,19 @@ setupSVPublisher()
|
|||
SampledValuesPublisher_setupComplete(svPublisher);
|
||||
}
|
||||
|
||||
static void sVCBEventHandler (SVControlBlock* svcb, int event, void* parameter)
|
||||
{
|
||||
if (event == IEC61850_SVCB_EVENT_ENABLE)
|
||||
svcbEnabled = 1;
|
||||
else if (event == IEC61850_SVCB_EVENT_DISABLE)
|
||||
svcbEnabled = 0;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char** argv)
|
||||
{
|
||||
IedServer iedServer = IedServer_create(&iedModel);
|
||||
|
||||
// TODO set initial measurement and status values from process
|
||||
|
||||
/* MMS server will be instructed to start listening to client connections. */
|
||||
IedServer_start(iedServer, 102);
|
||||
|
||||
|
@ -100,6 +107,15 @@ main(int argc, char** argv)
|
|||
int voltage = 1;
|
||||
int current = 1;
|
||||
|
||||
SVControlBlock* svcb = IedModel_getSVControlBlock(&iedModel, IEDMODEL_MUnn_LLN0, "MSVCB01");
|
||||
|
||||
if (svcb == NULL) {
|
||||
printf("Lookup svcb failed!\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
IedServer_setSVCBHandler(iedServer, svcb, sVCBEventHandler, NULL);
|
||||
|
||||
while (running) {
|
||||
|
||||
uint64_t timeval = Hal_getTimeInMs();
|
||||
|
@ -118,7 +134,7 @@ main(int argc, char** argv)
|
|||
|
||||
IedServer_unlockDataModel(iedServer);
|
||||
|
||||
if (1) {
|
||||
if (svcbEnabled) {
|
||||
|
||||
SV_ASDU_setINT32(asdu, amp1, current);
|
||||
SV_ASDU_setINT32(asdu, amp2, current);
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
LIBIEC_HOME=../../../
|
||||
|
||||
PROJECT_BINARY_NAME = sv_test_publisher
|
||||
PROJECT_SOURCES = sv_publisher.c
|
||||
# PROJECT_SOURCES += remote_control.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)
|
||||
|
||||
|
|
@ -1,571 +0,0 @@
|
|||
/*
|
||||
* sv_publisher.c
|
||||
*
|
||||
* Copyright 2013 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"
|
||||
|
||||
#define DEBUG_SV_PUBLISHER 1
|
||||
|
||||
#define CONFIG_SV_DEFAULT_DST_ADDRESS CONFIG_GOOSE_DEFAULT_DST_ADDRESS
|
||||
|
||||
#define CONFIG_SV_DEFAULT_PRIORITY 4
|
||||
#define CONFIG_SV_DEFAULT_VLAN_ID 0
|
||||
#define CONFIG_SV_DEFAULT_APPID 0x4000
|
||||
|
||||
#define SV_MAX_MESSAGE_SIZE 1518
|
||||
|
||||
#define IEC61850_SV_SMPSYNC_NOT_SYNCHRONIZED 0
|
||||
#define IEC61850_SV_SMPSYNC_SYNCED_UNSPEC_LOCAL_CLOCK 1
|
||||
#define IEC61850_SV_SMPSYNC_SYNCED_GLOBAL_CLOCK 2
|
||||
|
||||
#define IEC61850_SV_SMPMOD_PER_NOMINAL_PERIOD 0
|
||||
#define IEC61850_SV_SMPMOD_SAMPLES_PER_SECOND 1
|
||||
#define IEC61850_SV_SMPMOD_SECONDS_PER_SAMPLE 2
|
||||
|
||||
typedef struct sCommParameters {
|
||||
uint8_t vlanPriority;
|
||||
uint16_t vlanId;
|
||||
uint16_t appId;
|
||||
uint8_t dstAddress[6];
|
||||
} CommParameters;
|
||||
|
||||
typedef struct sSV_ASDU* SV_ASDU;
|
||||
|
||||
struct sSV_ASDU {
|
||||
char* svID;
|
||||
char* datset;
|
||||
int dataSize;
|
||||
|
||||
bool hasRefrTm;
|
||||
bool hasSmpRate;
|
||||
bool hasSmpMod;
|
||||
|
||||
uint8_t* _dataBuffer;
|
||||
|
||||
uint8_t smpSynch;
|
||||
uint16_t smpCnt;
|
||||
uint32_t confRev;
|
||||
|
||||
uint8_t* smpCntBuf;
|
||||
|
||||
SV_ASDU _next;
|
||||
};
|
||||
|
||||
typedef struct sSampledValuesPublisher* SampledValuesPublisher;
|
||||
|
||||
struct sSampledValuesPublisher {
|
||||
uint8_t* buffer;
|
||||
uint16_t appId;
|
||||
EthernetSocket ethernetSocket;
|
||||
|
||||
int lengthField; /* can probably be removed since packets have fixed size! */
|
||||
int payloadStart;
|
||||
|
||||
int payloadLength; /* length of payload buffer */
|
||||
|
||||
int asduCount; /* number of ASDUs in the APDU */
|
||||
SV_ASDU asduLIst;
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
static void
|
||||
preparePacketBuffer(SampledValuesPublisher self, CommParameters* parameters, char* interfaceID)
|
||||
{
|
||||
uint8_t srcAddr[6];
|
||||
|
||||
if (interfaceID != NULL)
|
||||
Ethernet_getInterfaceMACAddress(interfaceID, srcAddr);
|
||||
else
|
||||
Ethernet_getInterfaceMACAddress(CONFIG_ETHERNET_INTERFACE_ID, srcAddr);
|
||||
|
||||
uint8_t defaultDstAddr[] = CONFIG_SV_DEFAULT_DST_ADDRESS;
|
||||
|
||||
uint8_t* dstAddr;
|
||||
uint8_t priority;
|
||||
uint16_t vlanId;
|
||||
uint16_t appId;
|
||||
|
||||
if (parameters == NULL) {
|
||||
dstAddr = defaultDstAddr;
|
||||
priority = CONFIG_SV_DEFAULT_PRIORITY;
|
||||
vlanId = CONFIG_SV_DEFAULT_VLAN_ID;
|
||||
appId = CONFIG_SV_DEFAULT_APPID;
|
||||
}
|
||||
else {
|
||||
dstAddr = parameters->dstAddress;
|
||||
priority = parameters->vlanPriority;
|
||||
vlanId = parameters->vlanId;
|
||||
appId = parameters->appId;
|
||||
}
|
||||
|
||||
if (interfaceID != NULL)
|
||||
self->ethernetSocket = Ethernet_createSocket(interfaceID, dstAddr);
|
||||
else
|
||||
self->ethernetSocket = Ethernet_createSocket(CONFIG_ETHERNET_INTERFACE_ID, dstAddr);
|
||||
|
||||
self->buffer = (uint8_t*) GLOBAL_MALLOC(SV_MAX_MESSAGE_SIZE);
|
||||
|
||||
memcpy(self->buffer, dstAddr, 6);
|
||||
memcpy(self->buffer + 6, srcAddr, 6);
|
||||
|
||||
int bufPos = 12;
|
||||
|
||||
/* Priority tag - IEEE 802.1Q */
|
||||
self->buffer[bufPos++] = 0x81;
|
||||
self->buffer[bufPos++] = 0x00;
|
||||
|
||||
uint8_t tci1 = priority << 5;
|
||||
tci1 += vlanId / 256;
|
||||
|
||||
uint8_t tci2 = vlanId % 256;
|
||||
|
||||
self->buffer[bufPos++] = tci1; /* Priority + VLAN-ID */
|
||||
self->buffer[bufPos++] = tci2; /* VLAN-ID */
|
||||
|
||||
/* EtherType Sampled Values */
|
||||
self->buffer[bufPos++] = 0x88;
|
||||
self->buffer[bufPos++] = 0xBa;
|
||||
|
||||
/* APPID */
|
||||
self->buffer[bufPos++] = appId / 256;
|
||||
self->buffer[bufPos++] = appId % 256;
|
||||
|
||||
self->lengthField = bufPos;
|
||||
|
||||
/* Length */
|
||||
self->buffer[bufPos++] = 0x00;
|
||||
self->buffer[bufPos++] = 0x08;
|
||||
|
||||
/* Reserved1 */
|
||||
self->buffer[bufPos++] = 0x00;
|
||||
self->buffer[bufPos++] = 0x00;
|
||||
|
||||
/* Reserved2 */
|
||||
self->buffer[bufPos++] = 0x00;
|
||||
self->buffer[bufPos++] = 0x00;
|
||||
|
||||
self->payloadStart = bufPos;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
encodeUInt16FixedSize(uint16_t value, uint8_t* buffer, int bufPos)
|
||||
{
|
||||
uint8_t* valueArray = (uint8_t*) &value;
|
||||
|
||||
#if (ORDER_LITTLE_ENDIAN == 1)
|
||||
buffer[bufPos++] = valueArray[1];
|
||||
buffer[bufPos++] = valueArray[0];
|
||||
#else
|
||||
buffer[bufPos++] = valueArray[0];
|
||||
buffer[bufPos++] = valueArray[1];
|
||||
#endif
|
||||
|
||||
return bufPos;
|
||||
}
|
||||
|
||||
static int
|
||||
encodeUInt32FixedSize(uint32_t value, uint8_t* buffer, int bufPos)
|
||||
{
|
||||
uint8_t* valueArray = (uint8_t*) &value;
|
||||
|
||||
#if (ORDER_LITTLE_ENDIAN == 1)
|
||||
buffer[bufPos++] = valueArray[3];
|
||||
buffer[bufPos++] = valueArray[2];
|
||||
buffer[bufPos++] = valueArray[1];
|
||||
buffer[bufPos++] = valueArray[0];
|
||||
#else
|
||||
buffer[bufPos++] = valueArray[0];
|
||||
buffer[bufPos++] = valueArray[1];
|
||||
buffer[bufPos++] = valueArray[2];
|
||||
buffer[bufPos++] = valueArray[3];
|
||||
#endif
|
||||
|
||||
return bufPos;
|
||||
}
|
||||
|
||||
|
||||
SampledValuesPublisher
|
||||
SampledValuesPublisher_create()
|
||||
{
|
||||
SampledValuesPublisher self = GLOBAL_CALLOC(1, sizeof(struct sSampledValuesPublisher));
|
||||
|
||||
self->asduLIst = NULL;
|
||||
|
||||
preparePacketBuffer(self, NULL, "eth0");
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
SV_ASDU
|
||||
SampledValuesPublisher_addASDU(SampledValuesPublisher self, char* svID, char* datset, uint32_t confRev)
|
||||
{
|
||||
SV_ASDU newAsdu = GLOBAL_CALLOC(1, sizeof(struct sSV_ASDU));
|
||||
|
||||
newAsdu->svID = svID;
|
||||
newAsdu->datset = datset;
|
||||
newAsdu->confRev = confRev;
|
||||
|
||||
newAsdu->_next = NULL;
|
||||
|
||||
/* append new ASDU to list */
|
||||
if (self->asduLIst == NULL)
|
||||
self->asduLIst = newAsdu;
|
||||
else {
|
||||
SV_ASDU lastAsdu = self->asduLIst;
|
||||
|
||||
while (lastAsdu->_next != NULL)
|
||||
lastAsdu = lastAsdu->_next;
|
||||
|
||||
lastAsdu->_next = newAsdu;
|
||||
}
|
||||
|
||||
return newAsdu;
|
||||
}
|
||||
|
||||
static int
|
||||
SV_ASDU_getEncodedSize(SV_ASDU self)
|
||||
{
|
||||
int encodedSize = 0;
|
||||
|
||||
/* svID */
|
||||
encodedSize += ( 2 + strlen(self->svID) );
|
||||
|
||||
/* datset */
|
||||
if (self->datset != NULL)
|
||||
encodedSize += ( 2 + strlen(self->datset) );
|
||||
|
||||
/* smpCnt */
|
||||
encodedSize += 4;
|
||||
|
||||
/* confRef */
|
||||
encodedSize += 6;
|
||||
|
||||
/* refrTm */
|
||||
if (self->hasRefrTm)
|
||||
encodedSize += 10; /* ??? */
|
||||
|
||||
/* smpSynch */
|
||||
encodedSize += 3;
|
||||
|
||||
/* smpRate */
|
||||
if (self->hasSmpRate)
|
||||
encodedSize += 4;
|
||||
|
||||
/* sample */
|
||||
encodedSize += 2;
|
||||
encodedSize += self->dataSize;
|
||||
|
||||
/* smpMod */
|
||||
if (self->hasSmpMod)
|
||||
encodedSize += 4;
|
||||
|
||||
return encodedSize;
|
||||
}
|
||||
|
||||
static int
|
||||
SV_ASDU_encodeToBuffer(SV_ASDU self, uint8_t* buffer, int bufPos)
|
||||
{
|
||||
int encodedSize = SV_ASDU_getEncodedSize(self);
|
||||
|
||||
/* tag and length field */
|
||||
bufPos = BerEncoder_encodeTL(0x30, encodedSize, buffer, bufPos);
|
||||
|
||||
/* svID */
|
||||
bufPos = BerEncoder_encodeStringWithTag(0x80, self->svID, buffer, bufPos);
|
||||
|
||||
/* DatSet */
|
||||
if (self->datset != NULL)
|
||||
bufPos = BerEncoder_encodeStringWithTag(0x81, self->datset, buffer, bufPos);
|
||||
|
||||
uint8_t octetString[4];
|
||||
|
||||
/* SmpCnt */
|
||||
bufPos = BerEncoder_encodeTL(0x82, 2, buffer, bufPos);
|
||||
self->smpCntBuf = buffer + bufPos;
|
||||
bufPos = encodeUInt16FixedSize(self->smpCnt, buffer, bufPos);
|
||||
|
||||
/* ConfRev */
|
||||
bufPos = BerEncoder_encodeTL(0x83, 4, buffer, bufPos);
|
||||
bufPos = encodeUInt32FixedSize(self->confRev, buffer, bufPos);
|
||||
|
||||
/* RefrTm */
|
||||
//TODO implement me
|
||||
|
||||
/* SmpSynch */
|
||||
bufPos = BerEncoder_encodeTL(0x85, 1, buffer, bufPos);
|
||||
buffer[bufPos++] = self->smpSynch;
|
||||
|
||||
/* SmpRate */
|
||||
//TODO implement me
|
||||
|
||||
/* Sample */
|
||||
bufPos = BerEncoder_encodeTL(0x87, self->dataSize, buffer, bufPos);
|
||||
|
||||
self->_dataBuffer = buffer + bufPos;
|
||||
|
||||
bufPos += self->dataSize; /* data has to inserted by user before sending message */
|
||||
|
||||
/* SmpMod */
|
||||
//TODO implement me
|
||||
|
||||
return bufPos;
|
||||
}
|
||||
|
||||
void
|
||||
SampledValuesPublisher_setupComplete(SampledValuesPublisher self)
|
||||
{
|
||||
int numberOfAsdu = 0;
|
||||
|
||||
/* determine number of ASDUs and length of all ASDUs */
|
||||
SV_ASDU nextAsdu = self->asduLIst;
|
||||
int totalASDULength = 0;
|
||||
|
||||
while (nextAsdu != NULL) {
|
||||
numberOfAsdu++;
|
||||
int asduLength = SV_ASDU_getEncodedSize(nextAsdu);
|
||||
|
||||
/* tag and length field */
|
||||
asduLength += BerEncoder_determineLengthSize(asduLength);
|
||||
asduLength++;
|
||||
|
||||
totalASDULength += asduLength;
|
||||
|
||||
nextAsdu = nextAsdu->_next;
|
||||
}
|
||||
|
||||
/* encode frame to buffer */
|
||||
int sequenceSize = 1 + BerEncoder_determineLengthSize(totalASDULength) + totalASDULength;
|
||||
|
||||
int innerSize = 2 + BerEncoder_UInt32determineEncodedSize(numberOfAsdu) + sequenceSize;
|
||||
|
||||
uint8_t* buffer = self->buffer + self->payloadStart;
|
||||
|
||||
int bufPos = BerEncoder_encodeTL(0x60, innerSize, buffer, 0);
|
||||
|
||||
/* noASDU */
|
||||
bufPos = BerEncoder_encodeUInt32WithTL(0x80, numberOfAsdu, buffer, bufPos);
|
||||
|
||||
/* seqASDU */
|
||||
bufPos = BerEncoder_encodeTL(0xa2, totalASDULength, buffer, bufPos);
|
||||
|
||||
nextAsdu = self->asduLIst;
|
||||
|
||||
while (nextAsdu != NULL) {
|
||||
bufPos = SV_ASDU_encodeToBuffer(nextAsdu, buffer, bufPos);
|
||||
|
||||
nextAsdu = nextAsdu->_next;
|
||||
}
|
||||
|
||||
/* Update length field */
|
||||
int payloadLength = bufPos;
|
||||
|
||||
size_t msgLength = payloadLength + 8;
|
||||
|
||||
int lengthIndex = self->lengthField;
|
||||
|
||||
self->buffer[lengthIndex] = msgLength / 256;
|
||||
self->buffer[lengthIndex + 1] = msgLength & 0xff;
|
||||
|
||||
self->payloadLength = payloadLength;
|
||||
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SampledValuesPublisher_publish(SampledValuesPublisher self)
|
||||
{
|
||||
if (DEBUG_SV_PUBLISHER)
|
||||
printf("SV_PUBLISHER: send SV message\n");
|
||||
|
||||
Ethernet_sendPacket(self->ethernetSocket, self->buffer, self->payloadStart + self->payloadLength);
|
||||
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SampledValuesPublisher_destroy(SampledValuesPublisher self)
|
||||
{
|
||||
GLOBAL_FREEMEM(self->buffer);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SV_ASDU_resetBuffer(SV_ASDU self)
|
||||
{
|
||||
self->dataSize = 0;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
SV_ASDU_addINT8(SV_ASDU self)
|
||||
{
|
||||
int index = self->dataSize;
|
||||
|
||||
self->dataSize += 1;
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
void
|
||||
SV_ASDU_setINT8(SV_ASDU self, int index, int8_t value)
|
||||
{
|
||||
self->_dataBuffer[index] = value;
|
||||
}
|
||||
|
||||
int
|
||||
SV_ASDU_addINT32(SV_ASDU self)
|
||||
{
|
||||
int index = self->dataSize;
|
||||
|
||||
self->dataSize += 4;
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
int
|
||||
SV_ASDU_addFLOAT(SV_ASDU self)
|
||||
{
|
||||
int index = self->dataSize;
|
||||
|
||||
self->dataSize += 4;
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
void
|
||||
SV_ASDU_setFLOAT(SV_ASDU self, int index, float value)
|
||||
{
|
||||
uint8_t* buf = (uint8_t*) &value;
|
||||
|
||||
|
||||
#if (ORDER_LITTLE_ENDIAN == 1)
|
||||
BerEncoder_revertByteOrder(buf, 4);
|
||||
#endif
|
||||
|
||||
int i;
|
||||
|
||||
uint8_t* buffer = self->_dataBuffer + index;
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
buffer[i] = buf[i];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SV_ASDU_setSmpCnt(SV_ASDU self, uint16_t value)
|
||||
{
|
||||
//TODO write value to correct field in buffer
|
||||
}
|
||||
|
||||
void
|
||||
SV_ASDU_increaseSmpCnt(SV_ASDU self)
|
||||
{
|
||||
self->smpCnt++;
|
||||
|
||||
encodeUInt16FixedSize(self->smpCnt, self->smpCntBuf, 0);
|
||||
}
|
||||
|
||||
#if 0
|
||||
void
|
||||
SV_ASDU_setRefrTm(SV_ASDU self, Timestamp refrTm)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
int
|
||||
main(int argc, char** argv)
|
||||
{
|
||||
SampledValuesPublisher svPublisher = SampledValuesPublisher_create();
|
||||
|
||||
SV_ASDU asdu1 = SampledValuesPublisher_addASDU(svPublisher, "svpub1", NULL, 1);
|
||||
|
||||
int float1 = SV_ASDU_addFLOAT(asdu1);
|
||||
int float2 = SV_ASDU_addFLOAT(asdu1);
|
||||
|
||||
SV_ASDU asdu2 = SampledValuesPublisher_addASDU(svPublisher, "svpub2", NULL, 1);
|
||||
|
||||
int float3 = SV_ASDU_addFLOAT(asdu2);
|
||||
int float4 = SV_ASDU_addFLOAT(asdu2);
|
||||
|
||||
SampledValuesPublisher_setupComplete(svPublisher);
|
||||
|
||||
float fVal1 = 1234.5678f;
|
||||
float fVal2 = 0.12345f;
|
||||
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 10; i++) {
|
||||
SV_ASDU_setFLOAT(asdu1, float1, fVal1);
|
||||
SV_ASDU_setFLOAT(asdu1, float2, fVal2);
|
||||
|
||||
SV_ASDU_increaseSmpCnt(asdu1);
|
||||
SV_ASDU_increaseSmpCnt(asdu2);
|
||||
|
||||
fVal1 += 1.1f;
|
||||
fVal2 += 0.1f;
|
||||
|
||||
SampledValuesPublisher_publish(svPublisher);
|
||||
}
|
||||
|
||||
SampledValuesPublisher_destroy(svPublisher);
|
||||
}
|
||||
|
||||
int
|
||||
main1(int argc, char** argv)
|
||||
{
|
||||
SampledValuesPublisher svPublisher = SampledValuesPublisher_create();
|
||||
|
||||
SV_ASDU asdu = SampledValuesPublisher_addASDU(svPublisher, "svpub1", NULL, 1);
|
||||
|
||||
int float1 = SV_ASDU_addFLOAT(asdu);
|
||||
int float2 = SV_ASDU_addFLOAT(asdu);
|
||||
|
||||
SampledValuesPublisher_setupComplete(svPublisher);
|
||||
|
||||
|
||||
float fVal1 = 1234.5678f;
|
||||
float fVal2 = 0.12345f;
|
||||
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 10; i++) {
|
||||
SV_ASDU_setFLOAT(asdu, float1, fVal1);
|
||||
SV_ASDU_setFLOAT(asdu, float2, fVal2);
|
||||
SV_ASDU_increaseSmpCnt(asdu);
|
||||
|
||||
fVal1 += 1.1f;
|
||||
fVal2 += 0.1f;
|
||||
|
||||
SampledValuesPublisher_publish(svPublisher);
|
||||
}
|
||||
|
||||
SampledValuesPublisher_destroy(svPublisher);
|
||||
}
|
||||
|
|
@ -383,6 +383,9 @@ IedModel_setIedName(IedModel* self, const char* iedName);
|
|||
ModelNode*
|
||||
IedModel_getModelNodeByObjectReference(IedModel* model, const char* objectReference);
|
||||
|
||||
SVControlBlock*
|
||||
IedModel_getSVControlBlock(IedModel* self, LogicalNode* parentLN, const char* svcbName);
|
||||
|
||||
/**
|
||||
* \brief Lookup a model node by its short (normalized) reference
|
||||
*
|
||||
|
|
|
@ -906,6 +906,40 @@ IedServer_setWaitForExecutionHandler(IedServer self, DataObject* node, ControlWa
|
|||
|
||||
/**@}*/
|
||||
|
||||
/**
|
||||
* @defgroup IEC61850_SERVER_SVCB Server side sampled values control block (SVCB) handling
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** Control block has been enabled by client */
|
||||
#define IEC61850_SVCB_EVENT_ENABLE 1
|
||||
|
||||
/** Control block has been disabled by client */
|
||||
#define IEC61850_SVCB_EVENT_DISABLE 0
|
||||
|
||||
/**
|
||||
* \brief callback handler for SVCB events.
|
||||
*
|
||||
* \param svcb the related SVCB instance
|
||||
* \param the event type
|
||||
* \param user defined parameter
|
||||
*/
|
||||
typedef void (*SVCBEventHandler) (SVControlBlock* svcb, int event, void* parameter);
|
||||
|
||||
/**
|
||||
* \brief Set a handler for SVCB control block events (enable/disable)
|
||||
*
|
||||
* \param self the instance of IedServer to operate on.
|
||||
* \param svcb the SVCB control block instance
|
||||
* \param handler the event handler to be used
|
||||
* \param parameter a user provided parameter that is passed to the handler.
|
||||
*/
|
||||
void
|
||||
IedServer_setSVCBHandler(IedServer self, SVControlBlock* svcb, SVCBEventHandler handler, void* parameter);
|
||||
|
||||
/**@}*/
|
||||
|
||||
/**
|
||||
* @defgroup IEC61850_SERVER_EXTERNAL_ACCESS Handle external access to data model and access control
|
||||
*
|
||||
|
|
|
@ -44,4 +44,7 @@ MmsDataAccessError
|
|||
LIBIEC61850_SV_writeAccessSVControlBlock(MmsMapping* self, MmsDomain* domain, char* variableIdOrig,
|
||||
MmsValue* value, MmsServerConnection connection);
|
||||
|
||||
void
|
||||
LIBIEC61850_SV_setSVCBHandler(MmsMapping* self, SVControlBlock* svcb, SVCBEventHandler handler, void* parameter);
|
||||
|
||||
#endif /* LIBIEC61850_SRC_IEC61850_INC_PRIVATE_MMS_SV_H_ */
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
#include "reporting.h"
|
||||
|
||||
#include "libiec61850_platform_includes.h"
|
||||
#include "mms_sv.h"
|
||||
|
||||
#ifndef DEBUG_IED_SERVER
|
||||
#define DEBUG_IED_SERVER 0
|
||||
|
@ -661,6 +662,16 @@ IedServer_setWaitForExecutionHandler(IedServer self, DataObject* node, ControlWa
|
|||
}
|
||||
#endif /* (CONFIG_IEC61850_CONTROL_SERVICE == 1) */
|
||||
|
||||
#if (CONFIG_IEC61850_SAMPLED_VALUES_SUPPORT == 1)
|
||||
|
||||
void
|
||||
IedServer_setSVCBHandler(IedServer self, SVControlBlock* svcb, SVCBEventHandler handler, void* parameter)
|
||||
{
|
||||
LIBIEC61850_SV_setSVCBHandler(self->mmsMapping, svcb, handler, parameter);
|
||||
}
|
||||
|
||||
#endif /* (CONFIG_IEC61850_SAMPLED_VALUES_SUPPORT == 1) */
|
||||
|
||||
MmsValue*
|
||||
IedServer_getAttributeValue(IedServer self, DataAttribute* dataAttribute)
|
||||
{
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
#include "mms_mapping_internal.h"
|
||||
|
||||
struct sMmsSampledValueControlBlock {
|
||||
char* name;
|
||||
SVControlBlock* svcb;
|
||||
|
||||
bool svEna;
|
||||
MmsServerConnection reservedByClient;
|
||||
|
@ -50,6 +50,10 @@ struct sMmsSampledValueControlBlock {
|
|||
|
||||
MmsValue* svEnaValue;
|
||||
MmsValue* resvValue;
|
||||
|
||||
|
||||
SVCBEventHandler eventHandler;
|
||||
void* eventHandlerParameter;
|
||||
};
|
||||
|
||||
MmsSampledValueControlBlock
|
||||
|
@ -79,7 +83,7 @@ lookupSVCB(MmsMapping* self, MmsDomain* domain, char* lnName, char* objectName)
|
|||
|
||||
if (mmsSVCB->domain == domain) {
|
||||
if (strcmp(mmsSVCB->logicalNode->name, lnName) == 0) {
|
||||
if (strcmp(mmsSVCB->name, objectName) == 0) {
|
||||
if (strcmp(mmsSVCB->svcb->name, objectName) == 0) {
|
||||
return mmsSVCB;
|
||||
}
|
||||
}
|
||||
|
@ -94,17 +98,25 @@ lookupSVCB(MmsMapping* self, MmsDomain* domain, char* lnName, char* objectName)
|
|||
static void
|
||||
MmsSampledValueControlBlock_enable(MmsSampledValueControlBlock self)
|
||||
{
|
||||
//TODO call application callback handler
|
||||
self->svEna = true;
|
||||
MmsValue_setBoolean(self->svEnaValue, true);
|
||||
|
||||
if (DEBUG_IED_SERVER)
|
||||
printf("IED_SERVER: enable SVCB %s\n", self->svcb->name);
|
||||
|
||||
self->eventHandler(self->svcb, IEC61850_SVCB_EVENT_ENABLE, self->eventHandlerParameter);
|
||||
}
|
||||
|
||||
static void
|
||||
MmsSampledValueControlBlock_disable(MmsSampledValueControlBlock self)
|
||||
{
|
||||
//TODO call application callback handler
|
||||
self->svEna = false;
|
||||
MmsValue_setBoolean(self->svEnaValue, false);
|
||||
|
||||
if (DEBUG_IED_SERVER)
|
||||
printf("IED_SERVER: disable SVCB %s\n", self->svcb->name);
|
||||
|
||||
self->eventHandler(self->svcb, IEC61850_SVCB_EVENT_DISABLE, self->eventHandlerParameter);
|
||||
}
|
||||
|
||||
static bool
|
||||
|
@ -364,6 +376,29 @@ createDataSetReference(char* buffer, char* domainName, char* lnName, char* dataS
|
|||
StringUtils_createStringInBuffer(buffer, 5, domainName, "/", lnName, "$", dataSetName);
|
||||
}
|
||||
|
||||
void
|
||||
LIBIEC61850_SV_setSVCBHandler(MmsMapping* self, SVControlBlock* svcb, SVCBEventHandler handler, void* parameter)
|
||||
{
|
||||
LinkedList svcbElement = LinkedList_getNext(self->svControls);
|
||||
|
||||
while (svcbElement != NULL) {
|
||||
MmsSampledValueControlBlock mmsSVCB = (MmsSampledValueControlBlock) svcbElement->data;
|
||||
|
||||
if (mmsSVCB->svcb == svcb) {
|
||||
mmsSVCB->eventHandler = handler;
|
||||
mmsSVCB->eventHandlerParameter = parameter;
|
||||
break;
|
||||
}
|
||||
|
||||
svcbElement = LinkedList_getNext(svcbElement);
|
||||
}
|
||||
|
||||
if (DEBUG_IED_SERVER) {
|
||||
if (svcbElement == NULL)
|
||||
printf("IED_SERVER: setSVCBHandler failed\n");
|
||||
}
|
||||
}
|
||||
|
||||
MmsVariableSpecification*
|
||||
LIBIEC61850_SV_createSVControlBlocks(MmsMapping* self, MmsDomain* domain,
|
||||
LogicalNode* logicalNode, int svCount, bool unicast)
|
||||
|
@ -480,7 +515,7 @@ LIBIEC61850_SV_createSVControlBlocks(MmsMapping* self, MmsDomain* domain,
|
|||
mmsSvCb->mmsType = svTypeSpec;
|
||||
mmsSvCb->domain = domain;
|
||||
mmsSvCb->logicalNode = logicalNode;
|
||||
mmsSvCb->name = svControlBlock->name;
|
||||
mmsSvCb->svcb = svControlBlock;
|
||||
|
||||
LinkedList_add(self->svControls, (void*) mmsSvCb);
|
||||
|
||||
|
|
|
@ -276,6 +276,30 @@ IedModel_getModelNodeByObjectReference(IedModel* model, const char* objectRefere
|
|||
return ModelNode_getChild((ModelNode*) ld, separator + 1);
|
||||
}
|
||||
|
||||
#if (CONFIG_IEC61850_SAMPLED_VALUES_SUPPORT == 1)
|
||||
|
||||
SVControlBlock*
|
||||
IedModel_getSVControlBlock(IedModel* self, LogicalNode* parentLN, const char* svcbName)
|
||||
{
|
||||
SVControlBlock* retVal = NULL;
|
||||
|
||||
SVControlBlock* svCb = self->svCBs;
|
||||
|
||||
while (svCb != NULL) {
|
||||
if ((svCb->parent == parentLN) && (strcmp(svCb->name, svcbName) == 0)) {
|
||||
retVal = svCb;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
svCb = svCb->sibling;
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
#endif /* (CONFIG_IEC61850_SAMPLED_VALUES_SUPPORT == 1) */
|
||||
|
||||
ModelNode*
|
||||
IedModel_getModelNodeByShortObjectReference(IedModel* model, const char* objectReference)
|
||||
{
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
#include "stack_config.h"
|
||||
#include "libiec61850_platform_includes.h"
|
||||
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "sv_publisher.h"
|
||||
|
||||
#include "hal_ethernet.h"
|
||||
|
@ -64,6 +64,9 @@ struct sSV_ASDU {
|
|||
uint16_t smpCnt;
|
||||
uint32_t confRev;
|
||||
|
||||
uint64_t refrTm;
|
||||
uint8_t smpMod;
|
||||
|
||||
uint8_t* smpCntBuf;
|
||||
|
||||
SV_ASDU _next;
|
||||
|
@ -224,6 +227,39 @@ encodeInt32FixedSize(int32_t value, uint8_t* buffer, int bufPos)
|
|||
return bufPos;
|
||||
}
|
||||
|
||||
static int
|
||||
encodeUtcTime(uint64_t timeval, uint8_t* buffer, int bufPos)
|
||||
{
|
||||
uint32_t timeval32 = (timeval / 1000LL);
|
||||
|
||||
uint8_t* timeArray = (uint8_t*) &timeval32;
|
||||
uint8_t* valueArray = buffer + bufPos;
|
||||
|
||||
#if (ORDER_LITTLE_ENDIAN == 1)
|
||||
valueArray[0] = timeArray[3];
|
||||
valueArray[1] = timeArray[2];
|
||||
valueArray[2] = timeArray[1];
|
||||
valueArray[3] = timeArray[0];
|
||||
#else
|
||||
valueArray[0] = timeArray[0];
|
||||
valueArray[1] = timeArray[1];
|
||||
valueArray[2] = timeArray[2];
|
||||
valueArray[3] = timeArray[3];
|
||||
#endif
|
||||
|
||||
uint32_t remainder = (timeval % 1000LL);
|
||||
uint32_t fractionOfSecond = (remainder) * 16777 + ((remainder * 216) / 1000);
|
||||
|
||||
/* encode fraction of second */
|
||||
valueArray[4] = ((fractionOfSecond >> 16) & 0xff);
|
||||
valueArray[5] = ((fractionOfSecond >> 8) & 0xff);
|
||||
valueArray[6] = (fractionOfSecond & 0xff);
|
||||
|
||||
/* encode time quality */
|
||||
valueArray[7] = 0x0a; /* 10 bit sub-second time accuracy */
|
||||
|
||||
return bufPos + 8;
|
||||
}
|
||||
|
||||
SampledValuesPublisher
|
||||
SampledValuesPublisher_create(const char* interfaceId)
|
||||
|
@ -330,7 +366,10 @@ SV_ASDU_encodeToBuffer(SV_ASDU self, uint8_t* buffer, int bufPos)
|
|||
bufPos = encodeUInt32FixedSize(self->confRev, buffer, bufPos);
|
||||
|
||||
/* RefrTm */
|
||||
//TODO implement me
|
||||
if (self->hasRefrTm) {
|
||||
bufPos = BerEncoder_encodeTL(0x84, 8, buffer, bufPos);
|
||||
bufPos = encodeUtcTime(self->refrTm, buffer, bufPos);
|
||||
}
|
||||
|
||||
/* SmpSynch */
|
||||
bufPos = BerEncoder_encodeTL(0x85, 1, buffer, bufPos);
|
||||
|
@ -347,7 +386,14 @@ SV_ASDU_encodeToBuffer(SV_ASDU self, uint8_t* buffer, int bufPos)
|
|||
bufPos += self->dataSize; /* data has to inserted by user before sending message */
|
||||
|
||||
/* SmpMod */
|
||||
//TODO implement me
|
||||
if (self->hasSmpMod) {
|
||||
bufPos = BerEncoder_encodeTL(0x84, 4, buffer, bufPos);
|
||||
buffer[bufPos++] = 0;
|
||||
buffer[bufPos++] = 0;
|
||||
buffer[bufPos++] = 0;
|
||||
buffer[bufPos++] = self->smpMod;
|
||||
|
||||
}
|
||||
|
||||
return bufPos;
|
||||
}
|
||||
|
@ -515,43 +561,17 @@ SV_ASDU_increaseSmpCnt(SV_ASDU self)
|
|||
encodeUInt16FixedSize(self->smpCnt, self->smpCntBuf, 0);
|
||||
}
|
||||
|
||||
#if 0
|
||||
void
|
||||
SV_ASDU_setRefrTm(SV_ASDU self, Timestamp refrTm)
|
||||
SV_ASDU_setRefrTm(SV_ASDU self, uint64_t refrTm)
|
||||
{
|
||||
self->hasRefrTm = true;
|
||||
self->refrTm = refrTm;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
int
|
||||
main1(int argc, char** argv)
|
||||
void
|
||||
SV_ASDU_setSmpMod(SV_ASDU self, uint8_t smpMod)
|
||||
{
|
||||
SampledValuesPublisher svPublisher = SampledValuesPublisher_create("eth1");
|
||||
|
||||
SV_ASDU asdu = SampledValuesPublisher_addASDU(svPublisher, "svpub1", NULL, 1);
|
||||
|
||||
int float1 = SV_ASDU_addFLOAT(asdu);
|
||||
int float2 = SV_ASDU_addFLOAT(asdu);
|
||||
|
||||
SampledValuesPublisher_setupComplete(svPublisher);
|
||||
|
||||
|
||||
float fVal1 = 1234.5678f;
|
||||
float fVal2 = 0.12345f;
|
||||
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 10; i++) {
|
||||
SV_ASDU_setFLOAT(asdu, float1, fVal1);
|
||||
SV_ASDU_setFLOAT(asdu, float2, fVal2);
|
||||
SV_ASDU_increaseSmpCnt(asdu);
|
||||
|
||||
fVal1 += 1.1f;
|
||||
fVal2 += 0.1f;
|
||||
|
||||
SampledValuesPublisher_publish(svPublisher);
|
||||
}
|
||||
|
||||
SampledValuesPublisher_destroy(svPublisher);
|
||||
self->hasSmpMod = true;
|
||||
self->smpMod = smpMod;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
|
@ -81,7 +81,19 @@ SV_ASDU_setSmpCnt(SV_ASDU self, uint16_t value);
|
|||
void
|
||||
SV_ASDU_increaseSmpCnt(SV_ASDU self);
|
||||
|
||||
void
|
||||
SV_ASDU_setRefrTm(SV_ASDU self, uint64_t refrTm);
|
||||
|
||||
|
||||
/**
|
||||
* \brief Set the sample mode of the ASDU
|
||||
*
|
||||
* If not set the transmitted ASDU will not contain an sampleMod value
|
||||
*
|
||||
* \param self the SV_ASDU
|
||||
*
|
||||
* \param smpMod one of IEC61850_SV_SMPMOD_PER_NOMINAL_PERIOD, IEC61850_SV_SMPMOD_SAMPLES_PER_SECOND or IEC61850_SV_SMPMOD_SECONDS_PER_SAMPLE
|
||||
*/
|
||||
void
|
||||
SV_ASDU_setSmpMod(SV_ASDU self, uint8_t smpMod);
|
||||
|
||||
#endif /* LIBIEC61850_SRC_SAMPLED_VALUES_SV_PUBLISHER_H_ */
|
||||
|
|
|
@ -513,4 +513,6 @@ EXPORTS
|
|||
ClientSVControlBlock_getDstAddress
|
||||
ClientSVControlBlock_getOptFlds
|
||||
ClientSVControlBlock_getSmpMod
|
||||
ClientSVControlBlock_getNoASDU
|
||||
ClientSVControlBlock_getNoASDU
|
||||
IedServer_setSVCBHandler
|
||||
IedModel_getSVControlBlock
|
|
@ -563,4 +563,6 @@ EXPORTS
|
|||
ClientSVControlBlock_getDstAddress
|
||||
ClientSVControlBlock_getOptFlds
|
||||
ClientSVControlBlock_getSmpMod
|
||||
ClientSVControlBlock_getNoASDU
|
||||
ClientSVControlBlock_getNoASDU
|
||||
IedServer_setSVCBHandler
|
||||
IedModel_getSVControlBlock
|
Loading…
Add table
Reference in a new issue