/* Main routine of AsyncIP. * * Author: Steffen Vogel * 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 #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 "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; }