From 56e89181962bb7dc124eb3ae6054c2215d64fc7e Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Wed, 27 Jul 2022 15:46:17 +0200 Subject: [PATCH] Final code-style tweaks --- .../schemas/config/nodes/iec60870-5-104.yaml | 19 ++- include/villas/nodes/iec60870.hpp | 82 +++++----- lib/nodes/iec60870.cpp | 154 ++++++++++++------ 3 files changed, 154 insertions(+), 101 deletions(-) diff --git a/doc/openapi/components/schemas/config/nodes/iec60870-5-104.yaml b/doc/openapi/components/schemas/config/nodes/iec60870-5-104.yaml index 7f1f7108d..0a34b7657 100644 --- a/doc/openapi/components/schemas/config/nodes/iec60870-5-104.yaml +++ b/doc/openapi/components/schemas/config/nodes/iec60870-5-104.yaml @@ -15,38 +15,45 @@ allOf: type: object properties: duplicate_ioa_is_sequence: - description: Treat consecutive signals with the same IOA as a sequence by assigning subsequent IOAs. type: boolean default: false + description: | + Treat consecutive signals with the same IOA as a sequence by assigning subsequent IOAs. + signals: type: array items: $ref: ./signals/iec60870_signal.yaml address: - description: Hostname or IP address for the IEC60870 slave to listen on. type: string default: localhost + description: | + Hostname or IP address for the IEC60870 slave to listen on. port: - description: Port number of the IEC60870 slave. type: number default: 2404 + description: | + Port number of the IEC60870 slave. ca: - description: Common Address of the IEC60870 slave. type: number default: 1 + description: | + Common Address of the IEC60870 slave. low_priority_queue: - description: Message queue size for the periodic messages (increase on dropped simulation data messages). type: number default: 100 + description: | + Message queue size for the periodic messages (increase on dropped simulation data messages). high_priority_queue: - description: Message queue size for interrogation responses (increase on missing signals in interrogation response). type: number default: 100 + description: | + Message queue size for interrogation responses (increase on missing signals in interrogation response). apci_t0: type: number diff --git a/include/villas/nodes/iec60870.hpp b/include/villas/nodes/iec60870.hpp index 36e9e24e2..c08f0144f 100644 --- a/include/villas/nodes/iec60870.hpp +++ b/include/villas/nodes/iec60870.hpp @@ -5,6 +5,7 @@ * @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC * @license Apache 2.0 *********************************************************************************/ + #pragma once #include @@ -28,26 +29,16 @@ namespace iec60870 { class ASDUData { public: enum Type { - // SinglePointInformation - SINGLE_POINT = M_SP_NA_1, - // SinglePointWithCP56Time2a - SINGLE_POINT_WITH_TIMESTAMP = M_SP_TB_1, - // DoublePointInformation - DOUBLE_POINT = M_DP_NA_1, - // DoublePointWithCP56Time2a - DOUBLE_POINT_WITH_TIMESTAMP = M_DP_TB_1, - // MeasuredValueScaled - SCALED_INT = M_ME_NB_1, - // MeasuredValueScaledWithCP56Time2a - SCALED_INT_WITH_TIMESTAMP = M_ME_TE_1, - // MeasuredValueNormalized - NORMALIZED_FLOAT = M_ME_NA_1, - // MeasuredValueNormalizedWithCP56Time2a - NORMALIZED_FLOAT_WITH_TIMESTAMP = M_ME_TD_1, - // MeasuredValueShort - SHORT_FLOAT = M_ME_NC_1, - // MeasuredValueShortWithCP56Time2a - SHORT_FLOAT_WITH_TIMESTAMP = M_ME_TF_1, + SINGLE_POINT = M_SP_NA_1, // SinglePointInformation + SINGLE_POINT_WITH_TIMESTAMP = M_SP_TB_1, // SinglePointWithCP56Time2a + DOUBLE_POINT = M_DP_NA_1, // DoublePointInformation + DOUBLE_POINT_WITH_TIMESTAMP = M_DP_TB_1, // DoublePointWithCP56Time2a + SCALED_INT = M_ME_NB_1, // MeasuredValueScaled + SCALED_INT_WITH_TIMESTAMP = M_ME_TE_1, // MeasuredValueScaledWithCP56Time2a + NORMALIZED_FLOAT = M_ME_NA_1, // MeasuredValueNormalized + NORMALIZED_FLOAT_WITH_TIMESTAMP = M_ME_TD_1, // MeasuredValueNormalizedWithCP56Time2a + SHORT_FLOAT = M_ME_NC_1, // MeasuredValueShort + SHORT_FLOAT_WITH_TIMESTAMP = M_ME_TF_1, // MeasuredValueShortWithCP56Time2a }; struct Sample { @@ -56,29 +47,37 @@ public: std::optional timestamp; }; - // parse the config json + // Parse the config json static ASDUData parse(json_t *signal_json, std::optional last_data, bool duplicate_ioa_is_sequence); - // does this data include a timestamp + // Does this data include a timestamp bool hasTimestamp() const; - // the IEC104 type + + // The IEC104 type ASDUData::Type type() const; - // the config file identifier for this type + + // The config file identifier for this type char const * name() const; - // get equivalent IEC104 type without timestamp (e.g. for general interrogation response) + + // Get equivalent IEC104 type without timestamp (e.g. for general interrogation response) ASDUData::Type typeWithoutTimestamp() const; - // get equivalent ASDUData without timestamp (e.g. for general interrogation response) + + // Get equivalent ASDUData without timestamp (e.g. for general interrogation response) ASDUData withoutTimestamp() const; - // corresponding signal type + + // Corresponding signal type SignalType signalType() const; - // check if ASDU contains this data + + // Check if ASDU contains this data std::optional checkASDU(CS101_ASDU const &asdu) const; - // add SignalData to an ASDU, returns false when sample couldn't be added (insufficient space in ASDU) + + // Add SignalData to an ASDU, returns false when sample couldn't be added (insufficient space in ASDU) bool addSampleToASDU(CS101_ASDU &asdu, ASDUData::Sample sample) const; - // every value in an ASDU has an associated "information object address" (ioa) + // Every value in an ASDU has an associated "information object address" (ioa) int ioa; - // start of the ioa sequence + + // Start of the ioa sequence int ioa_sequence_start; private: struct Descriptor { @@ -105,31 +104,33 @@ private: ASDUData(ASDUData::Descriptor const *descriptor, int ioa, int ioa_sequence_start); - // lookup datatype for config key asdu_type + // Lookup datatype for config key asdu_type static std::optional lookupName(char const *name, bool with_timestamp, int ioa, int ioa_sequence_start); - // lookup datatype for config key asdu_type_id + + // Lookup datatype for config key asdu_type_id static std::optional lookupTypeId(char const *type_id, int ioa, int ioa_sequence_start); - // lookup datatype for numeric type identifier + + // Lookup datatype for numeric type identifier static std::optional lookupType(int type, int ioa, int ioa_sequence_start); - // descriptor within the descriptors table above + // Descriptor within the descriptors table above ASDUData::Descriptor const *descriptor; }; class SlaveNode : public Node { protected: struct Server { - // slave state + // Slave state enum { NONE, STOPPED, READY } state; - // config (use explicit defaults) + // Config (use explicit defaults) std::string local_address; int local_port; int common_address; int low_priority_queue; int high_priority_queue; - // config (use lib60870 defaults if std::nullopt) + // Config (use lib60870 defaults if std::nullopt) std::optional apci_t0; std::optional apci_t1; std::optional apci_t2; @@ -137,7 +138,7 @@ protected: std::optional apci_k; std::optional apci_w; - // lib60870 + // Lib60870 CS104_Slave slave; CS101_AppLayerParameters asdu_app_layer_parameters; } server; @@ -183,9 +184,6 @@ public: virtual int stop() override; - - // virtual - // std::string & getDetails() override; }; } /* namespace iec60870 */ diff --git a/lib/nodes/iec60870.cpp b/lib/nodes/iec60870.cpp index fc9c6b0ca..86cd676d3 100644 --- a/lib/nodes/iec60870.cpp +++ b/lib/nodes/iec60870.cpp @@ -4,12 +4,13 @@ * @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC * @license Apache 2.0 *********************************************************************************/ + #include + #include #include #include #include -#include #include #include @@ -52,7 +53,7 @@ ASDUData ASDUData::parse(json_t *signal_json, std::optional last_data, with_timestamp = with_timestamp != -1 ? with_timestamp != 0 : false; - // increase the ioa if it is found twice to make it a sequence + // Increase the ioa if it is found twice to make it a sequence if ( duplicate_ioa_is_sequence && last_data && ioa == last_data->ioa_sequence_start) { @@ -82,6 +83,7 @@ std::optional ASDUData::lookupTypeId(char const *type_id, int ioa, int auto check = [type_id] (Descriptor descriptor) { return !strcmp(descriptor.type_id, type_id); }; + auto descriptor = std::find_if(begin(descriptors), end(descriptors), check); if (descriptor != end(descriptors)) return ASDUData { &*descriptor, ioa, ioa_sequence_start }; @@ -94,6 +96,7 @@ std::optional ASDUData::lookupName(char const *name, bool with_timesta auto check = [name, with_timestamp] (Descriptor descriptor) { return !strcmp(descriptor.name, name) && descriptor.has_timestamp == with_timestamp; }; + auto descriptor = std::find_if(begin(descriptors), end(descriptors), check); if (descriptor != end(descriptors)) return ASDUData { &*descriptor, ioa, ioa_sequence_start }; @@ -106,6 +109,7 @@ std::optional ASDUData::lookupType(int type, int ioa, int ioa_sequence auto check = [type] (Descriptor descriptor) { return descriptor.type == type; }; + auto descriptor = std::find_if(begin(descriptors), end(descriptors), check); if (descriptor != end(descriptors)) return ASDUData { &*descriptor, ioa, ioa_sequence_start }; @@ -198,7 +202,8 @@ std::optional ASDUData::checkASDU(CS101_ASDU const &asdu) cons break; } - default: assert(!"unreachable"); + default: + assert(!"unreachable"); } std::optional time_cp56; @@ -233,7 +238,8 @@ std::optional ASDUData::checkASDU(CS101_ASDU const &asdu) cons break; } - default: time_cp56 = std::nullopt; + default: + time_cp56 = std::nullopt; } InformationObject_destroy(io); @@ -326,10 +332,13 @@ bool ASDUData::addSampleToASDU(CS101_ASDU &asdu, ASDUData::Sample sample) const break; } - default: assert(!"unreachable"); + default: + assert(!"unreachable"); } + bool successfully_added = CS101_ASDU_addInformationObject(asdu, io); InformationObject_destroy(io); + return successfully_added; } @@ -339,28 +348,39 @@ ASDUData::ASDUData(ASDUData::Descriptor const *descriptor, int ioa, int ioa_sequ void SlaveNode::createSlave() noexcept { - // destroy slave id it was already created + // Destroy slave id it was already created destroySlave(); - // create the slave object + // Create the slave object server.slave = CS104_Slave_create(server.low_priority_queue, server.high_priority_queue); CS104_Slave_setServerMode(server.slave, CS104_MODE_SINGLE_REDUNDANCY_GROUP); - // configure the slave according to config + // Configure the slave according to config server.asdu_app_layer_parameters = CS104_Slave_getAppLayerParameters(server.slave); CS104_APCIParameters apci_parameters = CS104_Slave_getConnectionParameters(server.slave); - if (server.apci_t0) apci_parameters->t0 = *server.apci_t0; - if (server.apci_t1) apci_parameters->t1 = *server.apci_t1; - if (server.apci_t2) apci_parameters->t2 = *server.apci_t2; - if (server.apci_t3) apci_parameters->t3 = *server.apci_t3; - if (server.apci_k) apci_parameters->k = *server.apci_k; - if (server.apci_w) apci_parameters->w = *server.apci_w; + if (server.apci_t0) + apci_parameters->t0 = *server.apci_t0; + + if (server.apci_t1) + apci_parameters->t1 = *server.apci_t1; + + if (server.apci_t2) + apci_parameters->t2 = *server.apci_t2; + + if (server.apci_t3) + apci_parameters->t3 = *server.apci_t3; + + if (server.apci_k) + apci_parameters->k = *server.apci_k; + + if (server.apci_w) + apci_parameters->w = *server.apci_w; CS104_Slave_setLocalAddress(server.slave, server.local_address.c_str()); CS104_Slave_setLocalPort(server.slave, server.local_port); - // setup callbacks into the class + // Setup callbacks into the class CS104_Slave_setClockSyncHandler(server.slave, [] (void *tcp_node, IMasterConnection connection, CS101_ASDU asdu, CP56Time2a new_time) { auto self = static_cast (tcp_node); return self->onClockSync(connection, asdu, new_time); @@ -423,8 +443,9 @@ void SlaveNode::stopSlave() noexcept server.state = SlaveNode::Server::STOPPED; if (CS104_Slave_getNumberOfQueueEntries(server.slave, NULL) != 0) - logger->info("waiting for last messages in queue"); - // wait for all messages to be send before really stopping + logger->info("Waiting for last messages in queue"); + + // Wait for all messages to be send before really stopping while ( (CS104_Slave_getNumberOfQueueEntries(server.slave, NULL) != 0) && (CS104_Slave_getOpenConnections(server.slave) != 0)) std::this_thread::sleep_for(100ms); @@ -434,45 +455,48 @@ void SlaveNode::stopSlave() noexcept void SlaveNode::debugPrintMessage(IMasterConnection connection, uint8_t* message, int message_size, bool sent) const noexcept { - /// ToDo: debug print the message bytes as trace + /// TODO: debug print the message bytes as trace } void SlaveNode::debugPrintConnection(IMasterConnection connection, CS104_PeerConnectionEvent event) const noexcept { switch (event) { case CS104_CON_EVENT_CONNECTION_OPENED: - logger->info("client connected"); + logger->info("Client connected"); break; + case CS104_CON_EVENT_CONNECTION_CLOSED: - logger->info("client disconnected"); + logger->info("Client disconnected"); break; + case CS104_CON_EVENT_ACTIVATED: - logger->info("connection activated"); + logger->info("Connection activated"); break; + case CS104_CON_EVENT_DEACTIVATED: - logger->info("connection closed"); + logger->info("Connection closed"); break; } } bool SlaveNode::onClockSync(IMasterConnection connection, CS101_ASDU asdu, CP56Time2a new_time) const noexcept { - logger->warn("received clock sync command (unimplemented)"); + logger->warn("Received clock sync command (unimplemented)"); return true; } bool SlaveNode::onInterrogation(IMasterConnection connection, CS101_ASDU asdu, QualifierOfInterrogation qoi) const noexcept { switch (qoi) { - // send last values without timestamps + // Send last values without timestamps case IEC60870_QOI_STATION: { IMasterConnection_sendACT_CON(connection, asdu, false); - logger->debug("received general interrogation"); + logger->debug("Received general interrogation"); auto guard = std::lock_guard { output.last_values_mutex }; - for(auto const &asdu_type : output.asdu_types) { + for (auto const &asdu_type : output.asdu_types) { for (unsigned i = 0; i < output.mapping.size();) { auto signal_asdu = CS101_ASDU_create( IMasterConnection_getApplicationLayerParameters(connection), @@ -491,7 +515,7 @@ bool SlaveNode::onInterrogation(IMasterConnection connection, CS101_ASDU asdu, Q if (asdu_data.type() != asdu_type) continue; - if(asdu_data.addSampleToASDU(signal_asdu, ASDUData::Sample { + if (asdu_data.addSampleToASDU(signal_asdu, ASDUData::Sample { last_value, IEC60870_QUALITY_GOOD, std::nullopt @@ -509,10 +533,10 @@ bool SlaveNode::onInterrogation(IMasterConnection connection, CS101_ASDU asdu, Q break; } - // negative acknowledgement + // Negative acknowledgement default: IMasterConnection_sendACT_CON(connection, asdu, true); - logger->warn("ignoring interrogation type {}", qoi); + logger->warn("Ignoring interrogation type {}", qoi); } return true; @@ -520,17 +544,17 @@ bool SlaveNode::onInterrogation(IMasterConnection connection, CS101_ASDU asdu, Q bool SlaveNode::onASDU(IMasterConnection connection, CS101_ASDU asdu) const noexcept { - logger->warn("ignoring asdu type {}", CS101_ASDU_getTypeID(asdu)); + logger->warn("Ignoring ASDU type {}", CS101_ASDU_getTypeID(asdu)); return true; } void SlaveNode::sendPeriodicASDUsForSample(Sample const *sample) const noexcept(false) { - // ASDUs may only carry one type of asdu + // ASDUs may only carry one type of ASDU for (auto const &type : output.asdu_types) { - // search all occurences of this ASDU type + // Search all occurrences of this ASDU type for (unsigned signal = 0; signal < MIN(sample->length, output.mapping.size());) { - // create an ASDU for periodic transimission + // Create an ASDU for periodic transmission CS101_ASDU asdu = CS101_ASDU_create( server.asdu_app_layer_parameters, 0, @@ -544,7 +568,7 @@ void SlaveNode::sendPeriodicASDUsForSample(Sample const *sample) const noexcept( do { auto &asdu_data = output.mapping[signal]; - // this signal_data does not belong in this ASDU + // This signal_data does not belong in this ASDU if (asdu_data.type() != type) continue; @@ -586,13 +610,12 @@ int SlaveNode::_write(Sample *samples[], unsigned sample_count) for (unsigned sample_index = 0; sample_index < sample_count; sample_index++) { Sample const *sample = samples[sample_index]; - // update last_values + // Update last_values output.last_values_mutex.lock(); - for (unsigned i = 0; i < MIN(sample->length, output.last_values.size()); i++) { + for (unsigned i = 0; i < MIN(sample->length, output.last_values.size()); i++) output.last_values[i] = sample->data[i]; - } - output.last_values_mutex.unlock(); + output.last_values_mutex.unlock(); sendPeriodicASDUsForSample(sample); } @@ -604,14 +627,14 @@ SlaveNode::SlaveNode(const std::string &name) : { server.state = SlaveNode::Server::NONE; - // server config (use explicit defaults) + // Server config (use explicit defaults) server.local_address = "0.0.0.0"; server.local_port = 2404; server.common_address = 1; server.low_priority_queue = 100; server.high_priority_queue = 100; - // config (use lib60870 defaults if std::nullopt) + // Config (use lib60870 defaults if std::nullopt) server.apci_t0 = std::nullopt; server.apci_t1 = std::nullopt; server.apci_t2 = std::nullopt; @@ -619,7 +642,7 @@ SlaveNode::SlaveNode(const std::string &name) : server.apci_k = std::nullopt; server.apci_w = std::nullopt; - // output config + // Output config output.enabled = false; output.mapping = {}; output.asdu_types = {}; @@ -648,7 +671,8 @@ int SlaveNode::parse(json_t *json, const uuid_t sn_uuid) int apci_t3 = -1; int apci_k = -1; int apci_w = -1; - if(json_unpack_ex(json, &err, 0, "{ s?: o, s?: s, s?: i, s?: i, s?: i, s?: i, s?: i, s?: i, s?: i, s?: i, s?: i, s?: i }", + + ret = json_unpack_ex(json, &err, 0, "{ s?: o, s?: s, s?: i, s?: i, s?: i, s?: i, s?: i, s?: i, s?: i, s?: i, s?: i, s?: i }", "out", &out_json, "address", &address, "port", &server.local_port, @@ -661,27 +685,42 @@ int SlaveNode::parse(json_t *json, const uuid_t sn_uuid) "apci_t3", &apci_t3, "apci_k", &apci_k, "apci_w", &apci_w - )) + ); + if (ret) throw ConfigError(json, err, "node-config-node-iec60870-5-104"); - if(apci_t0 != -1) server.apci_t0 = apci_t0; - if(apci_t1 != -1) server.apci_t1 = apci_t1; - if(apci_t2 != -1) server.apci_t2 = apci_t2; - if(apci_t3 != -1) server.apci_t3 = apci_t3; - if(apci_k != -1) server.apci_k = apci_k; - if(apci_w != -1) server.apci_w = apci_w; + if (apci_t0 != -1) + server.apci_t0 = apci_t0; + + if (apci_t1 != -1) + server.apci_t1 = apci_t1; + + if (apci_t2 != -1) + server.apci_t2 = apci_t2; + + if (apci_t3 != -1) + server.apci_t3 = apci_t3; + + if (apci_k != -1) + server.apci_k = apci_k; + + if (apci_w != -1) + server.apci_w = apci_w; if (address) server.local_address = address; json_t *signals_json = nullptr; int duplicate_ioa_is_sequence = false; + if (out_json) { output.enabled = true; - if(json_unpack_ex(out_json, &err, 0, "{ s: o, s?: b }", + + ret = json_unpack_ex(out_json, &err, 0, "{ s: o, s?: b }", "signals", &signals_json, "duplicate_ioa_is_sequence", &duplicate_ioa_is_sequence - )) + ); + if (ret) throw ConfigError(out_json, err, "node-config-node-iec60870-5-104"); } @@ -689,32 +728,41 @@ int SlaveNode::parse(json_t *json, const uuid_t sn_uuid) json_t *signal_json; size_t i; std::optional last_data = std::nullopt; + json_array_foreach(signals_json, i, signal_json) { auto signal = signals ? signals->getByIndex(i) : Signal::Ptr{}; auto asdu_data = ASDUData::parse(signal_json, last_data, duplicate_ioa_is_sequence); last_data = asdu_data; SignalData initial_value; + if (signal) { - if (signal->type != asdu_data.signalType()) + if (signal->type != asdu_data.signalType()) { throw RuntimeError("Type mismatch! Expected type {} for signal {}, but found {}", signalTypeToString(asdu_data.signalType()), signal->name, signalTypeToString(signal->type) ); + } + switch (signal->type) { case SignalType::BOOLEAN: initial_value.b = false; break; + case SignalType::INTEGER: initial_value.i = 0; break; + case SignalType::FLOAT: initial_value.f = 0; break; - default: assert(!"unreachable"); + + default: + assert(!"unreachable"); } } else initial_value.f = 0.0; + output.mapping.push_back(asdu_data); output.last_values.push_back(initial_value); }