Merge branch 'sv'
commit
365f105af1
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,21 @@
|
||||
include_directories(
|
||||
.
|
||||
)
|
||||
|
||||
set(server_example2_SRCS
|
||||
server_example2.c
|
||||
static_model.c
|
||||
)
|
||||
|
||||
IF(WIN32)
|
||||
set_source_files_properties(${server_example2_SRCS}
|
||||
PROPERTIES LANGUAGE CXX)
|
||||
ENDIF(WIN32)
|
||||
|
||||
add_executable(server_example2
|
||||
${server_example2_SRCS}
|
||||
)
|
||||
|
||||
target_link_libraries(server_example2
|
||||
iec61850
|
||||
)
|
@ -0,0 +1,24 @@
|
||||
LIBIEC_HOME=../..
|
||||
|
||||
PROJECT_BINARY_NAME = server_example2
|
||||
PROJECT_SOURCES = server_example2.c
|
||||
PROJECT_SOURCES += static_model.c
|
||||
|
||||
PROJECT_ICD_FILE = complexModel.icd
|
||||
|
||||
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
|
||||
|
||||
model: $(PROJECT_ICD_FILE)
|
||||
java -jar $(LIBIEC_HOME)/tools/model_generator/genmodel.jar $(PROJECT_ICD_FILE)
|
||||
|
||||
$(PROJECT_BINARY_NAME): $(PROJECT_SOURCES) $(LIB_NAME)
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -o $(PROJECT_BINARY_NAME) $(PROJECT_SOURCES) $(INCLUDES) $(LIB_NAME) $(LDFLAGS) $(LDLIBS)
|
||||
|
||||
clean:
|
||||
rm -f $(PROJECT_BINARY_NAME)
|
||||
|
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* server_example2.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 "iec61850_server.h"
|
||||
#include "hal_thread.h"
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
/* Include the generated header with the model access handles */
|
||||
#include "static_model.h"
|
||||
|
||||
/* import IEC 61850 device model created from SCL-File */
|
||||
extern IedModel iedModel;
|
||||
|
||||
static int running = 0;
|
||||
|
||||
void sigint_handler(int signalId)
|
||||
{
|
||||
running = 0;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char** argv)
|
||||
{
|
||||
IedServer iedServer = IedServer_create(&iedModel);
|
||||
|
||||
// TODO get stored values from persistent storage
|
||||
|
||||
// TODO set initial measurement and status values from process
|
||||
|
||||
/* MMS server will be instructed to start listening to client connections. */
|
||||
IedServer_start(iedServer, 102);
|
||||
|
||||
if (!IedServer_isRunning(iedServer)) {
|
||||
printf("Starting server failed! Exit.\n");
|
||||
IedServer_destroy(iedServer);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
running = 1;
|
||||
|
||||
signal(SIGINT, sigint_handler);
|
||||
|
||||
float power = 500.f;
|
||||
|
||||
while (running) {
|
||||
|
||||
uint64_t timeval = Hal_getTimeInMs();
|
||||
|
||||
IedServer_unlockDataModel(iedServer);
|
||||
|
||||
power += 0.1f;
|
||||
|
||||
Thread_sleep(500);
|
||||
}
|
||||
|
||||
/* stop MMS server - close TCP server socket and all client sockets */
|
||||
IedServer_stop(iedServer);
|
||||
|
||||
/* Cleanup - free all resources */
|
||||
IedServer_destroy(iedServer);
|
||||
|
||||
return 0;
|
||||
} /* main() */
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,161 @@
|
||||
/*
|
||||
* static_model.h
|
||||
*
|
||||
* automatically generated from sv.icd
|
||||
*/
|
||||
|
||||
#ifndef STATIC_MODEL_H_
|
||||
#define STATIC_MODEL_H_
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "iec61850_model.h"
|
||||
|
||||
extern IedModel iedModel;
|
||||
extern LogicalDevice iedModel_MUnn;
|
||||
extern LogicalNode iedModel_MUnn_LLN0;
|
||||
extern DataObject iedModel_MUnn_LLN0_Mod;
|
||||
extern DataAttribute iedModel_MUnn_LLN0_Mod_ctlVal;
|
||||
extern DataAttribute iedModel_MUnn_LLN0_Mod_stVal;
|
||||
extern DataAttribute iedModel_MUnn_LLN0_Mod_q;
|
||||
extern DataAttribute iedModel_MUnn_LLN0_Mod_t;
|
||||
extern LogicalNode iedModel_MUnn_TCTR1;
|
||||
extern DataObject iedModel_MUnn_TCTR1_Amp;
|
||||
extern DataAttribute iedModel_MUnn_TCTR1_Amp_instMag;
|
||||
extern DataAttribute iedModel_MUnn_TCTR1_Amp_instMag_i;
|
||||
extern DataAttribute iedModel_MUnn_TCTR1_Amp_q;
|
||||
extern DataAttribute iedModel_MUnn_TCTR1_Amp_sVC;
|
||||
extern DataAttribute iedModel_MUnn_TCTR1_Amp_sVC_scaleFactor;
|
||||
extern DataAttribute iedModel_MUnn_TCTR1_Amp_sVC_offset;
|
||||
extern LogicalNode iedModel_MUnn_TCTR2;
|
||||
extern DataObject iedModel_MUnn_TCTR2_Amp;
|
||||
extern DataAttribute iedModel_MUnn_TCTR2_Amp_instMag;
|
||||
extern DataAttribute iedModel_MUnn_TCTR2_Amp_instMag_i;
|
||||
extern DataAttribute iedModel_MUnn_TCTR2_Amp_q;
|
||||
extern DataAttribute iedModel_MUnn_TCTR2_Amp_sVC;
|
||||
extern DataAttribute iedModel_MUnn_TCTR2_Amp_sVC_scaleFactor;
|
||||
extern DataAttribute iedModel_MUnn_TCTR2_Amp_sVC_offset;
|
||||
extern LogicalNode iedModel_MUnn_TCTR3;
|
||||
extern DataObject iedModel_MUnn_TCTR3_Amp;
|
||||
extern DataAttribute iedModel_MUnn_TCTR3_Amp_instMag;
|
||||
extern DataAttribute iedModel_MUnn_TCTR3_Amp_instMag_i;
|
||||
extern DataAttribute iedModel_MUnn_TCTR3_Amp_q;
|
||||
extern DataAttribute iedModel_MUnn_TCTR3_Amp_sVC;
|
||||
extern DataAttribute iedModel_MUnn_TCTR3_Amp_sVC_scaleFactor;
|
||||
extern DataAttribute iedModel_MUnn_TCTR3_Amp_sVC_offset;
|
||||
extern LogicalNode iedModel_MUnn_TCTR4;
|
||||
extern DataObject iedModel_MUnn_TCTR4_Amp;
|
||||
extern DataAttribute iedModel_MUnn_TCTR4_Amp_instMag;
|
||||
extern DataAttribute iedModel_MUnn_TCTR4_Amp_instMag_i;
|
||||
extern DataAttribute iedModel_MUnn_TCTR4_Amp_q;
|
||||
extern DataAttribute iedModel_MUnn_TCTR4_Amp_sVC;
|
||||
extern DataAttribute iedModel_MUnn_TCTR4_Amp_sVC_scaleFactor;
|
||||
extern DataAttribute iedModel_MUnn_TCTR4_Amp_sVC_offset;
|
||||
extern LogicalNode iedModel_MUnn_TVTR1;
|
||||
extern DataObject iedModel_MUnn_TVTR1_Vol;
|
||||
extern DataAttribute iedModel_MUnn_TVTR1_Vol_instMag;
|
||||
extern DataAttribute iedModel_MUnn_TVTR1_Vol_instMag_i;
|
||||
extern DataAttribute iedModel_MUnn_TVTR1_Vol_q;
|
||||
extern DataAttribute iedModel_MUnn_TVTR1_Vol_sVC;
|
||||
extern DataAttribute iedModel_MUnn_TVTR1_Vol_sVC_scaleFactor;
|
||||
extern DataAttribute iedModel_MUnn_TVTR1_Vol_sVC_offset;
|
||||
extern LogicalNode iedModel_MUnn_TVTR2;
|
||||
extern DataObject iedModel_MUnn_TVTR2_Vol;
|
||||
extern DataAttribute iedModel_MUnn_TVTR2_Vol_instMag;
|
||||
extern DataAttribute iedModel_MUnn_TVTR2_Vol_instMag_i;
|
||||
extern DataAttribute iedModel_MUnn_TVTR2_Vol_q;
|
||||
extern DataAttribute iedModel_MUnn_TVTR2_Vol_sVC;
|
||||
extern DataAttribute iedModel_MUnn_TVTR2_Vol_sVC_scaleFactor;
|
||||
extern DataAttribute iedModel_MUnn_TVTR2_Vol_sVC_offset;
|
||||
extern LogicalNode iedModel_MUnn_TVTR3;
|
||||
extern DataObject iedModel_MUnn_TVTR3_Vol;
|
||||
extern DataAttribute iedModel_MUnn_TVTR3_Vol_instMag;
|
||||
extern DataAttribute iedModel_MUnn_TVTR3_Vol_instMag_i;
|
||||
extern DataAttribute iedModel_MUnn_TVTR3_Vol_q;
|
||||
extern DataAttribute iedModel_MUnn_TVTR3_Vol_sVC;
|
||||
extern DataAttribute iedModel_MUnn_TVTR3_Vol_sVC_scaleFactor;
|
||||
extern DataAttribute iedModel_MUnn_TVTR3_Vol_sVC_offset;
|
||||
extern LogicalNode iedModel_MUnn_TVTR4;
|
||||
extern DataObject iedModel_MUnn_TVTR4_Vol;
|
||||
extern DataAttribute iedModel_MUnn_TVTR4_Vol_instMag;
|
||||
extern DataAttribute iedModel_MUnn_TVTR4_Vol_instMag_i;
|
||||
extern DataAttribute iedModel_MUnn_TVTR4_Vol_q;
|
||||
extern DataAttribute iedModel_MUnn_TVTR4_Vol_sVC;
|
||||
extern DataAttribute iedModel_MUnn_TVTR4_Vol_sVC_scaleFactor;
|
||||
extern DataAttribute iedModel_MUnn_TVTR4_Vol_sVC_offset;
|
||||
|
||||
|
||||
|
||||
#define IEDMODEL_MUnn (&iedModel_MUnn)
|
||||
#define IEDMODEL_MUnn_LLN0 (&iedModel_MUnn_LLN0)
|
||||
#define IEDMODEL_MUnn_LLN0_Mod (&iedModel_MUnn_LLN0_Mod)
|
||||
#define IEDMODEL_MUnn_LLN0_Mod_ctlVal (&iedModel_MUnn_LLN0_Mod_ctlVal)
|
||||
#define IEDMODEL_MUnn_LLN0_Mod_stVal (&iedModel_MUnn_LLN0_Mod_stVal)
|
||||
#define IEDMODEL_MUnn_LLN0_Mod_q (&iedModel_MUnn_LLN0_Mod_q)
|
||||
#define IEDMODEL_MUnn_LLN0_Mod_t (&iedModel_MUnn_LLN0_Mod_t)
|
||||
#define IEDMODEL_MUnn_TCTR1 (&iedModel_MUnn_TCTR1)
|
||||
#define IEDMODEL_MUnn_TCTR1_Amp (&iedModel_MUnn_TCTR1_Amp)
|
||||
#define IEDMODEL_MUnn_TCTR1_Amp_instMag (&iedModel_MUnn_TCTR1_Amp_instMag)
|
||||
#define IEDMODEL_MUnn_TCTR1_Amp_instMag_i (&iedModel_MUnn_TCTR1_Amp_instMag_i)
|
||||
#define IEDMODEL_MUnn_TCTR1_Amp_q (&iedModel_MUnn_TCTR1_Amp_q)
|
||||
#define IEDMODEL_MUnn_TCTR1_Amp_sVC (&iedModel_MUnn_TCTR1_Amp_sVC)
|
||||
#define IEDMODEL_MUnn_TCTR1_Amp_sVC_scaleFactor (&iedModel_MUnn_TCTR1_Amp_sVC_scaleFactor)
|
||||
#define IEDMODEL_MUnn_TCTR1_Amp_sVC_offset (&iedModel_MUnn_TCTR1_Amp_sVC_offset)
|
||||
#define IEDMODEL_MUnn_TCTR2 (&iedModel_MUnn_TCTR2)
|
||||
#define IEDMODEL_MUnn_TCTR2_Amp (&iedModel_MUnn_TCTR2_Amp)
|
||||
#define IEDMODEL_MUnn_TCTR2_Amp_instMag (&iedModel_MUnn_TCTR2_Amp_instMag)
|
||||
#define IEDMODEL_MUnn_TCTR2_Amp_instMag_i (&iedModel_MUnn_TCTR2_Amp_instMag_i)
|
||||
#define IEDMODEL_MUnn_TCTR2_Amp_q (&iedModel_MUnn_TCTR2_Amp_q)
|
||||
#define IEDMODEL_MUnn_TCTR2_Amp_sVC (&iedModel_MUnn_TCTR2_Amp_sVC)
|
||||
#define IEDMODEL_MUnn_TCTR2_Amp_sVC_scaleFactor (&iedModel_MUnn_TCTR2_Amp_sVC_scaleFactor)
|
||||
#define IEDMODEL_MUnn_TCTR2_Amp_sVC_offset (&iedModel_MUnn_TCTR2_Amp_sVC_offset)
|
||||
#define IEDMODEL_MUnn_TCTR3 (&iedModel_MUnn_TCTR3)
|
||||
#define IEDMODEL_MUnn_TCTR3_Amp (&iedModel_MUnn_TCTR3_Amp)
|
||||
#define IEDMODEL_MUnn_TCTR3_Amp_instMag (&iedModel_MUnn_TCTR3_Amp_instMag)
|
||||
#define IEDMODEL_MUnn_TCTR3_Amp_instMag_i (&iedModel_MUnn_TCTR3_Amp_instMag_i)
|
||||
#define IEDMODEL_MUnn_TCTR3_Amp_q (&iedModel_MUnn_TCTR3_Amp_q)
|
||||
#define IEDMODEL_MUnn_TCTR3_Amp_sVC (&iedModel_MUnn_TCTR3_Amp_sVC)
|
||||
#define IEDMODEL_MUnn_TCTR3_Amp_sVC_scaleFactor (&iedModel_MUnn_TCTR3_Amp_sVC_scaleFactor)
|
||||
#define IEDMODEL_MUnn_TCTR3_Amp_sVC_offset (&iedModel_MUnn_TCTR3_Amp_sVC_offset)
|
||||
#define IEDMODEL_MUnn_TCTR4 (&iedModel_MUnn_TCTR4)
|
||||
#define IEDMODEL_MUnn_TCTR4_Amp (&iedModel_MUnn_TCTR4_Amp)
|
||||
#define IEDMODEL_MUnn_TCTR4_Amp_instMag (&iedModel_MUnn_TCTR4_Amp_instMag)
|
||||
#define IEDMODEL_MUnn_TCTR4_Amp_instMag_i (&iedModel_MUnn_TCTR4_Amp_instMag_i)
|
||||
#define IEDMODEL_MUnn_TCTR4_Amp_q (&iedModel_MUnn_TCTR4_Amp_q)
|
||||
#define IEDMODEL_MUnn_TCTR4_Amp_sVC (&iedModel_MUnn_TCTR4_Amp_sVC)
|
||||
#define IEDMODEL_MUnn_TCTR4_Amp_sVC_scaleFactor (&iedModel_MUnn_TCTR4_Amp_sVC_scaleFactor)
|
||||
#define IEDMODEL_MUnn_TCTR4_Amp_sVC_offset (&iedModel_MUnn_TCTR4_Amp_sVC_offset)
|
||||
#define IEDMODEL_MUnn_TVTR1 (&iedModel_MUnn_TVTR1)
|
||||
#define IEDMODEL_MUnn_TVTR1_Vol (&iedModel_MUnn_TVTR1_Vol)
|
||||
#define IEDMODEL_MUnn_TVTR1_Vol_instMag (&iedModel_MUnn_TVTR1_Vol_instMag)
|
||||
#define IEDMODEL_MUnn_TVTR1_Vol_instMag_i (&iedModel_MUnn_TVTR1_Vol_instMag_i)
|
||||
#define IEDMODEL_MUnn_TVTR1_Vol_q (&iedModel_MUnn_TVTR1_Vol_q)
|
||||
#define IEDMODEL_MUnn_TVTR1_Vol_sVC (&iedModel_MUnn_TVTR1_Vol_sVC)
|
||||
#define IEDMODEL_MUnn_TVTR1_Vol_sVC_scaleFactor (&iedModel_MUnn_TVTR1_Vol_sVC_scaleFactor)
|
||||
#define IEDMODEL_MUnn_TVTR1_Vol_sVC_offset (&iedModel_MUnn_TVTR1_Vol_sVC_offset)
|
||||
#define IEDMODEL_MUnn_TVTR2 (&iedModel_MUnn_TVTR2)
|
||||
#define IEDMODEL_MUnn_TVTR2_Vol (&iedModel_MUnn_TVTR2_Vol)
|
||||
#define IEDMODEL_MUnn_TVTR2_Vol_instMag (&iedModel_MUnn_TVTR2_Vol_instMag)
|
||||
#define IEDMODEL_MUnn_TVTR2_Vol_instMag_i (&iedModel_MUnn_TVTR2_Vol_instMag_i)
|
||||
#define IEDMODEL_MUnn_TVTR2_Vol_q (&iedModel_MUnn_TVTR2_Vol_q)
|
||||
#define IEDMODEL_MUnn_TVTR2_Vol_sVC (&iedModel_MUnn_TVTR2_Vol_sVC)
|
||||
#define IEDMODEL_MUnn_TVTR2_Vol_sVC_scaleFactor (&iedModel_MUnn_TVTR2_Vol_sVC_scaleFactor)
|
||||
#define IEDMODEL_MUnn_TVTR2_Vol_sVC_offset (&iedModel_MUnn_TVTR2_Vol_sVC_offset)
|
||||
#define IEDMODEL_MUnn_TVTR3 (&iedModel_MUnn_TVTR3)
|
||||
#define IEDMODEL_MUnn_TVTR3_Vol (&iedModel_MUnn_TVTR3_Vol)
|
||||
#define IEDMODEL_MUnn_TVTR3_Vol_instMag (&iedModel_MUnn_TVTR3_Vol_instMag)
|
||||
#define IEDMODEL_MUnn_TVTR3_Vol_instMag_i (&iedModel_MUnn_TVTR3_Vol_instMag_i)
|
||||
#define IEDMODEL_MUnn_TVTR3_Vol_q (&iedModel_MUnn_TVTR3_Vol_q)
|
||||
#define IEDMODEL_MUnn_TVTR3_Vol_sVC (&iedModel_MUnn_TVTR3_Vol_sVC)
|
||||
#define IEDMODEL_MUnn_TVTR3_Vol_sVC_scaleFactor (&iedModel_MUnn_TVTR3_Vol_sVC_scaleFactor)
|
||||
#define IEDMODEL_MUnn_TVTR3_Vol_sVC_offset (&iedModel_MUnn_TVTR3_Vol_sVC_offset)
|
||||
#define IEDMODEL_MUnn_TVTR4 (&iedModel_MUnn_TVTR4)
|
||||
#define IEDMODEL_MUnn_TVTR4_Vol (&iedModel_MUnn_TVTR4_Vol)
|
||||
#define IEDMODEL_MUnn_TVTR4_Vol_instMag (&iedModel_MUnn_TVTR4_Vol_instMag)
|
||||
#define IEDMODEL_MUnn_TVTR4_Vol_instMag_i (&iedModel_MUnn_TVTR4_Vol_instMag_i)
|
||||
#define IEDMODEL_MUnn_TVTR4_Vol_q (&iedModel_MUnn_TVTR4_Vol_q)
|
||||
#define IEDMODEL_MUnn_TVTR4_Vol_sVC (&iedModel_MUnn_TVTR4_Vol_sVC)
|
||||
#define IEDMODEL_MUnn_TVTR4_Vol_sVC_scaleFactor (&iedModel_MUnn_TVTR4_Vol_sVC_scaleFactor)
|
||||
#define IEDMODEL_MUnn_TVTR4_Vol_sVC_offset (&iedModel_MUnn_TVTR4_Vol_sVC_offset)
|
||||
|
||||
#endif /* STATIC_MODEL_H_ */
|
||||
|
@ -0,0 +1,148 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- edited with XMLSPY v5 rel. 4 U (http://www.xmlspy.com) by Christoph Brunner (ABB Switzerland Ltd) -->
|
||||
<SCL xmlns="http://www.iec.ch/61850/2003/SCL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.iec.ch/61850/2003/SCL
|
||||
SCL.xsd">
|
||||
<Header id="9-2LE-Spec" nameStructure="FuncName" version="0.7" revision="1"/>
|
||||
<Communication>
|
||||
<SubNetwork name="subnetwork1" type="8-MMS">
|
||||
<Text>Station bus</Text>
|
||||
<BitRate unit="b/s">10</BitRate>
|
||||
<ConnectedAP iedName="TEMPLATE" apName="accessPoint1">
|
||||
<Address>
|
||||
<P type="IP">10.0.0.2</P>
|
||||
<P type="IP-SUBNET">255.255.255.0</P>
|
||||
<P type="IP-GATEWAY">10.0.0.1</P>
|
||||
<P type="OSI-TSEL">0001</P>
|
||||
<P type="OSI-PSEL">00000001</P>
|
||||
<P type="OSI-SSEL">0001</P>
|
||||
</Address>
|
||||
|
||||
<SMV ldInst="MUnn" cbName="MSVCB01">
|
||||
<Address>
|
||||
<P type="VLAN-ID">1</P>
|
||||
<P type="VLAN-PRIORITY">4</P>
|
||||
<P type="MAC-Address">01-0c-cd-04-00-01</P>
|
||||
<P type="APPID">1001</P>
|
||||
</Address>
|
||||
</SMV>
|
||||
|
||||
</ConnectedAP>
|
||||
</SubNetwork>
|
||||
</Communication>
|
||||
<Substation name="">
|
||||
<VoltageLevel name="">
|
||||
<Bay name="">
|
||||
<ConductingEquipment name="Inn" type="CTR">
|
||||
<SubEquipment name="A" phase="A">
|
||||
<LNode lnClass="TCTR" lnInst="1"/>
|
||||
</SubEquipment>
|
||||
<SubEquipment name="B" phase="B">
|
||||
<LNode lnClass="TCTR" lnInst="2"/>
|
||||
</SubEquipment>
|
||||
<SubEquipment name="C" phase="C">
|
||||
<LNode lnClass="TCTR" lnInst="3"/>
|
||||
</SubEquipment>
|
||||
<SubEquipment name="N" phase="N">
|
||||
<LNode lnClass="TCTR" lnInst="4"/>
|
||||
</SubEquipment>
|
||||
</ConductingEquipment>
|
||||
<ConductingEquipment name="Unn" type="VTR">
|
||||
<SubEquipment name="A" phase="A">
|
||||
<LNode lnClass="TVTR" lnInst="1"/>
|
||||
</SubEquipment>
|
||||
<SubEquipment name="B" phase="B">
|
||||
<LNode lnClass="TVTR" lnInst="2"/>
|
||||
</SubEquipment>
|
||||
<SubEquipment name="C" phase="C">
|
||||
<LNode lnClass="TVTR" lnInst="3"/>
|
||||
</SubEquipment>
|
||||
<SubEquipment name="N" phase="N">
|
||||
<LNode lnClass="TVTR" lnInst="4"/>
|
||||
</SubEquipment>
|
||||
</ConductingEquipment>
|
||||
</Bay>
|
||||
</VoltageLevel>
|
||||
</Substation>
|
||||
<IED name="TEMPLATE">
|
||||
<AccessPoint name="accessPoint1">
|
||||
<Server>
|
||||
<Authentication/>
|
||||
<LDevice inst="MUnn">
|
||||
<LN0 lnType="9-2LELLN0" lnClass="LLN0" inst="">
|
||||
<DataSet name="PhsMeas1">
|
||||
<FCDA lnClass="TCTR" ldInst="1" fc="MX" doName="Amp"/>
|
||||
<FCDA lnClass="TCTR" ldInst="2" fc="MX" doName="Amp"/>
|
||||
<FCDA lnClass="TCTR" ldInst="3" fc="MX" doName="Amp"/>
|
||||
<FCDA lnClass="TCTR" ldInst="4" fc="MX" doName="Amp"/>
|
||||
<FCDA lnClass="TVTR" ldInst="1" fc="MX" doName="Vol"/>
|
||||
<FCDA lnClass="TVTR" ldInst="2" fc="MX" doName="Vol"/>
|
||||
<FCDA lnClass="TVTR" ldInst="3" fc="MX" doName="Vol"/>
|
||||
<FCDA lnClass="TVTR" ldInst="4" fc="MX" doName="Vol"/>
|
||||
</DataSet>
|
||||
<SampledValueControl name="MSVCB01" datSet="PhsMeas1" smvID="xxxxMUnn01"
|
||||
smpRate="80" nofASDU="1" confRev="1">
|
||||
<SmvOpts refreshTime="false" sampleSynchronized="true"
|
||||
security="false" dataRef="false"/>
|
||||
</SampledValueControl>
|
||||
</LN0>
|
||||
<LN lnType="9-2LETCTR" lnClass="TCTR" inst="1"/>
|
||||
<LN lnType="9-2LETCTR" lnClass="TCTR" inst="2"/>
|
||||
<LN lnType="9-2LETCTR" lnClass="TCTR" inst="3"/>
|
||||
<LN lnType="9-2LETCTR" lnClass="TCTR" inst="4"/>
|
||||
<LN lnType="9-2LETVTR" lnClass="TVTR" inst="1"/>
|
||||
<LN lnType="9-2LETVTR" lnClass="TVTR" inst="2"/>
|
||||
<LN lnType="9-2LETVTR" lnClass="TVTR" inst="3"/>
|
||||
<LN lnType="9-2LETVTR" lnClass="TVTR" inst="4"/>
|
||||
</LDevice>
|
||||
</Server>
|
||||
</AccessPoint>
|
||||
</IED>
|
||||
<DataTypeTemplates>
|
||||
<LNodeType id="9-2LELLN0" lnClass="LLN0">
|
||||
<DO name="Mod" type="9-2LEINC"/>
|
||||
</LNodeType>
|
||||
<LNodeType id="9-2LETCTR" lnClass="TCTR">
|
||||
<DO name="Amp" type="9-2LESAVAmp"/>
|
||||
</LNodeType>
|
||||
<LNodeType id="9-2LETVTR" lnClass="TVTR">
|
||||
<DO name="Vol" type="9-2LESAVVol"/>
|
||||
</LNodeType>
|
||||
<DOType id="9-2LESAVAmp" cdc="SAV">
|
||||
<DA name="instMag" bType="Struct" type="9-2LEAV" fc="MX"/>
|
||||
<DA name="q" bType="Quality" fc="MX"/>
|
||||
<DA name="sVC" bType="Struct" type="9-2LEsVCAmp" fc="CF"/>
|
||||
</DOType>
|
||||
<DOType id="9-2LESAVVol" cdc="SAV">
|
||||
<DA name="instMag" bType="Struct" type="9-2LEAV" fc="MX"/>
|
||||
<DA name="q" bType="Quality" fc="MX"/>
|
||||
<DA name="sVC" bType="Struct" type="9-2LEsVCVol" fc="CF"/>
|
||||
</DOType>
|
||||
<DOType id="9-2LEINC" cdc="INC">
|
||||
<DA name="ctlVal" fc="CO" bType="INT32"/>
|
||||
<DA name="stVal" fc="ST" bType="INT32" dchg="true"/>
|
||||
<DA name="q" fc="ST" bType="Quality" dchg="true"/>
|
||||
<DA name="t" fc="ST" bType="Timestamp" dchg="true"/>
|
||||
</DOType>
|
||||
<DAType id="9-2LEAV">
|
||||
<BDA name="i" bType="INT32"/>
|
||||
</DAType>
|
||||
<DAType id="9-2LEsVCAmp">
|
||||
<BDA name="scaleFactor" bType="FLOAT32">
|
||||
<Val>0.001</Val>
|
||||
</BDA>
|
||||
<BDA name="offset" bType="FLOAT32">
|
||||
<Val>0</Val>
|
||||
</BDA>
|
||||
</DAType>
|
||||
<DAType id="9-2LEsVCVol">
|
||||
<BDA name="scaleFactor" bType="FLOAT32">
|
||||
<Val>0.01</Val>
|
||||
</BDA>
|
||||
<BDA name="offset" bType="FLOAT32">
|
||||
<Val>0</Val>
|
||||
</BDA>
|
||||
</DAType>
|
||||
</DataTypeTemplates>
|
||||
</SCL>
|
||||
|
@ -0,0 +1,17 @@
|
||||
|
||||
set(iec61850_sv_client_example_SRCS
|
||||
sv_client_example.c
|
||||
)
|
||||
|
||||
IF(WIN32)
|
||||
set_source_files_properties(${iec61850_sv_client_example_SRCS}
|
||||
PROPERTIES LANGUAGE CXX)
|
||||
ENDIF(WIN32)
|
||||
|
||||
add_executable(iec61850_sv_client_example
|
||||
${iec61850_sv_client_example_SRCS}
|
||||
)
|
||||
|
||||
target_link_libraries(iec61850_sv_client_example
|
||||
iec61850
|
||||
)
|
@ -0,0 +1,17 @@
|
||||
LIBIEC_HOME=../..
|
||||
|
||||
PROJECT_BINARY_NAME = sv_client_example
|
||||
PROJECT_SOURCES = sv_client_example.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)
|
@ -0,0 +1,119 @@
|
||||
/*
|
||||
* sv_client_example.c
|
||||
*
|
||||
* This example is intended to show how SV control blocks are accessed
|
||||
*/
|
||||
|
||||
#include "iec61850_client.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "hal_thread.h"
|
||||
|
||||
static void
|
||||
printDstAddr(PhyComAddress dstAddress)
|
||||
{
|
||||
printf(" addr: ");
|
||||
|
||||
int i;
|
||||
for (i = 0; i < 6; i++)
|
||||
printf("%02x", dstAddress.dstAddress[i]);
|
||||
|
||||
printf("\n prio: %u\n", dstAddress.vlanPriority);
|
||||
printf(" vid: %u\n", dstAddress.vlanId);
|
||||
printf(" appID: %u\n", dstAddress.appId);
|
||||
}
|
||||
|
||||
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]);
|
||||
|
||||
IedClientError error;
|
||||
|
||||
IedConnection con = IedConnection_create();
|
||||
|
||||
IedConnection_connect(con, &error, hostname, tcpPort);
|
||||
|
||||
if (error == IED_ERROR_OK) {
|
||||
|
||||
char* svcbRef = "simpleIOGenericIO/LLN0.Volt";
|
||||
|
||||
ClientSVControlBlock svcb = ClientSVControlBlock_create(con, svcbRef);
|
||||
|
||||
if (svcb != NULL) {
|
||||
if (ClientSVControlBlock_isMulticast(svcb))
|
||||
printf("SVCB is multicast\n");
|
||||
else
|
||||
printf("SVCB is unicast\n");
|
||||
|
||||
if (ClientSVControlBlock_setSvEna(svcb, true))
|
||||
printf("SVCB enabled\n");
|
||||
else
|
||||
printf("Failed to enable SVCB\n");
|
||||
|
||||
printf("SvEna state: %i\n", ClientSVControlBlock_getSvEna(svcb));
|
||||
|
||||
char* msvID = ClientSVControlBlock_getMsvID(svcb);
|
||||
|
||||
if (msvID != NULL) {
|
||||
printf("MsvID: %s\n", msvID);
|
||||
free(msvID);
|
||||
}
|
||||
|
||||
char* datSetName = ClientSVControlBlock_getDatSet(svcb);
|
||||
|
||||
if (datSetName != NULL) {
|
||||
printf("DatSet: %s\n", datSetName);
|
||||
free(datSetName);
|
||||
}
|
||||
|
||||
printf("ConfRev: %i\n", ClientSVControlBlock_getConfRev(svcb));
|
||||
printf("SmpRate: %i\n", ClientSVControlBlock_getSmpRate(svcb));
|
||||
printf("SmpMod: %i\n", ClientSVControlBlock_getSmpMod(svcb));
|
||||
|
||||
int optFlds = ClientSVControlBlock_getOptFlds(svcb);
|
||||
|
||||
printf("OptFlds: ");
|
||||
if (optFlds & IEC61850_SV_OPT_REFRESH_TIME)
|
||||
printf("refresh-time ");
|
||||
if (optFlds & IEC61850_SV_OPT_SAMPLE_SYNC)
|
||||
printf("sample-synch ");
|
||||
if (optFlds & IEC61850_SV_OPT_SAMPLE_RATE)
|
||||
printf("sample-rate ");
|
||||
if (optFlds & IEC61850_SV_OPT_DATA_SET)
|
||||
printf("date-set ");
|
||||
if (optFlds & IEC61850_SV_OPT_SECURITY)
|
||||
printf("security ");
|
||||
printf("\n");
|
||||
|
||||
printf("noASDU: %i\n", ClientSVControlBlock_getNoASDU(svcb));
|
||||
|
||||
PhyComAddress dstAddress = ClientSVControlBlock_getDstAddress(svcb);
|
||||
|
||||
printDstAddr(dstAddress);
|
||||
|
||||
}
|
||||
else {
|
||||
printf("SVCB %s does not exist on server!\n", svcbRef);
|
||||
}
|
||||
|
||||
IedConnection_close(con);
|
||||
}
|
||||
else {
|
||||
printf("Failed to connect to %s:%i\n", hostname, tcpPort);
|
||||
}
|
||||
|
||||
IedConnection_destroy(con);
|
||||
}
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,30 @@
|
||||
|
||||
set(sv_subscriber_example_SRCS
|
||||
sv_subscriber_example.c
|
||||
)
|
||||
|
||||
IF(WIN32)
|
||||
|
||||
set_source_files_properties(${sv_subscriber_example_SRCS}
|
||||
PROPERTIES LANGUAGE CXX)
|
||||
add_executable(sv_subscriber_example
|
||||
${sv_subscriber_example_SRCS}
|
||||
)
|
||||
|
||||
target_link_libraries(sv_subscriber_example
|
||||
iec61850
|
||||
)
|
||||
|
||||
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=../..
|
||||
|
||||
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,88 @@
|
||||
/*
|
||||
* sv_subscriber_example.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));
|
||||
|
||||
/*
|
||||
* Access to the data requires a priori knowledge of the data set.
|
||||
* For this example we assume a data set consisting of FLOAT32 values.
|
||||
* A FLOAT32 value is encoded as 4 bytes. You can find the first FLOAT32
|
||||
* value at byte position 0, the second value at byte position 4, the third
|
||||
* value at byte position 8, and so on.
|
||||
*
|
||||
* To prevent damages due configuration, please check the length of the
|
||||
* data block of the SV message before accessing the data.
|
||||
*/
|
||||
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) {
|
||||
SVReceiver_setInterfaceId(receiver, argv[1]);
|
||||
printf("Set interface id: %s\n", argv[1]);
|
||||
}
|
||||
else {
|
||||
printf("Using interface eth0\n");
|
||||
SVReceiver_setInterfaceId(receiver, "eth0");
|
||||
}
|
||||
|
||||
/* Create a subscriber listening to SV messages with APPID 4000h */
|
||||
SVSubscriber subscriber = SVSubscriber_create(NULL, 0x4000);
|
||||
|
||||
/* Install a callback handler for the subscriber */
|
||||
SVSubscriber_setListener(subscriber, svUpdateListener, NULL);
|
||||
|
||||
/* Connect the subscriber to the receiver */
|
||||
SVReceiver_addSubscriber(receiver, subscriber);
|
||||
|
||||
/* Start listening to SV messages - starts a new receiver background thread */
|
||||
SVReceiver_start(receiver);
|
||||
|
||||
signal(SIGINT, sigint_handler);
|
||||
|
||||
while (running)
|
||||
Thread_sleep(1);
|
||||
|
||||
/* Stop listening to SV messages */
|
||||
SVReceiver_stop(receiver);
|
||||
|
||||
/* Cleanup and free resources */
|
||||
SVReceiver_destroy(receiver);
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
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)
|
||||
|
||||
|
@ -0,0 +1,571 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
@ -0,0 +1,358 @@
|
||||
/*
|
||||
* client_sv_control.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 "iec61850_client.h"
|
||||
|
||||
#include "stack_config.h"
|
||||
|
||||
#include "ied_connection_private.h"
|
||||
|
||||
#include "libiec61850_platform_includes.h"
|
||||
|
||||
struct sClientSVControlBlock {
|
||||
IedConnection connection;
|
||||
bool isMulticast;
|
||||
char* reference;
|
||||
IedClientError lastError;
|
||||
};
|
||||
|
||||
ClientSVControlBlock
|
||||
ClientSVControlBlock_create(IedConnection connection, const char* reference)
|
||||
{
|
||||
bool isMulticast = false;
|
||||
|
||||
/* Check if CB exists and if it is a unicast or mulitcast CB */
|
||||
IedClientError error;
|
||||
MmsValue* value = IedConnection_readObject(connection, &error, reference, IEC61850_FC_MS);
|
||||
|
||||
if (value != NULL) {
|
||||
isMulticast = true;
|
||||
MmsValue_delete(value);
|
||||
}
|
||||
else {
|
||||
value = IedConnection_readObject(connection, &error, reference, IEC61850_FC_US);
|
||||
|
||||
if (value == NULL)
|
||||
return NULL;
|
||||
|
||||
MmsValue_delete(value);
|
||||
}
|
||||
|
||||
ClientSVControlBlock self = (ClientSVControlBlock) GLOBAL_CALLOC(1, sizeof(struct sClientSVControlBlock));
|
||||
|
||||
if (self) {
|
||||
self->connection = connection;
|
||||
self->reference = copyString(reference);
|
||||
self->isMulticast = isMulticast;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
void
|
||||
ClientSVControlBlock_destroy(ClientSVControlBlock self)
|
||||
{
|
||||
if (self) {
|
||||
GLOBAL_FREEMEM(self->reference);
|
||||
GLOBAL_FREEMEM(self);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
IedClientError
|
||||
ClientSVControlBlock_getLastComError(ClientSVControlBlock self)
|
||||
{
|
||||
return self->lastError;
|
||||
}
|
||||
|
||||
bool
|
||||
ClientSVControlBlock_isMulticast(ClientSVControlBlock self)
|
||||
{
|
||||
return self->isMulticast;
|
||||
}
|
||||
|
||||
static bool
|
||||
setBooleanVariable(ClientSVControlBlock self, const char* varName, bool value)
|
||||
{
|
||||
char refBuf[130];
|
||||
|
||||
strcpy(refBuf, self->reference);
|
||||
strcat(refBuf, ".");
|
||||
strcat(refBuf, varName);
|
||||
|
||||
self->lastError = IED_ERROR_OK;
|
||||
|
||||
if (self->isMulticast)
|
||||
IedConnection_writeBooleanValue(self->connection, &(self->lastError), refBuf, IEC61850_FC_MS, value);
|
||||
else
|
||||
IedConnection_writeBooleanValue(self->connection, &(self->lastError), refBuf, IEC61850_FC_US, value);
|
||||
|
||||
|
||||
if (self->lastError == IED_ERROR_OK)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
ClientSVControlBlock_setSvEna(ClientSVControlBlock self, bool svEna)
|
||||
{
|
||||
return setBooleanVariable(self, "SvEna", svEna);
|
||||
}
|
||||
|
||||
bool
|
||||
ClientSVControlBlock_setResv(ClientSVControlBlock self, bool svEna)
|
||||
{
|
||||
if (self->isMulticast == false)
|
||||
return setBooleanVariable(self, "SvEna", svEna);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
readBooleanVariable(ClientSVControlBlock self, const char* varName)
|
||||
{
|
||||
char refBuf[130];
|
||||
|
||||
strcpy(refBuf, self->reference);
|
||||
strcat(refBuf, ".");
|
||||
strcat(refBuf, varName);
|
||||
|
||||
self->lastError = IED_ERROR_OK;
|
||||
|
||||
bool retVal;
|
||||
|
||||
if (self->isMulticast)
|
||||
retVal = IedConnection_readBooleanValue(self->connection, &(self->lastError), refBuf, IEC61850_FC_MS);
|
||||
else
|
||||
retVal = IedConnection_readBooleanValue(self->connection, &(self->lastError), refBuf, IEC61850_FC_US);
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
bool
|
||||
ClientSVControlBlock_getSvEna(ClientSVControlBlock self)
|
||||
{
|
||||
return readBooleanVariable(self, "SvEna");
|
||||
}
|
||||
|
||||
bool
|
||||
ClientSVControlBlock_getResv(ClientSVControlBlock self)
|
||||
{
|
||||
return readBooleanVariable(self, "Resv");
|
||||
}
|
||||
|
||||
static char*
|
||||
readStringVariable(ClientSVControlBlock self, const char* varName)
|
||||
{
|
||||
char refBuf[130];
|
||||
|
||||
strcpy(refBuf, self->reference);
|
||||
strcat(refBuf, ".");
|
||||
strcat(refBuf, varName);
|
||||
|
||||
self->lastError = IED_ERROR_OK;
|
||||
|
||||
char* retVal;
|
||||
|
||||
if (self->isMulticast)
|
||||
retVal = IedConnection_readStringValue(self->connection, &(self->lastError), refBuf, IEC61850_FC_MS);
|
||||
else
|
||||
retVal = IedConnection_readStringValue(self->connection, &(self->lastError), refBuf, IEC61850_FC_US);
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
char*
|
||||
ClientSVControlBlock_getMsvID(ClientSVControlBlock self)
|
||||
{
|
||||
return readStringVariable(self, "MsvID");
|
||||
}
|
||||
|
||||
char*
|
||||
ClientSVControlBlock_getDatSet(ClientSVControlBlock self)
|
||||
{
|
||||
return readStringVariable(self, "DatSet");
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
readUIntVariable(ClientSVControlBlock self, const char* varName)
|
||||
{
|
||||
char refBuf[130];
|
||||
|
||||
strcpy(refBuf, self->reference);
|
||||
strcat(refBuf, ".");
|
||||
strcat(refBuf, varName);
|
||||
|
||||
self->lastError = IED_ERROR_OK;
|
||||
|
||||
uint32_t retVal;
|
||||
|
||||
if (self->isMulticast)
|
||||
retVal = IedConnection_readUnsigned32Value(self->connection, &(self->lastError), refBuf, IEC61850_FC_MS);
|
||||
else
|
||||
retVal = IedConnection_readUnsigned32Value(self->connection, &(self->lastError), refBuf, IEC61850_FC_US);
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
ClientSVControlBlock_getConfRev(ClientSVControlBlock self)
|
||||
{
|
||||
return readUIntVariable(self, "ConfRev");
|
||||
}
|
||||
|
||||
uint16_t
|
||||
ClientSVControlBlock_getSmpRate(ClientSVControlBlock self)
|
||||
{
|
||||
return readUIntVariable(self, "SmpRate");
|
||||
}
|
||||
|
||||
int
|
||||
ClientSVControlBlock_getOptFlds(ClientSVControlBlock self)
|
||||
{
|
||||
char refBuf[130];
|
||||
|
||||
strcpy(refBuf, self->reference);
|
||||
strcat(refBuf, ".");
|
||||
strcat(refBuf, "OptFlds");
|
||||
|
||||
self->lastError = IED_ERROR_OK;
|
||||
|
||||
MmsValue* optFlds;
|
||||
|
||||
if (self->isMulticast)
|
||||
optFlds = IedConnection_readObject(self->connection, &(self->lastError), refBuf, IEC61850_FC_MS);
|
||||
else
|
||||
optFlds = IedConnection_readObject(self->connection, &(self->lastError), refBuf, IEC61850_FC_US);
|
||||
|
||||
if (optFlds == NULL)
|
||||
return 0;
|
||||
|
||||
int retVal = 0;
|
||||
|
||||
if (MmsValue_getType(optFlds) == MMS_BIT_STRING)
|
||||
retVal = MmsValue_getBitStringAsInteger(optFlds);
|
||||
|
||||
MmsValue_delete(optFlds);
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
uint8_t
|
||||
ClientSVControlBlock_getSmpMod(ClientSVControlBlock self)
|
||||
{
|
||||
return readUIntVariable(self, "SmpMod");
|
||||
}
|
||||
|
||||
int
|
||||
ClientSVControlBlock_getNoASDU(ClientSVControlBlock self)
|
||||
{
|
||||
return readUIntVariable(self, "noASDU");
|
||||
}
|
||||
|
||||
PhyComAddress
|
||||
ClientSVControlBlock_getDstAddress(ClientSVControlBlock self)
|
||||
{
|
||||
char refBuf[130];
|
||||
|
||||
strcpy(refBuf, self->reference);
|
||||
strcat(refBuf, ".");
|
||||
strcat(refBuf, "DstAddress");
|
||||
|
||||
self->lastError = IED_ERROR_OK;
|
||||
|
||||
MmsValue* dstAddrValue;
|
||||
|
||||
if (self->isMulticast)
|
||||
dstAddrValue = IedConnection_readObject(self->connection, &(self->lastError), refBuf, IEC61850_FC_MS);
|
||||
else
|
||||
dstAddrValue = IedConnection_readObject(self->connection, &(self->lastError), refBuf, IEC61850_FC_US);
|
||||
|
||||
PhyComAddress retVal;
|
||||
memset(&retVal, 0, sizeof(retVal));
|
||||
|
||||
if (dstAddrValue == NULL) goto exit_error;
|
||||
|
||||
if (MmsValue_getType(dstAddrValue) != MMS_STRUCTURE) {
|
||||
if (DEBUG_IED_CLIENT) printf("IED_CLIENT: SVCB - addr has wrong type\n");
|
||||
goto exit_cleanup;
|
||||
}
|
||||
|
||||
if (MmsValue_getArraySize(dstAddrValue) != 4) {
|
||||
if (DEBUG_IED_CLIENT) printf("IED_CLIENT: SVCB - addr has wrong type\n");
|
||||
goto exit_cleanup;
|
||||
}
|
||||
|
||||
MmsValue* addr = MmsValue_getElement(dstAddrValue, 0);
|
||||
|
||||
if (MmsValue_getType(addr) != MMS_OCTET_STRING) {
|
||||
if (DEBUG_IED_CLIENT) printf("IED_CLIENT: SVCB - addr has wrong type\n");
|
||||
goto exit_cleanup;
|
||||
}
|
||||
|
||||
if (MmsValue_getOctetStringSize(addr) != 6) {
|
||||
if (DEBUG_IED_CLIENT) printf("IED_CLIENT: SVCB - addr has wrong size\n");
|
||||
goto exit_cleanup;
|
||||
}
|
||||
|
||||
uint8_t* addrBuf = MmsValue_getOctetStringBuffer(addr);
|
||||
|
||||
memcpy(&(retVal.dstAddress), addrBuf, 6);
|
||||
|
||||
MmsValue* prio = MmsValue_getElement(dstAddrValue, 1);
|
||||
|
||||
if (MmsValue_getType(prio) != MMS_UNSIGNED) {
|
||||
if (DEBUG_IED_CLIENT) printf("IED_CLIENT: SVCB - prio has wrong type\n");
|
||||
goto exit_cleanup;
|
||||
}
|
||||
|
||||
retVal.vlanPriority = MmsValue_toUint32(prio);
|
||||
|
||||
MmsValue* vid = MmsValue_getElement(dstAddrValue, 2);
|
||||
|
||||
if (MmsValue_getType(vid) != MMS_UNSIGNED) {
|
||||
if (DEBUG_IED_CLIENT) printf("IED_CLIENT: SVCB - vid has wrong type\n");
|
||||
goto exit_cleanup;
|
||||
}
|
||||
|
||||
retVal.vlanId = MmsValue_toUint32(vid);
|
||||
|
||||
MmsValue* appID = MmsValue_getElement(dstAddrValue, 3);
|
||||
|
||||
if (MmsValue_getType(appID) != MMS_UNSIGNED) {
|
||||
if (DEBUG_IED_CLIENT) printf("IED_CLIENT: SVCB - appID has wrong type\n");
|
||||
goto exit_cleanup;
|
||||
}
|
||||
|
||||
retVal.appId = MmsValue_toUint32(appID);
|
||||
|
||||
|
||||
exit_cleanup:
|
||||
MmsValue_delete(dstAddrValue);
|
||||
|
||||
exit_error:
|
||||
return retVal;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* mms_sv.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 LIBIEC61850_SRC_IEC61850_INC_PRIVATE_MMS_SV_H_
|
||||
#define LIBIEC61850_SRC_IEC61850_INC_PRIVATE_MMS_SV_H_
|
||||
|
||||
|
||||
typedef struct sMmsSampledValueControlBlock* MmsSampledValueControlBlock;
|
||||
|
||||
MmsSampledValueControlBlock
|
||||
MmsSampledValueControlBlock_create(void);
|
||||
|
||||
void
|
||||
MmsSampledValueControlBlock_destroy(MmsSampledValueControlBlock self);
|
||||
|
||||
MmsVariableSpecification*
|
||||
LIBIEC61850_SV_createSVControlBlocks(MmsMapping* self, MmsDomain* domain,
|
||||
LogicalNode* logicalNode, int svCount, bool unicast);
|
||||
|
||||
MmsValue*
|
||||
LIBIEC61850_SV_readAccessSampledValueControlBlock(MmsMapping* self, MmsDomain* domain, char* variableIdOrig);
|
||||
|
||||
MmsDataAccessError
|
||||
LIBIEC61850_SV_writeAccessSVControlBlock(MmsMapping* self, MmsDomain* domain, char* variableIdOrig,
|
||||
MmsValue* value, MmsServerConnection connection);
|
||||
|
||||
#endif /* LIBIEC61850_SRC_IEC61850_INC_PRIVATE_MMS_SV_H_ */
|
@ -0,0 +1,493 @@
|
||||
/*
|
||||
* mms_sv.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"
|
||||
|
||||
#if (CONFIG_IEC61850_SAMPLED_VALUES_SUPPORT == 1)
|
||||
|
||||
#include "libiec61850_platform_includes.h"
|
||||
#include "mms_mapping.h"
|
||||
#include "linked_list.h"
|
||||
#include "array_list.h"
|
||||
|
||||
#include "mms_sv.h"
|
||||
|
||||
#include "mms_mapping_internal.h"
|
||||
|
||||
struct sMmsSampledValueControlBlock {
|
||||
char* name;
|
||||
|
||||
bool svEna;
|
||||
MmsServerConnection reservedByClient;
|
||||
|
||||
char* dstAddress;
|
||||
|
||||
MmsDomain* domain;
|
||||
LogicalNode* logicalNode;
|
||||
|
||||
MmsVariableSpecification* mmsType;
|
||||
MmsValue* mmsValue;
|
||||
|
||||
MmsValue* svEnaValue;
|
||||
MmsValue* resvValue;
|
||||
};
|
||||
|
||||
MmsSampledValueControlBlock
|
||||
MmsSampledValueControlBlock_create()
|
||||
{
|
||||
MmsSampledValueControlBlock self = (MmsSampledValueControlBlock) GLOBAL_CALLOC(1, sizeof(struct sMmsSampledValueControlBlock));
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
MmsSampledValueControlBlock_destroy(MmsSampledValueControlBlock self)
|
||||
{
|
||||
MmsValue_delete(self->mmsValue);
|
||||
|
||||
GLOBAL_FREEMEM(self);
|
||||
}
|
||||
|
||||
static MmsSampledValueControlBlock
|
||||
lookupSVCB(MmsMapping* self, MmsDomain* domain, char* lnName, char* objectName)
|
||||
{
|
||||
LinkedList element = LinkedList_getNext(self->svControls);
|
||||
|
||||
while (element != NULL) {
|
||||
MmsSampledValueControlBlock mmsSVCB = (MmsSampledValueControlBlock) element->data;
|
||||
|
||||
if (mmsSVCB->domain == domain) {
|
||||
if (strcmp(mmsSVCB->logicalNode->name, lnName) == 0) {
|
||||
if (strcmp(mmsSVCB->name, objectName) == 0) {
|
||||
return mmsSVCB;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
element = LinkedList_getNext(element);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
MmsSampledValueControlBlock_enable(MmsSampledValueControlBlock self)
|
||||
{
|
||||
//TODO call application callback handler
|
||||
self->svEna = true;
|
||||
MmsValue_setBoolean(self->svEnaValue, true);
|
||||
}
|
||||
|
||||
static void
|
||||
MmsSampledValueControlBlock_disable(MmsSampledValueControlBlock self)
|
||||
{
|
||||
//TODO call application callback handler
|
||||
self->svEna = false;
|
||||
MmsValue_setBoolean(self->svEnaValue, false);
|
||||
}
|
||||
|
||||
static bool
|
||||
MmsSampledValueControlBlock_isEnabled(MmsSampledValueControlBlock self)
|
||||
{
|
||||
return self->svEna;
|
||||
}
|
||||
|
||||
MmsDataAccessError
|
||||
LIBIEC61850_SV_writeAccessSVControlBlock(MmsMapping* self, MmsDomain* domain, char* variableIdOrig,
|
||||
MmsValue* value, MmsServerConnection connection)
|
||||
{
|
||||
char variableId[130];
|
||||
|
||||
strncpy(variableId, variableIdOrig, 129);
|
||||
|
||||
char* separator = strchr(variableId, '$');
|
||||
|
||||
*separator = 0;
|
||||
|
||||
char* lnName = variableId;
|
||||
|
||||
if (lnName == NULL)
|
||||
return DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED;
|
||||
|
||||
char* objectName = MmsMapping_getNextNameElement(separator + 1);
|
||||
|
||||
if (objectName == NULL)
|
||||
return DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED;
|
||||
|
||||
char* varName = MmsMapping_getNextNameElement(objectName);
|
||||
|
||||
if (varName != NULL)
|
||||
*(varName - 1) = 0;
|
||||
else
|
||||
return DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED;
|
||||
|
||||
MmsSampledValueControlBlock mmsSVCB = lookupSVCB(self, domain, lnName, objectName);
|
||||
|
||||
if (mmsSVCB == NULL)
|
||||
return DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED;
|
||||
|
||||
if (mmsSVCB->reservedByClient != NULL) {
|
||||
if (mmsSVCB->reservedByClient != connection)
|
||||
return DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE;
|
||||
}
|
||||
|
||||
if (strcmp(varName, "Resv") == 0) {
|
||||
if (MmsValue_getType(value) != MMS_BOOLEAN)
|
||||
return DATA_ACCESS_ERROR_TYPE_INCONSISTENT;
|
||||
|
||||
if (MmsValue_getBoolean(value)) {
|
||||
mmsSVCB->reservedByClient = connection;
|
||||
MmsValue_setBoolean(mmsSVCB->resvValue, true);
|
||||
}
|
||||
else {
|
||||
mmsSVCB->reservedByClient = NULL;
|
||||
MmsValue_setBoolean(mmsSVCB->resvValue, false);
|
||||
}
|
||||
|
||||
return DATA_ACCESS_ERROR_SUCCESS;
|
||||
}
|
||||
else if (strcmp(varName, "SvEna") == 0) {
|
||||
if (MmsValue_getType(value) != MMS_BOOLEAN)
|
||||
return DATA_ACCESS_ERROR_TYPE_INCONSISTENT;
|
||||
|
||||
if (MmsValue_getBoolean(value))
|
||||
MmsSampledValueControlBlock_enable(mmsSVCB);
|
||||
else
|
||||
MmsSampledValueControlBlock_disable(mmsSVCB);
|
||||
|
||||
return DATA_ACCESS_ERROR_SUCCESS;
|
||||
}
|
||||
else {
|
||||
if (MmsSampledValueControlBlock_isEnabled(mmsSVCB))
|
||||
return DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE;
|
||||
else {
|
||||
bool allowAccess = false;
|
||||
|
||||
// In 61850-9-2 mapping only Resv and SvEna are writable!
|
||||
|
||||
if (allowAccess)
|
||||
return DATA_ACCESS_ERROR_SUCCESS;
|
||||
else
|
||||
return DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MmsValue*
|
||||
LIBIEC61850_SV_readAccessSampledValueControlBlock(MmsMapping* self, MmsDomain* domain, char* variableIdOrig)
|
||||
{
|
||||
MmsValue* value = NULL;
|
||||
|
||||
char variableId[130];
|
||||
|
||||
strncpy(variableId, variableIdOrig, 129);
|
||||
|
||||
char* separator = strchr(variableId, '$');
|
||||
|
||||
*separator = 0;
|
||||
|
||||
char* lnName = variableId;
|
||||
|
||||
if (lnName == NULL)
|
||||
return NULL;
|
||||
|
||||
char* objectName = MmsMapping_getNextNameElement(separator + 1);
|
||||
|
||||
if (objectName == NULL)
|
||||
return NULL;
|
||||
|
||||
char* varName = MmsMapping_getNextNameElement(objectName);
|
||||
|
||||
if (varName != NULL)
|
||||
*(varName - 1) = 0;
|
||||
|
||||
MmsSampledValueControlBlock mmsSVCB = lookupSVCB(self, domain, lnName, objectName);
|
||||
|
||||
if (mmsSVCB != NULL) {
|
||||
if (varName != NULL) {
|
||||
value = MmsValue_getSubElement(mmsSVCB->mmsValue, mmsSVCB->mmsType, varName);
|
||||
}
|
||||
else {
|
||||
value = mmsSVCB->mmsValue;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
static SVControlBlock*
|
||||
getSVCBForLogicalNodeWithIndex(MmsMapping* self, LogicalNode* logicalNode, int index, bool isUnicast)
|
||||
{
|
||||
int svCount = 0;
|
||||
|
||||
SVControlBlock* svcb = self->model->svCBs;
|
||||
|
||||
/* Iterate list of SvCBs */
|
||||
while (svcb != NULL ) {
|
||||
if ((svcb->parent == logicalNode) && (svcb->isUnicast == isUnicast)) {
|
||||
if (svCount == index)
|
||||
return svcb;
|
||||
|
||||
svCount++;
|
||||
}
|
||||
|
||||
svcb = svcb->sibling;
|
||||
}
|
||||
|
||||
return NULL ;
|
||||
}
|
||||
|
||||
|
||||
static MmsVariableSpecification*
|
||||
createSVControlBlockMmsStructure(char* gcbName, bool isUnicast)
|
||||
{
|
||||
MmsVariableSpecification* gcb = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
|
||||
gcb->name = copyString(gcbName);
|
||||
gcb->type = MMS_STRUCTURE;
|
||||
|
||||
int elementCount;
|
||||
|
||||
if (isUnicast)
|
||||
elementCount = 10;
|
||||
else
|
||||
elementCount = 9;
|
||||
|
||||
gcb->typeSpec.structure.elementCount = elementCount;
|
||||
gcb->typeSpec.structure.elements = (MmsVariableSpecification**)
|
||||
GLOBAL_CALLOC(elementCount, sizeof(MmsVariableSpecification*));
|
||||
|
||||
int currentElement = 0;
|
||||
|
||||
MmsVariableSpecification* namedVariable;
|
||||
|
||||
namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
|
||||
namedVariable->name = copyString("SvEna");
|
||||
namedVariable->type = MMS_BOOLEAN;
|
||||
|
||||
gcb->typeSpec.structure.elements[currentElement++] = namedVariable;
|
||||
|
||||
if (isUnicast) {
|
||||
namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
|
||||
namedVariable->name = copyString("Resv");
|
||||
namedVariable->type = MMS_BOOLEAN;
|
||||
|
||||
gcb->typeSpec.structure.elements[currentElement++] = namedVariable;
|
||||
}
|
||||
|
||||
namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
|
||||
if (isUnicast)
|
||||
namedVariable->name = copyString("UsvID");
|
||||
else
|
||||
namedVariable->name = copyString("MsvID");
|
||||
namedVariable->typeSpec.visibleString = -129;
|
||||
namedVariable->type = MMS_VISIBLE_STRING;
|
||||
|
||||
gcb->typeSpec.structure.elements[currentElement++] = namedVariable;
|
||||
|
||||
namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
|
||||
namedVariable->name = copyString("DatSet");
|
||||
namedVariable->typeSpec.visibleString = -129;
|
||||
namedVariable->type = MMS_VISIBLE_STRING;
|
||||
|
||||
gcb->typeSpec.structure.elements[currentElement++] = namedVariable;
|
||||
|
||||
namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
|
||||
namedVariable->name = copyString("ConfRev");
|
||||
namedVariable->type = MMS_INTEGER;
|
||||
namedVariable->typeSpec.integer = 32;
|
||||
|
||||
gcb->typeSpec.structure.elements[currentElement++] = namedVariable;
|
||||
|
||||
namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
|
||||
namedVariable->name = copyString("SmpRate");
|
||||
namedVariable->type = MMS_INTEGER;
|
||||
namedVariable->typeSpec.unsignedInteger = 32;
|
||||
|
||||
gcb->typeSpec.structure.elements[currentElement++] = namedVariable;
|
||||
|
||||
namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
|
||||
namedVariable->name = copyString("OptFlds");
|
||||
namedVariable->type = MMS_BIT_STRING;
|
||||
namedVariable->typeSpec.bitString = 5;
|
||||
|
||||
gcb->typeSpec.structure.elements[currentElement++] = namedVariable;
|
||||
|
||||
namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
|
||||
namedVariable->name = copyString("SmpMod");
|
||||
namedVariable->type = MMS_INTEGER;
|
||||
namedVariable->typeSpec.integer = 8;
|
||||
|
||||
gcb->typeSpec.structure.elements[currentElement++] = namedVariable;
|
||||
|
||||
namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
|
||||
namedVariable->name = copyString("DstAddress");
|
||||
MmsMapping_createPhyComAddrStructure(namedVariable);
|
||||
|
||||
gcb->typeSpec.structure.elements[currentElement++] = namedVariable;
|
||||
|
||||
namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
|
||||
namedVariable->name = copyString("noASDU");
|
||||
namedVariable->type = MMS_INTEGER;
|
||||
namedVariable->typeSpec.integer = 32;
|
||||
|
||||
gcb->typeSpec.structure.elements[currentElement++] = namedVariable;
|
||||
|
||||
return gcb;
|
||||
}
|
||||
|
||||
static void
|
||||
createDataSetReference(char* buffer, char* domainName, char* lnName, char* dataSetName)
|
||||
{
|
||||
StringUtils_createStringInBuffer(buffer, 5, domainName, "/", lnName, "$", dataSetName);
|
||||
}
|
||||
|
||||
MmsVariableSpecification*
|
||||
LIBIEC61850_SV_createSVControlBlocks(MmsMapping* self, MmsDomain* domain,
|
||||
LogicalNode* logicalNode, int svCount, bool unicast)
|
||||
{
|
||||
MmsVariableSpecification* namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1,
|
||||
sizeof(MmsVariableSpecification));
|
||||
|
||||
if (unicast)
|
||||
namedVariable->name = copyString("US");
|
||||
else
|
||||
namedVariable->name = copyString("MS");
|
||||
|
||||
namedVariable->type = MMS_STRUCTURE;
|
||||
|
||||
namedVariable->typeSpec.structure.elementCount = svCount;
|
||||
namedVariable->typeSpec.structure.elements = (MmsVariableSpecification**) GLOBAL_CALLOC(svCount,
|
||||
sizeof(MmsVariableSpecification*));
|
||||
|
||||
int currentSVCB = 0;
|
||||
|
||||
char dataRefBuffer[130];
|
||||
|
||||
while (currentSVCB < svCount) {
|
||||
SVControlBlock* svControlBlock = getSVCBForLogicalNodeWithIndex(
|
||||
self, logicalNode, currentSVCB, unicast);
|
||||
|
||||
MmsVariableSpecification* svTypeSpec = createSVControlBlockMmsStructure(svControlBlock->name, unicast);
|
||||
|
||||
MmsValue* svValues = MmsValue_newStructure(svTypeSpec);
|
||||
|
||||
namedVariable->typeSpec.structure.elements[currentSVCB] = svTypeSpec;
|
||||
|
||||
int currentIndex = 0;
|
||||
|
||||
/* SvEna */
|
||||
MmsValue* svEna = MmsValue_getElement(svValues, currentIndex++);
|
||||
|
||||
MmsValue* resv = NULL;
|
||||
|
||||
if (unicast) {
|
||||
/* Resv */
|
||||
resv = MmsValue_getElement(svValues, currentIndex++);
|
||||
}
|
||||
|
||||
|
||||
/* SvID */
|
||||
MmsValue* svID = MmsValue_getElement(svValues, currentIndex++);
|
||||
MmsValue_setVisibleString(svID, svControlBlock->svId);
|
||||
|
||||
/* DatSet */
|
||||
MmsValue* dataSetRef = MmsValue_getElement(svValues, currentIndex++);
|
||||
|
||||
createDataSetReference(dataRefBuffer, MmsDomain_getName(domain),
|
||||
logicalNode->name, svControlBlock->dataSetName);
|
||||
|
||||
MmsValue_setVisibleString(dataSetRef, dataRefBuffer);
|
||||
|
||||
/* ConfRev */
|
||||
MmsValue* confRev = MmsValue_getElement(svValues, currentIndex++);
|
||||
MmsValue_setInt32(confRev, svControlBlock->confRev);
|
||||
|
||||
/* SmpRate */
|
||||
MmsValue* smpRate = MmsValue_getElement(svValues, currentIndex++);
|
||||
MmsValue_setInt32(smpRate, svControlBlock->smpRate);
|
||||
|
||||
/* OptFlds */
|
||||
MmsValue* optFlds = MmsValue_getElement(svValues, currentIndex++);
|
||||
MmsValue_setBitStringFromInteger(optFlds, svControlBlock->optFlds);
|
||||
|
||||
/* SmpMod */
|
||||
MmsValue* smpMod = MmsValue_getElement(svValues, currentIndex++);
|
||||
MmsValue_setInt32(smpMod, svControlBlock->smpMod);
|
||||
|
||||
/* Set communication parameters - DstAddress */
|
||||
uint8_t priority = CONFIG_GOOSE_DEFAULT_PRIORITY;
|
||||
uint8_t dstAddr[] = CONFIG_GOOSE_DEFAULT_DST_ADDRESS;
|
||||
uint16_t vid = CONFIG_GOOSE_DEFAULT_VLAN_ID;
|
||||
uint16_t appId = CONFIG_GOOSE_DEFAULT_APPID;
|
||||
|
||||
if (svControlBlock->dstAddress != NULL) {
|
||||
priority = svControlBlock->dstAddress->vlanPriority;
|
||||
vid = svControlBlock->dstAddress->vlanId;
|
||||
appId = svControlBlock->dstAddress->appId;
|
||||
|
||||
int i;
|
||||
for (i = 0; i < 6; i++) {
|
||||
dstAddr[i] = svControlBlock->dstAddress->dstAddress[i];
|
||||
}
|
||||
}
|
||||
|
||||
MmsValue* dstAddress = MmsValue_getElement(svValues, currentIndex++);
|
||||
|
||||
MmsValue* addr = MmsValue_getElement(dstAddress, 0);
|
||||
MmsValue_setOctetString(addr, dstAddr, 6);
|
||||
|
||||
MmsValue* prio = MmsValue_getElement(dstAddress, 1);
|
||||
MmsValue_setUint8(prio, priority);
|
||||
|
||||
MmsValue* vlanId = MmsValue_getElement(dstAddress, 2);
|
||||
MmsValue_setUint16(vlanId, vid);
|
||||
|
||||
MmsValue* appIdVal = MmsValue_getElement(dstAddress, 3);
|
||||
MmsValue_setUint16(appIdVal, appId);
|
||||
|
||||
/* noASDU */
|
||||
MmsValue* noASDU = MmsValue_getElement(svValues, currentIndex++);
|
||||
MmsValue_setInt32(noASDU, svControlBlock->noASDU);
|
||||
|
||||
MmsSampledValueControlBlock mmsSvCb = MmsSampledValueControlBlock_create();
|
||||
|
||||
mmsSvCb->mmsValue = svValues;
|
||||
mmsSvCb->svEnaValue = svEna;
|
||||
mmsSvCb->resvValue = resv;
|
||||
mmsSvCb->mmsType = svTypeSpec;
|
||||
mmsSvCb->domain = domain;
|
||||
mmsSvCb->logicalNode = logicalNode;
|
||||
mmsSvCb->name = svControlBlock->name;
|
||||
|
||||
LinkedList_add(self->svControls, (void*) mmsSvCb);
|
||||
|
||||
currentSVCB++;
|
||||
}
|
||||
|
||||
return namedVariable;
|
||||
}
|
||||
|
||||
#endif /* (CONFIG_IEC61850_SAMPLED_VALUES_SUPPORT == 1) */
|
@ -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_CALLOC(1, 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,376 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
/**
|
||||
* \defgroup sv_subscriber_api_group IEC 61850 sampled values (SV) subscriber API
|
||||
*
|
||||
* The sampled values (SV) subscriber API consists of three different objects.
|
||||
* The \ref SVReceiver object is responsible for handling all SV Ethernet messages
|
||||
* for a specific Ethernet interface. If you want to receive SV messages on multiple
|
||||
* Ethernet interfaces you have to use several \ref SVReceiver instances.
|
||||
* An \ref SVSubscriber object is associated to a SV data stream that is identified by its appID
|
||||
* and destination Ethernet address. The \reg SVSubscriber object is used to install a callback
|
||||
* handler \ref SVUpdateListener that is invoked for each ASDU (application service data unit) received for the
|
||||
* associated stream. An \ref SVClientASDU is an object that represents a single ASDU. Each ASDU contains
|
||||
* some meta information that can be obtained by specific access functions like e.g.
|
||||
* \ref SVClientASDU_getSmpCnt to access the "SmpCnt" (sample count) attribute of the ASDU. The actual
|
||||
* measurement data contained in the ASDU does not consist of structured ASN.1 data but stored as raw binary
|
||||
* data. Without a priori knowledge of the dataset associated with the ASDU data stream it is not
|
||||
* possible to interpret the received data correctly. Therefore you have to provide the data access functions
|
||||
* with an index value to indicate the data type and the start of the data in the data block of the ASDU.
|
||||
* E.g. reading a data set consisting of two FLOAT32 values you can use two subsequent calls of
|
||||
* \ref SVClientASDU_getFLOAT32 one with index = 0 and the second one with index = 4.
|
||||
*
|
||||
* | IEC 61850 type | required bytes |
|
||||
* | -------------- | -------------- |
|
||||
* | BOOLEAN | 1 byte |
|
||||
* | INT8 | 1 byte |
|
||||
* | INT16 | 2 byte |
|
||||
* | INT32 | 4 byte |
|
||||
* | INT64 | 8 byte |
|
||||
* | INT8U | 1 byte |
|
||||
* | INT16U | 2 byte |
|
||||
* | INT24U | 3 byte |
|
||||
* | INT32U | 4 byte |
|
||||
* | FLOAT32 | 4 byte |
|
||||
* | FLOAT64 | 8 byte |
|
||||
* | ENUMERATED | 4 byte |
|
||||
* | CODED ENUM | 4 byte |
|
||||
* | OCTET STRING | 20 byte |
|
||||
* | VISIBLE STRING | 35 byte |
|
||||
* | TimeStamp | 8 byte |
|
||||
* | EntryTime | 6 byte |
|
||||
* | BITSTRING | 4 byte |
|
||||
*
|
||||
* The SV subscriber API can be used independent of the IEC 61850 client API. In order to access the SVCB via MMS you
|
||||
* have to use the IEC 61850 client API. Please see \ref ClientSVControlBlock object in section \ref IEC61850_CLIENT_SV.
|
||||
*
|
||||
*/
|
||||
/**@{*/
|
||||
|
||||
|
||||
/**
|
||||
* \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_ */
|
@ -0,0 +1,2 @@
|
||||
For GOOSE support add winpcap source and headers here. Rerun cmake to configure the build system for GOOSE support.
|
||||
|
@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
|
||||
mkdir build
|
||||
|
||||
find src/ -name "*.java" > listFile.tmp
|
||||
|
||||
javac -target 1.6 -source 1.6 -d build @listFile.tmp
|
||||
|
||||
jar cfm modelviewer.jar manifest-modelviewer.mf -C build/ com/
|
||||
|
||||
rm listFile.tmp
|
||||
rm -r build
|
Binary file not shown.
Binary file not shown.
@ -0,0 +1,2 @@
|
||||
Manifest-Version: 1.0
|
||||
Main-Class: com.libiec61850.tools.ModelViewer
|
Binary file not shown.
@ -0,0 +1,61 @@
|
||||
package com.libiec61850.scl.communication;
|
||||
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import com.libiec61850.scl.ParserUtils;
|
||||
import com.libiec61850.scl.SclParserException;
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
public class SMV {
|
||||
private String ldInst;
|
||||
private String cbName;
|
||||
|
||||
private PhyComAddress address;
|
||||
|
||||
public SMV(Node gseNode) throws SclParserException {
|
||||
ldInst = ParserUtils.parseAttribute(gseNode, "ldInst");
|
||||
cbName = ParserUtils.parseAttribute(gseNode, "cbName");
|
||||
|
||||
if ((ldInst == null) || (cbName == null))
|
||||
throw new SclParserException(gseNode, "SMV is missing required attribute");
|
||||
|
||||
Node addressNode = ParserUtils.getChildNodeWithTag(gseNode, "Address");
|
||||
|
||||
if (addressNode == null)
|
||||
throw new SclParserException(gseNode, "SMV is missing address definition!");
|
||||
|
||||
address = new PhyComAddress(addressNode);
|
||||
}
|
||||
|
||||
public String getLdInst() {
|
||||
return ldInst;
|
||||
}
|
||||
|
||||
public String getCbName() {
|
||||
return cbName;
|
||||
}
|
||||
|
||||
public PhyComAddress getAddress() {
|
||||
return address;
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue