mirror of
https://git.rwth-aachen.de/acs/public/villas/node/
synced 2025-03-09 00:00:00 +01:00
iec61850_sv: Fix IEC 61850-9-2 Sampled Values node and unit test
Signed-off-by: Steffen Vogel <post@steffenvogel.de>
This commit is contained in:
parent
4b896a8d7c
commit
cad2da3a59
7 changed files with 305 additions and 199 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -7,8 +7,6 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include <libiec61850/sv_publisher.h>
|
||||
#include <libiec61850/sv_subscriber.h>
|
||||
|
||||
|
@ -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);
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
*/
|
||||
|
||||
#include <cstring>
|
||||
#include <libiec61850/goose_receiver.h>
|
||||
#include <libiec61850/sv_subscriber.h>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,11 @@
|
|||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "villas/sample.hpp"
|
||||
#include <cstring>
|
||||
#include <libiec61850/sv_publisher.h>
|
||||
#include <libiec61850/sv_subscriber.h>
|
||||
#include <net/ethernet.h>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
|
||||
|
@ -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<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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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<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;
|
||||
}
|
||||
|
||||
int villas::node::iec61850_sv_destroy(NodeCompat *n) {
|
||||
int ret;
|
||||
auto *i = n->getData<struct iec61850_sv>();
|
||||
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue