2023-09-04 12:21:37 +02:00
|
|
|
/* Node type: IEC 61850-9-2 (Sampled Values).
|
2017-06-09 19:27:02 +02:00
|
|
|
*
|
2022-03-15 09:18:01 -04:00
|
|
|
* Author: Steffen Vogel <post@steffenvogel.de>
|
2022-03-15 09:28:57 -04:00
|
|
|
* SPDX-FileCopyrightText: 2014-2023 Institute for Automation of Complex Power Systems, RWTH Aachen University
|
2022-07-04 18:20:03 +02:00
|
|
|
* SPDX-License-Identifier: Apache-2.0
|
2017-06-09 19:27:02 +02:00
|
|
|
*/
|
|
|
|
|
2019-06-23 16:57:00 +02:00
|
|
|
#include <cstring>
|
2024-05-24 02:35:17 +02:00
|
|
|
#include <libiec61850/sv_publisher.h>
|
|
|
|
#include <libiec61850/sv_subscriber.h>
|
|
|
|
#include <net/ethernet.h>
|
2017-11-17 17:52:26 +01:00
|
|
|
#include <pthread.h>
|
|
|
|
#include <unistd.h>
|
2017-06-09 19:27:02 +02:00
|
|
|
|
2021-08-10 10:12:48 -04:00
|
|
|
#include <villas/exceptions.hpp>
|
2023-09-07 11:46:39 +02:00
|
|
|
#include <villas/node_compat.hpp>
|
2019-04-23 00:12:31 +02:00
|
|
|
#include <villas/nodes/iec61850_sv.hpp>
|
2024-05-27 08:42:13 +02:00
|
|
|
#include <villas/sample.hpp>
|
|
|
|
#include <villas/signal_data.hpp>
|
|
|
|
#include <villas/signal_type.hpp>
|
2023-09-07 11:46:39 +02:00
|
|
|
#include <villas/utils.hpp>
|
2017-06-09 19:27:02 +02:00
|
|
|
|
2017-11-17 17:52:26 +01:00
|
|
|
#define CONFIG_SV_DEFAULT_APPID 0x4000
|
|
|
|
#define CONFIG_SV_DEFAULT_DST_ADDRESS CONFIG_GOOSE_DEFAULT_DST_ADDRESS
|
|
|
|
#define CONFIG_SV_DEFAULT_PRIORITY 4
|
|
|
|
#define CONFIG_SV_DEFAULT_VLAN_ID 0
|
|
|
|
|
2021-05-10 00:12:30 +02:00
|
|
|
using namespace villas;
|
2019-06-04 16:55:38 +02:00
|
|
|
using namespace villas::utils;
|
2021-05-10 00:12:30 +02:00
|
|
|
using namespace villas::node;
|
2019-06-04 16:55:38 +02:00
|
|
|
|
2024-05-24 02:35:17 +02:00
|
|
|
static unsigned iec61850_sv_setup_asdu(NodeCompat *n, struct Sample *smp) {
|
|
|
|
auto *i = n->getData<struct iec61850_sv>();
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
static void iec61850_sv_listener(SVSubscriber subscriber, void *ctx,
|
|
|
|
SVSubscriber_ASDU asdu) {
|
|
|
|
auto *n = (NodeCompat *)ctx;
|
|
|
|
auto *i = n->getData<struct iec61850_sv>();
|
|
|
|
struct Sample *smp;
|
2017-11-17 17:52:26 +01:00
|
|
|
|
2024-05-24 02:35:17 +02:00
|
|
|
const char *sv_id = SVSubscriber_ASDU_getSvId(asdu);
|
|
|
|
int smp_cnt = SVSubscriber_ASDU_getSmpCnt(asdu);
|
|
|
|
size_t data_size = (size_t)SVSubscriber_ASDU_getDataSize(asdu);
|
2017-11-17 17:52:26 +01:00
|
|
|
|
2024-08-13 13:09:39 +02:00
|
|
|
n->logger->debug("Received sample: sv_id={}, smp_cnt={}", sv_id, smp_cnt);
|
2017-11-17 17:52:26 +01:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
smp = sample_alloc(&i->in.pool);
|
|
|
|
if (!smp) {
|
|
|
|
n->logger->warn("Pool underrun in subscriber");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-05-24 02:35:17 +02:00
|
|
|
smp->sequence = smp_cnt;
|
2023-09-07 11:46:39 +02:00
|
|
|
smp->flags = (int)SampleFlags::HAS_SEQUENCE | (int)SampleFlags::HAS_DATA;
|
|
|
|
smp->length = 0;
|
|
|
|
smp->signals = n->getInputSignals(false);
|
|
|
|
|
|
|
|
if (SVSubscriber_ASDU_hasRefrTm(asdu)) {
|
2024-05-24 02:35:17 +02:00
|
|
|
uint64_t t = SVSubscriber_ASDU_getRefrTmAsNs(asdu);
|
2023-09-07 11:46:39 +02:00
|
|
|
|
2024-05-24 02:35:17 +02:00
|
|
|
smp->ts.origin.tv_sec = t / 1000000000;
|
|
|
|
smp->ts.origin.tv_nsec = t % 1000000000;
|
2023-09-07 11:46:39 +02:00
|
|
|
smp->flags |= (int)SampleFlags::HAS_TS_ORIGIN;
|
|
|
|
}
|
|
|
|
|
2024-05-24 02:35:17 +02:00
|
|
|
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++) {
|
2023-09-07 11:46:39 +02:00
|
|
|
struct iec61850_type_descriptor *td =
|
|
|
|
(struct iec61850_type_descriptor *)list_at(&i->in.signals, j);
|
|
|
|
auto sig = smp->signals->getByIndex(j);
|
|
|
|
if (!sig)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
switch (td->iec_type) {
|
|
|
|
case IEC61850Type::INT8:
|
2024-05-24 02:35:17 +02:00
|
|
|
smp->data[j].i = SVSubscriber_ASDU_getINT8(asdu, off);
|
2023-09-07 11:46:39 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case IEC61850Type::INT16:
|
2024-05-24 02:35:17 +02:00
|
|
|
smp->data[j].i = SVSubscriber_ASDU_getINT16(asdu, off);
|
2023-09-07 11:46:39 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case IEC61850Type::INT32:
|
2024-05-24 02:35:17 +02:00
|
|
|
smp->data[j].i = SVSubscriber_ASDU_getINT32(asdu, off);
|
2023-09-07 11:46:39 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case IEC61850Type::INT8U:
|
2024-05-24 02:35:17 +02:00
|
|
|
smp->data[j].i = SVSubscriber_ASDU_getINT8U(asdu, off);
|
2023-09-07 11:46:39 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case IEC61850Type::INT16U:
|
2024-05-24 02:35:17 +02:00
|
|
|
smp->data[j].i = SVSubscriber_ASDU_getINT16U(asdu, off);
|
2023-09-07 11:46:39 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case IEC61850Type::INT32U:
|
2024-05-24 02:35:17 +02:00
|
|
|
smp->data[j].i = SVSubscriber_ASDU_getINT32U(asdu, off);
|
2023-09-07 11:46:39 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case IEC61850Type::FLOAT32:
|
2024-05-24 02:35:17 +02:00
|
|
|
smp->data[j].f = SVSubscriber_ASDU_getFLOAT32(asdu, off);
|
2023-09-07 11:46:39 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case IEC61850Type::FLOAT64:
|
2024-05-24 02:35:17 +02:00
|
|
|
smp->data[j].f = SVSubscriber_ASDU_getFLOAT64(asdu, off);
|
2023-09-07 11:46:39 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
default: {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-24 02:35:17 +02:00
|
|
|
off += td->size;
|
2023-09-07 11:46:39 +02:00
|
|
|
smp->length++;
|
|
|
|
}
|
|
|
|
|
|
|
|
int pushed __attribute__((unused));
|
|
|
|
pushed = queue_signalled_push(&i->in.queue, smp);
|
2017-06-09 19:27:02 +02:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
int villas::node::iec61850_sv_parse(NodeCompat *n, json_t *json) {
|
|
|
|
int ret;
|
|
|
|
auto *i = n->getData<struct iec61850_sv>();
|
|
|
|
|
|
|
|
const char *dst_address = nullptr;
|
|
|
|
const char *interface = nullptr;
|
2024-05-24 02:35:17 +02:00
|
|
|
const char *sv_id = nullptr;
|
|
|
|
const char *smp_mod = nullptr;
|
|
|
|
const char *smp_synch = nullptr;
|
|
|
|
|
|
|
|
int check_dst_address = -1;
|
2023-09-07 11:46:39 +02:00
|
|
|
|
|
|
|
json_t *json_in = nullptr;
|
|
|
|
json_t *json_out = nullptr;
|
|
|
|
json_t *json_signals = nullptr;
|
2024-05-24 02:35:17 +02:00
|
|
|
json_t *json_vlan = nullptr;
|
2023-09-07 11:46:39 +02:00
|
|
|
json_error_t err;
|
|
|
|
|
|
|
|
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,
|
|
|
|
"app_id", &i->app_id, "dst_address", &dst_address);
|
|
|
|
if (ret)
|
|
|
|
throw ConfigError(json, err, "node-config-node-iec61850-sv");
|
|
|
|
|
|
|
|
if (interface)
|
|
|
|
i->interface = strdup(interface);
|
|
|
|
|
2024-05-24 02:35:17 +02:00
|
|
|
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);
|
|
|
|
}
|
2023-09-07 11:46:39 +02:00
|
|
|
|
|
|
|
if (json_out) {
|
|
|
|
i->out.enabled = true;
|
|
|
|
|
|
|
|
ret = json_unpack_ex(
|
2024-05-24 02:35:17 +02:00
|
|
|
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);
|
2023-09-07 11:46:39 +02:00
|
|
|
if (ret)
|
|
|
|
throw ConfigError(json_out, err, "node-config-node-iec61850-sv-out");
|
|
|
|
|
2024-05-24 02:35:17 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2023-09-07 11:46:39 +02:00
|
|
|
else
|
2024-05-24 02:35:17 +02:00
|
|
|
throw ConfigError(
|
|
|
|
json_out, "node-config-node-iec61850-sv-out-smp-synch",
|
|
|
|
"Invalid value '{}' for setting 'smp_synch'", smp_synch);
|
2023-09-07 11:46:39 +02:00
|
|
|
}
|
|
|
|
|
2024-05-24 02:35:17 +02:00
|
|
|
i->out.sv_id = sv_id ? strdup(sv_id) : nullptr;
|
2023-09-07 11:46:39 +02:00
|
|
|
|
|
|
|
ret = iec61850_parse_signals(json_signals, &i->out.signals,
|
|
|
|
n->getOutputSignals());
|
|
|
|
if (ret <= 0)
|
2024-05-24 02:35:17 +02:00
|
|
|
throw ConfigError(json_signals,
|
|
|
|
"node-config-node-iec61850-sv-out-signals",
|
|
|
|
"Failed to parse setting 'signals'");
|
2023-09-07 11:46:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (json_in) {
|
|
|
|
i->in.enabled = true;
|
|
|
|
|
|
|
|
json_signals = nullptr;
|
|
|
|
ret =
|
2024-05-24 02:35:17 +02:00
|
|
|
json_unpack_ex(json_in, &err, 0, "{ s: o, s?: b }", "signals",
|
|
|
|
&json_signals, "check_dst_address", &check_dst_address);
|
2023-09-07 11:46:39 +02:00
|
|
|
if (ret)
|
|
|
|
throw ConfigError(json_in, err, "node-config-node-iec61850-in");
|
|
|
|
|
2024-05-24 02:35:17 +02:00
|
|
|
if (check_dst_address > 0)
|
|
|
|
i->in.check_dst_address = check_dst_address > 0;
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
ret = iec61850_parse_signals(json_signals, &i->in.signals,
|
|
|
|
n->getInputSignals(false));
|
|
|
|
if (ret <= 0)
|
2024-05-24 02:35:17 +02:00
|
|
|
throw ConfigError(json_signals, "node-config-node-iec61850-sv-in-signals",
|
|
|
|
"Failed to parse setting 'signals'");
|
2023-09-07 11:46:39 +02:00
|
|
|
|
|
|
|
i->in.total_size = ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
2017-06-09 19:27:02 +02:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
char *villas::node::iec61850_sv_print(NodeCompat *n) {
|
|
|
|
char *buf;
|
|
|
|
auto *i = n->getData<struct iec61850_sv>();
|
2017-11-17 17:52:26 +01:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
buf = strf("interface=%s, app_id=%#x, dst_address=%s", i->interface,
|
|
|
|
i->app_id, ether_ntoa(&i->dst_address));
|
2017-11-18 01:43:05 +01:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
// Publisher part
|
|
|
|
if (i->out.enabled) {
|
2024-05-24 02:35:17 +02:00
|
|
|
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());
|
2023-09-07 11:46:39 +02:00
|
|
|
}
|
2019-01-12 23:21:58 +01:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
// Subscriber part
|
|
|
|
if (i->in.enabled)
|
2024-05-24 02:35:17 +02:00
|
|
|
strcatf(&buf, ", in.#signals=%zu", list_length(&i->in.signals));
|
2017-11-18 01:43:05 +01:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
return buf;
|
|
|
|
}
|
2017-11-17 17:52:26 +01:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
int villas::node::iec61850_sv_start(NodeCompat *n) {
|
|
|
|
int ret;
|
|
|
|
auto *i = n->getData<struct iec61850_sv>();
|
|
|
|
|
|
|
|
// Initialize publisher
|
|
|
|
if (i->out.enabled) {
|
2024-05-24 02:35:17 +02:00
|
|
|
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);
|
2023-09-07 11:46:39 +02:00
|
|
|
|
2024-05-24 02:35:17 +02:00
|
|
|
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");
|
2023-09-07 11:46:39 +02:00
|
|
|
|
2024-05-24 02:35:17 +02:00
|
|
|
i->out.asdu =
|
|
|
|
SVPublisher_addASDU(i->out.publisher, i->out.sv_id,
|
|
|
|
n->getNameShort().c_str(), i->out.conf_rev);
|
2023-09-07 11:46:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Start subscriber
|
|
|
|
if (i->in.enabled) {
|
2024-05-24 02:35:17 +02:00
|
|
|
struct iec61850_receiver *r =
|
|
|
|
iec61850_receiver_create(iec61850_receiver::Type::SAMPLED_VALUES,
|
|
|
|
i->interface, i->in.check_dst_address);
|
2023-09-07 11:46:39 +02:00
|
|
|
|
|
|
|
i->in.receiver = r->sv;
|
2024-08-13 13:09:39 +02:00
|
|
|
i->in.subscriber = SVSubscriber_create(nullptr, i->app_id);
|
2023-09-07 11:46:39 +02:00
|
|
|
|
|
|
|
// Install a callback handler for the subscriber
|
|
|
|
SVSubscriber_setListener(i->in.subscriber, iec61850_sv_listener, n);
|
|
|
|
|
|
|
|
// Connect the subscriber to the receiver
|
|
|
|
SVReceiver_addSubscriber(i->in.receiver, i->in.subscriber);
|
|
|
|
|
|
|
|
// Initialize pool and queue to pass samples between threads
|
|
|
|
ret = pool_init(&i->in.pool, 1024,
|
|
|
|
SAMPLE_LENGTH(n->getInputSignals(false)->size()));
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
ret = queue_signalled_init(&i->in.queue, 1024);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
for (unsigned k = 0; k < list_length(&i->in.signals); k++) {
|
|
|
|
struct iec61850_type_descriptor *td =
|
|
|
|
(struct iec61850_type_descriptor *)list_at(&i->in.signals, k);
|
|
|
|
auto sig = n->getInputSignals(false)->getByIndex(k);
|
|
|
|
|
|
|
|
if (sig->type == SignalType::INVALID)
|
|
|
|
sig->type = td->type;
|
|
|
|
else if (sig->type != td->type)
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
2017-06-09 19:27:02 +02:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
int villas::node::iec61850_sv_stop(NodeCompat *n) {
|
|
|
|
int ret;
|
|
|
|
auto *i = n->getData<struct iec61850_sv>();
|
2017-12-06 16:44:50 +08:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
if (i->in.enabled)
|
|
|
|
SVReceiver_removeSubscriber(i->in.receiver, i->in.subscriber);
|
2017-12-06 16:44:50 +08:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
ret = queue_signalled_close(&i->in.queue);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
2021-08-10 10:12:48 -04:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
return 0;
|
2017-12-06 16:44:50 +08:00
|
|
|
}
|
|
|
|
|
2024-05-24 02:35:17 +02:00
|
|
|
int villas::node::iec61850_sv_init(NodeCompat *n) {
|
|
|
|
auto *i = n->getData<struct iec61850_sv>();
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
int villas::node::iec61850_sv_destroy(NodeCompat *n) {
|
|
|
|
int ret;
|
|
|
|
auto *i = n->getData<struct iec61850_sv>();
|
2017-11-17 17:52:26 +01:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
// Deinitialize publisher
|
|
|
|
if (i->out.enabled && i->out.publisher)
|
|
|
|
SVPublisher_destroy(i->out.publisher);
|
2017-11-17 17:52:26 +01:00
|
|
|
|
2024-05-24 02:35:17 +02:00
|
|
|
// Deinitialize subscriber
|
2023-09-07 11:46:39 +02:00
|
|
|
if (i->in.enabled) {
|
|
|
|
ret = queue_signalled_destroy(&i->in.queue);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
2017-11-17 17:52:26 +01:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
ret = pool_destroy(&i->in.pool);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
}
|
2017-06-09 19:27:02 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
return 0;
|
2017-06-09 19:27:02 +02:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
int villas::node::iec61850_sv_read(NodeCompat *n, struct Sample *const smps[],
|
|
|
|
unsigned cnt) {
|
|
|
|
int pulled;
|
|
|
|
auto *i = n->getData<struct iec61850_sv>();
|
|
|
|
struct Sample *smpt[cnt];
|
2017-06-09 19:27:02 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
if (!i->in.enabled)
|
|
|
|
return -1;
|
2018-04-04 11:39:32 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
pulled = queue_signalled_pull_many(&i->in.queue, (void **)smpt, cnt);
|
2017-11-17 17:52:26 +01:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
sample_copy_many(smps, smpt, pulled);
|
|
|
|
sample_decref_many(smpt, pulled);
|
2017-11-17 17:52:26 +01:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
return pulled;
|
2017-06-09 19:27:02 +02:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
int villas::node::iec61850_sv_write(NodeCompat *n, struct Sample *const smps[],
|
|
|
|
unsigned cnt) {
|
|
|
|
auto *i = n->getData<struct iec61850_sv>();
|
2017-11-17 17:52:26 +01:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
if (!i->out.enabled)
|
|
|
|
return -1;
|
2018-04-04 11:39:32 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
for (unsigned j = 0; j < cnt; j++) {
|
2024-05-24 02:35:17 +02:00
|
|
|
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++) {
|
2024-08-13 13:09:39 +02:00
|
|
|
auto *td = (struct iec61850_type_descriptor *)list_at(&i->out.signals, k);
|
|
|
|
auto sig = smp->signals->getByIndex(k);
|
2017-11-17 17:52:26 +01:00
|
|
|
|
2024-08-13 13:09:39 +02:00
|
|
|
SignalData data;
|
2017-11-17 17:52:26 +01:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
switch (td->iec_type) {
|
|
|
|
case IEC61850Type::INT8:
|
|
|
|
case IEC61850Type::INT32:
|
2024-08-13 13:09:39 +02:00
|
|
|
case IEC61850Type::INT64:
|
|
|
|
data = smp->data[k].cast(sig->type, SignalType::INTEGER);
|
2023-09-07 11:46:39 +02:00
|
|
|
break;
|
2017-11-17 17:52:26 +01:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
case IEC61850Type::FLOAT32:
|
|
|
|
case IEC61850Type::FLOAT64:
|
2024-08-13 13:09:39 +02:00
|
|
|
data = smp->data[k].cast(sig->type, SignalType::FLOAT);
|
2023-09-07 11:46:39 +02:00
|
|
|
break;
|
2017-11-17 17:52:26 +01:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
default: {
|
|
|
|
}
|
|
|
|
}
|
2017-11-17 17:52:26 +01:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
switch (td->iec_type) {
|
2024-08-13 13:09:39 +02:00
|
|
|
case IEC61850Type::BOOLEAN:
|
|
|
|
SVPublisher_ASDU_setINT8(i->out.asdu, off, data.b);
|
|
|
|
break;
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
case IEC61850Type::INT8:
|
2024-08-13 13:09:39 +02:00
|
|
|
SVPublisher_ASDU_setINT8(i->out.asdu, off, data.i);
|
2023-09-07 11:46:39 +02:00
|
|
|
break;
|
2019-03-09 13:34:51 +01:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
case IEC61850Type::INT32:
|
2024-08-13 13:09:39 +02:00
|
|
|
SVPublisher_ASDU_setINT32(i->out.asdu, off, data.i);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case IEC61850Type::INT64:
|
|
|
|
SVPublisher_ASDU_setINT64(i->out.asdu, off, data.i);
|
2023-09-07 11:46:39 +02:00
|
|
|
break;
|
2019-03-09 13:34:51 +01:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
case IEC61850Type::FLOAT32:
|
2024-08-13 13:09:39 +02:00
|
|
|
SVPublisher_ASDU_setFLOAT(i->out.asdu, off, data.f);
|
2023-09-07 11:46:39 +02:00
|
|
|
break;
|
2019-03-09 13:34:51 +01:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
case IEC61850Type::FLOAT64:
|
2024-08-13 13:09:39 +02:00
|
|
|
SVPublisher_ASDU_setFLOAT64(i->out.asdu, off, data.f);
|
2023-09-07 11:46:39 +02:00
|
|
|
break;
|
2019-03-09 13:34:51 +01:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
default: {
|
|
|
|
}
|
|
|
|
}
|
2017-11-17 17:52:26 +01:00
|
|
|
|
2024-05-24 02:35:17 +02:00
|
|
|
off += td->size;
|
2023-09-07 11:46:39 +02:00
|
|
|
}
|
2017-11-17 17:52:26 +01:00
|
|
|
|
2024-05-24 02:35:17 +02:00
|
|
|
if (smp->flags & (int)SampleFlags::HAS_SEQUENCE)
|
|
|
|
SVPublisher_ASDU_setSmpCnt(i->out.asdu, smp->sequence);
|
2017-11-17 17:52:26 +01:00
|
|
|
|
2024-05-24 02:35:17 +02:00
|
|
|
if (smp->flags & (int)SampleFlags::HAS_TS_ORIGIN) {
|
2024-08-13 13:09:39 +02:00
|
|
|
uint64_t t = smp->ts.origin.tv_sec * 1000000000 + smp->ts.origin.tv_nsec;
|
2017-11-17 17:52:26 +01:00
|
|
|
|
2024-05-24 02:35:17 +02:00
|
|
|
SVPublisher_ASDU_setRefrTmNs(i->out.asdu, t);
|
2023-09-07 11:46:39 +02:00
|
|
|
}
|
2017-11-17 17:52:26 +01:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
SVPublisher_publish(i->out.publisher);
|
|
|
|
}
|
2017-06-09 19:27:02 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
return cnt;
|
2017-11-17 17:52:26 +01:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
int villas::node::iec61850_sv_poll_fds(NodeCompat *n, int fds[]) {
|
|
|
|
auto *i = n->getData<struct iec61850_sv>();
|
2017-11-17 17:52:26 +01:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
fds[0] = queue_signalled_fd(&i->in.queue);
|
2019-01-21 15:47:34 +01:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
return 1;
|
2017-06-09 19:27:02 +02:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
static NodeCompatType p;
|
|
|
|
|
|
|
|
__attribute__((constructor(110))) static void register_plugin() {
|
|
|
|
p.name = "iec61850-9-2";
|
|
|
|
p.description = "IEC 61850-9-2 (Sampled Values)";
|
|
|
|
p.vectorize = 0;
|
|
|
|
p.size = sizeof(struct iec61850_sv);
|
|
|
|
p.type.start = iec61850_type_start;
|
|
|
|
p.type.stop = iec61850_type_stop;
|
2024-05-24 02:35:17 +02:00
|
|
|
p.init = iec61850_sv_init;
|
2023-09-07 11:46:39 +02:00
|
|
|
p.destroy = iec61850_sv_destroy;
|
|
|
|
p.parse = iec61850_sv_parse;
|
|
|
|
p.print = iec61850_sv_print;
|
|
|
|
p.start = iec61850_sv_start;
|
|
|
|
p.stop = iec61850_sv_stop;
|
|
|
|
p.read = iec61850_sv_read;
|
|
|
|
p.write = iec61850_sv_write;
|
|
|
|
p.poll_fds = iec61850_sv_poll_fds;
|
|
|
|
|
|
|
|
static NodeCompatFactory ncp(&p);
|
2019-04-23 00:36:06 +02:00
|
|
|
}
|