lpcmanager

656720f97e4d
Parents 21b43f97682b
Children a03f3c87d03c
Update IEC60870 files to accommodate proper timestamps for data points
--- a/iec60870/iec60870.py Thu May 21 18:29:21 2026 +0200
+++ b/iec60870/iec60870.py Tue May 26 13:50:51 2026 +0200
@@ -17,7 +17,8 @@
# direction: "I" monitor / process info to master, "Q" command / control from master
# size_code: memory tree prefix — X bit, B byte, W word, D dword (see list.txt)
# Time-tagged ASDUs use the same IEC cell mapping as the corresponding NA_1 type;
-# CP24/CP56 time tags are carried only in the protocol, not in the PLC binding.
+# CP24/CP56 tags map monotonic last-change to Linux host wall until C_CS_NA_1 (Xenomai: CLOCK_HOST_REALTIME).
+# C_CS_NA_1 binding stores master/client Unix seconds at last sync (not device time).
iec60870_asdu_types = {
# Monitoring — process information (1–21, 30–40)
"M_SP_NA_1 - Single-point information": (1, "BOOL", 1, "I", "X",
@@ -145,7 +146,7 @@
"Counter interrogation command"),
"C_RD_NA_1 - Read command": (102, "BOOL", 1, "Q", "X", "Read command"),
"C_CS_NA_1 - Clock synchronization command": (103, "UDINT", 32, "Q", "D",
- "Clock synchronization command"),
+ "Clock sync: master Unix seconds at last C_CS_NA_1 (client time)"),
"C_TS_NA_1 - Test command": (104, "BOOL", 1, "Q", "X", "Test command"),
"C_RP_NA_1 - Reset process command": (105, "BOOL", 1, "Q", "X",
"Reset process command"),
@@ -1291,4 +1292,4 @@
[(gen_c, cflags)],
LDFLAGS,
True,
- )
\ No newline at end of file
+ )
--- a/iec60870/iec60870_runtime.c Thu May 21 18:29:21 2026 +0200
+++ b/iec60870/iec60870_runtime.c Tue May 26 13:50:51 2026 +0200
@@ -6,6 +6,13 @@
#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"
@@ -52,8 +59,33 @@
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[] = {
@@ -82,17 +114,137 @@
*(IEC_BOOL *)p = v ? (IEC_BOOL)1 : (IEC_BOOL)0;
}
-/* CP24/CP56 helpers for time-tagged ASDUs (wall-clock; PLC does not drive protocol timestamps).
- * Buffers are caller-provided so concurrent iec_make_monitor_io() calls do not share static state. */
-static CP56Time2a iec_wall_cp56_buf(struct sCP56Time2a *buf) {
- CP56Time2a t = (CP56Time2a)buf;
- CP56Time2a_createFromMsTimestamp(t, (uint64_t)time(NULL) * 1000ULL);
- return t;
+/* 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 CP24Time2a iec_zero_cp24_buf(struct sCP24Time2a *buf) {
+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));
- return (CP24Time2a)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) {
@@ -100,12 +252,113 @@
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;
@@ -121,6 +374,9 @@
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(
@@ -128,11 +384,11 @@
case M_SP_TA_1:
return (InformationObject)SinglePointWithCP24Time2a_create(
(SinglePointWithCP24Time2a)mem, ioa, iec_get_bool_var(b->iec_var), q,
- iec_zero_cp24_buf(&cp24_z));
+ (CP24Time2a)&cp24_z);
case M_SP_TB_1:
return (InformationObject)SinglePointWithCP56Time2a_create(
(SinglePointWithCP56Time2a)mem, ioa, iec_get_bool_var(b->iec_var), q,
- iec_wall_cp56_buf(&cp56_wall));
+ (CP56Time2a)&cp56_wall);
case M_DP_NA_1: {
DoublePointValue dv = (DoublePointValue)(*(IEC_BYTE *)b->iec_var & (IEC_BYTE)3);
@@ -142,12 +398,12 @@
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, iec_zero_cp24_buf(&cp24_z));
+ (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, iec_wall_cp56_buf(&cp56_wall));
+ (DoublePointWithCP56Time2a)mem, ioa, dv, q, (CP56Time2a)&cp56_wall);
}
case M_ST_NA_1:
@@ -156,11 +412,11 @@
case M_ST_TA_1:
return (InformationObject)StepPositionWithCP24Time2a_create(
(StepPositionWithCP24Time2a)mem, ioa, (int)*(IEC_INT *)b->iec_var, false, q,
- iec_zero_cp24_buf(&cp24_z));
+ (CP24Time2a)&cp24_z);
case M_ST_TB_1:
return (InformationObject)StepPositionWithCP56Time2a_create(
(StepPositionWithCP56Time2a)mem, ioa, (int)*(IEC_INT *)b->iec_var, false, q,
- iec_wall_cp56_buf(&cp56_wall));
+ (CP56Time2a)&cp56_wall);
case M_BO_NA_1:
return (InformationObject)BitString32_createEx(
@@ -168,11 +424,11 @@
case M_BO_TA_1:
return (InformationObject)Bitstring32WithCP24Time2a_createEx(
(Bitstring32WithCP24Time2a)mem, ioa, (uint32_t)*(UDINT *)b->iec_var, q,
- iec_zero_cp24_buf(&cp24_z));
+ (CP24Time2a)&cp24_z);
case M_BO_TB_1:
return (InformationObject)Bitstring32WithCP56Time2a_createEx(
(Bitstring32WithCP56Time2a)mem, ioa, (uint32_t)*(UDINT *)b->iec_var, q,
- iec_wall_cp56_buf(&cp56_wall));
+ (CP56Time2a)&cp56_wall);
case M_ME_NA_1: {
float nv = NormalizedValue_fromScaled((int)(int16_t)*(IEC_UINT *)b->iec_var);
@@ -182,12 +438,12 @@
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, iec_zero_cp24_buf(&cp24_z));
+ (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, iec_wall_cp56_buf(&cp56_wall));
+ (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);
@@ -201,11 +457,11 @@
case M_ME_TB_1:
return (InformationObject)MeasuredValueScaledWithCP24Time2a_create(
(MeasuredValueScaledWithCP24Time2a)mem, ioa, (int)*(IEC_INT *)b->iec_var, q,
- iec_zero_cp24_buf(&cp24_z));
+ (CP24Time2a)&cp24_z);
case M_ME_TE_1:
return (InformationObject)MeasuredValueScaledWithCP56Time2a_create(
(MeasuredValueScaledWithCP56Time2a)mem, ioa, (int)*(IEC_INT *)b->iec_var, q,
- iec_wall_cp56_buf(&cp56_wall));
+ (CP56Time2a)&cp56_wall);
case M_ME_NC_1:
return (InformationObject)MeasuredValueShort_create(
@@ -213,11 +469,11 @@
case M_ME_TC_1:
return (InformationObject)MeasuredValueShortWithCP24Time2a_create(
(MeasuredValueShortWithCP24Time2a)mem, ioa, *(IEC_REAL *)b->iec_var, q,
- iec_zero_cp24_buf(&cp24_z));
+ (CP24Time2a)&cp24_z);
case M_ME_TF_1:
return (InformationObject)MeasuredValueShortWithCP56Time2a_create(
(MeasuredValueShortWithCP56Time2a)mem, ioa, *(IEC_REAL *)b->iec_var, q,
- iec_wall_cp56_buf(&cp56_wall));
+ (CP56Time2a)&cp56_wall);
case M_IT_NA_1:
bcr = BinaryCounterReading_create((BinaryCounterReading)&bcr_st,
@@ -227,12 +483,12 @@
bcr = BinaryCounterReading_create((BinaryCounterReading)&bcr_st,
(int32_t)(*(UDINT *)b->iec_var), 0, false, false, false);
return (InformationObject)IntegratedTotalsWithCP24Time2a_create(
- (IntegratedTotalsWithCP24Time2a)mem, ioa, bcr, iec_zero_cp24_buf(&cp24_z));
+ (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, iec_wall_cp56_buf(&cp56_wall));
+ (IntegratedTotalsWithCP56Time2a)mem, ioa, bcr, (CP56Time2a)&cp56_wall);
case M_EP_TA_1:
pack_u32 = (uint32_t)*(UDINT *)b->iec_var;
@@ -241,20 +497,20 @@
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), iec_zero_cp24_buf(&cp24_z));
+ (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),
- iec_zero_cp24_buf(&cp24_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), iec_zero_cp24_buf(&cp24_z));
+ (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;
@@ -264,21 +520,21 @@
SingleEvent_setQDP(se, (QualityDescriptorP)((pack_u32 >> 2) & 0xffu));
return (InformationObject)EventOfProtectionEquipmentWithCP56Time2a_create(
(EventOfProtectionEquipmentWithCP56Time2a)mem, ioa, se, iec_zero_cp16_buf(&cp16_z),
- iec_wall_cp56_buf(&cp56_wall));
+ (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), iec_wall_cp56_buf(&cp56_wall));
+ 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),
- iec_wall_cp56_buf(&cp56_wall));
+ (CP56Time2a)&cp56_wall);
case M_PS_NA_1:
memset(&scd_st, 0, sizeof(scd_st));
@@ -316,7 +572,7 @@
(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, iec_wall_cp56_buf(&cp56_wall));
+ (FileDirectory)mem, ioa, 0, (uint32_t)*(UDINT *)b->iec_var, 0, (CP56Time2a)&cp56_wall);
default:
free(mem);
@@ -379,7 +635,7 @@
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)
+ 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;
@@ -505,12 +761,6 @@
case C_RD_NA_1:
iec_set_bool_var(match->iec_var, true);
break;
- case C_CS_NA_1: {
- ClockSynchronizationCommand cs = (ClockSynchronizationCommand)io;
- *(UDINT *)match->iec_var =
- (UDINT)(CP56Time2a_toMsTimestamp(ClockSynchronizationCommand_getTime(cs)) & 0xffffffffu);
- break;
- }
case C_TS_NA_1:
iec_set_bool_var(match->iec_var, TestCommand_isValid((TestCommand)io));
break;
@@ -564,6 +814,7 @@
}
iec_wr_inc(srv);
+ iec_binding_touch((size_t)(match - iec60870_bindings));
IMasterConnection_sendACT_CON(connection, asdu, false);
return true;
}
@@ -595,8 +846,10 @@
(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) {
@@ -628,11 +881,27 @@
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:
@@ -650,6 +919,8 @@
}
void __publish_%(locstr)s(void) {
+ iec_wall_ref_maybe_refresh();
+ iec60870_poll_binding_changes();
}
int __cleanup_%(locstr)s(void) {
@@ -662,6 +933,8 @@
s->slave = NULL;
}
s->init_st = 0;
+ s->time_synced = false;
+ s->cs_plc_var = NULL;
}
return 0;
-}
\ No newline at end of file
+}