1
0
Fork 0
mirror of https://git.rwth-aachen.de/acs/public/villas/node/ synced 2025-03-09 00:00:00 +01:00

iec61850-goose: multiple producers

Signed-off-by: Philipp Jungkamp <philipp.jungkamp@rwth-aachen.de>
This commit is contained in:
Philipp Jungkamp 2023-02-17 15:21:05 +00:00
parent 2d643b8f47
commit 89813a8cc7
3 changed files with 556 additions and 134 deletions

View file

@ -3,26 +3,113 @@ nodes = {
goose = {
type = "iec61850-goose"
in = {
# ethernet interface to listen on
out = {
# Ethernet interface to publish on
interface = "lo"
# use the goose timestamp for a sample
# Array of goose publisher definitions
publishers = (
{
# Mandatory GOOSE publisher meta data
go_id = "AA1J1Q01A3LD0/LLN0.gcbdata"
go_cb_ref = "AA1J1Q01A3LD0/LLN0$GO$gcbdata"
data_set_ref = "AA1J1Q01A3LD0/LLN0$data"
dst_address = "01:0c:cd:01:00:00"
app_id = 2
conf_rev = 100
time_allowed_to_live = 11000
# Payload description with either constant data or values from a signal
data = (
{
# Mandatory MMS type
mms_type = "boolean"
# Name of the signal in the array below
signal = "ABB_cascade_state"
},
{
# Mandatory MMS type
mms_type = "bit-string"
# Type meta data
mms_bit_string_size = 13
# Constant value
value = 2048
}
)
},
{
go_id = "AA1J1Q01A3LD0/LLN0.gcbDataset_1"
go_cb_ref = "AA1J1Q01A3LD0/LLN0$GO$gcbDataset_1"
data_set_ref = "AA1J1Q01A3LD0/LLN0$Dataset_1"
dst_address = "01:0c:cd:01:00:01"
app_id = 1
conf_rev = 300
time_allowed_to_live = 22000
data = (
{
mms_type = "boolean"
signal = "ABB_cascade_state"
},
{
mms_type = "bit-string"
mms_bit_string_size = 13
value = 2048
},
{
mms_type = "bit-string"
mms_bit_string_size = 2
value = 0
},
{
mms_type = "bit-string"
mms_bit_string_size = 13
value = 2048
},
{
mms_type = "bit-string"
mms_bit_string_size = 13
value = 2048
},
{
mms_type = "bit-string"
mms_bit_string_size = 2
value = 0
}
)
}
)
signals = (
{
# The signal name used to identify the signal in a publishers data field
name = "ABB_cascade_state"
type = "boolean"
}
)
}
in = {
# Ethernet interface to listen on
interface = "lo"
# Use the goose timestamp for a sample
with_timestamp = true
# list of named subscriber definitions
# List of named subscriber definitions
subscribers = {
relay = {
# mandatory GoCbRef
# Mandatory GoCbRef
go_cb_ref = "AA1J1Q01A3LD0/LLN0$GO$gcbdata"
# optional filter by packet destination MAC address
# dst_address = "01:0c:cd:01:00:00"
# Optional filter by packet destination MAC address
dst_address = "01:0c:cd:01:00:00"
# optional filter by AppID
# app_id = 0
# Optional filter by AppID
app_id = 2
# optional trigger specification (either "always" or "change")
# Optional trigger specification (either "always" or "change")
#
# "always" = emit an updated sample for each incoming GOOSE message
# "change" = only emit an updated sample when SqNum is 0
@ -30,16 +117,18 @@ nodes = {
}
}
# mapping from goose events to signals
signals = (
{
name = "ABB_relay_state"
type = "boolean"
# mandatory MmsType specification
# Mandatory MmsType specification
mms_type = "boolean"
# mandatory subscriber name
# Mandatory subscriber name
subscriber = "relay"
# mandatory index within the received vector of GOOSE values
# Mandatory index within the received vector of GOOSE values
index = 0
},
{
@ -53,14 +142,3 @@ nodes = {
}
}
}
paths = (
{
in = "goose"
hooks = (
{
type = "print"
}
)
}
)

View file

@ -20,18 +20,33 @@
#include <villas/signal.hpp>
#include <libiec61850/goose_receiver.h>
#include <libiec61850/goose_subscriber.h>
#include <libiec61850/goose_publisher.h>
namespace villas {
namespace node {
namespace iec61850 {
// A GooseValue is a SignalData value with attached Metadata for the MmsType and SignalType
class GooseValue {
// A GooseSignal is a SignalData value with attached Metadata for the MmsType and SignalType
class GooseSignal {
public:
struct MetaSize { int value; };
union Meta {
int size;
};
struct Descriptor {
SignalType signal_type;
std::string name;
std::optional<MmsType> mms_type;
Meta default_meta;
};
using Type = Descriptor const *;
// The config file identifier for this type
char const * name() const;
std::string const & name() const;
// The type of this value
Type type() const;
// Corresponding mms type
std::optional<MmsType> mmsType() const;
@ -39,61 +54,52 @@ public:
// Corresponding signal type
SignalType signalType() const;
// Create a GooseValue from an MmsValue
static GooseValue fromMmsValue(MmsValue *mms_value);
// Create a GooseSignal from an MmsValue
static std::optional<GooseSignal> fromMmsValue(MmsValue *mms_value);
// Create a GooseValue from type name and SignalData value
static GooseValue fromNameAndValue(char *name, SignalData value);
// Create a GooseSignal from type name and SignalData value
static GooseSignal fromNameAndValue(char const *name, SignalData value, std::optional<Meta> meta = std::nullopt);
// Create a MmsValue from this GooseValue
// Create a MmsValue from this GooseSignal
std::optional<MmsValue *> toMmsValue() const;
// Default/Invalid GooseValue
GooseValue();
static std::optional<Type> lookupMmsType(int mms_type);
union Meta {
MetaSize size;
} meta;
static std::optional<Type> lookupMmsTypeName(char const *name);
GooseSignal();
GooseSignal(Type type, SignalData value, std::optional<Meta> meta = std::nullopt);
SignalData signal_data;
Meta meta;
private:
struct Descriptor {
SignalType signal_type;
char const *name;
std::optional<MmsType> mms_type;
Meta default_meta;
};
inline static std::array const descriptors {
Descriptor { SignalType::INVALID, "invalid" },
/* boolean signals */
// Boolean signals
Descriptor { SignalType::BOOLEAN, "boolean", MmsType::MMS_BOOLEAN },
/* integer signals */
Descriptor { SignalType::INTEGER, "integer", MmsType::MMS_INTEGER, MetaSize { 64 } },
Descriptor { SignalType::INTEGER, "unsigned", MmsType::MMS_UNSIGNED, MetaSize { 32 } },
Descriptor { SignalType::INTEGER, "bit-string", MmsType::MMS_BIT_STRING, MetaSize { 32 } },
// Integer signals
Descriptor { SignalType::INTEGER, "integer", MmsType::MMS_INTEGER, {.size = 64 } },
Descriptor { SignalType::INTEGER, "unsigned", MmsType::MMS_UNSIGNED, {.size = 32 } },
Descriptor { SignalType::INTEGER, "bit-string", MmsType::MMS_BIT_STRING, {.size = 32 } },
/* float signals */
Descriptor { SignalType::FLOAT, "float", MmsType::MMS_FLOAT, MetaSize { 64 } },
// Float signals
Descriptor { SignalType::FLOAT, "float", MmsType::MMS_FLOAT, {.size = 64 } },
};
static MmsValue * newMmsInteger(int64_t i, MetaSize meta);
static MmsValue * newMmsInteger(int64_t i, int size);
static MmsValue * newMmsUnsigned(uint64_t i, MetaSize meta);
static MmsValue * newMmsUnsigned(uint64_t i, int size);
static MmsValue * newMmsFloat(double i, MetaSize meta);
static MmsValue * newMmsBitString(uint32_t i, int size);
static std::optional<Descriptor const *> lookupMmsType(int mms_type);
static std::optional<Descriptor const *> lookupName(char *name);
GooseValue(Descriptor const *descriptor, SignalData value);
static MmsValue * newMmsFloat(double i, int size);
// Descriptor within the descriptors table above
Descriptor const *descriptor;
};
class GooseNode : public Node {
@ -106,7 +112,7 @@ protected:
struct InputMapping {
std::string subscriber;
unsigned int index;
GooseValue init;
GooseSignal dflt;
};
struct SubscriberConfig {
@ -120,11 +126,10 @@ protected:
SubscriberConfig subscriber_config;
GooseNode *node;
std::vector<GooseValue> values;
std::vector<GooseSignal> values;
};
struct Input {
bool enabled;
enum { NONE, STOPPED, READY } state;
GooseReceiver receiver;
CQueueSignalled queue;
@ -135,28 +140,71 @@ protected:
std::string interface_id;
bool with_timestamp;
unsigned int queue_length;
int signal_count;
} input;
struct OutputData {
std::optional<int> signal;
GooseSignal default_value;
};
struct PublisherConfig {
std::string go_id;
std::string go_cb_ref;
std::string data_set_ref;
std::array<uint8_t, 6> dst_address;
uint16_t app_id;
uint32_t conf_rev;
uint32_t time_allowed_to_live;
std::vector<OutputData> data;
};
struct OutputContext {
PublisherConfig config;
GoosePublisher publisher;
};
struct Output {
enum { NONE, STOPPED, READY } state;
std::vector<OutputContext> contexts;
std::string interface_id;
} output;
void createReceiver() noexcept;
void destroyReceiver() noexcept;
void startReceiver() noexcept(false);
void stopReceiver() noexcept;
void createPublishers() noexcept;
void destroyPublishers() noexcept;
void startPublishers() noexcept(false);
void stopPublishers() noexcept;
static void onEvent(GooseSubscriber subscriber, InputEventContext &context) noexcept;
void addSubscriber(InputEventContext &ctx) noexcept;
void pushSample(uint64_t timestamp) noexcept;
int _parse(json_t *json, json_error_t *err);
int parseInput(json_t *json, json_error_t *err);
int parseSubscriber(json_t *json, json_error_t *err, SubscriberConfig &sc);
int parseSubscribers(json_t *json, json_error_t *err, std::map<std::string, InputEventContext> &ctx);
int parseInputSignals(json_t *json, json_error_t *err, std::vector<InputMapping> &mappings);
int parseOutput(json_t *json, json_error_t *err);
int parsePublisherData(json_t *json, json_error_t *err, std::vector<OutputData> &data);
int parsePublisher(json_t *json, json_error_t *err, PublisherConfig &pc);
int parsePublishers(json_t *json, json_error_t *err, std::vector<OutputContext> &ctx);
virtual
int _read(struct Sample *smps[], unsigned cnt) override;
virtual
int _write(struct Sample *smps[], unsigned cnt) override;
public:
GooseNode(const std::string &name = "");

View file

@ -18,8 +18,10 @@ using namespace villas::node;
using namespace villas::utils;
using namespace villas::node::iec61850;
using namespace std::literals::chrono_literals;
using namespace std::literals::string_literals;
static std::optional<std::array<uint8_t, 6>> stringToMac(char *mac_string) {
static std::optional<std::array<uint8_t, 6>> stringToMac(char *mac_string)
{
std::array<uint8_t, 6> mac;
char *save;
char *token = strtok_r(mac_string, ":", &save);
@ -35,27 +37,28 @@ static std::optional<std::array<uint8_t, 6>> stringToMac(char *mac_string) {
return std::optional { mac };
}
std::optional<MmsType> GooseValue::mmsType() const
std::optional<MmsType> GooseSignal::mmsType() const
{
return descriptor->mms_type;
}
const char* GooseValue::name() const
std::string const & GooseSignal::name() const
{
return descriptor->name;
}
SignalType GooseValue::signalType() const
SignalType GooseSignal::signalType() const
{
return descriptor->signal_type;
}
GooseValue GooseValue::fromMmsValue(MmsValue *mms_value) {
std::optional<GooseSignal> GooseSignal::fromMmsValue(MmsValue *mms_value)
{
auto mms_type = MmsValue_getType(mms_value);
auto descriptor = lookupMmsType(mms_type);
SignalData data;
if (!descriptor) return {};
if (!descriptor) return std::nullopt;
switch (mms_type) {
case MmsType::MMS_BOOLEAN:
@ -74,21 +77,23 @@ GooseValue GooseValue::fromMmsValue(MmsValue *mms_value) {
data.f = MmsValue_toDouble(mms_value);
break;
default:
return {};
return std::nullopt;
}
return GooseValue { descriptor.value(), data };
return GooseSignal { descriptor.value(), data };
}
GooseValue GooseValue::fromNameAndValue(char *name, SignalData value) {
auto descriptor = lookupName(name);
GooseSignal GooseSignal::fromNameAndValue(char const *name, SignalData value, std::optional<Meta> meta)
{
auto descriptor = lookupMmsTypeName(name);
if (!descriptor) return {};
return GooseValue { descriptor.value(), value };
return GooseSignal { descriptor.value(), value, meta };
}
std::optional<MmsValue *> GooseValue::toMmsValue() const {
std::optional<MmsValue *> GooseSignal::toMmsValue() const
{
if (!descriptor->mms_type) return std::nullopt;
switch (*descriptor->mms_type) {
@ -98,6 +103,8 @@ std::optional<MmsValue *> GooseValue::toMmsValue() const {
return newMmsInteger(signal_data.i, meta.size);
case MmsType::MMS_UNSIGNED:
return newMmsUnsigned(static_cast<uint64_t>(signal_data.i), meta.size);
case MmsType::MMS_BIT_STRING:
return newMmsBitString(static_cast<uint32_t>(signal_data.i), meta.size);
case MmsType::MMS_FLOAT:
return newMmsFloat(signal_data.f, meta.size);
default:
@ -105,10 +112,20 @@ std::optional<MmsValue *> GooseValue::toMmsValue() const {
}
}
MmsValue * GooseValue::newMmsInteger(int64_t i, GooseValue::MetaSize size) {
auto mms_integer = MmsValue_newInteger(size.value);
MmsValue * GooseSignal::newMmsBitString(uint32_t i, int size)
{
auto mms_bit_string = MmsValue_newBitString(size);
switch (size.value) {
MmsValue_setBitStringFromInteger(mms_bit_string, i);
return mms_bit_string;
}
MmsValue * GooseSignal::newMmsInteger(int64_t i, int size)
{
auto mms_integer = MmsValue_newInteger(size);
switch (size) {
case 8:
MmsValue_setInt8(mms_integer, static_cast<int8_t>(i));
return mms_integer;
@ -126,10 +143,11 @@ MmsValue * GooseValue::newMmsInteger(int64_t i, GooseValue::MetaSize size) {
}
}
MmsValue * GooseValue::newMmsUnsigned(uint64_t u, GooseValue::MetaSize size) {
auto mms_unsigned = MmsValue_newUnsigned(size.value);
MmsValue * GooseSignal::newMmsUnsigned(uint64_t u, int size)
{
auto mms_unsigned = MmsValue_newUnsigned(size);
switch (size.value) {
switch (size) {
case 8:
MmsValue_setUint8(mms_unsigned, static_cast<uint8_t>(u));
return mms_unsigned;
@ -144,8 +162,9 @@ MmsValue * GooseValue::newMmsUnsigned(uint64_t u, GooseValue::MetaSize size) {
}
}
MmsValue * GooseValue::newMmsFloat(double d, GooseValue::MetaSize size) {
switch (size.value) {
MmsValue * GooseSignal::newMmsFloat(double d, int size)
{
switch (size) {
case 32:
return MmsValue_newFloat(static_cast<float>(d));
case 64:
@ -155,7 +174,7 @@ MmsValue * GooseValue::newMmsFloat(double d, GooseValue::MetaSize size) {
}
}
std::optional<GooseValue::Descriptor const *> GooseValue::lookupMmsType(int mms_type)
std::optional<GooseSignal::Type> GooseSignal::lookupMmsType(int mms_type)
{
auto check = [mms_type] (Descriptor descriptor) {
return descriptor.mms_type == mms_type;
@ -168,10 +187,10 @@ std::optional<GooseValue::Descriptor const *> GooseValue::lookupMmsType(int mms_
return std::nullopt;
}
std::optional<GooseValue::Descriptor const *> GooseValue::lookupName(char *name)
std::optional<GooseSignal::Type> GooseSignal::lookupMmsTypeName(char const *name)
{
auto check = [name] (Descriptor descriptor) {
return !strcmp(descriptor.name, name);
return descriptor.name == name;
};
auto descriptor = std::find_if(begin(descriptors), end(descriptors), check);
@ -181,15 +200,16 @@ std::optional<GooseValue::Descriptor const *> GooseValue::lookupName(char *name)
return std::nullopt;
}
GooseValue::GooseValue()
: signal_data({})
, descriptor(&descriptors[0])
GooseSignal::GooseSignal() :
signal_data({}),
descriptor(&descriptors[0])
{
}
GooseValue::GooseValue(GooseValue::Descriptor const *descriptor, SignalData data)
: signal_data(data)
, descriptor(descriptor)
GooseSignal::GooseSignal(GooseSignal::Descriptor const *descriptor, SignalData data, std::optional<Meta> meta) :
signal_data(data),
meta(meta.value_or(descriptor->default_meta)),
descriptor(descriptor)
{
}
@ -198,9 +218,9 @@ void GooseNode::onEvent(GooseSubscriber subscriber, GooseNode::InputEventContext
if (!GooseSubscriber_isValid(subscriber) || GooseSubscriber_needsCommission(subscriber))
return;
if ( ctx.subscriber_config.trigger == InputTrigger::CHANGE
&& !ctx.values.empty()
&& GooseSubscriber_getSqNum(subscriber) != 0)
if (ctx.subscriber_config.trigger == InputTrigger::CHANGE
&& !ctx.values.empty()
&& GooseSubscriber_getSqNum(subscriber) != 0)
return;
auto mms_values = GooseSubscriber_getDataSetValues(subscriber);
@ -214,7 +234,7 @@ void GooseNode::onEvent(GooseSubscriber subscriber, GooseNode::InputEventContext
for (unsigned int i = 0; i < MmsValue_getArraySize(mms_values); i++) {
auto mms_value = MmsValue_getElement(mms_values, i);
auto goose_value = GooseValue::fromMmsValue(mms_value);
auto goose_value = GooseSignal::fromMmsValue(mms_value).value();
ctx.values[i] = goose_value;
}
@ -231,7 +251,6 @@ void GooseNode::pushSample(uint64_t timestamp) noexcept
return;
}
sample->length = input.mappings.size();
sample->flags = (int) SampleFlags::HAS_DATA;
sample->signals = getInputSignals(false);
@ -245,12 +264,12 @@ void GooseNode::pushSample(uint64_t timestamp) noexcept
for (unsigned int signal = 0; signal < sample->length; signal++) {
auto& mapping = input.mappings[signal];
auto& values = input.contexts[mapping.subscriber].values;
sample->data[signal] = mapping.init.signal_data;
sample->data[signal] = mapping.dflt.signal_data;
if (mapping.index >= values.size())
continue;
if (mapping.init.mmsType() != values[mapping.index].mmsType()) {
if (mapping.dflt.mmsType() != values[mapping.index].mmsType()) {
logger->error("unexpected mms_type for signal {}", sample->signals->getByIndex(signal)->toString());
continue;
}
@ -296,14 +315,14 @@ void GooseNode::createReceiver() noexcept
for (auto& pair_key_context : input.contexts)
addSubscriber(pair_key_context.second);
input.state = GooseNode::Input::READY;
input.state = Input::READY;
}
void GooseNode::destroyReceiver() noexcept
{
int err __attribute__((unused));
if (input.state == GooseNode::Input::NONE)
if (input.state == Input::NONE)
return;
stopReceiver();
@ -312,12 +331,12 @@ void GooseNode::destroyReceiver() noexcept
err = queue_signalled_destroy(&input.queue);
input.state = GooseNode::Input::NONE;
input.state = Input::NONE;
}
void GooseNode::startReceiver() noexcept(false)
{
if (input.state == GooseNode::Input::NONE)
if (input.state == Input::NONE)
createReceiver();
else
stopReceiver();
@ -327,15 +346,15 @@ void GooseNode::startReceiver() noexcept(false)
if (!GooseReceiver_isRunning(input.receiver))
throw RuntimeError{"iec61850-GOOSE receiver could not be started"};
input.state = GooseNode::Input::READY;
input.state = Input::READY;
}
void GooseNode::stopReceiver() noexcept
{
if (input.state == GooseNode::Input::NONE)
if (input.state == Input::NONE)
return;
input.state = GooseNode::Input::STOPPED;
input.state = Input::STOPPED;
if (!GooseReceiver_isRunning(input.receiver))
return;
@ -343,13 +362,73 @@ void GooseNode::stopReceiver() noexcept
GooseReceiver_stop(input.receiver);
}
void GooseNode::createPublishers() noexcept
{
destroyPublishers();
for (auto &ctx : output.contexts) {
auto dst_address = ctx.config.dst_address;
auto comm = CommParameters {
/* vlanPriority */ 0,
/* vlanId */ 0,
ctx.config.app_id,
{}
};
memcpy(comm.dstAddress, dst_address.data(), dst_address.size());
ctx.publisher = GoosePublisher_createEx(&comm, output.interface_id.c_str(), false);
GoosePublisher_setGoID(ctx.publisher, ctx.config.go_id.data());
GoosePublisher_setGoCbRef(ctx.publisher, ctx.config.go_cb_ref.data());
GoosePublisher_setDataSetRef(ctx.publisher, ctx.config.data_set_ref.data());
GoosePublisher_setConfRev(ctx.publisher, ctx.config.conf_rev);
GoosePublisher_setTimeAllowedToLive(ctx.publisher, ctx.config.time_allowed_to_live);
}
output.state = Output::READY;
}
void GooseNode::destroyPublishers() noexcept
{
int err __attribute__((unused));
if (output.state == Output::NONE)
return;
stopPublishers();
for (auto &ctx : output.contexts)
GoosePublisher_destroy(ctx.publisher);
output.state = Output::NONE;
}
void GooseNode::startPublishers() noexcept(false)
{
if (output.state == Output::NONE)
createPublishers();
else
stopPublishers();
output.state = Output::READY;
}
void GooseNode::stopPublishers() noexcept
{
if (output.state == Output::NONE)
return;
output.state = Output::STOPPED;
}
int GooseNode::_read(Sample *samples[], unsigned sample_count)
{
int available_samples;
struct Sample *copies[sample_count];
if (input.state != GooseNode::Input::READY)
return -1;
if (input.state != Input::READY)
return 0;
available_samples = queue_signalled_pull_many(&input.queue, (void **) copies, sample_count);
sample_copy_many(samples, copies, available_samples);
@ -358,16 +437,48 @@ int GooseNode::_read(Sample *samples[], unsigned sample_count)
return available_samples;
}
int GooseNode::_write(Sample *samples[], unsigned sample_count)
{
if (output.state != Output::READY)
return 0;
for (unsigned int i = 0; i < sample_count; i++) {
auto sample = samples[i];
for (auto &ctx : output.contexts) {
auto data_set = LinkedList_create();
for (auto &data : ctx.config.data) {
auto goose_value = data.default_value;
auto signal = data.signal.value_or(-1);
if (signal >= 0 && signal < (int) sample->length)
goose_value.signal_data = sample->data[signal];
LinkedList_add(data_set, goose_value.toMmsValue().value());
}
GoosePublisher_increaseStNum(ctx.publisher);
GoosePublisher_publish(ctx.publisher, data_set);
LinkedList_destroyDeep(data_set, (LinkedListValueDeleteFunction) MmsValue_delete);
}
}
return sample_count;
}
GooseNode::GooseNode(const std::string &name) :
Node(name)
{
input.enabled = false;
input.state = GooseNode::Input::NONE;
input.state = Input::NONE;
input.contexts = {};
input.mappings = {};
input.interface_id = "lo";
input.queue_length = 1024;
output.state = Output::NONE;
output.interface_id = "lo";
}
GooseNode::~GooseNode()
@ -375,6 +486,7 @@ GooseNode::~GooseNode()
int err __attribute__((unused));
destroyReceiver();
destroyPublishers();
err = queue_signalled_destroy(&input.queue);
@ -397,40 +509,54 @@ int GooseNode::parse(json_t *json, const uuid_t sn_uuid)
return 0;
}
int GooseNode::_parse(json_t *json, json_error_t *err)
{
int ret;
json_t *in_json = nullptr;
ret = json_unpack_ex(json, err, 0, "{ s: o }",
"in", &in_json
json_t *out_json = nullptr;
ret = json_unpack_ex(json, err, 0, "{ s: o, s: o }",
"in", &in_json,
"out", &out_json
);
if (ret) return ret;
ret = parseInput(in_json, err);
if (ret) return ret;
ret = parseOutput(out_json, err);
if (ret) return ret;
return 0;
}
int GooseNode::parseInput(json_t *json, json_error_t *err)
{
int ret;
json_t *subscribers_json = nullptr;
json_t *in_signals_json = nullptr;
char const *in_interface_id = nullptr;
int in_with_timestamp = true;
ret = json_unpack_ex(in_json, err, 0, "{ s: o, s: o, s?: s, s: b }",
json_t *signals_json = nullptr;
char const *interface_id = nullptr;
int with_timestamp = true;
ret = json_unpack_ex(json, err, 0, "{ s: o, s: o, s?: s, s: b }",
"subscribers", &subscribers_json,
"signals", &in_signals_json,
"interface", &in_interface_id,
"with_timestamp", &in_with_timestamp
"signals", &signals_json,
"interface", &interface_id,
"with_timestamp", &with_timestamp
);
if (ret) return ret;
ret = parseSubscribers(subscribers_json, err, input.contexts);
if (ret) return ret;
ret = parseInputSignals(in_signals_json, err, input.mappings);
ret = parseInputSignals(signals_json, err, input.mappings);
if (ret) return ret;
input.interface_id = in_interface_id
? std::string { in_interface_id }
input.interface_id = interface_id
? std::string { interface_id }
: std::string { "eth0" };
input.with_timestamp = in_with_timestamp;
input.with_timestamp = with_timestamp;
return 0;
}
@ -514,26 +640,183 @@ int GooseNode::parseInputSignals(json_t *json, json_error_t *err, std::vector<Go
);
if (ret) return ret;
auto mapping_init = GooseValue::fromNameAndValue(mapping_type, signal->init);
if (mapping_init.signalType() == SignalType::INVALID)
auto mapping_dflt = GooseSignal::fromNameAndValue(mapping_type, signal->init);
if (mapping_dflt.signalType() == SignalType::INVALID)
throw RuntimeError("Invalid mms_type {}", mapping_type);
if (signal->type != mapping_init.signalType())
if (signal->type != mapping_dflt.signalType())
throw RuntimeError("Expected signal type {} for mms_type {}, but got {}",
signalTypeToString(mapping_init.signalType()),
signalTypeToString(mapping_dflt.signalType()),
mapping_type,
signalTypeToString(signal->type));
mappings.push_back(InputMapping {
mapping_subscriber,
mapping_index,
mapping_init
mapping_dflt
});
}
return 0;
}
int GooseNode::parseOutput(json_t *json, json_error_t *err)
{
int ret;
json_t *publishers_json = nullptr;
json_t *signals_json = nullptr;
char const *interface_id = nullptr;
ret = json_unpack_ex(json, err, 0, "{ s: o, s: o, s?: s }",
"publishers", &publishers_json,
"signals", &signals_json,
"interface", &interface_id
);
if (ret) return ret;
ret = parsePublishers(publishers_json, err, output.contexts);
if (ret) return ret;
output.interface_id = interface_id
? std::string { interface_id }
: std::string { "eth0" };
return 0;
}
int GooseNode::parsePublisherData(json_t *json, json_error_t *err, std::vector<OutputData> &data)
{
int ret;
int index;
json_t* signal_or_value_json;
json_array_foreach(json, index, signal_or_value_json) {
char const *mms_type = nullptr;
char const *signal_str = nullptr;
json_t *value_json = nullptr;
int integer_size = -1;
int unsigned_size = -1;
int bit_string_size = -1;
int float_size = -1;
ret = json_unpack_ex(signal_or_value_json, err, 0, "{ s:s, s?:s, s?:o, s?:i, s?:i, s?:i, s?:i }",
"mms_type", &mms_type,
"signal", &signal_str,
"value", &value_json,
"mms_integer_size", &integer_size,
"mms_unsigned_size", &unsigned_size,
"mms_bit_string_size", &bit_string_size,
"mms_float_size", &float_size
);
if (ret) return ret;
auto goose_type = GooseSignal::lookupMmsTypeName(mms_type).value();
std::optional<GooseSignal::Meta> meta = std::nullopt;
switch (goose_type->mms_type.value()) {
case MmsType::MMS_INTEGER:
if (integer_size != -1)
meta = {.size = integer_size };
break;
case MmsType::MMS_UNSIGNED:
if (unsigned_size != -1)
meta = {.size = unsigned_size };
break;
case MmsType::MMS_BIT_STRING:
if (bit_string_size != -1)
meta = {.size = bit_string_size };
break;
case MmsType::MMS_FLOAT:
if (float_size != -1)
meta = {.size = float_size };
break;
default:
break;
}
auto signal_data = SignalData {};
if (value_json) {
ret = signal_data.parseJson(goose_type->signal_type, value_json);
if (ret) return ret;
}
auto signal = std::optional<int> {};
if (signal_str)
signal = out.signals->getIndexByName(signal_str);
OutputData value = {
.signal = signal,
.default_value = GooseSignal { goose_type, signal_data, meta }
};
data.push_back(value);
};
return 0;
}
int GooseNode::parsePublisher(json_t *json, json_error_t *err, PublisherConfig &pc)
{
int ret;
char *go_id = nullptr;
char *go_cb_ref = nullptr;
char *data_set_ref = nullptr;
char *dst_address_str = nullptr;
int app_id = 0;
int conf_rev = 0;
int time_allowed_to_live = 0;
json_t *data_json = nullptr;
ret = json_unpack_ex(json, err, 0, "{ s:s, s:s, s:s, s:s, s:i, s:i, s:i, s:o }",
"go_id", &go_id,
"go_cb_ref", &go_cb_ref,
"data_set_ref", &data_set_ref,
"dst_address", &dst_address_str,
"app_id", &app_id,
"conf_rev", &conf_rev,
"time_allowed_to_live", &time_allowed_to_live,
"data", &data_json
);
if (ret) return ret;
std::optional dst_address = stringToMac(dst_address_str);
if (!dst_address)
throw RuntimeError("Invalid dst_address");
pc.go_id = std::string { go_id };
pc.go_cb_ref = std::string { go_cb_ref };
pc.data_set_ref = std::string { data_set_ref };
pc.dst_address = *dst_address;
pc.app_id = app_id;
pc.conf_rev = conf_rev;
pc.time_allowed_to_live = time_allowed_to_live;
parsePublisherData(data_json, err, pc.data);
if (ret) return ret;
return 0;
}
int GooseNode::parsePublishers(json_t *json, json_error_t *err, std::vector<OutputContext> &ctx)
{
int ret;
int index;
json_t* publisher_json;
json_array_foreach(json, index, publisher_json) {
PublisherConfig pc;
ret = parsePublisher(publisher_json, err, pc);
if (ret) return ret;
ctx.push_back(OutputContext { pc });
}
return 0;
}
std::vector<int> GooseNode::getPollFDs()
{
return { queue_signalled_fd(&input.queue) };
@ -556,7 +839,11 @@ int GooseNode::prepare()
int GooseNode::start()
{
startReceiver();
if (in.enabled)
startReceiver();
if (out.enabled)
startPublishers();
return Node::start();
}
@ -566,6 +853,7 @@ int GooseNode::stop()
int err __attribute__((unused));
stopReceiver();
stopPublishers();
err = queue_signalled_close(&input.queue);
@ -574,4 +862,12 @@ int GooseNode::stop()
static char name[] = "iec61850-goose";
static char description[] = "Subscribe to and publish GOOSE messages";
static NodePlugin<GooseNode, name, description, (int) NodeFactory::Flags::SUPPORTS_READ, 1> p;
static NodePlugin<
GooseNode,
name,
description,
(int) NodeFactory::Flags::SUPPORTS_READ
| (int) NodeFactory::Flags::SUPPORTS_WRITE
| (int) NodeFactory::Flags::SUPPORTS_POLL,
1
> p;