diff --git a/etc/examples/nodes/iec61850-goose.conf b/etc/examples/nodes/iec61850-goose.conf index 7def0007b..98ced2f4f 100644 --- a/etc/examples/nodes/iec61850-goose.conf +++ b/etc/examples/nodes/iec61850-goose.conf @@ -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" - } - ) - } -) diff --git a/include/villas/nodes/iec61850_goose.hpp b/include/villas/nodes/iec61850_goose.hpp index dba6eb882..56f84a082 100644 --- a/include/villas/nodes/iec61850_goose.hpp +++ b/include/villas/nodes/iec61850_goose.hpp @@ -20,18 +20,33 @@ #include #include #include +#include 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 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() 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 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 = std::nullopt); - // Create a MmsValue from this GooseValue + // Create a MmsValue from this GooseSignal std::optional toMmsValue() const; - // Default/Invalid GooseValue - GooseValue(); + static std::optional lookupMmsType(int mms_type); - union Meta { - MetaSize size; - } meta; + static std::optional lookupMmsTypeName(char const *name); + + GooseSignal(); + + GooseSignal(Type type, SignalData value, std::optional meta = std::nullopt); SignalData signal_data; - + Meta meta; private: - struct Descriptor { - SignalType signal_type; - char const *name; - std::optional 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 lookupMmsType(int mms_type); - - static std::optional 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 values; + std::vector 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 signal; + GooseSignal default_value; + }; + + struct PublisherConfig { + std::string go_id; + std::string go_cb_ref; + std::string data_set_ref; + std::array dst_address; + uint16_t app_id; + uint32_t conf_rev; + uint32_t time_allowed_to_live; + std::vector data; + }; + + struct OutputContext { + PublisherConfig config; + + GoosePublisher publisher; + }; + + struct Output { + enum { NONE, STOPPED, READY } state; + std::vector 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 &ctx); int parseInputSignals(json_t *json, json_error_t *err, std::vector &mappings); + int parseOutput(json_t *json, json_error_t *err); + int parsePublisherData(json_t *json, json_error_t *err, std::vector &data); + int parsePublisher(json_t *json, json_error_t *err, PublisherConfig &pc); + int parsePublishers(json_t *json, json_error_t *err, std::vector &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 = ""); diff --git a/lib/nodes/iec61850_goose.cpp b/lib/nodes/iec61850_goose.cpp index 77421bc48..9b649ac0b 100644 --- a/lib/nodes/iec61850_goose.cpp +++ b/lib/nodes/iec61850_goose.cpp @@ -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> stringToMac(char *mac_string) { +static std::optional> stringToMac(char *mac_string) +{ std::array mac; char *save; char *token = strtok_r(mac_string, ":", &save); @@ -35,27 +37,28 @@ static std::optional> stringToMac(char *mac_string) { return std::optional { mac }; } -std::optional GooseValue::mmsType() const +std::optional 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::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) +{ + auto descriptor = lookupMmsTypeName(name); if (!descriptor) return {}; - return GooseValue { descriptor.value(), value }; + return GooseSignal { descriptor.value(), value, meta }; } -std::optional GooseValue::toMmsValue() const { +std::optional GooseSignal::toMmsValue() const +{ if (!descriptor->mms_type) return std::nullopt; switch (*descriptor->mms_type) { @@ -98,6 +103,8 @@ std::optional GooseValue::toMmsValue() const { return newMmsInteger(signal_data.i, meta.size); case MmsType::MMS_UNSIGNED: return newMmsUnsigned(static_cast(signal_data.i), meta.size); + case MmsType::MMS_BIT_STRING: + return newMmsBitString(static_cast(signal_data.i), meta.size); case MmsType::MMS_FLOAT: return newMmsFloat(signal_data.f, meta.size); default: @@ -105,10 +112,20 @@ std::optional 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(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(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(d)); case 64: @@ -155,7 +174,7 @@ MmsValue * GooseValue::newMmsFloat(double d, GooseValue::MetaSize size) { } } -std::optional GooseValue::lookupMmsType(int mms_type) +std::optional GooseSignal::lookupMmsType(int mms_type) { auto check = [mms_type] (Descriptor descriptor) { return descriptor.mms_type == mms_type; @@ -168,10 +187,10 @@ std::optional GooseValue::lookupMmsType(int mms_ return std::nullopt; } -std::optional GooseValue::lookupName(char *name) +std::optional 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::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) : + 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::vectorinit); - 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 &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 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 {}; + + 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 &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 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 p; +static NodePlugin< + GooseNode, + name, + description, + (int) NodeFactory::Flags::SUPPORTS_READ + | (int) NodeFactory::Flags::SUPPORTS_WRITE + | (int) NodeFactory::Flags::SUPPORTS_POLL, + 1 +> p;