lpcmanager

656720f97e4d
Update IEC60870 files to accommodate proper timestamps for data points
/* Generated IEC 60870-5-104 (CS104) server runtime — instance %(locstr)s */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <time.h>
#if defined(_WIN32)
#include <windows.h>
#else
#include <unistd.h>
#include <sys/time.h>
#endif
#include "iec_types_all.h"
#include "iec60870_common.h"
#include "cs104_slave.h"
#include "cs101_information_objects.h"
#include "IEC104_%(locstr)s.h"
#ifndef IEC60870_WEAK
#if defined(__GNUC__) || defined(__clang__)
#define IEC60870_WEAK __attribute__((weak))
#else
#define IEC60870_WEAK
#endif
#endif
/* Referenced from __init_* before definition; silences -Wimplicit-function-declaration */
int __cleanup_%(locstr)s(void);
%(diag_intrinsic_global_block)s
%(diag_static_block)s
%(plc_memory_storage_block)s
%(loc_vars_block)s
struct iec60870_binding {
int server_index;
int type_id;
int ioa;
int is_command;
int bind_kind;
void *iec_var;
};
typedef struct {
const char *loc_label;
int common_address;
char ip_str[128];
int port;
int max_open;
int ca_sz;
int ioa_sz;
int cot_two_byte;
int oa;
int apci_k, apci_w, t0, t1, t2, t3;
UDINT *rd_ctr;
UDINT *wr_ctr;
BOOL *conn_bool;
CS104_Slave slave;
int init_st;
uint64_t sync_client_ms;
uint64_t sync_device_mono_ms;
bool time_synced;
void *cs_plc_var;
} iec60870_srv_t;
typedef union {
IEC_BOOL b;
IEC_BYTE by;
IEC_INT i;
IEC_UINT w;
IEC_REAL r;
UDINT u;
} iec_bind_shadow_t;
static iec_bind_shadow_t iec_binding_shadow[IEC60870_NUM_BINDINGS_%(locstr)s];
static uint64_t iec_binding_last_mono_ms[IEC60870_NUM_BINDINGS_%(locstr)s];
void PLC_GetTime(IEC_TIME *CURRENT_TIME);
#define IEC_WALL_EPOCH_MIN_MS 978307200000ULL /* 2001-01-01 UTC */
#ifndef CLOCK_HOST_REALTIME
#define IEC_CLOCK_HOST_REALTIME ((clockid_t)32) /* Xenomai Linux host wall clock */
#else
#define IEC_CLOCK_HOST_REALTIME CLOCK_HOST_REALTIME
#endif
%(extern_block)s
static struct iec60870_binding iec60870_bindings[] = {
%(bindings_rows)s
};
static const size_t iec60870_binding_count = IEC60870_NUM_BINDINGS_%(locstr)s;
%(srv_def)s
static void iec_rd_inc(iec60870_srv_t *s) {
if (s->rd_ctr)
*s->rd_ctr += 1U;
}
static void iec_wr_inc(iec60870_srv_t *s) {
if (s->wr_ctr)
*s->wr_ctr += 1U;
}
static bool iec_get_bool_var(void *p) {
return *(IEC_BOOL *)p != 0;
}
static void iec_set_bool_var(void *p, bool v) {
*(IEC_BOOL *)p = v ? (IEC_BOOL)1 : (IEC_BOOL)0;
}
/* Device monotonic clock for post-sync offset + Linux host wall for pre-sync tags.
* Xenomai RT: PLC_GetTime/CLOCK_REALTIME are Cobalt time; use CLOCK_HOST_REALTIME. */
static uint64_t iec_mono_ms(void) {
#if defined(_WIN32)
static LARGE_INTEGER freq;
LARGE_INTEGER counter;
if (freq.QuadPart == 0)
QueryPerformanceFrequency(&freq);
if (!QueryPerformanceCounter(&counter))
return 0;
return (uint64_t)(counter.QuadPart * 1000ULL / (uint64_t)freq.QuadPart);
#else
struct timespec ts;
if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0)
return 0;
return (uint64_t)ts.tv_sec * 1000ULL + (uint64_t)ts.tv_nsec / 1000000ULL;
#endif
}
static bool iec_timespec_plausible_ms(const struct timespec *ts, uint64_t *out_ms) {
uint64_t ms;
if (!ts || !out_ms)
return false;
if (ts->tv_sec < 0)
return false;
ms = (uint64_t)ts->tv_sec * 1000ULL + (uint64_t)ts->tv_nsec / 1000000ULL;
if (ms < IEC_WALL_EPOCH_MIN_MS)
return false;
*out_ms = ms;
return true;
}
static bool iec_try_clock_ms(clockid_t clk, uint64_t *out_ms) {
struct timespec ts;
if (!out_ms || clock_gettime(clk, &ts) != 0)
return false;
return iec_timespec_plausible_ms(&ts, out_ms);
}
static uint64_t iec_os_wall_ms(void) {
#if defined(_WIN32)
IEC_TIME now;
PLC_GetTime(&now);
return (uint64_t)now.tv_sec * 1000ULL +
(uint64_t)((now.tv_nsec >= 0) ? now.tv_nsec : 0) / 1000000ULL;
#else
uint64_t ms;
if (iec_try_clock_ms(IEC_CLOCK_HOST_REALTIME, &ms))
return ms;
if (iec_try_clock_ms(CLOCK_REALTIME, &ms))
return ms;
{
struct timeval tv;
if (gettimeofday(&tv, NULL) == 0) {
struct timespec gt;
gt.tv_sec = tv.tv_sec;
gt.tv_nsec = (long)tv.tv_usec * 1000L;
if (iec_timespec_plausible_ms(&gt, &ms))
return ms;
}
}
return 0;
#endif
}
static uint64_t iec_wall_ref_ms;
static uint64_t iec_wall_ref_mono_ms;
static bool iec_wall_ref_valid;
static uint64_t iec_wall_ref_last_try_mono_ms;
static void iec_wall_ref_maybe_refresh(void) {
uint64_t now_mono = iec_mono_ms();
uint64_t wall_ms;
if (iec_wall_ref_valid && (now_mono - iec_wall_ref_last_try_mono_ms) < 1000ULL)
return;
iec_wall_ref_last_try_mono_ms = now_mono;
wall_ms = iec_os_wall_ms();
if (wall_ms < IEC_WALL_EPOCH_MIN_MS)
return;
iec_wall_ref_ms = wall_ms;
iec_wall_ref_mono_ms = now_mono;
iec_wall_ref_valid = true;
}
static uint64_t iec_mono_to_wall_ms(uint64_t device_mono_ms) {
if (!iec_wall_ref_valid)
return device_mono_ms;
if (device_mono_ms >= iec_wall_ref_mono_ms)
return iec_wall_ref_ms + (device_mono_ms - iec_wall_ref_mono_ms);
return iec_wall_ref_ms;
}
static uint64_t iec_binding_tag_ms(const iec60870_srv_t *srv, size_t bind_idx) {
if (srv && srv->time_synced)
return srv->sync_client_ms +
(iec_binding_last_mono_ms[bind_idx] - srv->sync_device_mono_ms);
return iec_mono_to_wall_ms(iec_binding_last_mono_ms[bind_idx]);
}
static void iec_fill_cp56(struct sCP56Time2a *buf, uint64_t client_ms, bool valid) {
memset(buf, 0, sizeof(*buf));
if (valid) {
CP56Time2a_createFromMsTimestamp((CP56Time2a)buf, client_ms);
CP56Time2a_setInvalid((CP56Time2a)buf, false);
} else {
CP56Time2a_setInvalid((CP56Time2a)buf, true);
}
}
static void iec_fill_cp24(struct sCP24Time2a *buf, uint64_t client_ms, bool valid) {
uint64_t tod_ms;
memset(buf, 0, sizeof(*buf));
if (!valid) {
CP24Time2a_setInvalid((CP24Time2a)buf, true);
return;
}
tod_ms = client_ms %% 86400000ULL;
CP24Time2a_setMillisecond((CP24Time2a)buf, (int)(tod_ms %% 1000ULL));
CP24Time2a_setSecond((CP24Time2a)buf, (int)((tod_ms / 1000ULL) %% 60ULL));
CP24Time2a_setMinute((CP24Time2a)buf, (int)((tod_ms / 60000ULL) %% 60ULL));
CP24Time2a_setInvalid((CP24Time2a)buf, false);
}
static CP16Time2a iec_zero_cp16_buf(struct sCP16Time2a *buf) {
memset(buf, 0, sizeof(*buf));
return (CP16Time2a)buf;
}
static void iec_binding_shadow_read(size_t idx, struct iec60870_binding *b, iec_bind_shadow_t *out) {
switch (b->bind_kind) {
case 0:
out->b = *(IEC_BOOL *)b->iec_var;
break;
case 1:
out->by = *(IEC_BYTE *)b->iec_var;
break;
case 2:
out->i = *(IEC_INT *)b->iec_var;
break;
case 3:
out->w = *(IEC_UINT *)b->iec_var;
break;
case 4:
out->r = *(IEC_REAL *)b->iec_var;
break;
default:
out->u = *(UDINT *)b->iec_var;
break;
}
}
static bool iec_binding_shadow_differs(size_t idx, struct iec60870_binding *b) {
iec_bind_shadow_t cur;
iec_binding_shadow_read(idx, b, &cur);
switch (b->bind_kind) {
case 0:
return cur.b != iec_binding_shadow[idx].b;
case 1:
return cur.by != iec_binding_shadow[idx].by;
case 2:
return cur.i != iec_binding_shadow[idx].i;
case 3:
return cur.w != iec_binding_shadow[idx].w;
case 4:
return memcmp(&cur.r, &iec_binding_shadow[idx].r, sizeof(IEC_REAL)) != 0;
default:
return cur.u != iec_binding_shadow[idx].u;
}
}
static void iec_binding_shadow_store(size_t idx, struct iec60870_binding *b) {
iec_binding_shadow_read(idx, b, &iec_binding_shadow[idx]);
}
static void iec_binding_touch(size_t idx) {
iec_binding_last_mono_ms[idx] = iec_mono_ms();
iec_binding_shadow_store(idx, &iec60870_bindings[idx]);
}
static void iec60870_init_binding_times(void) {
size_t i;
uint64_t now_mono = iec_mono_ms();
iec_wall_ref_maybe_refresh();
for (i = 0; i < iec60870_binding_count; i++) {
if (iec60870_bindings[i].server_index < 0 || !iec60870_bindings[i].iec_var)
continue;
iec_binding_last_mono_ms[i] = now_mono;
iec_binding_shadow_store(i, &iec60870_bindings[i]);
}
}
static void iec60870_poll_binding_changes(void) {
size_t i;
for (i = 0; i < iec60870_binding_count; i++) {
struct iec60870_binding *b = &iec60870_bindings[i];
if (b->server_index < 0 || !b->iec_var)
continue;
if (iec_binding_shadow_differs(i, b))
iec_binding_touch(i);
}
}
static bool iec_clock_sync_handler(void *parameter, IMasterConnection connection, CS101_ASDU asdu,
CP56Time2a newTime) {
iec60870_srv_t *srv = (iec60870_srv_t *)parameter;
uint64_t client_ms = CP56Time2a_toMsTimestamp(newTime);
(void)connection;
(void)asdu;
if (!srv)
return false;
srv->sync_client_ms = client_ms;
srv->sync_device_mono_ms = iec_mono_ms();
srv->time_synced = true;
if (srv->cs_plc_var)
*(UDINT *)srv->cs_plc_var = (UDINT)(client_ms / 1000ULL);
return true;
}
static InformationObject iec_make_monitor_io(int type_id, int ioa, struct iec60870_binding *b) {
void *mem = malloc((size_t)InformationObject_getMaxSizeInMemory());
QualityDescriptor q = IEC60870_QUALITY_GOOD;
struct sCP56Time2a cp56_wall;
struct sCP24Time2a cp24_z;
struct sCP16Time2a cp16_z;
size_t bind_idx = (size_t)(b - iec60870_bindings);
iec60870_srv_t *srv =
(b->server_index >= 0) ? &iec60870_srv[b->server_index] : NULL;
uint64_t tag_ms = iec_binding_tag_ms(srv, bind_idx);
struct sBinaryCounterReading bcr_st;
BinaryCounterReading bcr;
uint32_t pack_u32;
uint8_t se_byte;
SingleEvent se;
StartEvent ste;
QualityDescriptorP qdp;
OutputCircuitInfo oci;
struct sStatusAndStatusChangeDetection scd_st;
StatusAndStatusChangeDetection scd;
uint8_t seg_dummy = 0;
if (!mem)
return NULL;
iec_fill_cp56(&cp56_wall, tag_ms, true);
iec_fill_cp24(&cp24_z, tag_ms, true);
switch (type_id) {
case M_SP_NA_1:
return (InformationObject)SinglePointInformation_create(
(SinglePointInformation)mem, ioa, iec_get_bool_var(b->iec_var), q);
case M_SP_TA_1:
return (InformationObject)SinglePointWithCP24Time2a_create(
(SinglePointWithCP24Time2a)mem, ioa, iec_get_bool_var(b->iec_var), q,
(CP24Time2a)&cp24_z);
case M_SP_TB_1:
return (InformationObject)SinglePointWithCP56Time2a_create(
(SinglePointWithCP56Time2a)mem, ioa, iec_get_bool_var(b->iec_var), q,
(CP56Time2a)&cp56_wall);
case M_DP_NA_1: {
DoublePointValue dv = (DoublePointValue)(*(IEC_BYTE *)b->iec_var & (IEC_BYTE)3);
return (InformationObject)DoublePointInformation_create(
(DoublePointInformation)mem, ioa, dv, q);
}
case M_DP_TA_1: {
DoublePointValue dv = (DoublePointValue)(*(IEC_BYTE *)b->iec_var & (IEC_BYTE)3);
return (InformationObject)DoublePointWithCP24Time2a_create(
(DoublePointWithCP24Time2a)mem, ioa, dv, q, (CP24Time2a)&cp24_z);
}
case M_DP_TB_1: {
DoublePointValue dv = (DoublePointValue)(*(IEC_BYTE *)b->iec_var & (IEC_BYTE)3);
return (InformationObject)DoublePointWithCP56Time2a_create(
(DoublePointWithCP56Time2a)mem, ioa, dv, q, (CP56Time2a)&cp56_wall);
}
case M_ST_NA_1:
return (InformationObject)StepPositionInformation_create(
(StepPositionInformation)mem, ioa, (int)*(IEC_INT *)b->iec_var, false, q);
case M_ST_TA_1:
return (InformationObject)StepPositionWithCP24Time2a_create(
(StepPositionWithCP24Time2a)mem, ioa, (int)*(IEC_INT *)b->iec_var, false, q,
(CP24Time2a)&cp24_z);
case M_ST_TB_1:
return (InformationObject)StepPositionWithCP56Time2a_create(
(StepPositionWithCP56Time2a)mem, ioa, (int)*(IEC_INT *)b->iec_var, false, q,
(CP56Time2a)&cp56_wall);
case M_BO_NA_1:
return (InformationObject)BitString32_createEx(
(BitString32)mem, ioa, (uint32_t)*(UDINT *)b->iec_var, q);
case M_BO_TA_1:
return (InformationObject)Bitstring32WithCP24Time2a_createEx(
(Bitstring32WithCP24Time2a)mem, ioa, (uint32_t)*(UDINT *)b->iec_var, q,
(CP24Time2a)&cp24_z);
case M_BO_TB_1:
return (InformationObject)Bitstring32WithCP56Time2a_createEx(
(Bitstring32WithCP56Time2a)mem, ioa, (uint32_t)*(UDINT *)b->iec_var, q,
(CP56Time2a)&cp56_wall);
case M_ME_NA_1: {
float nv = NormalizedValue_fromScaled((int)(int16_t)*(IEC_UINT *)b->iec_var);
return (InformationObject)MeasuredValueNormalized_create(
(MeasuredValueNormalized)mem, ioa, nv, q);
}
case M_ME_TA_1: {
float nv = NormalizedValue_fromScaled((int)(int16_t)*(IEC_UINT *)b->iec_var);
return (InformationObject)MeasuredValueNormalizedWithCP24Time2a_create(
(MeasuredValueNormalizedWithCP24Time2a)mem, ioa, nv, q, (CP24Time2a)&cp24_z);
}
case M_ME_TD_1: {
float nv = NormalizedValue_fromScaled((int)(int16_t)*(IEC_UINT *)b->iec_var);
return (InformationObject)MeasuredValueNormalizedWithCP56Time2a_create(
(MeasuredValueNormalizedWithCP56Time2a)mem, ioa, nv, q, (CP56Time2a)&cp56_wall);
}
case M_ME_ND_1: {
float nv = NormalizedValue_fromScaled((int)(int16_t)*(IEC_UINT *)b->iec_var);
return (InformationObject)MeasuredValueNormalizedWithoutQuality_create(
(MeasuredValueNormalizedWithoutQuality)mem, ioa, nv);
}
case M_ME_NB_1:
return (InformationObject)MeasuredValueScaled_create(
(MeasuredValueScaled)mem, ioa, (int)*(IEC_INT *)b->iec_var, q);
case M_ME_TB_1:
return (InformationObject)MeasuredValueScaledWithCP24Time2a_create(
(MeasuredValueScaledWithCP24Time2a)mem, ioa, (int)*(IEC_INT *)b->iec_var, q,
(CP24Time2a)&cp24_z);
case M_ME_TE_1:
return (InformationObject)MeasuredValueScaledWithCP56Time2a_create(
(MeasuredValueScaledWithCP56Time2a)mem, ioa, (int)*(IEC_INT *)b->iec_var, q,
(CP56Time2a)&cp56_wall);
case M_ME_NC_1:
return (InformationObject)MeasuredValueShort_create(
(MeasuredValueShort)mem, ioa, *(IEC_REAL *)b->iec_var, q);
case M_ME_TC_1:
return (InformationObject)MeasuredValueShortWithCP24Time2a_create(
(MeasuredValueShortWithCP24Time2a)mem, ioa, *(IEC_REAL *)b->iec_var, q,
(CP24Time2a)&cp24_z);
case M_ME_TF_1:
return (InformationObject)MeasuredValueShortWithCP56Time2a_create(
(MeasuredValueShortWithCP56Time2a)mem, ioa, *(IEC_REAL *)b->iec_var, q,
(CP56Time2a)&cp56_wall);
case M_IT_NA_1:
bcr = BinaryCounterReading_create((BinaryCounterReading)&bcr_st,
(int32_t)(*(UDINT *)b->iec_var), 0, false, false, false);
return (InformationObject)IntegratedTotals_create((IntegratedTotals)mem, ioa, bcr);
case M_IT_TA_1:
bcr = BinaryCounterReading_create((BinaryCounterReading)&bcr_st,
(int32_t)(*(UDINT *)b->iec_var), 0, false, false, false);
return (InformationObject)IntegratedTotalsWithCP24Time2a_create(
(IntegratedTotalsWithCP24Time2a)mem, ioa, bcr, (CP24Time2a)&cp24_z);
case M_IT_TB_1:
bcr = BinaryCounterReading_create((BinaryCounterReading)&bcr_st,
(int32_t)(*(UDINT *)b->iec_var), 0, false, false, false);
return (InformationObject)IntegratedTotalsWithCP56Time2a_create(
(IntegratedTotalsWithCP56Time2a)mem, ioa, bcr, (CP56Time2a)&cp56_wall);
case M_EP_TA_1:
pack_u32 = (uint32_t)*(UDINT *)b->iec_var;
se_byte = 0;
se = (SingleEvent)&se_byte;
SingleEvent_setEventState(se, (EventState)(pack_u32 & 3u));
SingleEvent_setQDP(se, (QualityDescriptorP)((pack_u32 >> 2) & 0xffu));
return (InformationObject)EventOfProtectionEquipment_create(
(EventOfProtectionEquipment)mem, ioa, se, iec_zero_cp16_buf(&cp16_z), (CP24Time2a)&cp24_z);
case M_EP_TB_1:
pack_u32 = (uint32_t)*(UDINT *)b->iec_var;
ste = (StartEvent)(pack_u32 & 0xffu);
qdp = (QualityDescriptorP)((pack_u32 >> 8) & 0xffu);
return (InformationObject)PackedStartEventsOfProtectionEquipment_create(
(PackedStartEventsOfProtectionEquipment)mem, ioa, ste, qdp, iec_zero_cp16_buf(&cp16_z),
(CP24Time2a)&cp24_z);
case M_EP_TC_1:
pack_u32 = (uint32_t)*(UDINT *)b->iec_var;
oci = (OutputCircuitInfo)(pack_u32 & 0xffu);
qdp = (QualityDescriptorP)((pack_u32 >> 8) & 0xffu);
return (InformationObject)PackedOutputCircuitInfo_create(
(PackedOutputCircuitInfo)mem, ioa, oci, qdp, iec_zero_cp16_buf(&cp16_z), (CP24Time2a)&cp24_z);
case M_EP_TD_1:
pack_u32 = (uint32_t)*(UDINT *)b->iec_var;
se_byte = 0;
se = (SingleEvent)&se_byte;
SingleEvent_setEventState(se, (EventState)(pack_u32 & 3u));
SingleEvent_setQDP(se, (QualityDescriptorP)((pack_u32 >> 2) & 0xffu));
return (InformationObject)EventOfProtectionEquipmentWithCP56Time2a_create(
(EventOfProtectionEquipmentWithCP56Time2a)mem, ioa, se, iec_zero_cp16_buf(&cp16_z),
(CP56Time2a)&cp56_wall);
case M_EP_TE_1:
pack_u32 = (uint32_t)*(UDINT *)b->iec_var;
ste = (StartEvent)(pack_u32 & 0xffu);
qdp = (QualityDescriptorP)((pack_u32 >> 8) & 0xffu);
return (InformationObject)PackedStartEventsOfProtectionEquipmentWithCP56Time2a_create(
(PackedStartEventsOfProtectionEquipmentWithCP56Time2a)mem, ioa, ste, qdp,
iec_zero_cp16_buf(&cp16_z), (CP56Time2a)&cp56_wall);
case M_EP_TF_1:
pack_u32 = (uint32_t)*(UDINT *)b->iec_var;
oci = (OutputCircuitInfo)(pack_u32 & 0xffu);
qdp = (QualityDescriptorP)((pack_u32 >> 8) & 0xffu);
return (InformationObject)PackedOutputCircuitInfoWithCP56Time2a_create(
(PackedOutputCircuitInfoWithCP56Time2a)mem, ioa, oci, qdp, iec_zero_cp16_buf(&cp16_z),
(CP56Time2a)&cp56_wall);
case M_PS_NA_1:
memset(&scd_st, 0, sizeof(scd_st));
scd = (StatusAndStatusChangeDetection)&scd_st;
StatusAndStatusChangeDetection_setSTn(scd, (uint16_t)(*(UDINT *)b->iec_var & 0xffffu));
return (InformationObject)PackedSinglePointWithSCD_create(
(PackedSinglePointWithSCD)mem, ioa, scd, q);
case M_EI_NA_1:
return (InformationObject)EndOfInitialization_create(
(EndOfInitialization)mem, (uint8_t)(iec_get_bool_var(b->iec_var) ? 1 : 0));
/* File transfer indications — PLC holds scalar summaries per IOA (length / qualifiers). */
case F_FR_NA_1:
return (InformationObject)FileReady_create(
(FileReady)mem, ioa, 0, (uint32_t)*(UDINT *)b->iec_var, true);
case F_SR_NA_1:
return (InformationObject)SectionReady_create(
(SectionReady)mem, ioa, 0, 0, (uint32_t)*(UDINT *)b->iec_var, false);
case F_LS_NA_1: {
uint32_t v = (uint32_t)*(UDINT *)b->iec_var;
/* Packed: NOF (bits 0–15), NOS (16–23), LSQ (24–31); CHS via extension if needed */
return (InformationObject)FileLastSegmentOrSection_create(
(FileLastSegmentOrSection)mem, ioa,
(uint16_t)(v & 0xffffu),
(uint8_t)((v >> 16) & 0xffu),
(uint8_t)((v >> 24) & 0xffu),
0);
}
case F_AF_NA_1:
return (InformationObject)FileACK_create(
(FileACK)mem, ioa, 0, 0, (uint8_t)(*(IEC_BYTE *)b->iec_var & 0xffu));
case F_SG_NA_1:
return (InformationObject)FileSegment_create(
(FileSegment)mem, ioa, 0, 0, &seg_dummy, 0);
case F_DR_TA_1:
return (InformationObject)FileDirectory_create(
(FileDirectory)mem, ioa, 0, (uint32_t)*(UDINT *)b->iec_var, 0, (CP56Time2a)&cp56_wall);
default:
free(mem);
return NULL;
}
}
static bool iec_send_monitor(IMasterConnection conn, iec60870_srv_t *srv, struct iec60870_binding *b) {
CS101_AppLayerParameters al = CS104_Slave_getAppLayerParameters(srv->slave);
int oa = srv->cot_two_byte ? srv->oa : 0;
InformationObject io = iec_make_monitor_io(b->type_id, b->ioa, b);
if (!io)
return false;
CS101_ASDU asdu = CS101_ASDU_create(al, false, CS101_COT_INTERROGATED_BY_STATION, oa,
srv->common_address, false, false);
if (!asdu) {
InformationObject_destroy(io);
return false;
}
if (!CS101_ASDU_addInformationObject(asdu, io)) {
InformationObject_destroy(io);
CS101_ASDU_destroy(asdu);
return false;
}
if (!IMasterConnection_sendASDU(conn, asdu)) {
CS101_ASDU_destroy(asdu);
return false;
}
CS101_ASDU_destroy(asdu);
iec_rd_inc(srv);
return true;
}
static bool iec_interrogation_handler(void *parameter, IMasterConnection connection, CS101_ASDU asdu, uint8_t qoi) {
(void)asdu;
iec60870_srv_t *srv = (iec60870_srv_t *)parameter;
size_t idx = (size_t)(srv - iec60870_srv);
size_t i;
if (qoi == IEC60870_QOI_STATION) {
for (i = 0; i < iec60870_binding_count; i++) {
struct iec60870_binding *b = &iec60870_bindings[i];
if (b->server_index < 0)
continue;
if ((size_t)b->server_index != idx)
continue;
if (b->is_command)
continue;
iec_send_monitor(connection, srv, b);
}
}
return true;
}
static bool iec_tid_handled_as_command(TypeID tid) {
int t = (int)tid;
if (t >= C_SC_NA_1 && t <= C_BO_NA_1)
return true;
if (t >= C_SC_TA_1 && t <= C_BO_TA_1)
return true;
if (t >= C_IC_NA_1 && t <= C_TS_TA_1 && t != C_CS_NA_1)
return true;
if (t >= P_ME_NA_1 && t <= P_AC_NA_1)
return true;
if (t == F_SC_NA_1 || t == F_SC_NB_1)
return true;
return false;
}
static bool iec_handle_command(iec60870_srv_t *srv, IMasterConnection connection, CS101_ASDU asdu) {
TypeID tid = CS101_ASDU_getTypeID(asdu);
InformationObject io = CS101_ASDU_getElement(asdu, 0);
size_t srv_idx = (size_t)(srv - iec60870_srv);
size_t i;
struct iec60870_binding *match = NULL;
float fv;
int scv;
if (!io)
return false;
{
int ioa = InformationObject_getObjectAddress(io);
for (i = 0; i < iec60870_binding_count; i++) {
if (iec60870_bindings[i].server_index < 0)
continue;
if ((size_t)iec60870_bindings[i].server_index != srv_idx)
continue;
if (!iec60870_bindings[i].is_command)
continue;
if (iec60870_bindings[i].ioa == ioa && iec60870_bindings[i].type_id == (int)tid) {
match = &iec60870_bindings[i];
break;
}
}
}
if (!match)
return false;
switch (tid) {
case C_SC_NA_1: {
SingleCommand sc = (SingleCommand)io;
iec_set_bool_var(match->iec_var, SingleCommand_getState(sc));
break;
}
case C_DC_NA_1: {
DoubleCommand dc = (DoubleCommand)io;
*(IEC_BYTE *)match->iec_var = (IEC_BYTE)(DoubleCommand_getState(dc) & 0xff);
break;
}
case C_RC_NA_1: {
StepCommand sc = (StepCommand)io;
*(IEC_BYTE *)match->iec_var = (IEC_BYTE)((int)StepCommand_getState(sc) & 0xff);
break;
}
case C_SE_NA_1: {
SetpointCommandNormalized sn = (SetpointCommandNormalized)io;
fv = SetpointCommandNormalized_getValue(sn);
scv = NormalizedValue_toScaled(fv);
*(IEC_UINT *)match->iec_var = (uint16_t)scv;
break;
}
case C_SE_NB_1: {
SetpointCommandScaled ss = (SetpointCommandScaled)io;
*(IEC_INT *)match->iec_var = (int16_t)SetpointCommandScaled_getValue(ss);
break;
}
case C_SE_NC_1: {
SetpointCommandShort sf = (SetpointCommandShort)io;
*(IEC_REAL *)match->iec_var = SetpointCommandShort_getValue(sf);
break;
}
case C_BO_NA_1: {
Bitstring32Command bc = (Bitstring32Command)io;
*(UDINT *)match->iec_var = (UDINT)Bitstring32Command_getValue(bc);
break;
}
case C_SC_TA_1:
iec_set_bool_var(match->iec_var, SingleCommand_getState((SingleCommand)io));
break;
case C_DC_TA_1: {
DoubleCommandWithCP56Time2a dc = (DoubleCommandWithCP56Time2a)io;
*(IEC_BYTE *)match->iec_var = (IEC_BYTE)(DoubleCommandWithCP56Time2a_getState(dc) & 0xff);
break;
}
case C_RC_TA_1: {
StepCommandWithCP56Time2a sc = (StepCommandWithCP56Time2a)io;
*(IEC_BYTE *)match->iec_var = (IEC_BYTE)((int)StepCommandWithCP56Time2a_getState(sc) & 0xff);
break;
}
case C_SE_TA_1: {
SetpointCommandNormalizedWithCP56Time2a sn = (SetpointCommandNormalizedWithCP56Time2a)io;
fv = SetpointCommandNormalizedWithCP56Time2a_getValue(sn);
scv = NormalizedValue_toScaled(fv);
*(IEC_UINT *)match->iec_var = (uint16_t)scv;
break;
}
case C_SE_TB_1: {
SetpointCommandScaledWithCP56Time2a ss = (SetpointCommandScaledWithCP56Time2a)io;
*(IEC_INT *)match->iec_var = (int16_t)SetpointCommandScaledWithCP56Time2a_getValue(ss);
break;
}
case C_SE_TC_1: {
SetpointCommandShortWithCP56Time2a sf = (SetpointCommandShortWithCP56Time2a)io;
*(IEC_REAL *)match->iec_var = SetpointCommandShortWithCP56Time2a_getValue(sf);
break;
}
case C_BO_TA_1: {
Bitstring32CommandWithCP56Time2a bc = (Bitstring32CommandWithCP56Time2a)io;
*(UDINT *)match->iec_var = (UDINT)Bitstring32CommandWithCP56Time2a_getValue(bc);
break;
}
case C_IC_NA_1: {
InterrogationCommand ic = (InterrogationCommand)io;
iec_set_bool_var(match->iec_var, InterrogationCommand_getQOI(ic) != 0);
break;
}
case C_CI_NA_1: {
CounterInterrogationCommand cic = (CounterInterrogationCommand)io;
*(UDINT *)match->iec_var = (UDINT)CounterInterrogationCommand_getQCC(cic);
break;
}
case C_RD_NA_1:
iec_set_bool_var(match->iec_var, true);
break;
case C_TS_NA_1:
iec_set_bool_var(match->iec_var, TestCommand_isValid((TestCommand)io));
break;
case C_RP_NA_1:
*(IEC_BYTE *)match->iec_var =
(IEC_BYTE)(ResetProcessCommand_getQRP((ResetProcessCommand)io) & 0xff);
break;
case C_CD_NA_1: {
DelayAcquisitionCommand da = (DelayAcquisitionCommand)io;
*(UDINT *)match->iec_var =
(UDINT)CP16Time2a_getEplapsedTimeInMs(DelayAcquisitionCommand_getDelay(da));
break;
}
case C_TS_TA_1:
*(IEC_UINT *)match->iec_var =
(IEC_UINT)TestCommandWithCP56Time2a_getCounter((TestCommandWithCP56Time2a)io);
break;
case P_ME_NA_1: {
ParameterNormalizedValue pn = (ParameterNormalizedValue)io;
fv = ParameterNormalizedValue_getValue(pn);
scv = NormalizedValue_toScaled(fv);
*(IEC_UINT *)match->iec_var = (uint16_t)scv;
break;
}
case P_ME_NB_1: {
ParameterScaledValue ps = (ParameterScaledValue)io;
*(IEC_INT *)match->iec_var = (int16_t)ParameterScaledValue_getValue(ps);
break;
}
case P_ME_NC_1: {
ParameterFloatValue pf = (ParameterFloatValue)io;
*(IEC_REAL *)match->iec_var = ParameterFloatValue_getValue(pf);
break;
}
case P_AC_NA_1:
*(IEC_BYTE *)match->iec_var =
(IEC_BYTE)(ParameterActivation_getQuality((ParameterActivation)io) & 0xff);
break;
case F_SC_NA_1:
*(IEC_BYTE *)match->iec_var =
(IEC_BYTE)(FileCallOrSelect_getSCQ((FileCallOrSelect)io) & 0xff);
break;
case F_SC_NB_1:
*(IEC_UINT *)match->iec_var = (IEC_UINT)QueryLog_getNOF((QueryLog)io);
break;
default:
return false;
}
iec_wr_inc(srv);
iec_binding_touch((size_t)(match - iec60870_bindings));
IMasterConnection_sendACT_CON(connection, asdu, false);
return true;
}
static bool iec_asdu_handler(void *parameter, IMasterConnection connection, CS101_ASDU asdu) {
iec60870_srv_t *srv = (iec60870_srv_t *)parameter;
TypeID tid = CS101_ASDU_getTypeID(asdu);
if (iec_tid_handled_as_command(tid))
return iec_handle_command(srv, connection, asdu);
iec_rd_inc(srv);
return false;
}
static void iec_connection_event(void *parameter, IMasterConnection connection, CS104_PeerConnectionEvent event) {
(void)connection;
iec60870_srv_t *srv = (iec60870_srv_t *)parameter;
if (srv->conn_bool) {
if (event == CS104_CON_EVENT_ACTIVATED)
iec_set_bool_var(srv->conn_bool, true);
/* Do not call CS104_Slave_getOpenConnections here — it re-enters the slave from inside
* lib60870 connection callbacks and can deadlock with multiple clients. conn_bool is
* refreshed every PLC cycle in __retrieve_*(). */
}
}
int __init_%(locstr)s(int argc, char **argv) {
int si;
(void)argc;
(void)argv;
iec_wall_ref_maybe_refresh();
for (si = 0; si < IEC60870_NUM_SERVERS_%(locstr)s; si++) {
iec60870_srv_t *s = &iec60870_srv[si];
size_t bi;
s->slave = CS104_Slave_create(200, 200);
if (!s->slave) {
fprintf(stderr, "IEC60870 %%s: CS104_Slave_create failed\\n", s->loc_label);
goto fail;
}
CS104_Slave_setLocalAddress(s->slave, s->ip_str);
CS104_Slave_setLocalPort(s->slave, s->port);
CS104_Slave_setMaxOpenConnections(s->slave, s->max_open);
/* Each master TCP session is an independent redundancy group. The default SINGLE_REDUNDANCY_GROUP
* shares one app-layer context across all peers; closing one connection can break others
* (e.g. two GiMonitor clients). */
CS104_Slave_setServerMode(s->slave, CS104_MODE_CONNECTION_IS_REDUNDANCY_GROUP);
{
CS101_AppLayerParameters al = CS104_Slave_getAppLayerParameters(s->slave);
al->sizeOfCA = s->ca_sz;
al->sizeOfIOA = s->ioa_sz;
al->sizeOfCOT = s->cot_two_byte ? 2 : 1;
al->originatorAddress = s->oa;
}
{
CS104_APCIParameters ap = CS104_Slave_getConnectionParameters(s->slave);
ap->k = s->apci_k;
ap->w = s->apci_w;
ap->t0 = s->t0;
ap->t1 = s->t1;
ap->t2 = s->t2;
ap->t3 = s->t3;
}
CS104_Slave_setInterrogationHandler(s->slave, iec_interrogation_handler, s);
CS104_Slave_setClockSyncHandler(s->slave, iec_clock_sync_handler, s);
CS104_Slave_setASDUHandler(s->slave, iec_asdu_handler, s);
CS104_Slave_setConnectionEventHandler(s->slave, iec_connection_event, s);
CS104_Slave_start(s->slave);
s->init_st = 1;
s->sync_client_ms = 0;
s->sync_device_mono_ms = 0;
s->time_synced = false;
s->cs_plc_var = NULL;
for (bi = 0; bi < iec60870_binding_count; bi++) {
if (iec60870_bindings[bi].server_index != si)
continue;
if (!iec60870_bindings[bi].is_command)
continue;
if (iec60870_bindings[bi].type_id == C_CS_NA_1) {
s->cs_plc_var = iec60870_bindings[bi].iec_var;
break;
}
}
}
iec60870_init_binding_times();
return 0;
fail:
__cleanup_%(locstr)s();
return -1;
}
void __retrieve_%(locstr)s(void) {
int si;
for (si = 0; si < IEC60870_NUM_SERVERS_%(locstr)s; si++) {
iec60870_srv_t *s = &iec60870_srv[si];
if (s->slave && s->conn_bool)
iec_set_bool_var(s->conn_bool, CS104_Slave_getOpenConnections(s->slave) > 0);
}
}
void __publish_%(locstr)s(void) {
iec_wall_ref_maybe_refresh();
iec60870_poll_binding_changes();
}
int __cleanup_%(locstr)s(void) {
int si;
for (si = 0; si < IEC60870_NUM_SERVERS_%(locstr)s; si++) {
iec60870_srv_t *s = &iec60870_srv[si];
if (s->slave) {
CS104_Slave_stop(s->slave);
CS104_Slave_destroy(s->slave);
s->slave = NULL;
}
s->init_st = 0;
s->time_synced = false;
s->cs_plc_var = NULL;
}
return 0;
}