diff --git a/Dockerfile.dev b/Dockerfile.dev index 452a7c6a0..7c56160be 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -16,8 +16,7 @@ FROM fedora:latest MAINTAINER Steffen Vogel # Toolchain -RUN dnf -y update && \ - dnf -y install \ +RUN dnf -y install \ gcc gcc-c++ \ pkgconfig make cmake \ autoconf automake autogen libtool \ @@ -25,8 +24,7 @@ RUN dnf -y update && \ texinfo git # Dependencies -RUN dnf -y update && \ - dnf -y install \ +RUN dnf -y install \ openssl openssl-devel \ libconfig-devel \ libnl3-devel \ @@ -34,8 +32,7 @@ RUN dnf -y update && \ jansson-devel # Several tools only needed for developement and testing -RUN dnf -y update && \ - dnf -y install \ +RUN dnf -y install \ doxygen dia graphviz \ openssh-clients \ rpmdevtools rpm-build \ @@ -45,6 +42,12 @@ RUN dnf -y update && \ valgrind \ gdb +# 32bit versions of some standard libraries for RT-LAB code +RUN dnf -y install \ + libstdc++-devel.i686 \ + libuuid-devel.i686 \ + glibc-devel.i686 + # Tools for debugging, coverage, profiling RUN pip install \ gcovr diff --git a/Makefile b/Makefile index a29611331..5d40c3f88 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,7 @@ ################################################################################### # Project modules -MODULES = lib plugins src tests thirdparty tools packaging doc etc web +MODULES = clients lib plugins src tests thirdparty tools packaging doc etc web # Default prefix for install target PREFIX ?= /usr/local diff --git a/clients/Makefile.inc b/clients/Makefile.inc new file mode 100644 index 000000000..94fb31e70 --- /dev/null +++ b/clients/Makefile.inc @@ -0,0 +1,20 @@ +ASYNCIP_PATH = $(SRCDIR)/clients/opal/villas_udp/models/send_receive + +ASYNCIP_OPTS = RTLAB_INTEL_COMPILER=0 \ + PROTOCOL=GTNET_SKT \ + OPAL_LIBS="-lSystem -luuid" \ + OPAL_LIBPATH=-L$(SRCDIR)/thirdparty/libopal/ \ + OPAL_INCPATH=-I$(SRCDIR)/thirdparty/libopal/include/opal + +clients: clients-opal + +clients-opal: + $(MAKE) -C $(ASYNCIP_PATH) -f Makefile.mk AsyncIP $(ASYNCIP_OPTS) + +clean-clients: + $(MAKE) -C $(ASYNCIP_PATH) -f Makefile.mk clean $(ASYNCIP_OPTS) + +install-clients: + $(MAKE) -C $(ASYNCIP_PATH) -f Makefile.mk install $(ASYNCIP_OPTS) + +.PHONY: clients clean-clients install-clients \ No newline at end of file diff --git a/clients/opal/README.md b/clients/opal/README.md index 629f87b1a..ee8f73aa3 100644 --- a/clients/opal/README.md +++ b/clients/opal/README.md @@ -1,14 +1,20 @@ # Asynchronous Process interface to VILLASnode / GTNET-SKT -To "models" folder of OPAL project folder copy: -folder: include -folder: src -file: villas.mk +## Add AsyncIP to new project ----------------------------------------------- +#### Step 1 -.llm file should contain the following: -note: path to libOpalAsyncApiCore.a depends on version of RT-Lab +Copy the following files to the _models_ folder of the RT-LAB project: + +- Folder: `include/` +- Folder: `src/` +- File: `Makefile.mk` + +#### Step 2 + +The `.llm` file should contain the following lines: + +**Note:** path to libOpalAsyncApiCore.a depends on version of RT-Lab ``` [ExtraPutFilesComp] @@ -18,27 +24,32 @@ include\msg.h=Ascii include\msg_format.h=Ascii include\socket.h=Ascii include\utils.h=Ascii -villas.mk=Ascii +Makefile.mk=Ascii src\msg.c=Ascii src\main.c=Ascii src\socket.c=Ascii src\utils.c=Ascii +src\compat.c=Ascii ``` --------------------------------------------------- +#### Step 3 In RT-Lab under Files tab, we should see the files listed above for .llm file --------------------------------------------------- +#### Step 4 -Development tab -> Compiler -> Compiler Command (makefile) add the following command +In RT-LAB model settings: Development tab -> Compiler -> Compiler Command (makefile) add the following command + +``` /usr/bin/make -f /usr/opalrt/common/bin/opalmodelmk +``` --------------------------------------------------- +#### Step 5 -max umber of values in UDP packets: -there’s a „#define“ inside the implementation which must be changed accordingly. -The #define is in file: model_directory/include/config.h There you will find a directive called MAX_VALUES. +Maximum number of values in UDP packets: + +There’s a `#define` inside the implementation which must be changed accordingly. +The #define is in file: `model_directory/include/config.h` There you will find a directive called MAX_VALUES. # Troubleshooting diff --git a/clients/opal/udp/models/send_receive/include/config.h b/clients/opal/udp/models/send_receive/include/config.h deleted file mode 100644 index cbf09b8af..000000000 --- a/clients/opal/udp/models/send_receive/include/config.h +++ /dev/null @@ -1,16 +0,0 @@ -/** Compiled-in settings - * - * @author Steffen Vogel - * @copyright 2014, Institute for Automation of Complex Power Systems, EONERC - * @file - */ - -#ifndef _CONFIG_H_ -#define _CONFIG_H_ - -#define PROGNAME "VILLASnode-OPAL-UDP" -#define VERSION "0.5" - -#define MAX_VALUES 64 - -#endif /* _CONFIG_H_ */ \ No newline at end of file diff --git a/clients/opal/udp/models/send_receive/include/msg.h b/clients/opal/udp/models/send_receive/include/msg.h deleted file mode 100644 index ee00377ab..000000000 --- a/clients/opal/udp/models/send_receive/include/msg.h +++ /dev/null @@ -1,37 +0,0 @@ -/** Message related functions. - * - * @file - * @author Steffen Vogel - * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC - *********************************************************************************/ - -#ifndef _MSG_H_ -#define _MSG_H_ - -#include "msg_format.h" - -/** Swaps message contents byte-order. - * - * Message can either be transmitted in little or big endian - * format. The actual endianess for a message is defined by the - * msg::endian field. This covers msg::length, msg::sequence, msg::data and msg::ts fields. - * Received message are usally converted to the endianess of the host. - * This is required for further sanity checks of the sequence number - * or parsing of the data. - * - * @param m A pointer to the message - */ -void msg_swap(struct msg *m); - -/** Check the consistency of a message. - * - * The functions checks the header fields of a message. - * - * @param m A pointer to the message - * @retval 0 The message header is valid. - * @retval <0 The message header is invalid. - */ -int msg_verify(struct msg *m); - -#endif /* _MSG_H_ */ - diff --git a/clients/opal/udp/models/send_receive/include/msg_format.h b/clients/opal/udp/models/send_receive/include/msg_format.h deleted file mode 100644 index 2b1b5cc79..000000000 --- a/clients/opal/udp/models/send_receive/include/msg_format.h +++ /dev/null @@ -1,97 +0,0 @@ -/** Message format - * - * @file - * @author Steffen Vogel - * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC - *********************************************************************************/ - -#ifndef _MSG_FORMAT_H_ -#define _MSG_FORMAT_H_ - -#include - -#ifdef __linux__ - #define _BSD_SOURCE 1 - #include -#elif defined(__PPC__) /* Xilinx toolchain */ - #include -#endif - -#include "config.h" - -/** Maximum number of dword values in a message */ -#define MSG_VALUES MAX_VALUES - -/** The current version number for the message format */ -#define MSG_VERSION 1 - -/** @todo Implement more message types */ -#define MSG_TYPE_DATA 0 /**< Message contains float values */ -#define MSG_TYPE_START 1 /**< Message marks the beginning of a new simulation case */ -#define MSG_TYPE_STOP 2 /**< Message marks the end of a simulation case */ - -#define MSG_ENDIAN_LITTLE 0 /**< Message values are in little endian format (float too!) */ -#define MSG_ENDIAN_BIG 1 /**< Message values are in bit endian format */ - -#if BYTE_ORDER == LITTLE_ENDIAN - #define MSG_ENDIAN_HOST MSG_ENDIAN_LITTLE -#elif BYTE_ORDER == BIG_ENDIAN - #define MSG_ENDIAN_HOST MSG_ENDIAN_BIG -#else - #error "Unknown byte order!" -#endif - -/** The total length of a message */ -#define MSG_LEN(msg) (4 * ((msg)->length + 4)) - -#define MSG_TS(msg) (struct timespec) { \ - .tv_sec = (msg)->ts.sec, \ - .tv_nsec = (msg)->ts.nsec \ -} - -/** Initialize a message */ -#define MSG_INIT(i) (struct msg) { \ - .version = MSG_VERSION, \ - .type = MSG_TYPE_DATA, \ - .endian = MSG_ENDIAN_HOST, \ - .length = i, \ - .sequence = 0, \ - .rsvd1 = 0, .rsvd2 = 0 \ -} - -/** This message format is used by all clients - * - * @diafile msg_format.dia - **/ -struct msg -{ -#if BYTE_ORDER == BIG_ENDIAN - unsigned version: 4; /**< Specifies the format of the remaining message (see MGS_VERSION) */ - unsigned type : 2; /**< Data or control message (see MSG_TYPE_*) */ - unsigned endian : 1; /**< Specifies the byteorder of the message (see MSG_ENDIAN_*) */ - unsigned rsvd1 : 1; /**< Reserved bits */ -#elif BYTE_ORDER == LITTLE_ENDIAN - unsigned rsvd1 : 1; /**< Reserved bits */ - unsigned endian : 1; /**< Specifies the byteorder of the message (see MSG_ENDIAN_*) */ - unsigned type : 2; /**< Data or control message (see MSG_TYPE_*) */ - unsigned version: 4; /**< Specifies the format of the remaining message (see MGS_VERSION) */ -#endif - unsigned rsvd2 : 8; /**< Reserved bits */ - - uint16_t length; /**< The number of values in msg::data[]. Endianess is specified in msg::endian. */ - uint32_t sequence; /**< The sequence number is incremented by one for consecutive messages. Endianess is specified in msg::endian. */ - - /** A timestamp per message. Endianess is specified in msg::endian. */ - struct { - uint32_t sec; /**< Seconds since 1970-01-01 00:00:00 */ - uint32_t nsec; /**< Nanoseconds of the current second. */ - } ts; - - /** The message payload. Endianess is specified in msg::endian. */ - union { - float f; /**< Floating point values (note msg::endian) */ - uint32_t i; /**< Integer values (note msg::endian) */ - } data[MSG_VALUES]; -} __attribute__((aligned(64), packed)); - -#endif /* _MSG_FORMAT_H_ */ diff --git a/clients/opal/udp/models/send_receive/include/socket.h b/clients/opal/udp/models/send_receive/include/socket.h deleted file mode 100644 index 336e18eb2..000000000 --- a/clients/opal/udp/models/send_receive/include/socket.h +++ /dev/null @@ -1,33 +0,0 @@ -/** Helper functions for socket - * - * Code example of an asynchronous program. This program is started - * by the asynchronous controller and demonstrates how to send and - * receive data to and from the asynchronous icons and a UDP or TCP - * port. - * - * @author Steffen Vogel - * @author Mathieu Dubé-Dallaire - * @copyright 2014, Institute for Automation of Complex Power Systems, EONERC - * @copyright 2003, OPAL-RT Technologies inc - * @file - */ - -#ifndef _SOCKET_H_ -#define _SOCKET_H_ - -#define RT -#include "OpalGenAsyncParamCtrl.h" - -#define UDP_PROTOCOL 1 -#define TCP_PROTOCOL 2 -#define EOK 0 - -int InitSocket(Opal_GenAsyncParam_Ctrl IconCtrlStruct); - -int SendPacket(char* DataSend, int datalength); - -int RecvPacket(char* DataRecv, int datalength, double timeout); - -int CloseSocket(Opal_GenAsyncParam_Ctrl IconCtrlStruct); - -#endif /* _SOCKET_H_ */ diff --git a/clients/opal/udp/models/send_receive/src/msg.c b/clients/opal/udp/models/send_receive/src/msg.c deleted file mode 100644 index dd472514e..000000000 --- a/clients/opal/udp/models/send_receive/src/msg.c +++ /dev/null @@ -1,42 +0,0 @@ -/** Message related functions. - * - * @author Steffen Vogel - * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC - *********************************************************************************/ - -#ifdef __linux__ - #include -#elif defined(__PPC__) /* Xilinx toolchain */ - #include - #define bswap_16(x) Xil_EndianSwap16(x) - #define bswap_32(x) Xil_EndianSwap32(x) -#endif - -#include "msg.h" - -void msg_swap(struct msg *m) -{ - m->length = bswap_16(m->length); - m->sequence = bswap_32(m->sequence); - m->ts.sec = bswap_32(m->ts.sec); - m->ts.nsec = bswap_32(m->ts.nsec); - - for (int i = 0; i < m->length; i++) - m->data[i].i = bswap_32(m->data[i].i); - - m->endian ^= 1; -} - -int msg_verify(struct msg *m) -{ - if (m->version != MSG_VERSION) - return -1; - else if (m->type != MSG_TYPE_DATA) - return -2; - else if ((m->length <= 0) || (m->length > MSG_VALUES)) - return -3; - else if ((m->rsvd1 != 0) || (m->rsvd2 != 0)) - return -4; - else - return 0; -} \ No newline at end of file diff --git a/clients/opal/udp/models/send_receive/src/socket.c b/clients/opal/udp/models/send_receive/src/socket.c deleted file mode 100644 index 298404a51..000000000 --- a/clients/opal/udp/models/send_receive/src/socket.c +++ /dev/null @@ -1,225 +0,0 @@ -/** Helper functions for socket - * - * Code example of an asynchronous program. This program is started - * by the asynchronous controller and demonstrates how to send and - * receive data to and from the asynchronous icons and a UDP or TCP - * port. - * - * @author Steffen Vogel - * @author Mathieu Dubé-Dallaire - * @copyright 2014, Institute for Automation of Complex Power Systems, EONERC - * @copyright 2003, OPAL-RT Technologies inc - * @file - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* Define RTLAB before including OpalPrint.h for messages to be sent - * to the OpalDisplay. Otherwise stdout will be used. */ -#define RTLAB -#include "OpalPrint.h" -#include "AsyncApi.h" - -#include "config.h" -#include "socket.h" - -/* Globals variables */ -struct sockaddr_in send_ad; /* Send address */ -struct sockaddr_in recv_ad; /* Receive address */ -int sd = -1; /* socket descriptor */ -int proto = UDP_PROTOCOL; - -int InitSocket(Opal_GenAsyncParam_Ctrl IconCtrlStruct) -{ - struct ip_mreq mreq; /* Multicast group structure */ - int socket_type; - int socket_proto; - unsigned char TTL = 1; - unsigned char LOOP = 0; - int rc; - - proto = (int) IconCtrlStruct.FloatParam[0]; - OpalPrint("%s: Version : %s\n", PROGNAME, VERSION); - - switch (proto) { - case UDP_PROTOCOL: /* Communication using UDP/IP protocol */ - socket_proto = IPPROTO_UDP; - socket_type = SOCK_DGRAM; - OpalPrint("%s: Protocol : UDP/IP\n", PROGNAME); - break; - - case TCP_PROTOCOL: /* Communication using TCP/IP protocol */ - socket_proto = IPPROTO_IP; - socket_type = SOCK_STREAM; - OpalPrint("%s: Protocol : TCP/IP\n", PROGNAME); - break; - - default: /* Protocol is not recognized */ - OpalPrint("%s: ERROR: Protocol (%d) not supported!\n", PROGNAME, proto); - return EINVAL; - } - - OpalPrint("%s: Remote Address : %s\n", PROGNAME, IconCtrlStruct.StringParam[0]); - OpalPrint("%s: Remote Port : %d\n", PROGNAME, (int) IconCtrlStruct.FloatParam[1]); - - /* Initialize the socket */ - if ((sd = socket(AF_INET, socket_type, socket_proto)) < 0) { - OpalPrint("%s: ERROR: Could not open socket\n", PROGNAME); - return EIO; - } - - /* Set the structure for the remote port and address */ - memset(&send_ad, 0, sizeof(send_ad)); - send_ad.sin_family = AF_INET; - send_ad.sin_addr.s_addr = inet_addr(IconCtrlStruct.StringParam[0]); - send_ad.sin_port = htons((u_short)IconCtrlStruct.FloatParam[1]); - - /* Set the structure for the local port and address */ - memset(&recv_ad, 0, sizeof(recv_ad)); - recv_ad.sin_family = AF_INET; - recv_ad.sin_addr.s_addr = INADDR_ANY; - recv_ad.sin_port = htons((u_short)IconCtrlStruct.FloatParam[2]); - - /* Bind local port and address to socket. */ - if (bind(sd, (struct sockaddr *) &recv_ad, sizeof(struct sockaddr_in)) == -1) { - OpalPrint("%s: ERROR: Could not bind local port to socket\n", PROGNAME); - return EIO; - } - else - OpalPrint("%s: Local Port : %d\n", PROGNAME, (int) IconCtrlStruct.FloatParam[2]); - - switch (proto) { - case UDP_PROTOCOL: /* Communication using UDP/IP protocol */ - /* If sending to a multicast address */ - if ((inet_addr(IconCtrlStruct.StringParam[0]) & inet_addr("240.0.0.0")) == inet_addr("224.0.0.0")) { - if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_TTL, (char *) &TTL, sizeof(TTL)) == -1) { - OpalPrint("%s: ERROR: Could not set TTL for multicast send (%d)\n", PROGNAME, errno); - return EIO; - } - if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_LOOP, (char *)&LOOP, sizeof(LOOP)) == -1) { - OpalPrint("%s: ERROR: Could not set loopback for multicast send (%d)\n", PROGNAME, errno); - return EIO; - } - - OpalPrint("%s: Configured socket for sending to multicast address\n", PROGNAME); - } - - /* If receiving from a multicast group, register for it. */ - if (inet_addr(IconCtrlStruct.StringParam[1]) > 0) { - if ((inet_addr(IconCtrlStruct.StringParam[1]) & inet_addr("240.0.0.0")) == inet_addr("224.0.0.0")) { - mreq.imr_multiaddr.s_addr = inet_addr(IconCtrlStruct.StringParam[1]); - mreq.imr_interface.s_addr = INADDR_ANY; - - /* Have the multicast socket join the multicast group */ - if (setsockopt(sd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *) &mreq, sizeof(mreq)) == -1) { - OpalPrint("%s: ERROR: Could not join multicast group (%d)\n", PROGNAME, errno); - return EIO; - } - - OpalPrint("%s: Added process to multicast group (%s)\n", - PROGNAME, IconCtrlStruct.StringParam[1]); - } - else { - OpalPrint("%s: WARNING: IP address for multicast group is not in multicast range. Ignored\n", - PROGNAME); - } - } - break; - - case TCP_PROTOCOL: /* Communication using TCP/IP protocol */ - OpalPrint("%s: Calling connect()\n", PROGNAME); - - /* Connect to server to start data transmission */ - rc = connect(sd, (struct sockaddr *) &send_ad, sizeof(send_ad)); - if (rc < 0) { - OpalPrint("%s: ERROR: Call to connect() failed\n", PROGNAME); - return EIO; - } - break; - } - - return EOK; -} - -int SendPacket(char* DataSend, int datalength) -{ - int err; - - if(sd < 0) - return -1; - - /* Send the packet */ - if (proto == TCP_PROTOCOL) - err = send(sd, DataSend, datalength, 0); - else - err = sendto(sd, DataSend, datalength, 0, (struct sockaddr *)&send_ad, sizeof(send_ad)); - - return err; -} - -int RecvPacket(char* DataRecv, int datalength, double timeout) -{ - int len; - struct sockaddr_in client_ad; - socklen_t client_ad_size = sizeof(client_ad); - fd_set sd_set; - struct timeval tv; - - if (sd < 0) - return -1; - - /* Set the descriptor set for the select() call */ - FD_ZERO(&sd_set); - FD_SET(sd, &sd_set); - - /* Set the tv structure to the correct timeout value */ - tv.tv_sec = (int) timeout; - tv.tv_usec = (int) ((timeout - tv.tv_sec) * 1000000); - - /* Wait for a packet. We use select() to have a timeout. This is - * necessary when reseting the model so we don't wait indefinitely - * and prevent the process from exiting and freeing the port for - * a future instance (model load). */ - switch (select(sd+1, &sd_set, (fd_set *) 0, (fd_set *) 0, &tv)) { - case -1: /* Error */ - return -1; - case 0: /* We hit the timeout */ - return 0; - default: - if (!(FD_ISSET(sd, &sd_set))) { - /* We received something, but it's not on "sd". Since sd is the only - * descriptor in the set... */ - OpalPrint("%s: RecvPacket: God, is that You trying to reach me?\n", PROGNAME); - return -1; - } - } - - /* Clear the DataRecv array (in case we receive an incomplete packet) */ - memset(DataRecv, 0, datalength); - - /* Perform the reception */ - if (proto == TCP_PROTOCOL) - len = recv(sd, DataRecv, datalength, 0); - else - len = recvfrom(sd, DataRecv, datalength, 0, (struct sockaddr *) &client_ad, &client_ad_size); - - return len; -} - -int CloseSocket(Opal_GenAsyncParam_Ctrl IconCtrlStruct) -{ - if (sd < 0) { - shutdown(sd, SHUT_RDWR); - close(sd); - } - - return 0; -} diff --git a/clients/opal/udp/models/send_receive/src/utils.c b/clients/opal/udp/models/send_receive/src/utils.c deleted file mode 100644 index e8c599807..000000000 --- a/clients/opal/udp/models/send_receive/src/utils.c +++ /dev/null @@ -1,74 +0,0 @@ -/** Configure Scheduler - * - * @file - * @author Steffen Vogel - * @author Mathieu Dubé-Dallaire - * @copyright 2003, OPAL-RT Technologies inc - * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC - *********************************************************************************/ - -#include - -/* Define RTLAB before including OpalPrint.h for messages to be sent - * to the OpalDisplay. Otherwise stdout will be used. */ -#define RTLAB -#include "OpalPrint.h" - -#include "config.h" -#include "utils.h" - -#if defined(__QNXNTO__) -# include -# include -# include -# include -#elif defined(__linux__) -# define _GNU_SOURCE 1 -# include -# if defined(__redhawk__) -# include -# include -# endif -#endif - -int AssignProcToCpu0(void) -{ -#if defined(__linux__) - #if defined(__redhawk__) - int rc; - pid_t pid = getpid(); - cpuset_t *pCpuset; - - pCpuset = cpuset_alloc(); - if (NULL == pCpuset) { - OpalPrint("Error allocating a cpuset\n"); - return ENOMEM; - } - - cpuset_init(pCpuset); - cpuset_set_cpu(pCpuset, 0, 1); - - rc = mpadvise(MPA_PRC_SETBIAS, MPA_TID, pid, pCpuset); - if (MPA_FAILURE == rc) { - rc = errno; - OpalPrint("Error from mpadvise, %d %s, for pid %d\n", errno, strerror(errno), pid); - cpuset_free(pCpuset); - return rc; - } - - cpuset_free(pCpuset); - #else - cpu_set_t bindSet; - CPU_ZERO(&bindSet); - CPU_SET(0, &bindSet); - - /* changing process cpu affinity */ - if (sched_setaffinity(0, sizeof(cpu_set_t), &bindSet) != 0) { - OpalPrint("Unable to bind the process to CPU 0. (sched_setaffinity errno %d)\n", errno); - return EINVAL; - } - - #endif - return EOK; -#endif /* __linux__ */ -} diff --git a/clients/opal/udp/models/send_receive/villas.mk b/clients/opal/udp/models/send_receive/villas.mk deleted file mode 100644 index 1e384d1be..000000000 --- a/clients/opal/udp/models/send_receive/villas.mk +++ /dev/null @@ -1,85 +0,0 @@ -# ----------------------------------------------------------------------------# -# Specify program name -PROGRAM = s2ss - -# ----------------------------------------------------------------------------# -# Specify default values if we are not compiling from RT-LAB -# -# ----------------------------------------------------------------------------# -TARGET_OPALRT_ROOT = /usr/opalrt - -# ----------------------------------------------------------------------------# -# QNX v6.x -# -ifeq "$(SYSNAME)" "nto" - CC = gcc - LD = $(CC) - TARGET_LIB = -lsocket -endif -# ----------------------------------------------------------------------------# - -# ----------------------------------------------------------------------------# -# RedHawk Linux -# -ifeq "$(shell uname)" "Linux" - RTLAB_INTEL_COMPILER ?= 1 - - # Intel Compiler support - ifeq ($(RTLAB_INTEL_COMPILER),1) - CC = opicc - LD = opicpc - # Gnu Compiler support - else - CC = gcc - LD = g++ - INTEL_LIBS = -limf -lirc - endif - - # RedHat or RedHawk - LINUX_FLAVOR = $(shell uname -r | grep RedHawk) - ifneq "$(LINUX_FLAVOR) " " " ### Linux (RedHat) - RH_FLAGS = -D_GNU_SOURCE -D__redhawk__ - RH_LIBS = -lccur_rt - else - RH_FLAGS = -D_GNU_SOURCE - endif - - TARGET_LIB = -lpthread -lm -ldl -lutil -lrt $(RH_LIBS) $(INTEL_LIBS) -endif -# ----------------------------------------------------------------------------# - -# Support for debugging symbols -ifeq ($(DEBUG),1) - CC_DEBUG_OPTS=-g -D_DEBUG - LD_DEBUG_OPTS=-g -else - CC_DEBUG_OPTS=-O - LD_DEBUG_OPTS= -endif - -INCLUDES = -I. -LIBPATH = -L. $(OPAL_LIBPATH) -CC_OPTS = -std=c99 -LD_OPTS = -OBJS = main.o msg.o utils.o socket.o - -ADDLIB = -lOpalCore -lOpalUtils -LIBS = -lOpalAsyncApiCore $(ADDLIB) $(TARGET_LIB) $(OPAL_LIBS) - -CFLAGS = -c $(CC_OPTS) $(CC_DEBUG_OPTS) $(RH_FLAGS) $(INCLUDES) -LDFLAGS = $(LD_OPTS) $(LD_DEBUG_OPTS) $(LIBPATH) - -all: $(PROGRAM) - -install: - \mkdir -p $(TARGET_OPALRT_ROOT)/local - \chmod 755 $(TARGET_OPALRT_ROOT)/local - \cp -f $(PROGRAM) $(TARGET_OPALRT_ROOT)/local - -clean: - \rm -f $(OBJS) $(PROGRAM) - -$(PROGRAM): $(OBJS) - $(LD) $(LDFLAGS) -o $@ $(OBJS) $(LIBS) - chmod 777 $@ - @echo "### Created executable: $(PROGRAM)" diff --git a/clients/opal/udp/.project b/clients/opal/villas_udp/.project similarity index 100% rename from clients/opal/udp/.project rename to clients/opal/villas_udp/.project diff --git a/clients/opal/udp/.settings/com.opalrt.rtlab.ui.application.prefs b/clients/opal/villas_udp/.settings/com.opalrt.rtlab.ui.application.prefs similarity index 100% rename from clients/opal/udp/.settings/com.opalrt.rtlab.ui.application.prefs rename to clients/opal/villas_udp/.settings/com.opalrt.rtlab.ui.application.prefs diff --git a/clients/opal/villas_udp/models/send_receive/.gitignore b/clients/opal/villas_udp/models/send_receive/.gitignore new file mode 100644 index 000000000..196fc2c48 --- /dev/null +++ b/clients/opal/villas_udp/models/send_receive/.gitignore @@ -0,0 +1,3 @@ +*.o +*.d +AsyncIP diff --git a/clients/opal/villas_udp/models/send_receive/Makefile.mk b/clients/opal/villas_udp/models/send_receive/Makefile.mk new file mode 100644 index 000000000..c5a0c6a3e --- /dev/null +++ b/clients/opal/villas_udp/models/send_receive/Makefile.mk @@ -0,0 +1,57 @@ +TARGET = AsyncIP + +VPATH = src + +RTLAB_INTEL_COMPILER ?= 1 + +# Compiler selection +ifeq ($(RTLAB_INTEL_COMPILER),1) + CC = opicc + LD = opicpc +else + CC = gcc + LD = g++ + + INTEL_LIBS = -limf -lirc + INTEL_OBJS = compat.o +endif + +# Support for debugging symbols +ifeq ($(DEBUG),1) + CC_DEBUG_OPTS = -g -D_DEBUG + LD_DEBUG_OPTS = -g +else + CC_DEBUG_OPTS = -O + LD_DEBUG_OPTS = +endif + +TARGET_LIB = -lpthread -lm -ldl -lutil -lrt $(INTEL_LIBS) + +INCLUDES = -I. $(OPAL_INCPATH) -Iinclude +LIBPATH = -L. $(OPAL_LIBPATH) +CC_OPTS = -m32 -std=c99 -D_GNU_SOURCE -MMD +LD_OPTS = -m32 +OBJS = main.o msg.o utils.o socket.o $(INTEL_OBJS) + +ifneq ($(PROTOCOL),) + CC_OPTS += -DPROTOCOL=$(PROTOCOL) +endif + +ADDLIB = -lOpalCore -lOpalUtils +LIBS = -lOpalAsyncApiCore $(ADDLIB) $(TARGET_LIB) $(OPAL_LIBS) + +CFLAGS = -c $(CC_OPTS) $(CC_DEBUG_OPTS) $(INCLUDES) +LDFLAGS = $(LD_OPTS) $(LD_DEBUG_OPTS) $(LIBPATH) + +all: $(TARGET) + +install: $(TARGET) + install -m 0755 -D -t $(DESTDIR)$(PREFIX)/bin $(TARGET) + +clean: + rm -f $(OBJS) $(OBJS:%.o=%.d) $(TARGET) + +$(TARGET): $(OBJS) + $(LD) $(LDFLAGS) -o $@ $(OBJS) $(LIBS) + +-include $(wildcard *.d) \ No newline at end of file diff --git a/clients/opal/villas_udp/models/send_receive/include/config.h b/clients/opal/villas_udp/models/send_receive/include/config.h new file mode 100644 index 000000000..b95d618cb --- /dev/null +++ b/clients/opal/villas_udp/models/send_receive/include/config.h @@ -0,0 +1,25 @@ +/** Compile-time configuration. + * + * @file + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + *********************************************************************************/ + +#ifndef _CONFIG_H_ +#define _CONFIG_H_ + +#define PROGNAME "VILLASnode-OPAL-UDP" +#define VERSION "0.6" + +#define MAX_VALUES 64 + +/* List of protocols */ +#define VILLAS 1 +#define GTNET_SKT 2 + +/* Default protocol */ +#ifndef PROTOCOL + #define PROTOCOL VILLAS +#endif + +#endif /* _CONFIG_H_ */ \ No newline at end of file diff --git a/clients/opal/villas_udp/models/send_receive/include/msg.h b/clients/opal/villas_udp/models/send_receive/include/msg.h new file mode 100644 index 000000000..7988d21b2 --- /dev/null +++ b/clients/opal/villas_udp/models/send_receive/include/msg.h @@ -0,0 +1,35 @@ +/** Message related functions + * + * @file + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + *********************************************************************************/ + +#pragma once + +/* Forward declarations. */ +struct msg; + +/** Swaps the byte-order of the message. + * + * Message are always transmitted in network (big endian) byte order. + * + * @param m A pointer to the message + */ +void msg_hdr_ntoh(struct msg *m); + +void msg_hdr_hton(struct msg *m); + +void msg_ntoh(struct msg *m); + +void msg_hton(struct msg *m); + +/** Check the consistency of a message. + * + * The functions checks the header fields of a message. + * + * @param m A pointer to the message + * @retval 0 The message header is valid. + * @retval <0 The message header is invalid. + */ +int msg_verify(struct msg *m); \ No newline at end of file diff --git a/clients/opal/villas_udp/models/send_receive/include/msg_format.h b/clients/opal/villas_udp/models/send_receive/include/msg_format.h new file mode 100644 index 000000000..f3adff5d6 --- /dev/null +++ b/clients/opal/villas_udp/models/send_receive/include/msg_format.h @@ -0,0 +1,68 @@ +/** Message format + * + * @file + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + *********************************************************************************/ + +#pragma once + +#include + +/** The current version number for the message format */ +#define MSG_VERSION 2 + +/** @todo Implement more message types */ +#define MSG_TYPE_DATA 0 /**< Message contains float values */ +#define MSG_TYPE_START 1 /**< Message marks the beginning of a new simulation case */ +#define MSG_TYPE_STOP 2 /**< Message marks the end of a simulation case */ + +/** The total size in bytes of a message */ +#define MSG_LEN(values) (sizeof(struct msg) + MSG_DATA_LEN(values)) + +/** The length of \p values values in bytes. */ +#define MSG_DATA_LEN(values) (sizeof(float) * (values)) + +/** The offset to the first data value in a message. */ +#define MSG_DATA_OFFSET(msg) ((char *) (msg) + offsetof(struct msg, data)) + +/** Initialize a message with default values */ +#define MSG_INIT(len, seq) (struct msg) {\ + .version = MSG_VERSION, \ + .type = MSG_TYPE_DATA, \ + .length = len, \ + .sequence = seq \ +} + +/** The timestamp of a message in struct timespec format */ +#define MSG_TS(msg) (struct timespec) { \ + .tv_sec = (msg)->ts.sec, \ + .tv_nsec = (msg)->ts.nsec \ +} + +/** This message format is used by all clients + * + * @diafile msg_format.dia + **/ +struct msg +{ + unsigned version: 4; /**< Specifies the format of the remaining message (see MGS_VERSION) */ + unsigned type : 2; /**< Data or control message (see MSG_TYPE_*) */ + unsigned rsvd1 : 2; /**< Reserved bits */ + unsigned rsvd2 : 8; /**< Reserved bits */ + + uint16_t length; /**< The number of values in msg::data[]. */ + uint32_t sequence; /**< The sequence number is incremented by one for consecutive messages. */ + + /** A timestamp per message. */ + struct { + uint32_t sec; /**< Seconds since 1970-01-01 00:00:00 */ + uint32_t nsec; /**< Nanoseconds of the current second. */ + } ts; + + /** The message payload. */ + union { + float f; /**< Floating point values. */ + uint32_t i; /**< Integer values. */ + } data[]; +} __attribute__((packed)); \ No newline at end of file diff --git a/clients/opal/villas_udp/models/send_receive/include/socket.h b/clients/opal/villas_udp/models/send_receive/include/socket.h new file mode 100644 index 000000000..1fd588bed --- /dev/null +++ b/clients/opal/villas_udp/models/send_receive/include/socket.h @@ -0,0 +1,33 @@ +/** Helper functions for sockets. + * + * @file + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + *********************************************************************************/ + +#ifndef _SOCKET_H_ +#define _SOCKET_H_ + +#include + +#define RT +#include "OpalGenAsyncParamCtrl.h" + +#define UDP_PROTOCOL 1 +#define TCP_PROTOCOL 2 + +struct socket { + struct sockaddr_in send_ad; /* Send address */ + struct sockaddr_in recv_ad; /* Receive address */ + int sd; /* socket descriptor */ +}; + +int socket_init(struct socket *s, Opal_GenAsyncParam_Ctrl IconCtrlStruct); + +int socket_send(struct socket *s, char *data, int len); + +int socket_recv(struct socket *s, char *data, int len, double timeout); + +int socket_close(struct socket *s, Opal_GenAsyncParam_Ctrl IconCtrlStruct); + +#endif /* _SOCKET_H_ */ diff --git a/clients/opal/udp/models/send_receive/include/utils.h b/clients/opal/villas_udp/models/send_receive/include/utils.h similarity index 91% rename from clients/opal/udp/models/send_receive/include/utils.h rename to clients/opal/villas_udp/models/send_receive/include/utils.h index f1c387d50..762aca20a 100644 --- a/clients/opal/udp/models/send_receive/include/utils.h +++ b/clients/opal/villas_udp/models/send_receive/include/utils.h @@ -1,4 +1,4 @@ -/** Configure Scheduler +/** Configure scheduler. * * @file * @author Steffen Vogel @@ -10,8 +10,6 @@ #ifndef _UTILS_H_ #define _UTILS_H_ -#define EOK 0 - int AssignProcToCpu0(void); #endif /* _UTILS_H_ */ diff --git a/clients/opal/udp/models/send_receive/send_receive.llm b/clients/opal/villas_udp/models/send_receive/send_receive.llm similarity index 92% rename from clients/opal/udp/models/send_receive/send_receive.llm rename to clients/opal/villas_udp/models/send_receive/send_receive.llm index 8516670ae..a8165608c 100644 --- a/clients/opal/udp/models/send_receive/send_receive.llm +++ b/clients/opal/villas_udp/models/send_receive/send_receive.llm @@ -37,22 +37,23 @@ INTERNAL_IGN_SOURCE_FILE=sfun_gen_async_ctrl.c sfun_recv_async.c sfun_send_async INTERNAL_LIBRARY2=-lOpalAsyncApiR2013a INTERNAL_LIBRARY3=-lOpalAsyncApiCore [ExtraGetFilesComp_1_RT_LAB] -s2ss=Binary|Async_Proc +AsyncIP=Binary|Async_Proc [ExtraPutFilesComp] include\config.h=Ascii include\msg.h=Ascii include\msg_format.h=Ascii include\socket.h=Ascii include\utils.h=Ascii -villas.mk=Ascii +Makefile.mk=Ascii src\msg.c=Ascii src\main.c=Ascii src\socket.c=Ascii src\utils.c=Ascii +src\compat.c=Ascii [ExtraPutFilesComp_1_RT_LAB] C:\OPAL-RT\RT-LAB\v11.0.2.410\common\lib\redhawk\libOpalAsyncApiCore.a=Binary|Other [ExtraPutFilesLoad_1_RT_LAB] -.\send_receive_sm_model\OpREDHAWKtarget\s2ss=Binary|Async_Proc +.\send_receive_sm_model\OpREDHAWKtarget\AsyncIP=Binary|Async_Proc [General] ATT_CHECKSUM1=1967915764 ATT_CHECKSUM2=1071010712 @@ -71,7 +72,7 @@ AutoRetrieveRtlab=ON CompilerVersion=AUTOMATIC DESCRIPTION= DinamoFlag=OFF -FILENAME=D:\svo\s2ss\clients\opal\udp\models\send_receive\send_receive.mdl +FILENAME=D:\svo\s2ss\clients\opal\villas_udp\models\send_receive\send_receive.mdl FORCE_RECOMPILE=0 IMPORTED_GLOBAL_VARIABLES=1 LastCompileRtlabVersion=v11.0.2.410 diff --git a/clients/opal/udp/models/send_receive/send_receive.mdl b/clients/opal/villas_udp/models/send_receive/send_receive.mdl similarity index 100% rename from clients/opal/udp/models/send_receive/send_receive.mdl rename to clients/opal/villas_udp/models/send_receive/send_receive.mdl diff --git a/clients/opal/villas_udp/models/send_receive/src/compat.c b/clients/opal/villas_udp/models/send_receive/src/compat.c new file mode 100644 index 000000000..e075e08bb --- /dev/null +++ b/clients/opal/villas_udp/models/send_receive/src/compat.c @@ -0,0 +1,30 @@ +/** Compatibility code for GCC + * + * OPAL-RT's libSystem.a links against some Intel + * + * @file + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + *********************************************************************************/ + +#include + +size_t __intel_sse2_strlen(const char *s) +{ + return strlen(s); +} + +void * _intel_fast_memset(void *b, int c, size_t len) +{ + return memset(b, c, len); +} + +void * _intel_fast_memcpy(void *restrict dst, const void *restrict src, size_t n) +{ + return memcpy(dst, src, n); +} + +int _intel_fast_memcmp(const void *s1, const void *s2, size_t n) +{ + return memcmp(s1, s2, n); +} diff --git a/clients/opal/udp/models/send_receive/src/main.c b/clients/opal/villas_udp/models/send_receive/src/main.c similarity index 55% rename from clients/opal/udp/models/send_receive/src/main.c rename to clients/opal/villas_udp/models/send_receive/src/main.c index 744399482..24ec3a456 100644 --- a/clients/opal/udp/models/send_receive/src/main.c +++ b/clients/opal/villas_udp/models/send_receive/src/main.c @@ -1,39 +1,22 @@ -/** Main +/** Main routine of AsyncIP. * - * Code example of an asynchronous program. This program is started - * by the asynchronous controller and demonstrates how to send and - * receive data to and from the asynchronous icons and a UDP or TCP - * port. - * - * @author Steffen Vogel - * @author Mathieu Dubé-Dallaire - * @copyright 2014, Institute for Automation of Complex Power Systems, EONERC - * @copyright 2003, OPAL-RT Technologies inc * @file - */ + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + *********************************************************************************/ /* Standard ANSI C headers needed for this program */ -#include -#include #include #include +#include +#include +#include #include -#include -#include #include #include #include #include -#if defined(__QNXNTO__) - #include - #include - #include -#elif defined(__linux__) - #define _GNU_SOURCE 1 - #include -#endif - /* Define RTLAB before including OpalPrint.h for messages to be sent * to the OpalDisplay. Otherwise stdout will be used. */ #define RTLAB @@ -42,57 +25,39 @@ /* This is the message format */ #include "config.h" -#include "msg.h" #include "socket.h" #include "utils.h" +#if PROTOCOL == VILLAS + #include "msg.h" + #include "msg_format.h" +#endif + /* This is just for initializing the shared memory access to communicate * with the RT-LAB model. It's easier to remember the arguments like this */ #define ASYNC_SHMEM_NAME argv[1] #define ASYNC_SHMEM_SIZE atoi(argv[2]) #define PRINT_SHMEM_NAME argv[3] -#ifdef _DEBUG // TODO: workaround -#define CPU_TICKS 3466948000 -struct msg *msg_send = NULL; +/* Global Variables */ +struct socket skt; -void Tick(int sig, siginfo_t *si, void *ptr) +static void * SendToIPPort(void *arg) { - Opal_GenAsyncParam_Ctrl *IconCtrlStruct; - unsigned long long CpuTime, CpuTimeStart; - double ModelTime; - - if (!msg_send) - return; - - IconCtrlStruct = (Opal_GenAsyncParam_Ctrl*) si->si_value.sival_ptr; - - OpalGetAsyncStartExecCpuTime(IconCtrlStruct, &CpuTimeStart); - OpalGetAsyncModelTime(IconCtrlStruct, &CpuTime, &ModelTime); - - OpalPrint("%s: CpuTime: %llu\tModelTime: %.3f\tSequence: %hu\tValue: %.2f\n", - PROGNAME, (CpuTime - CpuTimeStart) / CPU_TICKS, ModelTime, msg_send->sequence, msg_send->data[0].f); -} -#endif /* _DEBUG */ - -static void *SendToIPPort(void *arg) -{ - unsigned int SendID = 1; - unsigned int ModelState; - unsigned int i, n; - int nbSend = 0; - uint32_t seq = 0; + unsigned int ModelState, SendID = 1, Sequence = 0; + int nbSend = 0, ret, cnt, len; /* Data from OPAL-RT model */ - double mdldata[MSG_VALUES]; + double mdldata[MAX_VALUES]; int mdldata_size; - /* Data from VILLASnode */ - struct msg msg = MSG_INIT(0); - -#ifdef _DEBUG // TODO: workaround - msg_send = &msg; -#endif /* _DEBUG */ +#if PROTOCOL == VILLAS + char buf[MSG_LEN(MAX_VALUES)]; + struct msg *msg = (struct msg *) buf; +#elif PROTOCOL == GTNET_SKT + char buf[MAX_VALUES * sizeof(float)]; + float *msg = (float *) buf; +#endif OpalPrint("%s: SendToIPPort thread started\n", PROGNAME); @@ -104,11 +69,12 @@ static void *SendToIPPort(void *arg) do { /* This call unblocks when the 'Data Ready' line of a send icon is asserted. */ - if ((n = OpalWaitForAsyncSendRequest(&SendID)) != EOK) { + ret = OpalWaitForAsyncSendRequest(&SendID); + if (ret != EOK) { ModelState = OpalGetAsyncModelState(); if ((ModelState != STATE_RESET) && (ModelState != STATE_STOP)) { - OpalSetAsyncSendIconError(n, SendID); - OpalPrint("%s: OpalWaitForAsyncSendRequest(), errno %d\n", PROGNAME, n); + OpalSetAsyncSendIconError(ret, SendID); + OpalPrint("%s: OpalWaitForAsyncSendRequest(), errno %d\n", PROGNAME, ret); } continue; @@ -119,29 +85,44 @@ static void *SendToIPPort(void *arg) /* Get the size of the data being sent by the unblocking SendID */ OpalGetAsyncSendIconDataLength(&mdldata_size, SendID); - if (mdldata_size / sizeof(double) > MSG_VALUES) { + cnt = mdldata_size / sizeof(double); + if (cnt > MAX_VALUES) { OpalPrint("%s: Number of signals for SendID=%d exceeds allowed maximum (%d)\n", - PROGNAME, SendID, MSG_VALUES); + PROGNAME, SendID, MAX_VALUES); return NULL; } /* Read data from the model */ OpalGetAsyncSendIconData(mdldata, mdldata_size, SendID); - + +#if PROTOCOL == VILLAS /* Get current time */ struct timespec now; clock_gettime(CLOCK_REALTIME, &now); - msg.length = mdldata_size / sizeof(double); - for (i = 0; i < msg.length; i++) - msg.data[i].f = (float) mdldata[i]; + msg->length = mdldata_size / sizeof(double); + msg->sequence = Sequence++; + msg->ts.sec = now.tv_sec; + msg->ts.nsec = now.tv_nsec; - msg.sequence = seq++; - msg.ts.sec = now.tv_sec; - msg.ts.nsec = now.tv_nsec; + for (int i = 0; i < msg->length; i++) + msg->data[i].f = (float) mdldata[i]; + + msg_hton(msg); + + len = MSG_LEN(msg->length); +#elif PROTOCOL == GTNET_SKT + for (int i = 0; i < cnt; i++) + msg[i] = (float) mdldata[i]; + + len = mdldata_size / sizeof(double) * sizeof(float); +#else + #error Unknown protocol +#endif /* Perform the actual write to the ip port */ - if (SendPacket((char *) &msg, MSG_LEN(&msg)) < 0) + ret = socket_send(&skt, (char *) msg, len); + if (ret < 0) OpalSetAsyncSendIconError(errno, SendID); else OpalSetAsyncSendIconError(0, SendID); @@ -164,19 +145,24 @@ static void *SendToIPPort(void *arg) return NULL; } -static void *RecvFromIPPort(void *arg) +static void * RecvFromIPPort(void *arg) { - unsigned RecvID = 1; - unsigned i, n; - int nbRecv = 0; - unsigned ModelState; + unsigned int ModelState, RecvID = 1; + int nbRecv = 0, ret, cnt; /* Data from OPAL-RT model */ - double mdldata[MSG_VALUES]; + double mdldata[MAX_VALUES]; int mdldata_size; - /* Data from VILLASnode */ - struct msg msg = MSG_INIT(0); +#if PROTOCOL == VILLAS + char buf[MSG_LEN(MAX_VALUES)]; + struct msg *msg = (struct msg *) buf; +#elif PROTOCOL == GTNET_SKT + char buf[MAX_VALUES * sizeof(float)]; + float *msg = (float *) buf; +#else + #error Unknown protocol +#endif OpalPrint("%s: RecvFromIPPort thread started\n", PROGNAME); @@ -188,58 +174,56 @@ static void *RecvFromIPPort(void *arg) do { /* Receive message */ - n = RecvPacket((char *) &msg, sizeof(msg), 1.0); - if (n < 1) { + ret = socket_recv(&skt, (char *) msg, sizeof(buf), 1.0); + if (ret < 1) { ModelState = OpalGetAsyncModelState(); if ((ModelState != STATE_RESET) && (ModelState != STATE_STOP)) { - if (n == 0) /* timeout, so we continue silently */ + if (ret == 0) /* timeout, so we continue silently */ OpalPrint("%s: Timeout while waiting for data\n", PROGNAME, errno); - if (n == -1) /* a more serious error, so we print it */ + if (ret == -1) /* a more serious error, so we print it */ OpalPrint("%s: Error %d while waiting for data\n", PROGNAME, errno); continue; } break; } - - /* Check message contents */ - if (msg.version != MSG_VERSION) { - OpalPrint("%s: Received message with unknown version. Skipping..\n", PROGNAME); - continue; - } - if (msg.type != MSG_TYPE_DATA) { - OpalPrint("%s: Received no data. Skipping..\n", PROGNAME); - continue; - } - - /* Convert message to host endianess */ - if (msg.endian != MSG_ENDIAN_HOST) - msg_swap(&msg); - - if (n != MSG_LEN(&msg)) { - OpalPrint("%s: Received incoherent packet (size: %d, complete: %d)\n", PROGNAME, n, MSG_LEN(&msg)); - continue; - } - - /* Update OPAL model */ - OpalSetAsyncRecvIconStatus(msg.sequence, RecvID); /* Set the Status to the message ID */ - OpalSetAsyncRecvIconError(0, RecvID); /* Set the Error to 0 */ - /* Get the number of signals to send back to the model */ OpalGetAsyncRecvIconDataLength(&mdldata_size, RecvID); - if (mdldata_size / sizeof(double) > MSG_VALUES) { + cnt = mdldata_size / sizeof(double); + if (cnt > MAX_VALUES) { OpalPrint("%s: Number of signals for RecvID=%d (%d) exceeds allowed maximum (%d)\n", - PROGNAME, RecvID, mdldata_size / sizeof(double), MSG_VALUES); + PROGNAME, RecvID, cnt, MAX_VALUES); return NULL; } - if (mdldata_size / sizeof(double) > msg.length) +#if PROTOCOL == VILLAS + msg_ntoh(msg); + + ret = msg_verify(msg); + if (ret) { + OpalPrint("%s: Skipping invalid packet\n", PROGNAME); + continue; + } + + if (cnt > msg->length) { OpalPrint("%s: Number of signals for RecvID=%d (%d) exceeds what was received (%d)\n", - PROGNAME, RecvID, mdldata_size / sizeof(double), msg.length); + PROGNAME, RecvID, cnt, msg->length); + } + + for (int i = 0; i < msg->length; i++) + mdldata[i] = (double) msg->data[i].f; - for (i = 0; i < msg.length; i++) - mdldata[i] = (double) msg.data[i].f; + /* Update OPAL model */ + OpalSetAsyncRecvIconStatus(msg->sequence, RecvID); /* Set the Status to the message ID */ +#elif PROTOCOL == GTNET_SKT + for (int i = 0; i < cnt; i++) + mdldata[i] = (double) msg[i]; +#else + #error Unknown protocol +#endif + + OpalSetAsyncRecvIconError(0, RecvID); /* Set the Error to 0 */ OpalSetAsyncRecvIconData(mdldata, mdldata_size, RecvID); @@ -255,12 +239,11 @@ static void *RecvFromIPPort(void *arg) int main(int argc, char *argv[]) { - int err; + int ret; Opal_GenAsyncParam_Ctrl IconCtrlStruct; pthread_t tid_send, tid_recv; - pthread_attr_t attr_send, attr_recv; OpalPrint("%s: This is %s client version %s\n", PROGNAME, PROGNAME, VERSION); @@ -271,81 +254,61 @@ int main(int argc, char *argv[]) } /* Enable the OpalPrint function. This prints to the OpalDisplay. */ - if (OpalSystemCtrl_Register(PRINT_SHMEM_NAME) != EOK) { + ret = OpalSystemCtrl_Register(PRINT_SHMEM_NAME); + if (ret != EOK) { printf("%s: ERROR: OpalPrint() access not available\n", PROGNAME); exit(EXIT_FAILURE); } /* Open Share Memory created by the model. */ - if ((OpalOpenAsyncMem(ASYNC_SHMEM_SIZE, ASYNC_SHMEM_NAME)) != EOK) { + ret = OpalOpenAsyncMem(ASYNC_SHMEM_SIZE, ASYNC_SHMEM_NAME); + if (ret != EOK) { OpalPrint("%s: ERROR: Model shared memory not available\n", PROGNAME); exit(EXIT_FAILURE); } - /* For Redhawk, Assign this process to CPU 0 in order to support partial XHP */ AssignProcToCpu0(); /* Get IP Controler Parameters (ie: ip address, port number...) and * initialize the device on the QNX node. */ memset(&IconCtrlStruct, 0, sizeof(IconCtrlStruct)); - if ((err = OpalGetAsyncCtrlParameters(&IconCtrlStruct, sizeof(IconCtrlStruct))) != EOK) { - OpalPrint("%s: ERROR: Could not get controller parameters (%d).\n", PROGNAME, err); + + ret = OpalGetAsyncCtrlParameters(&IconCtrlStruct, sizeof(IconCtrlStruct)); + if (ret != EOK) { + OpalPrint("%s: ERROR: Could not get controller parameters (%d).\n", PROGNAME, ret); exit(EXIT_FAILURE); } /* Initialize socket */ - if (InitSocket(IconCtrlStruct) != EOK) { + ret = socket_init(&skt, IconCtrlStruct); + if (ret != EOK) { OpalPrint("%s: ERROR: Initialization failed.\n", PROGNAME); exit(EXIT_FAILURE); } -#ifdef _DEBUG - /* Setup signals */ - struct sigaction sa_tick = { - .sa_flags = SA_SIGINFO, - .sa_sigaction = Tick - }; - - sigemptyset(&sa_tick.sa_mask); - sigaction(SIGUSR1, &sa_tick, NULL); - - /* Setup timer */ - timer_t t; - struct sigevent sev = { - .sigev_notify = SIGEV_SIGNAL, - .sigev_signo = SIGUSR1, - .sigev_value.sival_ptr = &IconCtrlStruct - }; - - struct itimerspec its = { - .it_interval = { 1, 0 }, - .it_value = { 0, 1 } - }; - - timer_create(CLOCK_REALTIME, &sev, &t); - timer_settime(t, 0, &its, NULL); -#endif /* _DEBUG */ - /* Start send/receive threads */ - if ((pthread_create(&tid_send, NULL, SendToIPPort, NULL)) == -1) + ret = pthread_create(&tid_send, NULL, SendToIPPort, NULL); + if (ret == -1) OpalPrint("%s: ERROR: Could not create thread (SendToIPPort), errno %d\n", PROGNAME, errno); - if ((pthread_create(&tid_recv, NULL, RecvFromIPPort, NULL)) == -1) + + ret = pthread_create(&tid_recv, NULL, RecvFromIPPort, NULL); + if (ret == -1) OpalPrint("%s: ERROR: Could not create thread (RecvFromIPPort), errno %d\n", PROGNAME, errno); /* Wait for both threads to finish */ - if ((err = pthread_join(tid_send, NULL)) != 0) - OpalPrint("%s: ERROR: pthread_join (SendToIPPort), errno %d\n", PROGNAME, err); - if ((err = pthread_join(tid_recv, NULL)) != 0) - OpalPrint("%s: ERROR: pthread_join (RecvFromIPPort), errno %d\n", PROGNAME, err); + ret = pthread_join(tid_send, NULL); + if (ret != 0) + OpalPrint("%s: ERROR: pthread_join (SendToIPPort), errno %d\n", PROGNAME, ret); + + ret = pthread_join(tid_recv, NULL); + if (ret != 0) + OpalPrint("%s: ERROR: pthread_join (RecvFromIPPort), errno %d\n", PROGNAME, ret); /* Close the ip port and shared memories */ - CloseSocket(IconCtrlStruct); + socket_close(&skt, IconCtrlStruct); + OpalCloseAsyncMem (ASYNC_SHMEM_SIZE, ASYNC_SHMEM_NAME); OpalSystemCtrl_UnRegister(PRINT_SHMEM_NAME); -#ifdef _DEBUG - timer_delete(t); -#endif /* _DEBUG */ - return 0; } diff --git a/clients/opal/villas_udp/models/send_receive/src/msg.c b/clients/opal/villas_udp/models/send_receive/src/msg.c new file mode 100644 index 000000000..c61a16345 --- /dev/null +++ b/clients/opal/villas_udp/models/send_receive/src/msg.c @@ -0,0 +1,54 @@ +/** Message related functions. + * + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + *********************************************************************************/ + +#include + +#include "msg.h" +#include "msg_format.h" + +void msg_ntoh(struct msg *m) +{ + msg_hdr_ntoh(m); + + for (int i = 0; i < m->length; i++) + m->data[i].i = ntohl(m->data[i].i); +} + +void msg_hton(struct msg *m) +{ + for (int i = 0; i < m->length; i++) + m->data[i].i = htonl(m->data[i].i); + + msg_hdr_hton(m); +} + +void msg_hdr_hton(struct msg *m) +{ + m->length = htons(m->length); + m->sequence = htonl(m->sequence); + m->ts.sec = htonl(m->ts.sec); + m->ts.nsec = htonl(m->ts.nsec); +} + +void msg_hdr_ntoh(struct msg *m) +{ + m->length = ntohs(m->length); + m->sequence = ntohl(m->sequence); + m->ts.sec = ntohl(m->ts.sec); + m->ts.nsec = ntohl(m->ts.nsec); +} + +int msg_verify(struct msg *m) +{ + if (m->version != MSG_VERSION) + return -1; + else if (m->type != MSG_TYPE_DATA) + return -2; + else if ((m->rsvd1 != 0) || (m->rsvd2 != 0)) + return -3; + else + return 0; +} \ No newline at end of file diff --git a/clients/opal/villas_udp/models/send_receive/src/socket.c b/clients/opal/villas_udp/models/send_receive/src/socket.c new file mode 100644 index 000000000..a7f5cad76 --- /dev/null +++ b/clients/opal/villas_udp/models/send_receive/src/socket.c @@ -0,0 +1,174 @@ +/** Helper functions for sockets. + * + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + *********************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Define RTLAB before including OpalPrint.h for messages to be sent + * to the OpalDisplay. Otherwise stdout will be used. */ +#define RTLAB +#include "OpalPrint.h" +#include "AsyncApi.h" + +#include "config.h" +#include "socket.h" + +int socket_init(struct socket *s, Opal_GenAsyncParam_Ctrl IconCtrlStruct) +{ + struct ip_mreq mreq; /* Multicast group structure */ + unsigned char TTL = 1, LOOP = 0; + int rc, proto, ret; + + proto = (int) IconCtrlStruct.FloatParam[0]; + if (proto != UDP_PROTOCOL) { + OpalPrint("%s: This version of %s only supports UDP\n", PROGNAME, PROGNAME); + return EIO; + } + + + OpalPrint("%s: Version : %s\n", PROGNAME, VERSION); + OpalPrint("%s: Remote Address : %s\n", PROGNAME, IconCtrlStruct.StringParam[0]); + OpalPrint("%s: Remote Port : %d\n", PROGNAME, (int) IconCtrlStruct.FloatParam[1]); + + /* Initialize the socket */ + s->sd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (s->sd < 0) { + OpalPrint("%s: ERROR: Could not open socket\n", PROGNAME); + return EIO; + } + + /* Set the structure for the remote port and address */ + memset(&s->send_ad, 0, sizeof(s->send_ad)); + s->send_ad.sin_family = AF_INET; + s->send_ad.sin_addr.s_addr = inet_addr(IconCtrlStruct.StringParam[0]); + s->send_ad.sin_port = htons((u_short) IconCtrlStruct.FloatParam[1]); + + /* Set the structure for the local port and address */ + memset(&s->recv_ad, 0, sizeof(s->recv_ad)); + s->recv_ad.sin_family = AF_INET; + s->recv_ad.sin_addr.s_addr = INADDR_ANY; + s->recv_ad.sin_port = htons((u_short) IconCtrlStruct.FloatParam[2]); + + /* Bind local port and address to socket. */ + ret = bind(s->sd, (struct sockaddr *) &s->recv_ad, sizeof(struct sockaddr_in)); + if (ret == -1) { + OpalPrint("%s: ERROR: Could not bind local port to socket\n", PROGNAME); + return EIO; + } + else + OpalPrint("%s: Local Port : %d\n", PROGNAME, (int) IconCtrlStruct.FloatParam[2]); + + /* If sending to a multicast address */ + if ((inet_addr(IconCtrlStruct.StringParam[0]) & inet_addr("240.0.0.0")) == inet_addr("224.0.0.0")) { + ret = setsockopt(s->sd, IPPROTO_IP, IP_MULTICAST_TTL, (char *) &TTL, sizeof(TTL)); + if (ret == -1) { + OpalPrint("%s: ERROR: Could not set TTL for multicast send (%d)\n", PROGNAME, errno); + return EIO; + } + + ret = setsockopt(s->sd, IPPROTO_IP, IP_MULTICAST_LOOP, (char *)&LOOP, sizeof(LOOP)); + if (ret == -1) { + OpalPrint("%s: ERROR: Could not set loopback for multicast send (%d)\n", PROGNAME, errno); + return EIO; + } + + OpalPrint("%s: Configured socket for sending to multicast address\n", PROGNAME); + } + + /* If receiving from a multicast group, register for it. */ + if (inet_addr(IconCtrlStruct.StringParam[1]) > 0) { + if ((inet_addr(IconCtrlStruct.StringParam[1]) & inet_addr("240.0.0.0")) == inet_addr("224.0.0.0")) { + mreq.imr_multiaddr.s_addr = inet_addr(IconCtrlStruct.StringParam[1]); + mreq.imr_interface.s_addr = INADDR_ANY; + + /* Have the multicast socket join the multicast group */ + ret = setsockopt(s->sd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *) &mreq, sizeof(mreq)); + if (ret == -1) { + OpalPrint("%s: ERROR: Could not join multicast group (%d)\n", PROGNAME, errno); + return EIO; + } + + OpalPrint("%s: Added process to multicast group (%s)\n", + PROGNAME, IconCtrlStruct.StringParam[1]); + } + else { + OpalPrint("%s: WARNING: IP address for multicast group is not in multicast range. Ignored\n", + PROGNAME); + } + } + + return EOK; +} + +int socket_close(struct socket *s, Opal_GenAsyncParam_Ctrl IconCtrlStruct) +{ + if (s->sd < 0) { + shutdown(s->sd, SHUT_RDWR); + close(s->sd); + } + + return 0; +} + +int socket_send(struct socket *s, char *data, int len) +{ + if (s->sd < 0) + return -1; + + /* Send the packet */ + return sendto(s->sd, data, len, 0, (struct sockaddr *) &s->send_ad, sizeof(s->send_ad)); +} + +int socket_recv(struct socket *s, char *data, int len, double timeout) +{ + int ret; + struct sockaddr_in client_ad; + struct timeval tv; + socklen_t client_ad_size = sizeof(client_ad); + fd_set sd_set; + + if (s->sd < 0) + return -1; + + /* Set the descriptor set for the select() call */ + FD_ZERO(&sd_set); + FD_SET(s->sd, &sd_set); + + /* Set the tv structure to the correct timeout value */ + tv.tv_sec = (int) timeout; + tv.tv_usec = (int) ((timeout - tv.tv_sec) * 1000000); + + /* Wait for a packet. We use select() to have a timeout. This is + * necessary when reseting the model so we don't wait indefinitely + * and prevent the process from exiting and freeing the port for + * a future instance (model load). */ + ret = select(s->sd + 1, &sd_set, (fd_set *) 0, (fd_set *) 0, &tv); + switch (ret) { + case -1: /* Error */ + return -1; + case 0: /* We hit the timeout */ + return 0; + default: + if (!(FD_ISSET(s->sd, &sd_set))) { + /* We received something, but it's not on "sd". Since sd is the only + * descriptor in the set... */ + OpalPrint("%s: RecvPacket: God, is that You trying to reach me?\n", PROGNAME); + return -1; + } + } + + /* Clear the data array (in case we receive an incomplete packet) */ + memset(data, 0, len); + + /* Perform the reception */ + return recvfrom(s->sd, data, len, 0, (struct sockaddr *) &client_ad, &client_ad_size); +} \ No newline at end of file diff --git a/clients/opal/villas_udp/models/send_receive/src/utils.c b/clients/opal/villas_udp/models/send_receive/src/utils.c new file mode 100644 index 000000000..23778e008 --- /dev/null +++ b/clients/opal/villas_udp/models/send_receive/src/utils.c @@ -0,0 +1,36 @@ +/** Configure scheduler. + * + * @author Steffen Vogel + * @author Mathieu Dubé-Dallaire + * @copyright 2003, OPAL-RT Technologies inc + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + *********************************************************************************/ + +#include +#include + +/* Define RTLAB before including OpalPrint.h for messages to be sent + * to the OpalDisplay. Otherwise stdout will be used. */ +#define RTLAB +#include "OpalPrint.h" + +#include "config.h" +#include "utils.h" + +int AssignProcToCpu0(void) +{ + int ret; + cpu_set_t bindSet; + + CPU_ZERO(&bindSet); + CPU_SET(0, &bindSet); + + /* Changing process cpu affinity */ + ret = sched_setaffinity(0, sizeof(cpu_set_t), &bindSet); + if (ret) { + OpalPrint("Unable to bind the process to CPU 0: %d\n", errno); + return EINVAL; + } + + return 0; +} diff --git a/clients/opal/udp/s2ss_tests.llp b/clients/opal/villas_udp/villas_udp.llp similarity index 63% rename from clients/opal/udp/s2ss_tests.llp rename to clients/opal/villas_udp/villas_udp.llp index ff62308f7..544f22ba7 100644 --- a/clients/opal/udp/s2ss_tests.llp +++ b/clients/opal/villas_udp/villas_udp.llp @@ -6,14 +6,14 @@ 134.130.169.90:25252 C2357876-6DB6-422F-ABD4-AB47963523A2 ON - D:\svo\s2ss\clients\opal\udp\s2ss_tests.llp + D:\svo\s2ss\clients\opal\villas_udp\villas_udp.llp models/send_receive/send_receive.mdl - D:/svo/s2ss/clients/opal/udp/models/send_receive/send_receive.mdl - //E265/D/svo/s2ss/clients/opal/udp/models/send_receive/send_receive.mdl + D:/svo/s2ss/clients/opal/villas_udp/models/send_receive/send_receive.mdl + //E265/D/svo/s2ss/clients/opal/villas_udp/models/send_receive/send_receive.mdl diff --git a/include/villas/msg.h b/include/villas/msg.h index 130e06be1..7988d21b2 100644 --- a/include/villas/msg.h +++ b/include/villas/msg.h @@ -7,24 +7,22 @@ #pragma once -#include +/* Forward declarations. */ +struct msg; -#include "msg_format.h" - -struct node; - -/** Swaps the byte order of the header part of struct msg. +/** Swaps the byte-order of the message. * - * Message can either be transmitted in little or big endian - * format. The actual endianess for a message is defined by the - * msg::endian field. This covers msg::length, msg::sequence, msg::data and msg::ts fields. - * Received message are usally converted to the endianess of the host. - * This is required for further sanity checks of the sequence number - * or parsing of the data. + * Message are always transmitted in network (big endian) byte order. * * @param m A pointer to the message */ -void msg_hdr_swap(struct msg *m); +void msg_hdr_ntoh(struct msg *m); + +void msg_hdr_hton(struct msg *m); + +void msg_ntoh(struct msg *m); + +void msg_hton(struct msg *m); /** Check the consistency of a message. * diff --git a/include/villas/msg_format.h b/include/villas/msg_format.h index e63f90db0..f3adff5d6 100644 --- a/include/villas/msg_format.h +++ b/include/villas/msg_format.h @@ -9,32 +9,14 @@ #include -#ifdef __linux__ - #define _BSD_SOURCE 1 - #include -#elif defined(__PPC__) /* Xilinx toolchain */ - #include -#endif - /** The current version number for the message format */ -#define MSG_VERSION 1 +#define MSG_VERSION 2 /** @todo Implement more message types */ #define MSG_TYPE_DATA 0 /**< Message contains float values */ #define MSG_TYPE_START 1 /**< Message marks the beginning of a new simulation case */ #define MSG_TYPE_STOP 2 /**< Message marks the end of a simulation case */ -#define MSG_ENDIAN_LITTLE 0 /**< Message values are in little endian format (float too!) */ -#define MSG_ENDIAN_BIG 1 /**< Message values are in bit endian format */ - -#if BYTE_ORDER == LITTLE_ENDIAN - #define MSG_ENDIAN_HOST MSG_ENDIAN_LITTLE -#elif BYTE_ORDER == BIG_ENDIAN - #define MSG_ENDIAN_HOST MSG_ENDIAN_BIG -#else - #error "Unknown byte order!" -#endif - /** The total size in bytes of a message */ #define MSG_LEN(values) (sizeof(struct msg) + MSG_DATA_LEN(values)) @@ -48,7 +30,6 @@ #define MSG_INIT(len, seq) (struct msg) {\ .version = MSG_VERSION, \ .type = MSG_TYPE_DATA, \ - .endian = MSG_ENDIAN_HOST, \ .length = len, \ .sequence = seq \ } @@ -65,31 +46,23 @@ **/ struct msg { -#if BYTE_ORDER == BIG_ENDIAN unsigned version: 4; /**< Specifies the format of the remaining message (see MGS_VERSION) */ unsigned type : 2; /**< Data or control message (see MSG_TYPE_*) */ - unsigned endian : 1; /**< Specifies the byteorder of the message (see MSG_ENDIAN_*) */ - unsigned rsvd1 : 1; /**< Reserved bits */ -#elif BYTE_ORDER == LITTLE_ENDIAN - unsigned rsvd1 : 1; /**< Reserved bits */ - unsigned endian : 1; /**< Specifies the byteorder of the message (see MSG_ENDIAN_*) */ - unsigned type : 2; /**< Data or control message (see MSG_TYPE_*) */ - unsigned version: 4; /**< Specifies the format of the remaining message (see MGS_VERSION) */ -#endif + unsigned rsvd1 : 2; /**< Reserved bits */ unsigned rsvd2 : 8; /**< Reserved bits */ - uint16_t length; /**< The number of values in msg::data[]. Endianess is specified in msg::endian. */ - uint32_t sequence; /**< The sequence number is incremented by one for consecutive messages. Endianess is specified in msg::endian. */ + uint16_t length; /**< The number of values in msg::data[]. */ + uint32_t sequence; /**< The sequence number is incremented by one for consecutive messages. */ - /** A timestamp per message. Endianess is specified in msg::endian. */ + /** A timestamp per message. */ struct { uint32_t sec; /**< Seconds since 1970-01-01 00:00:00 */ uint32_t nsec; /**< Nanoseconds of the current second. */ } ts; - /** The message payload. Endianess is specified in msg::endian. */ + /** The message payload. */ union { - float f; /**< Floating point values (note msg::endian) */ - uint32_t i; /**< Integer values (note msg::endian) */ + float f; /**< Floating point values. */ + uint32_t i; /**< Integer values. */ } data[]; } __attribute__((packed)); \ No newline at end of file diff --git a/include/villas/nodes/socket.h b/include/villas/nodes/socket.h index 542fcb404..1d08dcb25 100644 --- a/include/villas/nodes/socket.h +++ b/include/villas/nodes/socket.h @@ -42,7 +42,11 @@ union sockaddr_union { struct socket { int sd; /**> The socket descriptor */ int mark; /**> Socket mark for netem, routing and filtering */ - int endian; /** Endianness of the data sent/received by the node */ + + enum { + SOCKET_ENDIAN_LITTLE, + SOCKET_ENDIAN_BIG + } endian; /** Endianness of the data sent/received by the node */ enum socket_layer layer; /**> The OSI / IP layer which should be used for this socket */ enum socket_header header; /**> Payload header type */ diff --git a/include/villas/webmsg.h b/include/villas/webmsg.h new file mode 100644 index 000000000..4cf7f6838 --- /dev/null +++ b/include/villas/webmsg.h @@ -0,0 +1,35 @@ +/** Message related functions + * + * @file + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + *********************************************************************************/ + +#pragma once + +/* Forward declarations. */ +struct webmsg; + +/** Swaps the byte-order of the message. + * + * Message are always transmitted in network (big endian) byte order. + * + * @param m A pointer to the message + */ +void webmsg_hdr_ntoh(struct webmsg *m); + +void webmsg_hdr_hton(struct webmsg *m); + +void webmsg_ntoh(struct webmsg *m); + +void webmsg_hton(struct webmsg *m); + +/** Check the consistency of a message. + * + * The functions checks the header fields of a message. + * + * @param m A pointer to the message + * @retval 0 The message header is valid. + * @retval <0 The message header is invalid. + */ +int msg_verify(struct webmsg *m); \ No newline at end of file diff --git a/include/villas/webmsg_format.h b/include/villas/webmsg_format.h index 19b600de6..0ec8080ed 100644 --- a/include/villas/webmsg_format.h +++ b/include/villas/webmsg_format.h @@ -1,4 +1,7 @@ -/** Binary websocket message format +/** Binary websocket message format. + * + * Note: Messages sent by the 'websocket' node-type are always send in little endian byte-order! + * This is different from the messages send with the 'socket' node-type! * * @file * @author Steffen Vogel @@ -11,36 +14,16 @@ #include -#include "msg_format.h" - -#ifdef __linux__ - #define _BSD_SOURCE 1 - #include -#elif defined(__PPC__) /* Xilinx toolchain */ - #include -#endif - /** The current version number for the message format */ -#define WEBMSG_VERSION 1 +#define WEBMSG_VERSION 2 /** @todo Implement more message types */ #define WEBMSG_TYPE_DATA 0 /**< Message contains float values */ #define WEBMSG_TYPE_START 1 /**< Message marks the beginning of a new simulation case */ #define WEBMSG_TYPE_STOP 2 /**< Message marks the end of a simulation case */ -#define WEBMSG_ENDIAN_LITTLE 0 /**< Message values are in little endian format (float too!) */ -#define WEBMSG_ENDIAN_BIG 1 /**< Message values are in bit endian format */ - -#if BYTE_ORDER == LITTLE_ENDIAN - #define WEBMSG_ENDIAN_HOST MSG_ENDIAN_LITTLE -#elif BYTE_ORDER == BIG_ENDIAN - #define WEBMSG_ENDIAN_HOST MSG_ENDIAN_BIG -#else - #error "Unknown byte order!" -#endif - /** The total size in bytes of a message */ -#define WEBMSG_LEN(values) (sizeof(struct webmsg) + MSG_DATA_LEN(values)) +#define WEBMSG_LEN(values) (sizeof(struct webmsg) + WEBMSG_DATA_LEN(values)) /** The length of \p values values in bytes. */ #define WEBMSG_DATA_LEN(values) (sizeof(float) * (values)) @@ -52,7 +35,6 @@ #define WEBMSG_INIT(len, seq) (struct webmsg) {\ .version = WEBMSG_VERSION, \ .type = WEBMSG_TYPE_DATA, \ - .endian = WEBMSG_ENDIAN_HOST, \ .length = len, \ .sequence = seq \ } @@ -69,33 +51,25 @@ **/ struct webmsg { -#if BYTE_ORDER == BIG_ENDIAN - unsigned version: 4; /**< Specifies the format of the remaining message (see MGS_VERSION) */ - unsigned type : 2; /**< Data or control message (see MSG_TYPE_*) */ - unsigned endian : 1; /**< Specifies the byteorder of the message (see MSG_ENDIAN_*) */ - unsigned rsvd1 : 1; /**< Reserved bits */ -#elif BYTE_ORDER == LITTLE_ENDIAN - unsigned rsvd1 : 1; /**< Reserved bits */ - unsigned endian : 1; /**< Specifies the byteorder of the message (see MSG_ENDIAN_*) */ + unsigned rsvd1 : 2; /**< Reserved bits */ unsigned type : 2; /**< Data or control message (see MSG_TYPE_*) */ unsigned version: 4; /**< Specifies the format of the remaining message (see MGS_VERSION) */ -#endif - uint8_t id; /**< The node index from / to which this sample received / sent to. + uint8_t id; /**< The node index from / to which this sample received / sent to. * Corresponds to the index of the node in the http://localhost/nodes.json array. */ - uint16_t length; /**< The number of values in msg::data[]. Endianess is specified in msg::endian. */ - uint32_t sequence; /**< The sequence number is incremented by one for consecutive messages. Endianess is specified in msg::endian. */ + uint16_t length; /**< The number of values in msg::data[]. */ + uint32_t sequence; /**< The sequence number is incremented by one for consecutive messages. */ - /** A timestamp per message. Endianess is specified in msg::endian. */ + /** A timestamp per message. */ struct { uint32_t sec; /**< Seconds since 1970-01-01 00:00:00 */ uint32_t nsec; /**< Nanoseconds of the current second. */ } ts; - /** The message payload. Endianess is specified in msg::endian. */ + /** The message payload. */ union { - float f; /**< Floating point values (note msg::endian) */ - uint32_t i; /**< Integer values (note msg::endian) */ + float f; /**< Floating point values. */ + uint32_t i; /**< Integer values. */ } data[]; } __attribute__((packed)); \ No newline at end of file diff --git a/lib/msg.c b/lib/msg.c index 006fae1a6..c61a16345 100644 --- a/lib/msg.c +++ b/lib/msg.c @@ -4,26 +4,41 @@ * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC *********************************************************************************/ -#ifdef __linux__ - #include -#elif defined(__PPC__) /* Xilinx toolchain */ - #include - #define bswap_16(x) Xil_EndianSwap16(x) - #define bswap_32(x) Xil_EndianSwap32(x) -#endif +#include #include "msg.h" -#include "node.h" -#include "utils.h" +#include "msg_format.h" -void msg_hdr_swap(struct msg *m) +void msg_ntoh(struct msg *m) { - m->length = bswap_16(m->length); - m->sequence = bswap_32(m->sequence); - m->ts.sec = bswap_32(m->ts.sec); - m->ts.nsec = bswap_32(m->ts.nsec); + msg_hdr_ntoh(m); + + for (int i = 0; i < m->length; i++) + m->data[i].i = ntohl(m->data[i].i); +} - m->endian ^= 1; +void msg_hton(struct msg *m) +{ + for (int i = 0; i < m->length; i++) + m->data[i].i = htonl(m->data[i].i); + + msg_hdr_hton(m); +} + +void msg_hdr_hton(struct msg *m) +{ + m->length = htons(m->length); + m->sequence = htonl(m->sequence); + m->ts.sec = htonl(m->ts.sec); + m->ts.nsec = htonl(m->ts.nsec); +} + +void msg_hdr_ntoh(struct msg *m) +{ + m->length = ntohs(m->length); + m->sequence = ntohl(m->sequence); + m->ts.sec = ntohl(m->ts.sec); + m->ts.nsec = ntohl(m->ts.nsec); } int msg_verify(struct msg *m) @@ -32,8 +47,8 @@ int msg_verify(struct msg *m) return -1; else if (m->type != MSG_TYPE_DATA) return -2; - else if ((m->rsvd1 != 0) || (m->rsvd2 != 0)) + else if ((m->rsvd1 != 0) || (m->rsvd2 != 0)) return -3; else return 0; -} +} \ No newline at end of file diff --git a/lib/nodes/socket.c b/lib/nodes/socket.c index 7ed72305c..33899b749 100644 --- a/lib/nodes/socket.c +++ b/lib/nodes/socket.c @@ -9,14 +9,7 @@ #include #include #include - -#ifdef __linux__ - #include -#elif defined(__PPC__) /* Xilinx toolchain */ - #include - #define bswap_16(x) Xil_EndianSwap16(x) - #define bswap_32(x) Xil_EndianSwap32(x) -#endif +#include #include "nodes/socket.h" #include "config.h" @@ -26,6 +19,7 @@ #include "kernel/nl.h" #include "kernel/tc.h" #include "msg.h" +#include "msg_format.h" #include "sample.h" #include "queue.h" #include "plugin.h" @@ -126,8 +120,8 @@ char * socket_print(struct node *n) endian = "auto"; else { switch (s->endian) { - case MSG_ENDIAN_LITTLE: endian = "little"; break; - case MSG_ENDIAN_BIG: endian = "big"; break; + case SOCKET_ENDIAN_LITTLE: endian = "little"; break; + case SOCKET_ENDIAN_BIG: endian = "big"; break; } } @@ -231,254 +225,255 @@ int socket_destroy(struct node *n) return 0; } +static int socket_read_none(struct node *n, struct sample *smps[], unsigned cnt) +{ + ssize_t bytes; + int length; + struct socket *s = n->_vd; + + /* The GTNETv2-SKT protocol send every sample in a single packet. + * socket_read() receives a single packet. */ + int iov_len = s->header == SOCKET_HEADER_FAKE ? 2 : 1; + struct iovec iov[iov_len]; + struct sample *smp = smps[0]; + + if (cnt < 1) + return 0; + + uint32_t header[3]; + if (s->header == SOCKET_HEADER_FAKE) { + iov[0].iov_base = header; + iov[0].iov_len = sizeof(header); + } + + /* Remaining values are payload */ + iov[iov_len-1].iov_base = &smp->data; + iov[iov_len-1].iov_len = SAMPLE_DATA_LEN(smp->capacity); + + struct msghdr mhdr = { + .msg_iov = iov, + .msg_iovlen = iov_len, + .msg_name = (struct sockaddr *) &s->remote, + .msg_namelen = sizeof(s->remote) + }; + + /* Receive next sample */ + bytes = recvmsg(s->sd, &mhdr, MSG_TRUNC); + if (bytes == 0) + error("Remote node %s closed the connection", node_name(n)); /** @todo Should we really hard fail here? */ + else if (bytes < 0) + serror("Failed recv from node %s", node_name(n)); + else if (bytes % 4 != 0) { + warn("Packet size is invalid: %zd Must be multiple of 4 bytes.", bytes); + recv(s->sd, NULL, 0, 0); /* empty receive buffer */ + return -1; + } + + /* Convert message to host endianess */ + for (int i = 0; i < ARRAY_LEN(header); i++) + header[i] = s->endian == SOCKET_ENDIAN_BIG + ? be32toh(header[i]) + : le32toh(header[i]); + + for (int i = 0; i < bytes / SAMPLE_DATA_LEN(1); i++) + smp->data[i].i = s->endian == SOCKET_ENDIAN_BIG + ? be32toh(smp->data[i].i) + : le32toh(smp->data[i].i); + + if (s->header == SOCKET_HEADER_FAKE) + length = (bytes - sizeof(header)) / SAMPLE_DATA_LEN(1); + else + length = bytes / SAMPLE_DATA_LEN(1); + + if (length > smp->capacity) { + warn("Node %s received more values than supported. Dropping %u values", node_name(n), length - smp->capacity); + length = smp->capacity; + } + + if (s->header == SOCKET_HEADER_FAKE) { + smp->sequence = header[0]; + smp->ts.origin.tv_sec = header[1]; + smp->ts.origin.tv_nsec = header[2]; + } + else { + smp->sequence = n->sequence++; /* Fake sequence no generated by VILLASnode */ + smp->ts.origin.tv_sec = -1; + smp->ts.origin.tv_nsec = -1; + } + + smp->ts.received.tv_sec = -1; + smp->ts.received.tv_nsec = -1; + + smp->length = length; + + return 1; /* GTNET-SKT sends every sample in a single packet */ +} + +static int socket_read_villas(struct node *n, struct sample *smps[], unsigned cnt) +{ + struct socket *s = n->_vd; + + int ret; + ssize_t bytes; + + /* Peak into message header of the first sample and to get total packet size. */ + bytes = recv(s->sd, NULL, 0, MSG_PEEK | MSG_TRUNC); + if (bytes < MSG_LEN(1) || bytes % 4 != 0) { + warn("Received invalid packet for node %s", node_name(n)); + recv(s->sd, NULL, 0, 0); /* empty receive buffer */ + return -1; + } + + char data[bytes]; + + /* Receive message from socket */ + bytes = recv(s->sd, data, bytes, 0); + if (bytes == 0) + error("Remote node %s closed the connection", node_name(n)); + else if (bytes < 0) + serror("Failed receive packet from node %s", node_name(n)); + + int received = 0; + char *ptr = data; + + struct msg *msg = (struct msg *) ptr; + struct sample *smp = smps[received]; + + while (ptr < data + bytes - sizeof(struct msg) && received < cnt) { + msg_ntoh(msg); + + ret = msg_verify(msg); + if (ret) { + warn("Received invalid packet for node %s", node_name(n)); + return -1; + } + + smp->length = msg->length; + smp->sequence = msg->sequence; + smp->ts.origin = MSG_TS(msg); + smp->ts.received.tv_sec = -1; + smp->ts.received.tv_nsec = -1; + + memcpy(smp->data, msg->data, SAMPLE_DATA_LEN(msg->length)); + + ptr += MSG_LEN(msg->length); + + msg = (struct msg *) ptr; + smp = smps[++received]; + } + + return received; +} + +static int socket_write_none(struct node *n, struct sample *smps[], unsigned cnt) +{ + struct socket *s = n->_vd; + + int sent = 0; + ssize_t bytes; + + if (cnt < 1) + return 0; + + for (int i = 0; i < cnt; i++) { + int off = s->header == SOCKET_HEADER_FAKE ? 3 : 0; + int len = smps[i]->length + off; + uint32_t data[len]; + + /* First three values are sequence, seconds and nano-seconds timestamps */ + if (s->header == SOCKET_HEADER_FAKE) { + data[0] = smps[i]->sequence; + data[1] = smps[i]->ts.origin.tv_sec; + data[2] = smps[i]->ts.origin.tv_nsec; + } + + for (int j = 0; j < smps[i]->length; j++) + data[off + j] = s->endian == SOCKET_ENDIAN_BIG + ? htobe32(smps[i]->data[j].i) + : htole32(smps[i]->data[j].i); + + bytes = sendto(s->sd, data, len * sizeof(data[0]), 0, + (struct sockaddr *) &s->remote, sizeof(s->remote)); + if (bytes < 0) + serror("Failed send to node %s", node_name(n)); + + sent++; + } + + return sent; +} + +static int socket_write_villas(struct node *n, struct sample *smps[], unsigned cnt) +{ + struct socket *s = n->_vd; + + ssize_t bytes = 0; + + for (int i = 0; i < cnt; i++) + bytes += MSG_LEN(smps[i]->length); + + char data[bytes], *ptr = data; + + struct msg *msg = (struct msg *) ptr; + + for (int i = 0; i < cnt; i++) { + *msg = MSG_INIT(smps[i]->length, smps[i]->sequence); + + msg->ts.sec = smps[i]->ts.origin.tv_sec; + msg->ts.nsec = smps[i]->ts.origin.tv_nsec; + + memcpy(msg->data, smps[i]->data, MSG_DATA_LEN(smps[i]->length)); + + msg_hton(msg); + + ptr += MSG_LEN(msg->length); + + msg = (struct msg *) ptr; + } + + /* Send message */ + bytes = sendto(s->sd, data, bytes, 0, (struct sockaddr *) &s->remote, sizeof(s->remote)); + if (bytes < 0) + serror("Failed send to node %s", node_name(n)); + + return cnt; +} + int socket_read(struct node *n, struct sample *smps[], unsigned cnt) { struct socket *s = n->_vd; - int samples, ret, received, length; - ssize_t bytes; - - if (s->header == SOCKET_HEADER_NONE || s->header == SOCKET_HEADER_FAKE) { - if (cnt < 1) - return 0; - - /* The GTNETv2-SKT protocol send every sample in a single packet. - * socket_read() receives a single packet. */ - int iov_len = s->header == SOCKET_HEADER_FAKE ? 2 : 1; - struct iovec iov[iov_len]; - struct sample *smp = smps[0]; - - uint32_t header[3]; - if (s->header == SOCKET_HEADER_FAKE) { - iov[0].iov_base = header; - iov[0].iov_len = sizeof(header); - } - - /* Remaining values are payload */ - iov[iov_len-1].iov_base = &smp->data; - iov[iov_len-1].iov_len = SAMPLE_DATA_LEN(smp->capacity); + switch (s->header) { + case SOCKET_HEADER_NONE: + case SOCKET_HEADER_FAKE: + return socket_read_none(n, smps, cnt); - struct msghdr mhdr = { - .msg_iov = iov, - .msg_iovlen = iov_len, - .msg_name = (struct sockaddr *) &s->remote, - .msg_namelen = sizeof(s->remote) - }; - - /* Receive next sample */ - bytes = recvmsg(s->sd, &mhdr, MSG_TRUNC); - if (bytes == 0) - error("Remote node %s closed the connection", node_name(n)); /** @todo Should we really hard fail here? */ - else if (bytes < 0) - serror("Failed recv from node %s", node_name(n)); - else if (bytes % 4 != 0) { - warn("Packet size is invalid: %zd Must be multiple of 4 bytes.", bytes); - recv(s->sd, NULL, 0, 0); /* empty receive buffer */ - return -1; - } - - /* Convert message to host endianess */ - if (s->endian != MSG_ENDIAN_HOST) { - for (int i = 0; i < ARRAY_LEN(header); i++) - header[i] = bswap_32(header[i]); - - for (int i = 0; i < bytes / SAMPLE_DATA_LEN(1); i++) - smp->data[i].i = bswap_32(smp->data[i].i); - } - - if (s->header == SOCKET_HEADER_FAKE) - length = (bytes - sizeof(header)) / SAMPLE_DATA_LEN(1); - else - length = bytes / SAMPLE_DATA_LEN(1); - - if (length > smp->capacity) { - warn("Node %s received more values than supported. Dropping %u values", node_name(n), length - smp->capacity); - length = smp->capacity; - } - - if (s->header == SOCKET_HEADER_FAKE) { - smp->sequence = header[0]; - smp->ts.origin.tv_sec = header[1]; - smp->ts.origin.tv_nsec = header[2]; - } - else { - smp->sequence = n->sequence++; /* Fake sequence no generated by VILLASnode */ - smp->ts.origin.tv_sec = -1; - smp->ts.origin.tv_nsec = -1; - } - - smp->ts.received.tv_sec = -1; - smp->ts.received.tv_nsec = -1; - - smp->length = length; - - received = 1; /* GTNET-SKT sends every sample in a single packet */ - } - else { - struct msg msgs[cnt]; - struct msg hdr; - struct iovec iov[2*cnt]; - struct msghdr mhdr = { - .msg_iov = iov - }; - - /* Peak into message header of the first sample and to get total packet size. */ - bytes = recv(s->sd, &hdr, sizeof(struct msg), MSG_PEEK | MSG_TRUNC); - if (bytes < sizeof(struct msg) || bytes % 4 != 0) { - warn("Packet size is invalid: %zd Must be multiple of 4 bytes.", bytes); - recv(s->sd, NULL, 0, 0); /* empty receive buffer */ - return -1; - } - - ret = msg_verify(&hdr); - if (ret) { - warn("Invalid message received: reason=%d, bytes=%zd", ret, bytes); - recv(s->sd, NULL, 0, 0); /* empty receive buffer */ - return -1; - } - - /* Convert message to host endianess */ - if (hdr.endian != MSG_ENDIAN_HOST) - msg_hdr_swap(&hdr); - - samples = bytes / MSG_LEN(hdr.length); - if (samples > cnt) { - warn("Node %s received more samples than supported. Dropping %u samples", node_name(n), samples - cnt); - samples = cnt; - } - - /* We expect that all received samples have the same amount of values! */ - for (int i = 0; i < samples; i++) { - iov[2*i+0].iov_base = &msgs[i]; - iov[2*i+0].iov_len = MSG_LEN(0); - - iov[2*i+1].iov_base = SAMPLE_DATA_OFFSET(smps[i]); - iov[2*i+1].iov_len = SAMPLE_DATA_LEN(hdr.length); - - mhdr.msg_iovlen += 2; - - if (hdr.length > smps[i]->capacity) - error("Node %s received more values than supported. Dropping %d values.", node_name(n), hdr.length - smps[i]->capacity); - } - - /* Receive message from socket */ - bytes = recvmsg(s->sd, &mhdr, 0); //--? samples - cnt samples dropped - if (bytes == 0) - error("Remote node %s closed the connection", node_name(n)); - else if (bytes < 0) - serror("Failed recv from node %s", node_name(n)); - - for (received = 0; received < samples; received++) { - struct msg *m = &msgs[received]; - struct sample *smp = smps[received]; - - ret = msg_verify(m); - if (ret) - break; - - if (m->length != hdr.length) - break; - - /* Convert message to host endianess */ - if (m->endian != MSG_ENDIAN_HOST) { - msg_hdr_swap(m); - - for (int i = 0; i < m->length; i++) - smp->data[i].i = bswap_32(smp->data[i].i); - } - - smp->length = m->length; - smp->sequence = m->sequence; - smp->ts.origin = MSG_TS(m); - smp->ts.received.tv_sec = -1; - smp->ts.received.tv_nsec = -1; - } + case SOCKET_HEADER_DEFAULT: + return socket_read_villas(n, smps, cnt); } - debug(LOG_SOCKET | 17, "Received message of %zd bytes: %u samples", bytes, received); - - return received; + return -1; } int socket_write(struct node *n, struct sample *smps[], unsigned cnt) { struct socket *s = n->_vd; - ssize_t bytes; - int sent = 0; - /* Construct iovecs */ - if (s->header == SOCKET_HEADER_NONE || s->header == SOCKET_HEADER_FAKE) { - if (cnt < 1) - return 0; - - for (int i = 0; i < cnt; i++) { - int off = s->header == SOCKET_HEADER_FAKE ? 3 : 0; - int len = smps[i]->length + off; - uint32_t data[len]; - - /* First three values are sequence, seconds and nano-seconds timestamps */ - if (s->header == SOCKET_HEADER_FAKE) { - data[0] = smps[i]->sequence; - data[1] = smps[i]->ts.origin.tv_sec; - data[2] = smps[i]->ts.origin.tv_nsec; - } - - for (int j = 0; j < smps[i]->length; j++) { - if (s->endian == MSG_ENDIAN_HOST) - data[off + j] = smps[i]->data[j].i; - else - data[off + j] = bswap_32(smps[i]->data[j].i); - } - - bytes = sendto(s->sd, data, len * sizeof(data[0]), 0, - (struct sockaddr *) &s->remote, sizeof(s->remote)); - if (bytes < 0) - serror("Failed send to node %s", node_name(n)); - - sent++; - - debug(LOG_SOCKET | 17, "Sent packet of %zd bytes with 1 sample", bytes); - } + switch (s->header) { + case SOCKET_HEADER_NONE: + case SOCKET_HEADER_FAKE: + return socket_write_none(n, smps, cnt); + + case SOCKET_HEADER_DEFAULT: + return socket_write_villas(n, smps, cnt); } - else { - struct msg msgs[cnt]; - struct iovec iov[2*cnt]; - struct msghdr mhdr = { - .msg_iov = iov, - .msg_iovlen = ARRAY_LEN(iov), - .msg_name = (struct sockaddr *) &s->remote, - .msg_namelen = sizeof(s->remote) - }; - - for (int i = 0; i < cnt; i++) { - - msgs[i] = MSG_INIT(smps[i]->length, smps[i]->sequence); - - msgs[i].ts.sec = smps[i]->ts.origin.tv_sec; - msgs[i].ts.nsec = smps[i]->ts.origin.tv_nsec; - - iov[i*2+0].iov_base = &msgs[i]; - iov[i*2+0].iov_len = MSG_LEN(0); - - iov[i*2+1].iov_base = SAMPLE_DATA_OFFSET(smps[i]); - iov[i*2+1].iov_len = SAMPLE_DATA_LEN(smps[i]->length); - } - - /* Send message */ - bytes = sendmsg(s->sd, &mhdr, 0); - if (bytes < 0) - serror("Failed send to node %s", node_name(n)); - - sent = cnt; /** @todo Find better way to determine how many values we actually sent */ - - debug(LOG_SOCKET | 17, "Sent packet of %zd bytes with %u samples", bytes, cnt); - } - - return sent; + + return -1; } int socket_parse(struct node *n, config_setting_t *cfg) { + config_setting_t *cfg_netem; const char *local, *remote, *layer, *hdr, *endian; int ret; @@ -513,12 +508,12 @@ int socket_parse(struct node *n, config_setting_t *cfg) } if (!config_setting_lookup_string(cfg, "endian", &endian)) - s->endian = MSG_ENDIAN_BIG; + s->endian = SOCKET_ENDIAN_BIG; else { if (!strcmp(endian, "big") || !strcmp(endian, "network")) - s->endian = MSG_ENDIAN_BIG; + s->endian = SOCKET_ENDIAN_BIG; else if (!strcmp(endian, "little")) - s->endian = MSG_ENDIAN_LITTLE; + s->endian = SOCKET_ENDIAN_LITTLE; else cerror(cfg, "Invalid endianness type '%s' for node %s", endian, node_name(n)); } @@ -541,7 +536,7 @@ int socket_parse(struct node *n, config_setting_t *cfg) remote, node_name(n), gai_strerror(ret)); } - config_setting_t *cfg_netem = config_setting_get_member(cfg, "netem"); + cfg_netem = config_setting_get_member(cfg, "netem"); if (cfg_netem) { int enabled = 1; if (!config_setting_lookup_bool(cfg_netem, "enabled", &enabled) || enabled) diff --git a/lib/nodes/websocket.c b/lib/nodes/websocket.c index 03df20766..8e84535db 100644 --- a/lib/nodes/websocket.c +++ b/lib/nodes/websocket.c @@ -13,6 +13,7 @@ #include #include "super_node.h" +#include "webmsg.h" #include "webmsg_format.h" #include "timing.h" #include "utils.h" @@ -119,7 +120,6 @@ static int websocket_connection_write(struct websocket_connection *c, struct sam msg->version = WEBMSG_VERSION; msg->type = WEBMSG_TYPE_DATA; - msg->endian = WEBMSG_ENDIAN_HOST; msg->length = smps[i]->length; msg->sequence = smps[i]->sequence; msg->id = c->node->id; diff --git a/lib/webmsg.c b/lib/webmsg.c new file mode 100644 index 000000000..489f9cd57 --- /dev/null +++ b/lib/webmsg.c @@ -0,0 +1,54 @@ +/** Websocket message related functions. + * + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + *********************************************************************************/ + +#include + +#include "webmsg.h" +#include "msg_format.h" + +void webmsg_ntoh(struct webmsg *m) +{ + msg_hdr_ntoh(m); + + for (int i = 0; i < m->length; i++) + m->data[i].i = ntohl(m->data[i].i); +} + +void msg_hton(struct webmsg *m) +{ + for (int i = 0; i < m->length; i++) + m->data[i].i = htonl(m->data[i].i); + + webmsg_hdr_hton(m); +} + +void webmsg_hdr_hton(struct webmsg *m) +{ + m->length = htole16(m->length); + m->sequence = htonle32(m->sequence); + m->ts.sec = htonle32(m->ts.sec); + m->ts.nsec = htonle32(m->ts.nsec); +} + +void webmsg_hdr_ntoh(struct webmsg *m) +{ + m->length = le16tohs(m->length); + m->sequence = le32tohl(m->sequence); + m->ts.sec = le32tohl(m->ts.sec); + m->ts.nsec = le32tohl(m->ts.nsec); +} + +int webmsg_verify(struct webmsg *m) +{ + if (m->version != WEBMSG_VERSION) + return -1; + else if (m->type != WEBMSG_TYPE_DATA) + return -2; + else if ((m->rsvd1 != 0) || (m->rsvd2 != 0)) + return -3; + else + return 0; +} \ No newline at end of file diff --git a/thirdparty/libopal b/thirdparty/libopal index 16b8a3b49..df13cf489 160000 --- a/thirdparty/libopal +++ b/thirdparty/libopal @@ -1 +1 @@ -Subproject commit 16b8a3b49af56fecd2b3734083fb4af9ea4a0192 +Subproject commit df13cf489f23564fe3507ca948cafe49c0849db2 diff --git a/web/socket/msg.js b/web/socket/msg.js index ad2a2f881..5d89535d0 100644 --- a/web/socket/msg.js +++ b/web/socket/msg.js @@ -1,8 +1,11 @@ -/** Javascript class for parsing binary messages +/** Javascript class for parsing binary messages. + * + * Note: Messages sent by the 'websocket' node-type are always send in little endian byte-order! + * This is different from the messages send with the 'socket' node-type! * * @file * @author Steffen Vogel - * @copyright 2016, Institute for Automation of Complex Power Systems, EONERC + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC *********************************************************************************/ /** * @addtogroup websocket @@ -15,7 +18,6 @@ function Msg(c) { this.sequence = typeof c.sequence === 'undefined' ? 0 : c.sequence; this.length = typeof c.length === 'undefined' ? 0 : c.length; - this.endian = typeof c.endian === 'undefined' ? Msg.prototype.ENDIAN_LITTLE : c.endian; this.version = typeof c.version === 'undefined' ? Msg.prototype.VERSION : c.version; this.type = typeof c.type === 'undefined' ? Msg.prototype.TYPE_DATA : c.type; this.id = typeof c.id === 'undefined' ? -1 : c.id; @@ -32,11 +34,7 @@ Msg.prototype.VERSION = 1; Msg.prototype.TYPE_DATA = 0; /**< Message contains float values */ -Msg.prototype.ENDIAN_LITTLE = 0; /**< Message values are in little endian format (float too!) */ -Msg.prototype.ENDIAN_BIG = 1; /**< Message values are in bit endian format */ - /* Some offsets in the binary message */ -Msg.prototype.OFFSET_ENDIAN = 1; Msg.prototype.OFFSET_TYPE = 2; Msg.prototype.OFFSET_VERSION = 4; @@ -48,30 +46,20 @@ Msg.bytes = function(len) Msg.fromArrayBuffer = function(data) { var bits = data.getUint8(0); - var endian = (bits >> Msg.prototype.OFFSET_ENDIAN) & 0x1 ? 0 : 1; var msg = new Msg({ - endian: (bits >> Msg.prototype.OFFSET_ENDIAN) & 0x1, version: (bits >> Msg.prototype.OFFSET_VERSION) & 0xF, type: (bits >> Msg.prototype.OFFSET_TYPE) & 0x3, - id: data.getUint8( 0x01, endian), - length: data.getUint16(0x02, endian), - sequence: data.getUint32(0x04, endian), - timestamp: data.getUint32(0x08, endian) * 1e3 + - data.getUint32(0x0C, endian) * 1e-6, + id: data.getUint8( 0x01), + length: data.getUint16(0x02, 1), + sequence: data.getUint32(0x04, 1), + timestamp: data.getUint32(0x08, 1) * 1e3 + + data.getUint32(0x0C, 1) * 1e-6, }); msg.blob = new DataView( data.buffer, data.byteOffset + 0x00, Msg.bytes(msg.length)); msg.data = new Float32Array(data.buffer, data.byteOffset + 0x10, msg.length); - if (msg.endian != host_endianess()) { - console.warn("Message is not given in host endianess!"); - - var data = new Uint32Array(msg.blob, 0x10); - for (var i = 0; i < data.length; i++) - data[i] = swap32(data[i]); - } - return msg; } @@ -101,7 +89,6 @@ Msg.prototype.toArrayBuffer = function() view = new DataView(buffer); var bits = 0; - bits |= (this.endian & 0x1) << Msg.prototype.OFFSET_ENDIAN; bits |= (this.version & 0xF) << Msg.prototype.OFFSET_VERSION; bits |= (this.type & 0x3) << Msg.prototype.OFFSET_TYPE; @@ -121,24 +108,4 @@ Msg.prototype.toArrayBuffer = function() return buffer; } -/** @todo parsing of big endian messages not yet supported */ -function swap16(val) -{ - return ((val & 0xFF) << 8) - | ((val >> 8) & 0xFF); -} - -function swap32(val) { - return ((val & 0xFF) << 24) - | ((val & 0xFF00) << 8) - | ((val >> 8) & 0xFF00) - | ((val >> 24) & 0xFF); -} - -function host_endianess() { - var buffer = new ArrayBuffer(2); - new DataView(buffer).setInt16(0, 256, true /* littleEndian */); - return new Int16Array(buffer)[0] === 256 ? 0 : 1; // Int16Array uses the platform's endianness. -}; - /** @} */ \ No newline at end of file