204 lines
7.8 KiB
C
204 lines
7.8 KiB
C
/* rtu.c — simulates a substation RTU */
|
|
|
|
#include <stdlib.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <signal.h>
|
|
|
|
#include "cs104_slave.h"
|
|
#include "hal_thread.h"
|
|
#include "hal_time.h"
|
|
|
|
static bool running = true;
|
|
static float voltage = 11.2; /* kV */
|
|
static float current = 450.0; /* A */
|
|
static bool breaker1 = false; /* OFF */
|
|
static bool breaker2 = false; /* OFF */
|
|
|
|
void sigint_handler(int s) { running = false; }
|
|
|
|
/* ── connection events ──────────────────────────────────────── */
|
|
static void connectionEventHandler(void* p, IMasterConnection con, CS104_PeerConnectionEvent event) {
|
|
if (event == CS104_CON_EVENT_CONNECTION_OPENED)
|
|
printf("[RTU] SCADA connected\n");
|
|
else if (event == CS104_CON_EVENT_CONNECTION_CLOSED)
|
|
printf("[RTU] SCADA disconnected\n");
|
|
else if (event == CS104_CON_EVENT_ACTIVATED)
|
|
printf("[RTU] Data transfer started\n");
|
|
}
|
|
|
|
/* ── accept all connections ─────────────────────────────────── */
|
|
static bool connectionRequestHandler(void* p, const char* ip) {
|
|
printf("[RTU] Connection request from %s — accepted\n", ip);
|
|
return true;
|
|
}
|
|
|
|
/* ── send all current values (General Interrogation) ────────── */
|
|
static bool interrogationHandler(void* p, IMasterConnection con, CS101_ASDU asdu, uint8_t qoi) {
|
|
printf("[RTU] General Interrogation received\n");
|
|
|
|
CS101_AppLayerParameters alParams = IMasterConnection_getApplicationLayerParameters(con);
|
|
|
|
/* confirm GI received */
|
|
IMasterConnection_sendACT_CON(con, asdu, false);
|
|
|
|
/* send voltage IOA 100 */
|
|
CS101_ASDU a1 = CS101_ASDU_create(alParams, false,
|
|
CS101_COT_INTERROGATED_BY_STATION, 0, 1, false, false);
|
|
InformationObject io1 = (InformationObject)
|
|
MeasuredValueShort_create(NULL, 100, voltage, IEC60870_QUALITY_GOOD);
|
|
CS101_ASDU_addInformationObject(a1, io1);
|
|
InformationObject_destroy(io1);
|
|
IMasterConnection_sendASDU(con, a1);
|
|
CS101_ASDU_destroy(a1);
|
|
|
|
/* send current IOA 101 */
|
|
CS101_ASDU a2 = CS101_ASDU_create(alParams, false,
|
|
CS101_COT_INTERROGATED_BY_STATION, 0, 1, false, false);
|
|
InformationObject io2 = (InformationObject)
|
|
MeasuredValueShort_create(NULL, 101, current, IEC60870_QUALITY_GOOD);
|
|
CS101_ASDU_addInformationObject(a2, io2);
|
|
InformationObject_destroy(io2);
|
|
IMasterConnection_sendASDU(con, a2);
|
|
CS101_ASDU_destroy(a2);
|
|
|
|
/* send breaker1 IOA 200 */
|
|
CS101_ASDU a3 = CS101_ASDU_create(alParams, false,
|
|
CS101_COT_INTERROGATED_BY_STATION, 0, 1, false, false);
|
|
InformationObject io3 = (InformationObject)
|
|
SinglePointInformation_create(NULL, 200, breaker1, IEC60870_QUALITY_GOOD);
|
|
CS101_ASDU_addInformationObject(a3, io3);
|
|
InformationObject_destroy(io3);
|
|
IMasterConnection_sendASDU(con, a3);
|
|
CS101_ASDU_destroy(a3);
|
|
|
|
/* send breaker2 IOA 201 */
|
|
CS101_ASDU a4 = CS101_ASDU_create(alParams, false,
|
|
CS101_COT_INTERROGATED_BY_STATION, 0, 1, false, false);
|
|
InformationObject io4 = (InformationObject)
|
|
SinglePointInformation_create(NULL, 201, breaker2, IEC60870_QUALITY_GOOD);
|
|
CS101_ASDU_addInformationObject(a4, io4);
|
|
InformationObject_destroy(io4);
|
|
IMasterConnection_sendASDU(con, a4);
|
|
CS101_ASDU_destroy(a4);
|
|
|
|
/* GI complete */
|
|
IMasterConnection_sendACT_TERM(con, asdu);
|
|
printf("[RTU] GI complete — sent voltage=%.1f current=%.1f breaker1=%d breaker2=%d\n",
|
|
voltage, current, breaker1, breaker2);
|
|
|
|
return true;
|
|
}
|
|
|
|
/* ── handle commands from SCADA ─────────────────────────────── */
|
|
static bool asduHandler(void* p, IMasterConnection con, CS101_ASDU asdu) {
|
|
|
|
/* single command C_SC_NA_1 */
|
|
if (CS101_ASDU_getTypeID(asdu) == C_SC_NA_1) {
|
|
if (CS101_ASDU_getCOT(asdu) == CS101_COT_ACTIVATION) {
|
|
|
|
InformationObject io = CS101_ASDU_getElement(asdu, 0);
|
|
if (io) {
|
|
int ioa = InformationObject_getObjectAddress(io);
|
|
bool state = SingleCommand_getState((SingleCommand)io);
|
|
InformationObject_destroy(io);
|
|
|
|
printf("[RTU] Command received — IOA=%d state=%s\n",
|
|
ioa, state ? "ON" : "OFF");
|
|
|
|
if (ioa == 200) {
|
|
breaker1 = state;
|
|
printf("[RTU] Breaker 1 → %s\n", breaker1 ? "ON" : "OFF");
|
|
CS101_ASDU_setCOT(asdu, CS101_COT_ACTIVATION_CON);
|
|
}
|
|
else if (ioa == 201) {
|
|
breaker2 = state;
|
|
printf("[RTU] Breaker 2 → %s\n", breaker2 ? "ON" : "OFF");
|
|
CS101_ASDU_setCOT(asdu, CS101_COT_ACTIVATION_CON);
|
|
}
|
|
else {
|
|
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_IOA);
|
|
}
|
|
|
|
IMasterConnection_sendASDU(con, asdu);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* ── main ───────────────────────────────────────────────────── */
|
|
int main() {
|
|
signal(SIGINT, sigint_handler);
|
|
|
|
CS104_Slave slave = CS104_Slave_create(10, 10);
|
|
CS104_Slave_setLocalAddress(slave, "0.0.0.0");
|
|
CS104_Slave_setLocalPort(slave, 2404);
|
|
|
|
CS101_AppLayerParameters alParams = CS104_Slave_getAppLayerParameters(slave);
|
|
|
|
CS104_Slave_setConnectionEventHandler(slave, connectionEventHandler, NULL);
|
|
CS104_Slave_setConnectionRequestHandler(slave, connectionRequestHandler, NULL);
|
|
CS104_Slave_setInterrogationHandler(slave, interrogationHandler, NULL);
|
|
CS104_Slave_setASDUHandler(slave, asduHandler, NULL);
|
|
|
|
CS104_Slave_start(slave);
|
|
|
|
if (!CS104_Slave_isRunning(slave)) {
|
|
printf("[RTU] Failed to start!\n");
|
|
CS104_Slave_destroy(slave);
|
|
return 1;
|
|
}
|
|
|
|
printf("[RTU] Running on port 2404\n");
|
|
printf("[RTU] IOA 100=voltage 101=current 200=breaker1 201=breaker2\n\n");
|
|
|
|
uint64_t lastSent = 0;
|
|
|
|
while (running) {
|
|
uint64_t now = Hal_getMonotonicTimeInMs();
|
|
|
|
/* every 3s — send spontaneous measurements */
|
|
if (now >= lastSent + 3000) {
|
|
lastSent = now;
|
|
|
|
/* simulate small fluctuations */
|
|
voltage += ((rand() % 10) - 5) * 0.1;
|
|
current += ((rand() % 20) - 10) * 1.0;
|
|
|
|
/* voltage — IOA 100 */
|
|
CS101_ASDU a1 = CS101_ASDU_create(alParams, false,
|
|
CS101_COT_PERIODIC, 0, 1, false, false);
|
|
InformationObject io1 = (InformationObject)
|
|
MeasuredValueShort_create(NULL, 100, voltage, IEC60870_QUALITY_GOOD);
|
|
CS101_ASDU_addInformationObject(a1, io1);
|
|
InformationObject_destroy(io1);
|
|
CS104_Slave_enqueueASDU(slave, a1);
|
|
CS101_ASDU_destroy(a1);
|
|
|
|
/* current — IOA 101 */
|
|
CS101_ASDU a2 = CS101_ASDU_create(alParams, false,
|
|
CS101_COT_PERIODIC, 0, 1, false, false);
|
|
InformationObject io2 = (InformationObject)
|
|
MeasuredValueShort_create(NULL, 101, current, IEC60870_QUALITY_GOOD);
|
|
CS101_ASDU_addInformationObject(a2, io2);
|
|
InformationObject_destroy(io2);
|
|
CS104_Slave_enqueueASDU(slave, a2);
|
|
CS101_ASDU_destroy(a2);
|
|
|
|
printf("[RTU →] voltage=%.2fkV current=%.1fA breaker1=%s breaker2=%s\n",
|
|
voltage, current,
|
|
breaker1 ? "ON" : "OFF",
|
|
breaker2 ? "ON" : "OFF");
|
|
}
|
|
|
|
Thread_sleep(10);
|
|
}
|
|
|
|
printf("[RTU] Shutting down\n");
|
|
CS104_Slave_stop(slave);
|
|
CS104_Slave_destroy(slave);
|
|
return 0;
|
|
} |