mirror of
https://git.rwth-aachen.de/acs/public/villas/node/
synced 2025-03-30 00:00:11 +01:00
362 lines
10 KiB
C
362 lines
10 KiB
C
/* Main routine of AsyncIP.
|
|
*
|
|
* Author: Steffen Vogel <post@steffenvogel.de>
|
|
* SPDX-FileCopyrightText: 2014-2023 Institute for Automation of Complex Power Systems, RWTH Aachen University
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
// Standard ANSI C headers needed for this program
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <pthread.h>
|
|
#include <signal.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
/* Define RTLAB before including OpalPrint.h for messages to be sent
|
|
* to the OpalDisplay. Otherwise stdout will be used. */
|
|
#define RTLAB
|
|
#include "AsyncApi.h"
|
|
#include "OpalPrint.h"
|
|
|
|
// This is the message format
|
|
#include "config.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]
|
|
|
|
// Global Variables
|
|
struct socket skt;
|
|
|
|
static void *SendToIPPort(void *arg) {
|
|
unsigned int ModelState, SendID, Sequence = 0;
|
|
int nbSend = 0, ret, cnt, len;
|
|
|
|
// Data from OPAL-RT model
|
|
double mdldata[MAX_VALUES];
|
|
int mdldata_size;
|
|
|
|
#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);
|
|
|
|
OpalGetNbAsyncSendIcon(&nbSend);
|
|
if (nbSend < 1) {
|
|
OpalPrint("%s: SendToIPPort: No transimission block for this controller. "
|
|
"Stopping thread.\n",
|
|
PROGNAME);
|
|
return NULL;
|
|
}
|
|
|
|
do {
|
|
// This call unblocks when the 'Data Ready' line of a send icon is asserted.
|
|
ret = OpalWaitForAsyncSendRequest(&SendID);
|
|
if (ret != EOK) {
|
|
ModelState = OpalGetAsyncModelState();
|
|
if ((ModelState != STATE_RESET) && (ModelState != STATE_STOP)) {
|
|
OpalSetAsyncSendIconError(ret, SendID);
|
|
OpalPrint("%s: OpalWaitForAsyncSendRequest(), errno %d\n", PROGNAME,
|
|
ret);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
// No errors encountered yet
|
|
OpalSetAsyncSendIconError(0, SendID);
|
|
|
|
// Get the size of the data being sent by the unblocking SendID
|
|
OpalGetAsyncSendIconDataLength(&mdldata_size, SendID);
|
|
cnt = mdldata_size / sizeof(double);
|
|
if (cnt > MAX_VALUES) {
|
|
OpalPrint("%s: Number of signals for SendID=%d exceeds allowed maximum "
|
|
"(%d). Limiting...\n",
|
|
PROGNAME, SendID, MAX_VALUES);
|
|
cnt = MAX_VALUES;
|
|
}
|
|
|
|
// 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->version = MSG_VERSION;
|
|
msg->type = MSG_TYPE_DATA;
|
|
msg->reserved1 = 0;
|
|
msg->id = SendID;
|
|
msg->length = cnt;
|
|
msg->sequence = Sequence++;
|
|
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];
|
|
|
|
len = MSG_LEN(msg->length);
|
|
|
|
msg_hton(msg);
|
|
#elif PROTOCOL == GTNET_SKT
|
|
uint32_t *imsg = (uint32_t *)msg;
|
|
for (int i = 0; i < cnt; i++) {
|
|
msg[i] = (float)mdldata[i];
|
|
imsg[i] = htonl(imsg[i]);
|
|
}
|
|
|
|
len = cnt * sizeof(float);
|
|
#else
|
|
#error Unknown protocol
|
|
#endif
|
|
|
|
// Perform the actual write to the ip port
|
|
ret = socket_send(&skt, (char *)msg, len);
|
|
if (ret < 0)
|
|
OpalSetAsyncSendIconError(errno, SendID);
|
|
else
|
|
OpalSetAsyncSendIconError(0, SendID);
|
|
|
|
/* This next call allows the execution of the "asynchronous" process
|
|
* to actually be synchronous with the model. To achieve this, you
|
|
* should set the "Sending Mode" in the Async_Send block to
|
|
* NEED_REPLY_BEFORE_NEXT_SEND or NEED_REPLY_NOW. This will force
|
|
* the model to wait for this process to call this
|
|
* OpalAsyncSendRequestDone function before continuing. */
|
|
OpalAsyncSendRequestDone(SendID);
|
|
|
|
/* Before continuing, we make sure that the real-time model
|
|
* has not been stopped. If it has, we quit. */
|
|
ModelState = OpalGetAsyncModelState();
|
|
} while ((ModelState != STATE_RESET) && (ModelState != STATE_STOP));
|
|
|
|
OpalPrint("%s: SendToIPPort: Finished\n", PROGNAME);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void *RecvFromIPPort(void *arg) {
|
|
unsigned int ModelState, RecvID;
|
|
int nbRecv = 0, ret, cnt;
|
|
|
|
// Data from OPAL-RT model
|
|
double mdldata[MAX_VALUES];
|
|
int mdldata_size;
|
|
|
|
#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);
|
|
|
|
OpalGetNbAsyncRecvIcon(&nbRecv);
|
|
if (nbRecv < 1) {
|
|
OpalPrint("%s: RecvFromIPPort: No reception block for this controller. "
|
|
"Stopping thread.\n",
|
|
PROGNAME);
|
|
return NULL;
|
|
}
|
|
|
|
// Get list of RecvIds
|
|
unsigned int RecvIDs[nbRecv];
|
|
ret = OpalGetAsyncRecvIDList(RecvIDs, sizeof(RecvIDs));
|
|
if (ret != EOK) {
|
|
OpalPrint("%s: Failed to get list of RecvIDs\n", PROGNAME);
|
|
return NULL;
|
|
}
|
|
|
|
do {
|
|
// Receive message
|
|
ret = socket_recv(&skt, (char *)msg, sizeof(buf), 1.0);
|
|
if (ret < 1) {
|
|
ModelState = OpalGetAsyncModelState();
|
|
if ((ModelState != STATE_RESET) && (ModelState != STATE_STOP)) {
|
|
if (ret == 0) // timeout, so we continue silently
|
|
OpalPrint("%s: Timeout while waiting for data\n", PROGNAME, errno);
|
|
if (ret == -1) // a more serious error, so we print it
|
|
OpalPrint("%s: Error %d while waiting for data\n", PROGNAME, errno);
|
|
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
#if PROTOCOL == VILLAS
|
|
RecvID = msg->id;
|
|
#elif PROTOCOL == GTNET_SKT
|
|
RecvID = 1;
|
|
#else
|
|
#error Unknown protocol
|
|
#endif
|
|
// Check if this RecvID exists
|
|
for (int i = 0; i < nbRecv; i++) {
|
|
if (RecvIDs[i] == RecvID)
|
|
goto found;
|
|
}
|
|
|
|
OpalPrint("%s: Received message with non-existent RecvID=%d. Changing to "
|
|
"RecvID=%d...\n",
|
|
PROGNAME, RecvID, RecvIDs[0]);
|
|
RecvID = RecvIDs[0];
|
|
|
|
found: // Get the number of signals to send back to the model
|
|
OpalGetAsyncRecvIconDataLength(&mdldata_size, RecvID);
|
|
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, cnt, MAX_VALUES);
|
|
return NULL;
|
|
}
|
|
|
|
#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, cnt, msg->length);
|
|
}
|
|
|
|
for (int 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
|
|
uint32_t *imsg = (uint32_t *)msg;
|
|
for (int i = 0; i < cnt; i++) {
|
|
imsg[i] = ntohl(imsg[i]);
|
|
mdldata[i] = (double)msg[i];
|
|
}
|
|
#else
|
|
#error Unknown protocol
|
|
#endif
|
|
|
|
OpalSetAsyncRecvIconError(0, RecvID); // Set the Error to 0
|
|
|
|
OpalSetAsyncRecvIconData(mdldata, mdldata_size, RecvID);
|
|
|
|
/* Before continuing, we make sure that the real-time model
|
|
* has not been stopped. If it has, we quit. */
|
|
ModelState = OpalGetAsyncModelState();
|
|
} while ((ModelState != STATE_RESET) && (ModelState != STATE_STOP));
|
|
|
|
OpalPrint("%s: RecvFromIPPort: Finished\n", PROGNAME);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int main(int argc, char *argv[]) {
|
|
int ret;
|
|
|
|
Opal_GenAsyncParam_Ctrl IconCtrlStruct;
|
|
|
|
pthread_t tid_send, tid_recv;
|
|
|
|
OpalPrint("%s: This is %s client version %s\n", PROGNAME, PROGNAME, VERSION);
|
|
|
|
// Check for the proper arguments to the program
|
|
if (argc < 4) {
|
|
printf("Invalid Arguments: 1-AsyncShmemName 2-AsyncShmemSize "
|
|
"3-PrintShmemName\n");
|
|
exit(0);
|
|
}
|
|
|
|
// Enable the OpalPrint function. This prints to the OpalDisplay.
|
|
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.
|
|
ret = OpalOpenAsyncMem(ASYNC_SHMEM_SIZE, ASYNC_SHMEM_NAME);
|
|
if (ret != EOK) {
|
|
OpalPrint("%s: ERROR: Model shared memory not available\n", PROGNAME);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
AssignProcToCpu0();
|
|
|
|
/* Get IP Controler Parameters (ie: ip address, port number...) and
|
|
* initialize the device on the QNX node. */
|
|
memset(&IconCtrlStruct, 0, sizeof(IconCtrlStruct));
|
|
|
|
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
|
|
ret = socket_init(&skt, IconCtrlStruct);
|
|
if (ret != EOK) {
|
|
OpalPrint("%s: ERROR: Failed to create socket.\n", PROGNAME);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
// Start send/receive threads
|
|
ret = pthread_create(&tid_send, NULL, SendToIPPort, NULL);
|
|
if (ret < 0)
|
|
OpalPrint("%s: ERROR: Could not create thread (SendToIPPort), errno %d\n",
|
|
PROGNAME, errno);
|
|
|
|
ret = pthread_create(&tid_recv, NULL, RecvFromIPPort, NULL);
|
|
if (ret < 0)
|
|
OpalPrint("%s: ERROR: Could not create thread (RecvFromIPPort), errno %d\n",
|
|
PROGNAME, errno);
|
|
|
|
// Wait for both threads to finish
|
|
ret = pthread_join(tid_send, NULL);
|
|
if (ret)
|
|
OpalPrint("%s: ERROR: pthread_join (SendToIPPort), errno %d\n", PROGNAME,
|
|
ret);
|
|
|
|
ret = pthread_join(tid_recv, NULL);
|
|
if (ret)
|
|
OpalPrint("%s: ERROR: pthread_join (RecvFromIPPort), errno %d\n", PROGNAME,
|
|
ret);
|
|
|
|
// Close the ip port and shared memories
|
|
socket_close(&skt, IconCtrlStruct);
|
|
|
|
OpalCloseAsyncMem(ASYNC_SHMEM_SIZE, ASYNC_SHMEM_NAME);
|
|
OpalSystemCtrl_UnRegister(PRINT_SHMEM_NAME);
|
|
|
|
return 0;
|
|
}
|