173 lines
6.0 KiB
C
173 lines
6.0 KiB
C
/* scada.c — simulates a SCADA control center */
|
|
|
|
#include <stdlib.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <signal.h>
|
|
#include <string.h>
|
|
|
|
#include "cs104_connection.h"
|
|
#include "hal_thread.h"
|
|
#include "hal_time.h"
|
|
|
|
static bool running = true;
|
|
|
|
void sigint_handler(int s) { running = false; }
|
|
|
|
/* ── called for every ASDU received from RTU ───────────────── */
|
|
static bool asduReceivedHandler(void* p, int address, CS101_ASDU asdu) {
|
|
int typeId = CS101_ASDU_getTypeID(asdu);
|
|
int cot = CS101_ASDU_getCOT(asdu);
|
|
|
|
/* measured float value M_ME_NC_1 */
|
|
if (typeId == M_ME_NC_1) {
|
|
int i;
|
|
for (i = 0; i < CS101_ASDU_getNumberOfElements(asdu); i++) {
|
|
MeasuredValueShort mv = (MeasuredValueShort)
|
|
CS101_ASDU_getElement(asdu, i);
|
|
int ioa = InformationObject_getObjectAddress((InformationObject)mv);
|
|
float value = MeasuredValueShort_getValue(mv);
|
|
InformationObject_destroy((InformationObject)mv);
|
|
|
|
if (ioa == 100)
|
|
printf("[SCADA ←] Voltage IOA=100 %.2f kV\n", value);
|
|
else if (ioa == 101)
|
|
printf("[SCADA ←] Current IOA=101 %.1f A\n", value);
|
|
else
|
|
printf("[SCADA ←] Float IOA=%d %.2f\n", ioa, value);
|
|
}
|
|
}
|
|
|
|
/* single point M_SP_NA_1 */
|
|
else if (typeId == M_SP_NA_1) {
|
|
int i;
|
|
for (i = 0; i < CS101_ASDU_getNumberOfElements(asdu); i++) {
|
|
SinglePointInformation sp = (SinglePointInformation)
|
|
CS101_ASDU_getElement(asdu, i);
|
|
int ioa = InformationObject_getObjectAddress((InformationObject)sp);
|
|
bool state = SinglePointInformation_getValue(sp);
|
|
InformationObject_destroy((InformationObject)sp);
|
|
|
|
if (ioa == 200)
|
|
printf("[SCADA ←] Breaker1 IOA=200 %s\n", state ? "ON" : "OFF");
|
|
else if (ioa == 201)
|
|
printf("[SCADA ←] Breaker2 IOA=201 %s\n", state ? "ON" : "OFF");
|
|
else
|
|
printf("[SCADA ←] Point IOA=%d %s\n", ioa, state ? "ON" : "OFF");
|
|
}
|
|
}
|
|
|
|
/* command confirmation */
|
|
else if (typeId == C_SC_NA_1) {
|
|
if (cot == CS101_COT_ACTIVATION_CON)
|
|
printf("[SCADA ←] Command confirmed by RTU\n");
|
|
else if (cot == CS101_COT_UNKNOWN_IOA)
|
|
printf("[SCADA ←] Command rejected — unknown IOA\n");
|
|
}
|
|
|
|
else {
|
|
printf("[SCADA ←] TypeID=%d COT=%d\n", typeId, cot);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* ── connection state ───────────────────────────────────────── */
|
|
static void connectionHandler(void* p, CS104_Connection con, CS104_ConnectionEvent event) {
|
|
if (event == CS104_CONNECTION_OPENED)
|
|
printf("[SCADA] Connected to RTU\n");
|
|
else if (event == CS104_CONNECTION_CLOSED)
|
|
printf("[SCADA] Disconnected from RTU\n");
|
|
else if (event == CS104_CONNECTION_STARTDT_CON_RECEIVED)
|
|
printf("[SCADA] STARTDT confirmed — data transfer active\n");
|
|
else if (event == CS104_CONNECTION_STOPDT_CON_RECEIVED)
|
|
printf("[SCADA] STOPDT confirmed\n");
|
|
}
|
|
|
|
/* ── send a single command to RTU ───────────────────────────── */
|
|
void send_command(CS104_Connection con, int ioa, bool state) {
|
|
CS101_AppLayerParameters alParams =
|
|
CS104_Connection_getAppLayerParameters(con);
|
|
|
|
CS101_ASDU asdu = CS101_ASDU_create(alParams, false,
|
|
CS101_COT_ACTIVATION, 0, 1, false, false);
|
|
|
|
InformationObject io = (InformationObject)
|
|
SingleCommand_create(NULL, ioa, state, false, 0);
|
|
|
|
CS101_ASDU_addInformationObject(asdu, io);
|
|
InformationObject_destroy(io);
|
|
|
|
CS104_Connection_sendASDU(con, asdu);
|
|
CS101_ASDU_destroy(asdu);
|
|
|
|
printf("[SCADA →] Command sent — IOA=%d state=%s\n",
|
|
ioa, state ? "ON" : "OFF");
|
|
}
|
|
|
|
/* ── send general interrogation ─────────────────────────────── */
|
|
void send_gi(CS104_Connection con) {
|
|
CS104_Connection_sendInterrogationCommand(con,
|
|
CS101_COT_ACTIVATION, 1, IEC60870_QOI_STATION);
|
|
printf("[SCADA →] General Interrogation sent\n");
|
|
}
|
|
|
|
/* ── main ───────────────────────────────────────────────────── */
|
|
int main(int argc, char** argv) {
|
|
signal(SIGINT, sigint_handler);
|
|
|
|
const char* ip = (argc > 1) ? argv[1] : "127.0.0.1";
|
|
|
|
printf("[SCADA] Connecting to RTU at %s:2404\n\n", ip);
|
|
|
|
CS104_Connection con = CS104_Connection_create(ip, 2404);
|
|
|
|
CS104_Connection_setConnectionHandler(con, connectionHandler, NULL);
|
|
CS104_Connection_setASDUReceivedHandler(con, asduReceivedHandler, NULL);
|
|
|
|
CS104_Connection_connect(con);
|
|
Thread_sleep(1000);
|
|
|
|
/* start data transfer */
|
|
CS104_Connection_sendStartDT(con);
|
|
Thread_sleep(500);
|
|
|
|
/* request all current values */
|
|
send_gi(con);
|
|
Thread_sleep(2000);
|
|
|
|
/* main loop — send commands periodically */
|
|
uint64_t lastCmd = 0;
|
|
bool b1_state = false;
|
|
bool b2_state = false;
|
|
|
|
printf("\n[SCADA] Running — commands every 5s, Ctrl+C to stop\n\n");
|
|
|
|
while (running) {
|
|
uint64_t now = Hal_getMonotonicTimeInMs();
|
|
|
|
/* every 5s — toggle breaker1 then breaker2 alternately */
|
|
if (now >= lastCmd + 5000) {
|
|
lastCmd = now;
|
|
|
|
static int cmd_count = 0;
|
|
cmd_count++;
|
|
|
|
if (cmd_count % 2 == 0) {
|
|
b1_state = !b1_state;
|
|
send_command(con, 200, b1_state); /* toggle breaker1 */
|
|
} else {
|
|
b2_state = !b2_state;
|
|
send_command(con, 201, b2_state); /* toggle breaker2 */
|
|
}
|
|
}
|
|
|
|
Thread_sleep(10);
|
|
}
|
|
|
|
printf("\n[SCADA] Shutting down\n");
|
|
CS104_Connection_sendStopDT(con);
|
|
Thread_sleep(500);
|
|
CS104_Connection_destroy(con);
|
|
return 0;
|
|
} |