2023-09-04 12:21:37 +02:00
|
|
|
/* Node type: IEC 61850 - GOOSE.
|
2023-01-31 11:12:51 +00:00
|
|
|
*
|
|
|
|
* Author: Philipp Jungkamp <philipp.jungkamp@rwth-aachen.de>
|
|
|
|
* SPDX-FileCopyrightText: 2023 Institute for Automation of Complex Power Systems, RWTH Aachen University
|
|
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <algorithm>
|
2023-03-28 13:16:09 +00:00
|
|
|
#include <chrono>
|
2023-05-02 16:11:41 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
#include <villas/exceptions.hpp>
|
2023-01-31 11:12:51 +00:00
|
|
|
#include <villas/node_compat.hpp>
|
|
|
|
#include <villas/nodes/iec61850_goose.hpp>
|
|
|
|
#include <villas/sample.hpp>
|
|
|
|
#include <villas/super_node.hpp>
|
2023-09-07 11:46:39 +02:00
|
|
|
#include <villas/utils.hpp>
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-05-02 16:11:41 +02:00
|
|
|
using namespace std::literals::chrono_literals;
|
|
|
|
using namespace std::literals::string_literals;
|
|
|
|
|
2023-01-31 11:12:51 +00:00
|
|
|
using namespace villas;
|
|
|
|
using namespace villas::node;
|
|
|
|
using namespace villas::utils;
|
|
|
|
using namespace villas::node::iec61850;
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
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);
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
for (auto &i : mac) {
|
|
|
|
if (!token)
|
|
|
|
return std::nullopt;
|
|
|
|
|
|
|
|
i = static_cast<uint8_t>(strtol(token, NULL, 16));
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
token = strtok_r(NULL, ":", &save);
|
|
|
|
}
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
return std::optional{mac};
|
2023-01-31 11:12:51 +00:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
MmsType GooseSignal::mmsType() const { return descriptor->mms_type; }
|
|
|
|
|
|
|
|
std::string const &GooseSignal::name() const { return descriptor->name; }
|
|
|
|
|
|
|
|
SignalType GooseSignal::signalType() const { return descriptor->signal_type; }
|
|
|
|
|
|
|
|
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 std::nullopt;
|
|
|
|
|
|
|
|
switch (mms_type) {
|
|
|
|
case MmsType::MMS_BOOLEAN:
|
|
|
|
data.b = MmsValue_getBoolean(mms_value);
|
|
|
|
break;
|
|
|
|
case MmsType::MMS_INTEGER:
|
|
|
|
data.i = MmsValue_toInt64(mms_value);
|
|
|
|
break;
|
|
|
|
case MmsType::MMS_UNSIGNED:
|
|
|
|
data.i = static_cast<int64_t>(MmsValue_toUint32(mms_value));
|
|
|
|
break;
|
|
|
|
case MmsType::MMS_BIT_STRING:
|
|
|
|
data.i = static_cast<int64_t>(MmsValue_getBitStringAsInteger(mms_value));
|
|
|
|
break;
|
|
|
|
case MmsType::MMS_FLOAT:
|
|
|
|
data.f = MmsValue_toDouble(mms_value);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
|
|
|
|
return GooseSignal{descriptor.value(), data};
|
|
|
|
}
|
|
|
|
|
|
|
|
std::optional<GooseSignal>
|
|
|
|
GooseSignal::fromNameAndValue(char const *name, SignalData value,
|
|
|
|
std::optional<Meta> meta) {
|
|
|
|
auto descriptor = lookupMmsTypeName(name);
|
|
|
|
|
|
|
|
if (!descriptor)
|
|
|
|
return std::nullopt;
|
|
|
|
|
|
|
|
return GooseSignal{descriptor.value(), value, meta};
|
|
|
|
}
|
|
|
|
|
|
|
|
MmsValue *GooseSignal::toMmsValue() const {
|
|
|
|
switch (descriptor->mms_type) {
|
|
|
|
case MmsType::MMS_BOOLEAN:
|
|
|
|
return MmsValue_newBoolean(signal_data.b);
|
|
|
|
case MmsType::MMS_INTEGER:
|
|
|
|
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:
|
|
|
|
throw RuntimeError{"invalid mms type"};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
MmsValue *GooseSignal::newMmsBitString(uint32_t i, int size) {
|
|
|
|
auto mms_bitstring = MmsValue_newBitString(size);
|
|
|
|
|
|
|
|
MmsValue_setBitStringFromInteger(mms_bitstring, i);
|
|
|
|
|
|
|
|
return mms_bitstring;
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
case 16:
|
|
|
|
MmsValue_setInt16(mms_integer, static_cast<int16_t>(i));
|
|
|
|
return mms_integer;
|
|
|
|
case 32:
|
|
|
|
MmsValue_setInt32(mms_integer, static_cast<int32_t>(i));
|
|
|
|
return mms_integer;
|
|
|
|
case 64:
|
|
|
|
MmsValue_setInt64(mms_integer, static_cast<int64_t>(i));
|
|
|
|
return mms_integer;
|
|
|
|
default:
|
|
|
|
throw RuntimeError{"invalid mms integer size"};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
MmsValue *GooseSignal::newMmsUnsigned(uint64_t u, int size) {
|
|
|
|
auto mms_unsigned = MmsValue_newUnsigned(size);
|
|
|
|
|
|
|
|
switch (size) {
|
|
|
|
case 8:
|
|
|
|
MmsValue_setUint8(mms_unsigned, static_cast<uint8_t>(u));
|
|
|
|
return mms_unsigned;
|
|
|
|
case 16:
|
|
|
|
MmsValue_setUint16(mms_unsigned, static_cast<uint16_t>(u));
|
|
|
|
return mms_unsigned;
|
|
|
|
case 32:
|
|
|
|
MmsValue_setUint32(mms_unsigned, static_cast<uint32_t>(u));
|
|
|
|
return mms_unsigned;
|
|
|
|
default:
|
|
|
|
throw RuntimeError{"invalid mms integer size"};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
MmsValue *GooseSignal::newMmsFloat(double d, int size) {
|
|
|
|
switch (size) {
|
|
|
|
case 32:
|
|
|
|
return MmsValue_newFloat(static_cast<float>(d));
|
|
|
|
case 64:
|
|
|
|
return MmsValue_newDouble(d);
|
|
|
|
default:
|
|
|
|
throw RuntimeError{"invalid mms float size"};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
std::optional<GooseSignal::Type> GooseSignal::lookupMmsType(int mms_type) {
|
|
|
|
auto check = [mms_type](Descriptor descriptor) {
|
|
|
|
return descriptor.mms_type == mms_type;
|
|
|
|
};
|
|
|
|
|
|
|
|
auto descriptor = std::find_if(begin(descriptors), end(descriptors), check);
|
|
|
|
if (descriptor != end(descriptors))
|
|
|
|
return &*descriptor;
|
|
|
|
else
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::optional<GooseSignal::Type>
|
|
|
|
GooseSignal::lookupMmsTypeName(char const *name) {
|
|
|
|
auto check = [name](Descriptor descriptor) {
|
|
|
|
return descriptor.name == name;
|
|
|
|
};
|
|
|
|
|
|
|
|
auto descriptor = std::find_if(begin(descriptors), end(descriptors), check);
|
|
|
|
if (descriptor != end(descriptors))
|
|
|
|
return &*descriptor;
|
|
|
|
else
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
|
|
|
|
GooseSignal::GooseSignal(GooseSignal::Descriptor const *descriptor,
|
|
|
|
SignalData data, std::optional<Meta> meta)
|
|
|
|
: signal_data(data), meta(meta.value_or(descriptor->default_meta)),
|
|
|
|
descriptor(descriptor) {}
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-03-14 15:30:40 +00:00
|
|
|
bool iec61850::operator==(GooseSignal &lhs, GooseSignal &rhs) {
|
2023-09-07 11:46:39 +02:00
|
|
|
if (lhs.mmsType() != rhs.mmsType())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
switch (lhs.mmsType()) {
|
|
|
|
case MmsType::MMS_INTEGER:
|
|
|
|
case MmsType::MMS_UNSIGNED:
|
|
|
|
case MmsType::MMS_BIT_STRING:
|
|
|
|
case MmsType::MMS_FLOAT:
|
|
|
|
if (lhs.meta.size != rhs.meta.size)
|
|
|
|
return false;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (lhs.signalType()) {
|
|
|
|
case SignalType::BOOLEAN:
|
|
|
|
return lhs.signal_data.b == rhs.signal_data.b;
|
|
|
|
case SignalType::INTEGER:
|
|
|
|
return lhs.signal_data.i == rhs.signal_data.i;
|
|
|
|
case SignalType::FLOAT:
|
|
|
|
return lhs.signal_data.f == rhs.signal_data.f;
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
2023-03-14 15:30:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool iec61850::operator!=(GooseSignal &lhs, GooseSignal &rhs) {
|
2023-09-07 11:46:39 +02:00
|
|
|
return !(lhs == rhs);
|
2023-03-14 15:30:40 +00:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
void GooseNode::onEvent(GooseSubscriber subscriber,
|
|
|
|
GooseNode::InputEventContext &ctx) noexcept {
|
|
|
|
if (!GooseSubscriber_isValid(subscriber) ||
|
|
|
|
GooseSubscriber_needsCommission(subscriber))
|
|
|
|
return;
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
int last_state_num = ctx.last_state_num;
|
|
|
|
int state_num = GooseSubscriber_getStNum(subscriber);
|
|
|
|
ctx.last_state_num = state_num;
|
2023-03-14 15:30:40 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
if (ctx.subscriber_config.trigger == InputTrigger::CHANGE &&
|
|
|
|
!ctx.values.empty() && state_num == last_state_num)
|
|
|
|
return;
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
auto mms_values = GooseSubscriber_getDataSetValues(subscriber);
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
if (MmsValue_getType(mms_values) != MmsType::MMS_ARRAY) {
|
|
|
|
ctx.node->logger->warn("DataSet is not an array");
|
|
|
|
return;
|
|
|
|
}
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
ctx.values.clear();
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
for (unsigned int i = 0; i < MmsValue_getArraySize(mms_values); i++) {
|
|
|
|
auto mms_value = MmsValue_getElement(mms_values, i);
|
|
|
|
auto goose_value = GooseSignal::fromMmsValue(mms_value);
|
|
|
|
ctx.values.push_back(goose_value);
|
|
|
|
}
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
uint64_t timestamp = GooseSubscriber_getTimestamp(subscriber);
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
ctx.node->pushSample(timestamp);
|
2023-01-31 11:12:51 +00:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
void GooseNode::pushSample(uint64_t timestamp) noexcept {
|
|
|
|
Sample *sample = sample_alloc(&input.pool);
|
|
|
|
if (!sample) {
|
|
|
|
logger->warn("Pool underrun");
|
|
|
|
return;
|
|
|
|
}
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
sample->length = input.mappings.size();
|
|
|
|
sample->flags = (int)SampleFlags::HAS_DATA;
|
|
|
|
sample->signals = getInputSignals(false);
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
if (input.with_timestamp) {
|
|
|
|
sample->flags |= (int)SampleFlags::HAS_TS_ORIGIN;
|
|
|
|
sample->ts.origin.tv_sec = timestamp / 1000;
|
|
|
|
sample->ts.origin.tv_nsec = 1000 * (timestamp % 1000);
|
|
|
|
}
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
for (unsigned int signal = 0; signal < sample->length; signal++) {
|
|
|
|
auto &mapping = input.mappings[signal];
|
|
|
|
auto &values = input.contexts[mapping.subscriber].values;
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
if (mapping.index >= values.size() || !values[mapping.index]) {
|
|
|
|
auto signal_str = sample->signals->getByIndex(signal)->toString();
|
|
|
|
logger->error("tried to access unavailable goose value for signal {}",
|
|
|
|
signal_str);
|
|
|
|
continue;
|
|
|
|
}
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
if (mapping.type->mms_type != values[mapping.index]->mmsType()) {
|
|
|
|
auto signal_str = sample->signals->getByIndex(signal)->toString();
|
|
|
|
logger->error("received unexpected mms_type for signal {}", signal_str);
|
|
|
|
continue;
|
|
|
|
}
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
sample->data[signal] = values[mapping.index]->signal_data;
|
|
|
|
}
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
if (queue_signalled_push(&input.queue, sample) != 1)
|
|
|
|
logger->warn("Failed to enqueue samples");
|
2023-01-31 11:12:51 +00:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
void GooseNode::addSubscriber(GooseNode::InputEventContext &ctx) noexcept {
|
|
|
|
GooseSubscriber subscriber;
|
|
|
|
SubscriberConfig &sc = ctx.subscriber_config;
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
ctx.node = this;
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
subscriber = GooseSubscriber_create(sc.go_cb_ref.data(), NULL);
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
if (sc.dst_address)
|
|
|
|
GooseSubscriber_setDstMac(subscriber, sc.dst_address->data());
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
if (sc.app_id)
|
|
|
|
GooseSubscriber_setAppId(subscriber, *sc.app_id);
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
GooseSubscriber_setListener(
|
|
|
|
subscriber,
|
|
|
|
[](GooseSubscriber goose_subscriber, void *event_context) {
|
|
|
|
auto context = static_cast<InputEventContext *>(event_context);
|
|
|
|
onEvent(goose_subscriber, *context);
|
|
|
|
},
|
|
|
|
&ctx);
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
GooseReceiver_addSubscriber(input.receiver, subscriber);
|
2023-01-31 11:12:51 +00:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
void GooseNode::createReceiver() noexcept {
|
|
|
|
destroyReceiver();
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
input.receiver = GooseReceiver_create();
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
GooseReceiver_setInterfaceId(input.receiver, input.interface_id.c_str());
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
for (auto &pair_key_context : input.contexts)
|
|
|
|
addSubscriber(pair_key_context.second);
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
input.state = Input::READY;
|
2023-01-31 11:12:51 +00:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
void GooseNode::destroyReceiver() noexcept {
|
|
|
|
int err __attribute__((unused));
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
if (input.state == Input::NONE)
|
|
|
|
return;
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
stopReceiver();
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
GooseReceiver_destroy(input.receiver);
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
err = queue_signalled_destroy(&input.queue);
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
input.state = Input::NONE;
|
2023-01-31 11:12:51 +00:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
void GooseNode::startReceiver() noexcept(false) {
|
|
|
|
if (input.state == Input::NONE)
|
|
|
|
createReceiver();
|
|
|
|
else
|
|
|
|
stopReceiver();
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
GooseReceiver_start(input.receiver);
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
if (!GooseReceiver_isRunning(input.receiver))
|
|
|
|
throw RuntimeError{"iec61850-GOOSE receiver could not be started"};
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
input.state = Input::READY;
|
2023-01-31 11:12:51 +00:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
void GooseNode::stopReceiver() noexcept {
|
|
|
|
if (input.state == Input::NONE)
|
|
|
|
return;
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
input.state = Input::STOPPED;
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
if (!GooseReceiver_isRunning(input.receiver))
|
|
|
|
return;
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
GooseReceiver_stop(input.receiver);
|
2023-01-31 11:12:51 +00:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
void GooseNode::createPublishers() noexcept {
|
|
|
|
destroyPublishers();
|
2023-02-17 15:21:05 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
for (auto &ctx : output.contexts) {
|
|
|
|
auto dst_address = ctx.config.dst_address;
|
|
|
|
auto comm = CommParameters{/* vlanPriority */ 0,
|
|
|
|
/* vlanId */ 0,
|
|
|
|
ctx.config.app_id,
|
|
|
|
{}};
|
2023-02-17 15:21:05 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
memcpy(comm.dstAddress, dst_address.data(), dst_address.size());
|
2023-02-17 15:21:05 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
ctx.publisher =
|
|
|
|
GoosePublisher_createEx(&comm, output.interface_id.c_str(), false);
|
2023-02-17 15:21:05 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
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);
|
|
|
|
}
|
2023-02-17 15:21:05 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
output.state = Output::READY;
|
2023-02-17 15:21:05 +00:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
void GooseNode::destroyPublishers() noexcept {
|
|
|
|
int err __attribute__((unused));
|
2023-02-17 15:21:05 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
if (output.state == Output::NONE)
|
|
|
|
return;
|
2023-02-17 15:21:05 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
stopPublishers();
|
2023-02-17 15:21:05 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
for (auto &ctx : output.contexts)
|
|
|
|
GoosePublisher_destroy(ctx.publisher);
|
2023-02-17 15:21:05 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
output.state = Output::NONE;
|
2023-02-17 15:21:05 +00:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
void GooseNode::startPublishers() noexcept(false) {
|
|
|
|
if (output.state == Output::NONE)
|
|
|
|
createPublishers();
|
|
|
|
else
|
|
|
|
stopPublishers();
|
2023-02-17 15:21:05 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
output.resend_thread_stop = false;
|
|
|
|
output.resend_thread = std::thread(resend_thread, &output);
|
2023-03-28 13:16:09 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
output.state = Output::READY;
|
2023-02-17 15:21:05 +00:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
void GooseNode::stopPublishers() noexcept {
|
|
|
|
if (output.state == Output::NONE)
|
|
|
|
return;
|
2023-02-17 15:21:05 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
if (output.resend_thread) {
|
|
|
|
auto lock = std::unique_lock{output.send_mutex};
|
|
|
|
output.resend_thread_stop = true;
|
|
|
|
lock.unlock();
|
2023-03-28 13:16:09 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
output.resend_thread_cv.notify_all();
|
|
|
|
output.resend_thread->join();
|
|
|
|
output.resend_thread = std::nullopt;
|
|
|
|
}
|
2023-03-28 13:16:09 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
output.state = Output::STOPPED;
|
2023-02-17 15:21:05 +00:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
int GooseNode::_read(Sample *samples[], unsigned sample_count) {
|
|
|
|
int available_samples;
|
|
|
|
struct Sample *copies[sample_count];
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
if (input.state != Input::READY)
|
|
|
|
return 0;
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
available_samples =
|
|
|
|
queue_signalled_pull_many(&input.queue, (void **)copies, sample_count);
|
|
|
|
sample_copy_many(samples, copies, available_samples);
|
|
|
|
sample_decref_many(copies, available_samples);
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
return available_samples;
|
2023-01-31 11:12:51 +00:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
void GooseNode::publish_values(GoosePublisher publisher,
|
|
|
|
std::vector<GooseSignal> &values, bool changed,
|
|
|
|
int burst) noexcept {
|
|
|
|
auto data_set = LinkedList_create();
|
|
|
|
|
|
|
|
for (auto &value : values) {
|
|
|
|
LinkedList_add(data_set, value.toMmsValue());
|
|
|
|
}
|
|
|
|
|
|
|
|
if (changed)
|
|
|
|
GoosePublisher_increaseStNum(publisher);
|
|
|
|
|
|
|
|
do {
|
|
|
|
GoosePublisher_publish(publisher, data_set);
|
|
|
|
} while (changed && --burst > 0);
|
|
|
|
|
|
|
|
LinkedList_destroyDeep(data_set,
|
|
|
|
(LinkedListValueDeleteFunction)MmsValue_delete);
|
|
|
|
}
|
|
|
|
|
|
|
|
void GooseNode::resend_thread(GooseNode::Output *output) noexcept {
|
|
|
|
using namespace std::chrono;
|
|
|
|
|
|
|
|
auto interval = duration_cast<steady_clock::duration>(
|
|
|
|
duration<double>(output->resend_interval));
|
|
|
|
auto lock = std::unique_lock{output->send_mutex};
|
|
|
|
time_point<steady_clock> next_sample_time;
|
|
|
|
|
|
|
|
// wait for the first GooseNode::_write call
|
|
|
|
output->resend_thread_cv.wait(lock);
|
2023-03-14 15:30:40 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
while (!output->resend_thread_stop) {
|
|
|
|
if (output->changed) {
|
|
|
|
output->changed = false;
|
|
|
|
next_sample_time = steady_clock::now() + interval;
|
|
|
|
}
|
2023-03-14 15:30:40 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
auto status = output->resend_thread_cv.wait_until(lock, next_sample_time);
|
2023-03-14 15:30:40 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
if (status == std::cv_status::no_timeout || output->changed)
|
|
|
|
continue;
|
2023-03-14 15:30:40 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
for (auto &ctx : output->contexts)
|
|
|
|
publish_values(ctx.publisher, ctx.values, false);
|
|
|
|
|
|
|
|
next_sample_time = next_sample_time + interval;
|
|
|
|
}
|
2023-03-14 15:30:40 +00:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
int GooseNode::_write(Sample *samples[], unsigned sample_count) {
|
|
|
|
auto lock = std::unique_lock{output.send_mutex};
|
2023-04-11 15:04:41 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
for (unsigned int i = 0; i < sample_count; i++) {
|
|
|
|
auto sample = samples[i];
|
2023-03-28 13:16:09 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
for (auto &ctx : output.contexts) {
|
|
|
|
bool changed = false;
|
2023-03-28 13:16:09 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
for (unsigned int data_index = 0; data_index < ctx.config.data.size();
|
|
|
|
data_index++) {
|
|
|
|
auto data = ctx.config.data[data_index];
|
|
|
|
auto goose_value = data.default_value;
|
|
|
|
auto signal = data.signal;
|
|
|
|
if (signal && *signal < sample->length)
|
|
|
|
goose_value.signal_data = sample->data[*signal];
|
2023-03-28 13:16:09 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
if (ctx.values.size() <= data_index) {
|
|
|
|
changed = true;
|
|
|
|
ctx.values.push_back(goose_value);
|
|
|
|
} else if (ctx.values[data_index] != goose_value) {
|
|
|
|
changed = true;
|
|
|
|
ctx.values[data_index] = goose_value;
|
|
|
|
}
|
|
|
|
}
|
2023-03-28 13:16:09 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
if (changed) {
|
|
|
|
output.changed = true;
|
|
|
|
publish_values(ctx.publisher, ctx.values, changed, ctx.config.burst);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-03-28 13:16:09 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
if (output.changed) {
|
|
|
|
lock.unlock();
|
|
|
|
output.resend_thread_cv.notify_all();
|
|
|
|
}
|
2023-03-28 13:16:09 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
return sample_count;
|
2023-03-28 13:16:09 +00:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
GooseNode::GooseNode(const uuid_t &id, const std::string &name)
|
|
|
|
: Node(id, name) {
|
|
|
|
input.state = Input::NONE;
|
|
|
|
|
|
|
|
input.contexts = {};
|
|
|
|
input.mappings = {};
|
|
|
|
input.interface_id = "lo";
|
|
|
|
input.queue_length = 1024;
|
2023-02-17 15:21:05 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
output.state = Output::NONE;
|
|
|
|
output.interface_id = "lo";
|
|
|
|
output.changed = false;
|
|
|
|
output.resend_interval = 1.;
|
|
|
|
output.resend_thread = std::nullopt;
|
|
|
|
}
|
2023-02-17 15:21:05 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
GooseNode::~GooseNode() {
|
|
|
|
int err __attribute__((unused));
|
2023-02-17 15:21:05 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
destroyReceiver();
|
|
|
|
destroyPublishers();
|
2023-03-14 15:30:40 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
err = queue_signalled_destroy(&input.queue);
|
|
|
|
|
|
|
|
err = pool_destroy(&input.pool);
|
|
|
|
}
|
2023-02-17 15:21:05 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
int GooseNode::parse(json_t *json) {
|
|
|
|
int ret;
|
|
|
|
json_error_t err;
|
2023-02-17 15:21:05 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
ret = Node::parse(json);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
2023-02-17 15:21:05 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
json_t *json_in = nullptr;
|
|
|
|
json_t *json_out = nullptr;
|
|
|
|
ret = json_unpack_ex(json, &err, 0, "{ s: o, s: o }", "in", &json_in, "out",
|
|
|
|
&json_out);
|
|
|
|
if (ret)
|
|
|
|
throw ConfigError(json, err, "node-config-node-iec61850-8-1");
|
|
|
|
|
|
|
|
parseInput(json_in);
|
|
|
|
parseOutput(json_out);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void GooseNode::parseInput(json_t *json) {
|
|
|
|
int ret;
|
|
|
|
json_error_t err;
|
|
|
|
|
|
|
|
json_t *json_subscribers = nullptr;
|
|
|
|
json_t *json_signals = nullptr;
|
|
|
|
char const *interface_id = input.interface_id.c_str();
|
|
|
|
int with_timestamp = true;
|
|
|
|
ret = json_unpack_ex(json, &err, 0, "{ s: o, s: o, s?: s, s: b }",
|
|
|
|
"subscribers", &json_subscribers, "signals",
|
|
|
|
&json_signals, "interface", &interface_id,
|
|
|
|
"with_timestamp", &with_timestamp);
|
|
|
|
if (ret)
|
|
|
|
throw ConfigError(json, err, "node-config-node-iec61850-8-1");
|
|
|
|
|
|
|
|
parseSubscribers(json_subscribers, input.contexts);
|
|
|
|
parseInputSignals(json_signals, input.mappings);
|
|
|
|
|
|
|
|
input.interface_id = interface_id;
|
|
|
|
input.with_timestamp = with_timestamp;
|
2023-02-17 15:21:05 +00:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
void GooseNode::parseSubscriber(json_t *json, GooseNode::SubscriberConfig &sc) {
|
|
|
|
int ret;
|
|
|
|
json_error_t err;
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
char *go_cb_ref = nullptr;
|
|
|
|
char *dst_address_str = nullptr;
|
|
|
|
char *trigger = nullptr;
|
|
|
|
int app_id = 0;
|
|
|
|
ret = json_unpack_ex(json, &err, 0, "{ s: s, s?: s, s?: s, s?: i }",
|
|
|
|
"go_cb_ref", &go_cb_ref, "trigger", &trigger,
|
|
|
|
"dst_address", &dst_address_str, "app_id", &app_id);
|
|
|
|
if (ret)
|
|
|
|
throw ConfigError(json, err, "node-config-node-iec61850-8-1");
|
2023-02-17 15:21:05 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
sc.go_cb_ref = std::string{go_cb_ref};
|
|
|
|
|
|
|
|
if (!trigger || !strcmp(trigger, "always"))
|
|
|
|
sc.trigger = InputTrigger::ALWAYS;
|
|
|
|
else if (!strcmp(trigger, "change"))
|
|
|
|
sc.trigger = InputTrigger::CHANGE;
|
|
|
|
else
|
|
|
|
throw RuntimeError("Unknown trigger type");
|
|
|
|
|
|
|
|
if (dst_address_str) {
|
|
|
|
std::optional dst_address = stringToMac(dst_address_str);
|
|
|
|
if (!dst_address)
|
|
|
|
throw RuntimeError("Invalid dst_address");
|
|
|
|
sc.dst_address = *dst_address;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (app_id != 0)
|
|
|
|
sc.app_id = static_cast<int16_t>(app_id);
|
2023-01-31 11:12:51 +00:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
void GooseNode::parseSubscribers(
|
|
|
|
json_t *json, std::map<std::string, InputEventContext> &ctx) {
|
|
|
|
char const *key;
|
|
|
|
json_t *json_subscriber;
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
if (!json_is_object(json))
|
|
|
|
throw RuntimeError("subscribers is not an object");
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
json_object_foreach(json, key, json_subscriber) {
|
|
|
|
SubscriberConfig sc;
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
parseSubscriber(json_subscriber, sc);
|
|
|
|
|
|
|
|
ctx[key] = InputEventContext{sc};
|
|
|
|
}
|
2023-01-31 11:12:51 +00:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
void GooseNode::parseInputSignals(
|
|
|
|
json_t *json, std::vector<GooseNode::InputMapping> &mappings) {
|
|
|
|
int ret;
|
|
|
|
json_error_t err;
|
|
|
|
int index;
|
|
|
|
json_t *value;
|
|
|
|
|
|
|
|
mappings.clear();
|
|
|
|
|
|
|
|
json_array_foreach(json, index, value) {
|
|
|
|
char *mapping_subscriber;
|
|
|
|
unsigned int mapping_index;
|
|
|
|
char *mapping_type_name;
|
|
|
|
ret = json_unpack_ex(value, &err, 0, "{ s: s, s: i, s: s }", "subscriber",
|
|
|
|
&mapping_subscriber, "index", &mapping_index,
|
|
|
|
"mms_type", &mapping_type_name);
|
|
|
|
if (ret)
|
|
|
|
throw ConfigError(json, err, "node-config-node-iec61850-8-1");
|
|
|
|
|
|
|
|
auto mapping_type =
|
|
|
|
GooseSignal::lookupMmsTypeName(mapping_type_name).value();
|
|
|
|
|
|
|
|
mappings.push_back(InputMapping{
|
|
|
|
mapping_subscriber,
|
|
|
|
mapping_index,
|
|
|
|
mapping_type,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void GooseNode::parseOutput(json_t *json) {
|
|
|
|
int ret;
|
|
|
|
json_error_t err;
|
|
|
|
|
|
|
|
json_t *json_publishers = nullptr;
|
|
|
|
json_t *json_signals = nullptr;
|
|
|
|
char const *interface_id = output.interface_id.c_str();
|
|
|
|
ret = json_unpack_ex(json, &err, 0, "{ s: o, s: o, s?: s, s?: f }",
|
|
|
|
"publishers", &json_publishers, "signals", &json_signals,
|
|
|
|
"interface", &interface_id, "resend_interval",
|
|
|
|
&output.resend_interval);
|
|
|
|
if (ret)
|
|
|
|
throw ConfigError(json, err, "node-config-node-iec61850-8-1");
|
|
|
|
|
|
|
|
parsePublishers(json_publishers, output.contexts);
|
|
|
|
|
|
|
|
output.interface_id = interface_id;
|
|
|
|
}
|
|
|
|
|
|
|
|
void GooseNode::parsePublisherData(json_t *json,
|
|
|
|
std::vector<OutputData> &data) {
|
|
|
|
int ret;
|
|
|
|
json_error_t err;
|
|
|
|
int index;
|
|
|
|
json_t *json_signal_or_value;
|
|
|
|
|
|
|
|
if (!json_is_array(json))
|
|
|
|
throw RuntimeError("publisher data is not an array");
|
|
|
|
|
|
|
|
json_array_foreach(json, index, json_signal_or_value) {
|
|
|
|
char const *mms_type = nullptr;
|
|
|
|
char const *signal_str = nullptr;
|
|
|
|
json_t *json_value = nullptr;
|
|
|
|
int bitstring_size = -1;
|
|
|
|
ret = json_unpack_ex(json_signal_or_value, &err, 0,
|
|
|
|
"{ s: s, s?: s, s?: o, s?: i }", "mms_type", &mms_type,
|
|
|
|
"signal", &signal_str, "value", &json_value,
|
|
|
|
"mms_bitstring_size", &bitstring_size);
|
|
|
|
if (ret)
|
|
|
|
throw ConfigError(json, err, "node-config-node-iec61850-8-1");
|
|
|
|
|
|
|
|
auto goose_type = GooseSignal::lookupMmsTypeName(mms_type).value();
|
|
|
|
std::optional<GooseSignal::Meta> meta = std::nullopt;
|
|
|
|
|
|
|
|
if (goose_type->mms_type == MmsType::MMS_BIT_STRING && bitstring_size != -1)
|
|
|
|
meta = {.size = bitstring_size};
|
|
|
|
|
|
|
|
auto signal_data = SignalData{};
|
|
|
|
|
|
|
|
if (json_value) {
|
|
|
|
ret = signal_data.parseJson(goose_type->signal_type, json_value);
|
|
|
|
if (ret)
|
|
|
|
throw ConfigError(json, err, "node-config-node-iec61850-8-1");
|
|
|
|
}
|
|
|
|
|
|
|
|
auto signal = std::optional<int>{};
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
if (signal_str)
|
|
|
|
signal = out.signals->getIndexByName(signal_str);
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
OutputData value = {.signal = signal,
|
|
|
|
.default_value =
|
|
|
|
GooseSignal{goose_type, signal_data, meta}};
|
2023-02-17 15:21:05 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
data.push_back(value);
|
|
|
|
};
|
2023-02-17 15:21:05 +00:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
void GooseNode::parsePublisher(json_t *json, PublisherConfig &pc) {
|
|
|
|
int ret;
|
|
|
|
json_error_t err;
|
2023-02-17 15:21:05 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
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;
|
|
|
|
int burst = 1;
|
|
|
|
json_t *json_data = nullptr;
|
|
|
|
ret = json_unpack_ex(
|
|
|
|
json, &err, 0,
|
|
|
|
"{ s: s, s: s, s: s, s: s, s: i, 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, "burst", &burst, "data",
|
|
|
|
&json_data);
|
|
|
|
if (ret)
|
|
|
|
throw ConfigError(json, err, "node-config-node-iec61850-8-1");
|
2023-02-17 15:21:05 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
std::optional dst_address = stringToMac(dst_address_str);
|
|
|
|
if (!dst_address)
|
|
|
|
throw RuntimeError("Invalid dst_address");
|
2023-02-17 15:21:05 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
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;
|
|
|
|
pc.burst = burst;
|
2023-02-17 15:21:05 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
parsePublisherData(json_data, pc.data);
|
2023-02-17 15:21:05 +00:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
void GooseNode::parsePublishers(json_t *json, std::vector<OutputContext> &ctx) {
|
|
|
|
int index;
|
|
|
|
json_t *json_publisher;
|
2023-02-17 15:21:05 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
json_array_foreach(json, index, json_publisher) {
|
|
|
|
PublisherConfig pc;
|
2023-02-17 15:21:05 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
parsePublisher(json_publisher, pc);
|
2023-02-17 15:21:05 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
ctx.push_back(OutputContext{pc});
|
|
|
|
}
|
2023-02-17 15:21:05 +00:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
std::vector<int> GooseNode::getPollFDs() {
|
|
|
|
return {queue_signalled_fd(&input.queue)};
|
2023-01-31 11:12:51 +00:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
int GooseNode::prepare() {
|
|
|
|
int ret;
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
ret = pool_init(&input.pool, input.queue_length,
|
|
|
|
SAMPLE_LENGTH(getInputSignals(false)->size()));
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
ret = queue_signalled_init(&input.queue, input.queue_length);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
return Node::prepare();
|
2023-01-31 11:12:51 +00:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
int GooseNode::start() {
|
|
|
|
if (in.enabled)
|
|
|
|
startReceiver();
|
2023-02-17 15:21:05 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
if (out.enabled)
|
|
|
|
startPublishers();
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
return Node::start();
|
2023-01-31 11:12:51 +00:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
int GooseNode::stop() {
|
|
|
|
int err __attribute__((unused));
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
stopReceiver();
|
|
|
|
stopPublishers();
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
err = queue_signalled_close(&input.queue);
|
2023-01-31 11:12:51 +00:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
return Node::stop();
|
2023-01-31 11:12:51 +00:00
|
|
|
}
|
|
|
|
|
2023-03-28 13:16:09 +00:00
|
|
|
// Register node
|
2023-04-18 11:17:12 +02:00
|
|
|
static char name[] = "iec61850-8-1";
|
|
|
|
static char description[] = "IEC 61850-8-1 (GOOSE)";
|
2023-09-07 11:46:39 +02:00
|
|
|
static NodePlugin<GooseNode, name, description,
|
|
|
|
(int)NodeFactory::Flags::SUPPORTS_READ |
|
|
|
|
(int)NodeFactory::Flags::SUPPORTS_WRITE |
|
|
|
|
(int)NodeFactory::Flags::SUPPORTS_POLL,
|
|
|
|
1>
|
|
|
|
p;
|