diff --git a/doc/openapi/components/schemas/config/nodes/iec61850-9-2.yaml b/doc/openapi/components/schemas/config/nodes/iec61850-9-2.yaml index 8ed2b0f11..2e1b6bbc1 100644 --- a/doc/openapi/components/schemas/config/nodes/iec61850-9-2.yaml +++ b/doc/openapi/components/schemas/config/nodes/iec61850-9-2.yaml @@ -10,6 +10,10 @@ allOf: in: type: object properties: + check_dst_address: + type: boolean + default: false + signals: type: array items: @@ -19,34 +23,50 @@ allOf: type: object required: - signals - - svid + - sv_id properties: signals: type: array items: $ref: ./signals/iec61850_signal.yaml - svid: + sv_id: type: string - confrev: + conf_rev: type: integer - smpmod: + smp_mod: type: string enum: - per_nominal_period - samples_per_second - seconds_per_sample - smprate: + smp_synch: + type: string + enum: + - not_synchronized + - local_clock + - global_clock + + smp_rate: type: integer - vlan_id: - type: integer + vlan: + type: object + properties: + enabled: + type: boolean + default: true - vlan_priority: - type: integer + id: + type: integer + default: 0 + + priority: + type: integer + default: 4 interface: type: string @@ -54,8 +74,10 @@ allOf: app_id: type: integer + default: 0x4000 dst_address: type: string + default: 01:0c:cd:01:00:01 - $ref: ../node.yaml diff --git a/etc/examples/nodes/iec61850-9-2.conf b/etc/examples/nodes/iec61850-9-2.conf index d7318fb4e..4fbd00ab7 100644 --- a/etc/examples/nodes/iec61850-9-2.conf +++ b/etc/examples/nodes/iec61850-9-2.conf @@ -3,10 +3,10 @@ nodes = { sampled_values_node = { - type = "iec61850-9-2", + type = "iec61850-9-2" - interface = "lo", - dst_address = "01:0c:cd:01:00:01", + interface = "lo" + dst_address = "01:0c:cd:01:00:01" out = { signals = ( @@ -16,10 +16,12 @@ nodes = { { iec_type = "int32" } ) - svid = "test1234", - smpmod = "samples_per_second", - confrev = 55 + sv_id = "test1234" + smp_mod = "samples_per_second" + smp_synch = "local_clock" + conf_rev = 55 }, + in = { signals = ( { iec_type = "float32" }, @@ -27,6 +29,8 @@ nodes = { { iec_type = "int8" }, { iec_type = "int32" } ) + + check_dst_address = false } } } diff --git a/include/villas/nodes/iec61850.hpp b/include/villas/nodes/iec61850.hpp index 0e4931bc2..bbb1afc0f 100644 --- a/include/villas/nodes/iec61850.hpp +++ b/include/villas/nodes/iec61850.hpp @@ -91,7 +91,8 @@ struct iec61850_receiver * iec61850_receiver_lookup(enum iec61850_receiver::Type t, const char *intf); struct iec61850_receiver * -iec61850_receiver_create(enum iec61850_receiver::Type t, const char *intf); +iec61850_receiver_create(enum iec61850_receiver::Type t, const char *intf, + bool check_dst_address); int iec61850_receiver_start(struct iec61850_receiver *r); diff --git a/include/villas/nodes/iec61850_sv.hpp b/include/villas/nodes/iec61850_sv.hpp index 65d40851f..2ec17f0d5 100644 --- a/include/villas/nodes/iec61850_sv.hpp +++ b/include/villas/nodes/iec61850_sv.hpp @@ -7,8 +7,6 @@ #pragma once -#include - #include #include @@ -30,15 +28,16 @@ struct iec61850_sv { struct { bool enabled; + bool check_dst_address; SVSubscriber subscriber; SVReceiver receiver; struct CQueueSignalled queue; struct Pool pool; - struct List signals; // Mappings of type struct iec61850_type_descriptor - int total_size; + + unsigned total_size; } in; struct { @@ -47,16 +46,22 @@ struct iec61850_sv { SVPublisher publisher; SVPublisher_ASDU asdu; - char *svid; + char *sv_id; - int vlan_priority; - int vlan_id; - int smpmod; - int smprate; - int confrev; + struct { + bool enabled; + int priority; + int id; + } vlan; + + int smp_mod; + int smp_synch; + int smp_rate; + int conf_rev; struct List signals; // Mappings of type struct iec61850_type_descriptor - int total_size; + + unsigned asdu_length; } out; }; @@ -70,6 +75,8 @@ int iec61850_sv_start(NodeCompat *n); int iec61850_sv_stop(NodeCompat *n); +int iec61850_sv_init(NodeCompat *n); + int iec61850_sv_destroy(NodeCompat *n); int iec61850_sv_read(NodeCompat *n, struct Sample *const smps[], unsigned cnt); diff --git a/lib/nodes/iec61850.cpp b/lib/nodes/iec61850.cpp index 2ac58e9b3..680185325 100644 --- a/lib/nodes/iec61850.cpp +++ b/lib/nodes/iec61850.cpp @@ -6,6 +6,8 @@ */ #include +#include +#include #include #include @@ -283,9 +285,8 @@ villas::node::iec61850_receiver_lookup(enum iec61850_receiver::Type t, return nullptr; } -struct iec61850_receiver * -villas::node::iec61850_receiver_create(enum iec61850_receiver::Type t, - const char *intf) { +struct iec61850_receiver *villas::node::iec61850_receiver_create( + enum iec61850_receiver::Type t, const char *intf, bool check_dst_address) { struct iec61850_receiver *r; // Check if there is already a SVReceiver for this interface @@ -307,6 +308,8 @@ villas::node::iec61850_receiver_create(enum iec61850_receiver::Type t, case iec61850_receiver::Type::SAMPLED_VALUES: r->sv = SVReceiver_create(); SVReceiver_setInterfaceId(r->sv, r->interface); + if (check_dst_address) + SVReceiver_enableDestAddrCheck(r->sv); break; } diff --git a/lib/nodes/iec61850_sv.cpp b/lib/nodes/iec61850_sv.cpp index 27d1f5b9d..9dc3cc6c6 100644 --- a/lib/nodes/iec61850_sv.cpp +++ b/lib/nodes/iec61850_sv.cpp @@ -5,7 +5,11 @@ * SPDX-License-Identifier: Apache-2.0 */ +#include "villas/sample.hpp" #include +#include +#include +#include #include #include @@ -23,36 +27,62 @@ using namespace villas; using namespace villas::utils; using namespace villas::node; +static unsigned iec61850_sv_setup_asdu(NodeCompat *n, struct Sample *smp) { + auto *i = n->getData(); + + unsigned new_length = MIN(list_length(&i->out.signals), smp->length); + + SVPublisher_ASDU_resetBuffer(i->out.asdu); + SVPublisher_ASDU_enableRefrTm(i->out.asdu); + + for (unsigned k = 0; k < new_length; k++) { + struct iec61850_type_descriptor *td = + (struct iec61850_type_descriptor *)list_at(&i->out.signals, k); + + switch (td->iec_type) { + case IEC61850Type::INT8: + SVPublisher_ASDU_addINT8(i->out.asdu); + break; + + case IEC61850Type::INT32: + SVPublisher_ASDU_addINT32(i->out.asdu); + break; + + case IEC61850Type::FLOAT32: + SVPublisher_ASDU_addFLOAT(i->out.asdu); + break; + + case IEC61850Type::FLOAT64: + SVPublisher_ASDU_addFLOAT64(i->out.asdu); + break; + + default: { + } + } + } + + // Recalculate payload length + SVPublisher_setupComplete(i->out.publisher); + + return new_length; +} + static void iec61850_sv_listener(SVSubscriber subscriber, void *ctx, SVSubscriber_ASDU asdu) { auto *n = (NodeCompat *)ctx; auto *i = n->getData(); struct Sample *smp; - const char *svid = SVSubscriber_ASDU_getSvId(asdu); - int smpcnt = SVSubscriber_ASDU_getSmpCnt(asdu); - int confrev = SVSubscriber_ASDU_getConfRev(asdu); - int sz; + const char *sv_id = SVSubscriber_ASDU_getSvId(asdu); + int smp_cnt = SVSubscriber_ASDU_getSmpCnt(asdu); + int smp_mod = SVSubscriber_ASDU_getSmpMod(asdu); + int smp_synch = SVSubscriber_ASDU_getSmpSynch(asdu); + int conf_rev = SVSubscriber_ASDU_getConfRev(asdu); + size_t data_size = (size_t)SVSubscriber_ASDU_getDataSize(asdu); - n->logger->debug("Received SV: svid={}, smpcnt={}, confrev={}", svid, smpcnt, - confrev); - - sz = SVSubscriber_ASDU_getDataSize(asdu); - if (sz < i->in.total_size) { - n->logger->warn("Received truncated ASDU: size={}, expected={}", - SVSubscriber_ASDU_getDataSize(asdu), i->in.total_size); - return; - } - - /* Access to the data requires a priori knowledge of the data set. - * For this example we assume a data set consisting of FLOAT32 values. - * A FLOAT32 value is encoded as 4 bytes. You can find the first FLOAT32 - * value at byte position 0, the second value at byte position 4, the third - * value at byte position 8, and so on. - * - * To prevent damages due configuration, please check the length of the - * data block of the SV message before accessing the data. - */ + n->logger->debug("Received sample: sv_id={}, smp_mod={}, smp_sync={}, " + "smp_cnt={}, conf_rev={}", + sv_id, smp_mod, smp_synch, smp_cnt, conf_rev); smp = sample_alloc(&i->in.pool); if (!smp) { @@ -60,21 +90,23 @@ static void iec61850_sv_listener(SVSubscriber subscriber, void *ctx, return; } - smp->sequence = smpcnt; + smp->sequence = smp_cnt; smp->flags = (int)SampleFlags::HAS_SEQUENCE | (int)SampleFlags::HAS_DATA; smp->length = 0; smp->signals = n->getInputSignals(false); if (SVSubscriber_ASDU_hasRefrTm(asdu)) { - uint64_t refrtm = SVSubscriber_ASDU_getRefrTmAsMs(asdu); + uint64_t t = SVSubscriber_ASDU_getRefrTmAsNs(asdu); - smp->ts.origin.tv_sec = refrtm / 1000; - smp->ts.origin.tv_nsec = (refrtm % 1000) * 1000000; + smp->ts.origin.tv_sec = t / 1000000000; + smp->ts.origin.tv_nsec = t % 1000000000; smp->flags |= (int)SampleFlags::HAS_TS_ORIGIN; } - unsigned offset = 0; - for (size_t j = 0; j < list_length(&i->in.signals); j++) { + for (size_t j = 0, off = 0; + j < MIN(list_length(&i->in.signals), smp->capacity) && + off < MIN(i->in.total_size, data_size); + j++) { struct iec61850_type_descriptor *td = (struct iec61850_type_descriptor *)list_at(&i->in.signals, j); auto sig = smp->signals->getByIndex(j); @@ -83,43 +115,42 @@ static void iec61850_sv_listener(SVSubscriber subscriber, void *ctx, switch (td->iec_type) { case IEC61850Type::INT8: - smp->data[j].i = SVSubscriber_ASDU_getINT8(asdu, offset); + smp->data[j].i = SVSubscriber_ASDU_getINT8(asdu, off); break; case IEC61850Type::INT16: - smp->data[j].i = SVSubscriber_ASDU_getINT16(asdu, offset); + smp->data[j].i = SVSubscriber_ASDU_getINT16(asdu, off); break; case IEC61850Type::INT32: - smp->data[j].i = SVSubscriber_ASDU_getINT32(asdu, offset); + smp->data[j].i = SVSubscriber_ASDU_getINT32(asdu, off); break; case IEC61850Type::INT8U: - smp->data[j].i = SVSubscriber_ASDU_getINT8U(asdu, offset); + smp->data[j].i = SVSubscriber_ASDU_getINT8U(asdu, off); break; case IEC61850Type::INT16U: - smp->data[j].i = SVSubscriber_ASDU_getINT16U(asdu, offset); + smp->data[j].i = SVSubscriber_ASDU_getINT16U(asdu, off); break; case IEC61850Type::INT32U: - smp->data[j].i = SVSubscriber_ASDU_getINT32U(asdu, offset); + smp->data[j].i = SVSubscriber_ASDU_getINT32U(asdu, off); break; case IEC61850Type::FLOAT32: - smp->data[j].f = SVSubscriber_ASDU_getFLOAT32(asdu, offset); + smp->data[j].f = SVSubscriber_ASDU_getFLOAT32(asdu, off); break; case IEC61850Type::FLOAT64: - smp->data[j].f = SVSubscriber_ASDU_getFLOAT64(asdu, offset); + smp->data[j].f = SVSubscriber_ASDU_getFLOAT64(asdu, off); break; default: { } } - offset += td->size; - + off += td->size; smp->length++; } @@ -133,29 +164,18 @@ int villas::node::iec61850_sv_parse(NodeCompat *n, json_t *json) { const char *dst_address = nullptr; const char *interface = nullptr; - const char *svid = nullptr; - const char *smpmod = nullptr; + const char *sv_id = nullptr; + const char *smp_mod = nullptr; + const char *smp_synch = nullptr; + + int check_dst_address = -1; json_t *json_in = nullptr; json_t *json_out = nullptr; json_t *json_signals = nullptr; + json_t *json_vlan = nullptr; json_error_t err; - // Default values - i->out.enabled = false; - i->in.enabled = false; - i->out.smpmod = -1; // do not set smpmod - i->out.smprate = -1; // do not set smpmod - i->out.confrev = 1; - i->out.vlan_priority = CONFIG_SV_DEFAULT_PRIORITY; - i->out.vlan_id = CONFIG_SV_DEFAULT_VLAN_ID; - - i->app_id = CONFIG_SV_DEFAULT_APPID; - - uint8_t tmp[] = CONFIG_SV_DEFAULT_DST_ADDRESS; - memcpy(i->dst_address.ether_addr_octet, tmp, - sizeof(i->dst_address.ether_addr_octet)); - ret = json_unpack_ex(json, &err, 0, "{ s?: o, s?: o, s: s, s?: i, s?: s }", "out", &json_out, "in", &json_in, "interface", &interface, @@ -166,39 +186,68 @@ int villas::node::iec61850_sv_parse(NodeCompat *n, json_t *json) { if (interface) i->interface = strdup(interface); - if (dst_address) - ether_aton_r(dst_address, &i->dst_address); + if (dst_address) { + struct ether_addr *addr = ether_aton_r(dst_address, &i->dst_address); + if (addr == nullptr) + throw ConfigError(json, "node-config-node-iec61850-sv-dst-address", + "Invalid setting 'dst_address': {}", dst_address); + } if (json_out) { i->out.enabled = true; ret = json_unpack_ex( - json_out, &err, 0, "{ s: o, s: s, s?: i, s?: s, s?: i, s?: i, s?: i }", - "signals", &json_signals, "svid", &svid, "confrev", &i->out.confrev, - "smpmod", &smpmod, "smprate", &i->out.smprate, "vlan_id", - &i->out.vlan_id, "vlan_priority", &i->out.vlan_priority); + json_out, &err, 0, "{ s: o, s: s, s?: i, s?: s, s?: i, s?: s, s?: o }", + "signals", &json_signals, "sv_id", &sv_id, "conf_rev", &i->out.conf_rev, + "smp_mod", &smp_mod, "smp_rate", &i->out.smp_rate, "smp_synch", + &smp_synch, "vlan", &json_vlan); if (ret) throw ConfigError(json_out, err, "node-config-node-iec61850-sv-out"); - if (smpmod) { - if (!strcmp(smpmod, "per_nominal_period")) - i->out.smpmod = IEC61850_SV_SMPMOD_PER_NOMINAL_PERIOD; - else if (!strcmp(smpmod, "samples_per_second")) - i->out.smpmod = IEC61850_SV_SMPMOD_SAMPLES_PER_SECOND; - else if (!strcmp(smpmod, "seconds_per_sample")) - i->out.smpmod = IEC61850_SV_SMPMOD_SECONDS_PER_SAMPLE; - else - throw RuntimeError("Invalid value '{}' for setting 'smpmod'", smpmod); + if (json_vlan != nullptr) { + int enabled = -1; + json_unpack_ex(json_vlan, &err, 0, "{ s?: b, s?: i, s?: i }", "enabled", + &enabled, "id", &i->out.vlan.id, "priority", + &i->out.vlan.priority); + + if (enabled >= 0) { + i->out.vlan.enabled = enabled > 0; + } } - i->out.svid = svid ? strdup(svid) : nullptr; + if (smp_mod) { + if (!strcmp(smp_mod, "per_nominal_period")) + i->out.smp_mod = IEC61850_SV_SMPMOD_PER_NOMINAL_PERIOD; + else if (!strcmp(smp_mod, "samples_per_second")) + i->out.smp_mod = IEC61850_SV_SMPMOD_SAMPLES_PER_SECOND; + else if (!strcmp(smp_mod, "seconds_per_sample")) + i->out.smp_mod = IEC61850_SV_SMPMOD_SECONDS_PER_SAMPLE; + else + throw ConfigError(json_out, "node-config-node-iec61850-sv-out-smp-mod", + "Invalid value '{}' for setting 'smp_mod'", smp_mod); + } + + if (smp_synch) { + if (!strcmp(smp_synch, "not_synchronized")) + i->out.smp_synch = IEC61850_SV_SMPSYNC_NOT_SYNCHRONIZED; + else if (!strcmp(smp_synch, "local_clock")) + i->out.smp_synch = IEC61850_SV_SMPSYNC_SYNCED_UNSPEC_LOCAL_CLOCK; + else if (!strcmp(smp_synch, "global_clock")) + i->out.smp_synch = IEC61850_SV_SMPSYNC_SYNCED_GLOBAL_CLOCK; + else + throw ConfigError( + json_out, "node-config-node-iec61850-sv-out-smp-synch", + "Invalid value '{}' for setting 'smp_synch'", smp_synch); + } + + i->out.sv_id = sv_id ? strdup(sv_id) : nullptr; ret = iec61850_parse_signals(json_signals, &i->out.signals, n->getOutputSignals()); if (ret <= 0) - throw RuntimeError("Failed to parse setting 'signals'"); - - i->out.total_size = ret; + throw ConfigError(json_signals, + "node-config-node-iec61850-sv-out-signals", + "Failed to parse setting 'signals'"); } if (json_in) { @@ -206,14 +255,19 @@ int villas::node::iec61850_sv_parse(NodeCompat *n, json_t *json) { json_signals = nullptr; ret = - json_unpack_ex(json_in, &err, 0, "{ s: o }", "signals", &json_signals); + json_unpack_ex(json_in, &err, 0, "{ s: o, s?: b }", "signals", + &json_signals, "check_dst_address", &check_dst_address); if (ret) throw ConfigError(json_in, err, "node-config-node-iec61850-in"); + if (check_dst_address > 0) + i->in.check_dst_address = check_dst_address > 0; + ret = iec61850_parse_signals(json_signals, &i->in.signals, n->getInputSignals(false)); if (ret <= 0) - throw RuntimeError("Failed to parse setting 'signals'"); + throw ConfigError(json_signals, "node-config-node-iec61850-sv-in-signals", + "Failed to parse setting 'signals'"); i->in.total_size = ret; } @@ -230,16 +284,23 @@ char *villas::node::iec61850_sv_print(NodeCompat *n) { // Publisher part if (i->out.enabled) { - strcatf(&buf, - ", pub.svid=%s, pub.vlan_prio=%d, pub.vlan_id=%#x, pub.confrev=%d, " - "pub.#fields=%zu", - i->out.svid, i->out.vlan_priority, i->out.vlan_id, i->out.confrev, - n->getOutputSignals()->size()); + strcatf(&buf, ", out.sv_id=%s, out.conf_rev=%d", i->out.sv_id, + i->out.conf_rev); + + if (i->out.vlan.enabled) { + strcatf(&buf, + ", out.vlan.enabled=true, out.vlan.priority=%d, pub.vlan.id=%#x", + i->out.vlan.priority, i->out.vlan.id); + } + + auto output_signals = n->getOutputSignals(); + if (output_signals) + strcatf(&buf, ", out.#signals=%zu", output_signals->size()); } // Subscriber part if (i->in.enabled) - strcatf(&buf, ", sub.#fields=%zu", list_length(&i->in.signals)); + strcatf(&buf, ", in.#signals=%zu", list_length(&i->in.signals)); return buf; } @@ -250,53 +311,26 @@ int villas::node::iec61850_sv_start(NodeCompat *n) { // Initialize publisher if (i->out.enabled) { - i->out.publisher = SVPublisher_create(nullptr, i->interface); + CommParameters comm_params = {.vlanPriority = uint8_t(i->out.vlan.priority), + .vlanId = uint16_t(i->out.vlan.id), + .appId = uint16_t(i->app_id)}; + memcpy(comm_params.dstAddress, i->dst_address.ether_addr_octet, 6); + + i->out.publisher = + SVPublisher_createEx(&comm_params, i->interface, i->out.vlan.enabled); + if (i->out.publisher == nullptr) + throw RuntimeError("Failed to create SV publisher"); + i->out.asdu = - SVPublisher_addASDU(i->out.publisher, i->out.svid, - n->getNameShort().c_str(), i->out.confrev); - - for (unsigned k = 0; k < list_length(&i->out.signals); k++) { - struct iec61850_type_descriptor *td = - (struct iec61850_type_descriptor *)list_at(&i->out.signals, k); - - switch (td->iec_type) { - case IEC61850Type::INT8: - SVPublisher_ASDU_addINT8(i->out.asdu); - break; - - case IEC61850Type::INT32: - SVPublisher_ASDU_addINT32(i->out.asdu); - break; - - case IEC61850Type::FLOAT32: - SVPublisher_ASDU_addFLOAT(i->out.asdu); - break; - - case IEC61850Type::FLOAT64: - SVPublisher_ASDU_addFLOAT64(i->out.asdu); - break; - - default: { - } - } - } - - if (i->out.smpmod >= 0) - SVPublisher_ASDU_setSmpMod(i->out.asdu, i->out.smpmod); - - SVPublisher_ASDU_enableRefrTm(i->out.asdu); - - // if (s->out.smprate >= 0) - // SV_ASDU_setSmpRate(i->out.asdu, i->out.smprate); - - // Start publisher - SVPublisher_setupComplete(i->out.publisher); + SVPublisher_addASDU(i->out.publisher, i->out.sv_id, + n->getNameShort().c_str(), i->out.conf_rev); } // Start subscriber if (i->in.enabled) { - struct iec61850_receiver *r = iec61850_receiver_create( - iec61850_receiver::Type::SAMPLED_VALUES, i->interface); + struct iec61850_receiver *r = + iec61850_receiver_create(iec61850_receiver::Type::SAMPLED_VALUES, + i->interface, i->in.check_dst_address); i->in.receiver = r->sv; i->in.subscriber = @@ -347,6 +381,32 @@ int villas::node::iec61850_sv_stop(NodeCompat *n) { return 0; } +int villas::node::iec61850_sv_init(NodeCompat *n) { + auto *i = n->getData(); + + uint8_t tmp[] = CONFIG_SV_DEFAULT_DST_ADDRESS; + memcpy(i->dst_address.ether_addr_octet, tmp, + sizeof(i->dst_address.ether_addr_octet)); + + i->app_id = CONFIG_SV_DEFAULT_APPID; + + i->in.enabled = false; + + i->out.enabled = false; + i->out.smp_mod = -1; // Do not set smp_mod + i->out.smp_synch = -1; // Do not set smp_synch + i->out.smp_rate = -1; // Do not set smp_rate + i->out.conf_rev = 1; + + i->out.vlan.enabled = true; + i->out.vlan.priority = CONFIG_SV_DEFAULT_PRIORITY; + i->out.vlan.id = CONFIG_SV_DEFAULT_VLAN_ID; + + i->out.asdu_length = 0; + + return 0; +} + int villas::node::iec61850_sv_destroy(NodeCompat *n) { int ret; auto *i = n->getData(); @@ -355,7 +415,7 @@ int villas::node::iec61850_sv_destroy(NodeCompat *n) { if (i->out.enabled && i->out.publisher) SVPublisher_destroy(i->out.publisher); - // Deinitialise subscriber + // Deinitialize subscriber if (i->in.enabled) { ret = queue_signalled_destroy(&i->in.queue); if (ret) @@ -394,28 +454,40 @@ int villas::node::iec61850_sv_write(NodeCompat *n, struct Sample *const smps[], return -1; for (unsigned j = 0; j < cnt; j++) { - unsigned offset = 0; - for (unsigned k = 0; k < MIN(smps[j]->length, list_length(&i->out.signals)); - k++) { + auto *smp = smps[j]; + + unsigned asdu_length = MIN(smp->length, list_length(&i->out.signals)); + if (i->out.asdu_length != asdu_length) + i->out.asdu_length = iec61850_sv_setup_asdu(n, smp); + + if (i->out.smp_mod >= 0) + SVPublisher_ASDU_setSmpMod(i->out.asdu, i->out.smp_mod); + + if (i->out.smp_synch >= 0) + SVPublisher_ASDU_setSmpSynch(i->out.asdu, i->out.smp_synch); + + if (i->out.smp_rate >= 0) + SVPublisher_ASDU_setSmpRate(i->out.asdu, i->out.smp_rate); + + unsigned off = 0; + for (unsigned k = 0; k < i->out.asdu_length; k++) { struct iec61850_type_descriptor *td = (struct iec61850_type_descriptor *)list_at(&i->out.signals, k); - int ival = 0; - double fval = 0; + int i_val = 0; + double f_val = 0; switch (td->iec_type) { case IEC61850Type::INT8: case IEC61850Type::INT32: - ival = sample_format(smps[j], k) == SignalType::FLOAT - ? smps[j]->data[k].f - : smps[j]->data[k].i; + i_val = sample_format(smp, k) == SignalType::FLOAT ? smp->data[k].f + : smp->data[k].i; break; case IEC61850Type::FLOAT32: case IEC61850Type::FLOAT64: - fval = sample_format(smps[j], k) == SignalType::FLOAT - ? smps[j]->data[k].f - : smps[j]->data[k].i; + f_val = sample_format(smp, k) == SignalType::FLOAT ? smp->data[k].f + : smp->data[k].i; break; default: { @@ -424,35 +496,36 @@ int villas::node::iec61850_sv_write(NodeCompat *n, struct Sample *const smps[], switch (td->iec_type) { case IEC61850Type::INT8: - SVPublisher_ASDU_setINT8(i->out.asdu, offset, ival); + SVPublisher_ASDU_setINT8(i->out.asdu, off, i_val); break; case IEC61850Type::INT32: - SVPublisher_ASDU_setINT32(i->out.asdu, offset, ival); + SVPublisher_ASDU_setINT32(i->out.asdu, off, i_val); break; case IEC61850Type::FLOAT32: - SVPublisher_ASDU_setFLOAT(i->out.asdu, offset, fval); + SVPublisher_ASDU_setFLOAT(i->out.asdu, off, f_val); break; case IEC61850Type::FLOAT64: - SVPublisher_ASDU_setFLOAT64(i->out.asdu, offset, fval); + SVPublisher_ASDU_setFLOAT64(i->out.asdu, off, f_val); break; default: { } } - offset += td->size; + off += td->size; } - SVPublisher_ASDU_setSmpCnt(i->out.asdu, smps[j]->sequence); + if (smp->flags & (int)SampleFlags::HAS_SEQUENCE) + SVPublisher_ASDU_setSmpCnt(i->out.asdu, smp->sequence); - if (smps[j]->flags & (int)SampleFlags::HAS_TS_ORIGIN) { - uint64_t refrtm = smps[j]->ts.origin.tv_sec * 1000 + - smps[j]->ts.origin.tv_nsec / 1000000; + if (smp->flags & (int)SampleFlags::HAS_TS_ORIGIN) { + uint64_t t = + smp->ts.origin.tv_sec * 1000000000 + smp->ts.origin.tv_nsec; - SVPublisher_ASDU_setRefrTm(i->out.asdu, refrtm); + SVPublisher_ASDU_setRefrTmNs(i->out.asdu, t); } SVPublisher_publish(i->out.publisher); @@ -478,6 +551,7 @@ __attribute__((constructor(110))) static void register_plugin() { p.size = sizeof(struct iec61850_sv); p.type.start = iec61850_type_start; p.type.stop = iec61850_type_stop; + p.init = iec61850_sv_init; p.destroy = iec61850_sv_destroy; p.parse = iec61850_sv_parse; p.print = iec61850_sv_print; diff --git a/tests/integration/pipe-loopback-iec61850-9-2.sh b/tests/integration/pipe-loopback-iec61850-9-2.sh index 32efab3dd..e20eee748 100755 --- a/tests/integration/pipe-loopback-iec61850-9-2.sh +++ b/tests/integration/pipe-loopback-iec61850-9-2.sh @@ -6,9 +6,6 @@ # SPDX-FileCopyrightText: 2014-2023 Institute for Automation of Complex Power Systems, RWTH Aachen University # SPDX-License-Identifier: Apache-2.0 -echo "Test is broken" -exit 99 - set -e DIR=$(mktemp -d) @@ -26,33 +23,31 @@ cat > config.json << EOF { "nodes": { "node1": { - "type": "iec61850-9-2", + "type": "iec61850-9-2", - "interface": "lo", + "interface": "lo", - "out": { - "svid": "1234", - "signals": [ - { "iec_type": "float32" }, - { "iec_type": "float32" }, - { "iec_type": "float32" }, - { "iec_type": "float32" } - ] - }, - "in": { - "signals": [ - { "iec_type": "float32" }, - { "iec_type": "float32" }, - { "iec_type": "float32" }, - { "iec_type": "float32" } - ] - } + "out": { + "sv_id": "1234", + "signals": { + "iec_type": "float32", + "count": 64 + } + }, + "in": { + "signals": { + "iec_type": "float32", + "count": 64 + } + } } } } EOF villas signal -l ${NUM_SAMPLES} -v 4 -n random > input.dat +villas signal -l ${NUM_SAMPLES} -v 8 -n random > input.dat +villas signal -l ${NUM_SAMPLES} -v 6 -n random > input.dat villas pipe -l ${NUM_SAMPLES} config.json node1 > output.dat < input.dat