From ebab8619b078d78ef7b6fb3706833f591f84b33a Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 25 Jan 2024 12:19:35 +0000 Subject: [PATCH 01/20] - fixed: wrong values for Resv and ResvTms are sent during RCB activation Signed-off-by: Michael Zillgith --- src/iec61850_client_connection.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/iec61850_client_connection.cpp b/src/iec61850_client_connection.cpp index c7cd286..12b50ad 100644 --- a/src/iec61850_client_connection.cpp +++ b/src/iec61850_client_connection.cpp @@ -348,10 +348,14 @@ configureRcb (const std::shared_ptr& rs, bool isBuffered = ClientReportControlBlock_isBuffered (rcb); - if (isBuffered) + if (isBuffered) { parametersMask |= RCB_ELEMENT_RESV_TMS; - else + ClientReportControlBlock_setResvTms(rcb, 10); + } + else { parametersMask |= RCB_ELEMENT_RESV; + ClientReportControlBlock_setResv(rcb, true); + } if (rs->trgops != -1) { From 789fed200adc41d3793314a34697420c65059c40 Mon Sep 17 00:00:00 2001 From: Jude Mingay Date: Thu, 1 Feb 2024 09:54:59 +0000 Subject: [PATCH 02/20] Fixed reconfigure typo Signed-off-by: Jude Mingay --- src/plugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugin.cpp b/src/plugin.cpp index 0de0ee7..2cb1807 100644 --- a/src/plugin.cpp +++ b/src/plugin.cpp @@ -198,7 +198,7 @@ extern "C" if (config.itemExists ("protocol_stack") && config.itemExists ("exchanged_data") - && config.itemExists ("tls")) + && config.itemExists ("tls_conf")) iec61850->setJsonConfig (config.getValue ("protocol_stack"), config.getValue ("exchanged_data"), config.getValue ("tls_conf")); From 51c8592c15b629178bde7dc2a7b502e9e837913d Mon Sep 17 00:00:00 2001 From: Jude Mingay Date: Thu, 1 Feb 2024 10:01:20 +0000 Subject: [PATCH 03/20] Remove duplicate getMonotonicTimeInMs function (RFD-121) Signed-off-by: Jude Mingay --- src/iec61850_client.cpp | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/iec61850_client.cpp b/src/iec61850_client.cpp index 262de35..4881d66 100644 --- a/src/iec61850_client.cpp +++ b/src/iec61850_client.cpp @@ -50,20 +50,6 @@ isCommandCdcType (CDCTYPE type) return type >= SPC && type < SPG; } -static uint64_t -getMonotonicTimeInMs () -{ - uint64_t timeVal = 0; - - struct timespec ts; - - if (clock_gettime (CLOCK_MONOTONIC, &ts) == 0) - { - timeVal = ((uint64_t)ts.tv_sec * 1000LL) + (ts.tv_nsec / 1000000); - } - - return timeVal; -} static long getValueInt (Datapoint* dp) From fe81269c4040eb99a7ee05e9b6311a3913d6ea7f Mon Sep 17 00:00:00 2001 From: Jude Mingay Date: Thu, 1 Feb 2024 11:14:46 +0000 Subject: [PATCH 04/20] Remove dynamic datasets from reports before restarting Signed-off-by: Jude Mingay --- src/iec61850_client_connection.cpp | 35 ++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/iec61850_client_connection.cpp b/src/iec61850_client_connection.cpp index 0ed879d..3996a32 100644 --- a/src/iec61850_client_connection.cpp +++ b/src/iec61850_client_connection.cpp @@ -536,6 +536,41 @@ IEC61850ClientConnection::cleanUp () } } + for(const auto &dataset: m_config->getDatasets()){ + if(dataset.second->dynamic){ + for(const auto &rcb : m_config->getReportSubscriptions()){ + if(rcb.second->datasetRef == dataset.second->datasetRef){ + IedClientError error; + if(m_connection){ + ClientReportControlBlock block = IedConnection_getRCBValues (m_connection, &error, + rcb.second->rcbRef.c_str (), nullptr); + + if(!block){ + Iec61850Utility::log_debug("RCB %s not found, continue", rcb.second->rcbRef.c_str()); + continue; + } + ClientReportControlBlock_setDataSetReference(block, ""); + uint32_t parametersMask; + if(ClientReportControlBlock_hasResvTms(block)){ + parametersMask |= ClientReportControlBlock_getResvTms(block); + } + else{ + parametersMask |= ClientReportControlBlock_getResv(block); + } + parametersMask |= ClientReportControlBlock_getTrgOps(block); + parametersMask |= ClientReportControlBlock_getBufTm(block); + parametersMask |= ClientReportControlBlock_getIntgPd(block); + parametersMask |= ClientReportControlBlock_getGI(block); + parametersMask |= ClientReportControlBlock_getRptEna(block); + + IedConnection_setRCBValues(m_connection,&error,block, parametersMask, true); + Iec61850Utility::log_debug("Update RCB %s", rcb.second->rcbRef.c_str()); + } + } + } + } + } + if (!m_connDataSetDirectoryPairs.empty ()) { for (const auto& entry : m_connDataSetDirectoryPairs) From 68566e14f6c4c0ee8af52bb9c328da5ae4826ae0 Mon Sep 17 00:00:00 2001 From: Jude Mingay Date: Thu, 1 Feb 2024 11:57:43 +0000 Subject: [PATCH 05/20] Implement purgeBuf for BRCBs when configuration is updated Signed-off-by: Jude Mingay --- include/iec61850.hpp | 3 +++ src/iec61850_client_connection.cpp | 29 ++++++++++++++++++++++------- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/include/iec61850.hpp b/include/iec61850.hpp index 63d7896..9f628c3 100644 --- a/include/iec61850.hpp +++ b/include/iec61850.hpp @@ -145,6 +145,8 @@ class IEC61850Client void sendCommandAck (const std::string& label, ControlModel mode, bool terminated); + bool firstTimeConnect = true; + private: std::shared_ptr > m_connections = nullptr; @@ -152,6 +154,7 @@ class IEC61850Client IEC61850ClientConnection* m_active_connection = nullptr; std::mutex m_activeConnectionMtx; + enum class ConnectionStatus { STARTED, diff --git a/src/iec61850_client_connection.cpp b/src/iec61850_client_connection.cpp index 0ed879d..9696caa 100644 --- a/src/iec61850_client_connection.cpp +++ b/src/iec61850_client_connection.cpp @@ -314,16 +314,28 @@ IEC61850ClientConnection::reportCallbackFunction (void* parameter, static int configureRcb (const std::shared_ptr& rs, - ClientReportControlBlock rcb) + ClientReportControlBlock rcb, bool firstTimeConnect) { uint32_t parametersMask = 0; bool isBuffered = ClientReportControlBlock_isBuffered (rcb); if (isBuffered) + { parametersMask |= RCB_ELEMENT_RESV_TMS; + ClientReportControlBlock_setResvTms (rcb, 1000); + + if (firstTimeConnect) + { + parametersMask |= RCB_ELEMENT_PURGE_BUF; + ClientReportControlBlock_setPurgeBuf (rcb, true); + } + } else + { + ClientReportControlBlock_setResv (rcb, true); parametersMask |= RCB_ELEMENT_RESV; + } if (rs->trgops != -1) { @@ -340,11 +352,6 @@ configureRcb (const std::shared_ptr& rs, parametersMask |= RCB_ELEMENT_INTG_PD; ClientReportControlBlock_setIntgPd (rcb, rs->intgpd); } - if (rs->gi) - { - parametersMask |= RCB_ELEMENT_GI; - ClientReportControlBlock_setGI (rcb, rs->gi); - } if (!rs->datasetRef.empty ()) { @@ -356,6 +363,12 @@ configureRcb (const std::shared_ptr& rs, rcb, modifiedDataSetRef.c_str ()); } + if (rs->gi) + { + parametersMask |= RCB_ELEMENT_GI; + ClientReportControlBlock_setGI (rcb, rs->gi); + } + ClientReportControlBlock_setRptEna (rcb, true); parametersMask |= RCB_ELEMENT_RPT_ENA; @@ -407,7 +420,8 @@ IEC61850ClientConnection::m_configRcb () continue; } - uint32_t parametersMask = configureRcb (rs, rcb); + uint32_t parametersMask + = configureRcb (rs, rcb, m_client->firstTimeConnect); auto connDataSetPair = new std::pair ( @@ -1020,6 +1034,7 @@ IEC61850ClientConnection::_conThread () m_connectionState = CON_STATE_CONNECTED; m_connecting = false; m_connected = true; + m_client->firstTimeConnect = false; } } else if (getMonotonicTimeInMs () From 98b683024160a7f44026d36368af9bc98053d49b Mon Sep 17 00:00:00 2001 From: Jude Mingay Date: Thu, 1 Feb 2024 14:29:02 +0000 Subject: [PATCH 06/20] Remove dataset from rcb when reconfiguring plugin Signed-off-by: Jude Mingay --- src/iec61850_client_connection.cpp | 12 ++++-- tests/test_iec61850_reporting.cpp | 69 +++++++++++++++++++++++++----- 2 files changed, 66 insertions(+), 15 deletions(-) diff --git a/src/iec61850_client_connection.cpp b/src/iec61850_client_connection.cpp index 3996a32..b5ae226 100644 --- a/src/iec61850_client_connection.cpp +++ b/src/iec61850_client_connection.cpp @@ -540,17 +540,19 @@ IEC61850ClientConnection::cleanUp () if(dataset.second->dynamic){ for(const auto &rcb : m_config->getReportSubscriptions()){ if(rcb.second->datasetRef == dataset.second->datasetRef){ - IedClientError error; - if(m_connection){ + IedClientError error = IED_ERROR_OK; + if(m_connection && IedConnection_getState(m_connection) == IED_STATE_CONNECTED){ ClientReportControlBlock block = IedConnection_getRCBValues (m_connection, &error, rcb.second->rcbRef.c_str (), nullptr); if(!block){ Iec61850Utility::log_debug("RCB %s not found, continue", rcb.second->rcbRef.c_str()); + m_client->logIedClientError(error, "Get RCB in clean up"); continue; } + ClientReportControlBlock_setDataSetReference(block, ""); - uint32_t parametersMask; + uint32_t parametersMask = 0; if(ClientReportControlBlock_hasResvTms(block)){ parametersMask |= ClientReportControlBlock_getResvTms(block); } @@ -562,9 +564,11 @@ IEC61850ClientConnection::cleanUp () parametersMask |= ClientReportControlBlock_getIntgPd(block); parametersMask |= ClientReportControlBlock_getGI(block); parametersMask |= ClientReportControlBlock_getRptEna(block); - + IedConnection_setRCBValues(m_connection,&error,block, parametersMask, true); Iec61850Utility::log_debug("Update RCB %s", rcb.second->rcbRef.c_str()); + + ClientReportControlBlock_destroy(block); } } } diff --git a/tests/test_iec61850_reporting.cpp b/tests/test_iec61850_reporting.cpp index 906989c..891ea95 100644 --- a/tests/test_iec61850_reporting.cpp +++ b/tests/test_iec61850_reporting.cpp @@ -247,14 +247,6 @@ class ReportingTest : public testing::Test void TearDown () override { - iec61850->stop (); - delete iec61850; - - for (auto reading : storedReadings) - { - delete reading; - } - storedReadings.clear (); } static bool @@ -518,9 +510,18 @@ TEST_F (ReportingTest, ReportingWithStaticDataset) double expectedMagVal = 1.2; verifyDatapoint (mag, "f", &expectedMagVal); - IedServer_stop (server); - IedServer_destroy (server); - IedModel_destroy (model); + iec61850->stop (); + delete iec61850; + + for (auto reading : storedReadings) + { + delete reading; + } + storedReadings.clear (); + + IedServer_stop (server); + IedServer_destroy (server); + IedModel_destroy (model); } TEST_F (ReportingTest, ReportingWithDynamicDataset) @@ -596,6 +597,15 @@ TEST_F (ReportingTest, ReportingWithDynamicDataset) verifyDatapoint (qDp, "Validity", &expectedValidity); + iec61850->stop (); + delete iec61850; + + for (auto reading : storedReadings) + { + delete reading; + } + storedReadings.clear (); + IedServer_stop (server); IedServer_destroy (server); IedModel_destroy (model); @@ -661,6 +671,15 @@ TEST_F (ReportingTest, ReportingGI) int expectedStVal = false; verifyDatapoint (SPC, "stVal", &expectedStVal); + iec61850->stop (); + delete iec61850; + + for (auto reading : storedReadings) + { + delete reading; + } + storedReadings.clear (); + IedServer_stop (server); IedServer_destroy (server); IedModel_destroy (model); @@ -753,6 +772,16 @@ TEST_F (ReportingTest, ReportingSetpointCommand) verifyDatapoint (SPC, "stVal", &expectedStVal); delete pair; + + iec61850->stop (); + delete iec61850; + + for (auto reading : storedReadings) + { + delete reading; + } + storedReadings.clear (); + IedServer_stop (server); IedServer_destroy (server); IedModel_destroy (model); @@ -840,6 +869,15 @@ TEST_F (ReportingTest, ReportingUpdateQuality) verifyDatapoint (qDp, "Source"); verifyDatapoint (qDp, "operatorBlocked"); + iec61850->stop (); + delete iec61850; + + for (auto reading : storedReadings) + { + delete reading; + } + storedReadings.clear (); + IedServer_stop (server); IedServer_destroy (server); IedModel_destroy (model); @@ -967,6 +1005,15 @@ TEST_F (ReportingTest, ReportingChangeValueMultipleTimes) expectedMagVal = 1.5; verifyDatapoint (mag, "f", &expectedMagVal); + iec61850->stop (); + delete iec61850; + + for (auto reading : storedReadings) + { + delete reading; + } + storedReadings.clear (); + IedServer_stop (server); IedServer_destroy (server); IedModel_destroy (model); From c9af3602ad1dfadb67cf965fbf7eb59440302978 Mon Sep 17 00:00:00 2001 From: Jude Mingay Date: Thu, 1 Feb 2024 14:51:37 +0000 Subject: [PATCH 07/20] Save last report entryID for buffered RCB Signed-off-by: Jude Mingay --- include/iec61850_client_connection.hpp | 1 + src/iec61850_client_connection.cpp | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/include/iec61850_client_connection.hpp b/include/iec61850_client_connection.hpp index 5c1a3a8..11fccc8 100644 --- a/include/iec61850_client_connection.hpp +++ b/include/iec61850_client_connection.hpp @@ -150,6 +150,7 @@ class IEC61850ClientConnection bool m_connecting = false; bool m_started = false; bool m_useTls = false; + MmsValue* lastEntryId = nullptr; TLSConfiguration m_tlsConfig = nullptr; diff --git a/src/iec61850_client_connection.cpp b/src/iec61850_client_connection.cpp index 9696caa..fb3f66e 100644 --- a/src/iec61850_client_connection.cpp +++ b/src/iec61850_client_connection.cpp @@ -277,6 +277,8 @@ IEC61850ClientConnection::reportCallbackFunction (void* parameter, time_t unixTime = 0; + con->lastEntryId = ClientReport_getEntryId(report); + if (ClientReport_hasTimestamp (report)) { unixTime = ClientReport_getTimestamp (report) / 1000; @@ -314,7 +316,7 @@ IEC61850ClientConnection::reportCallbackFunction (void* parameter, static int configureRcb (const std::shared_ptr& rs, - ClientReportControlBlock rcb, bool firstTimeConnect) + ClientReportControlBlock rcb, bool firstTimeConnect, MmsValue* lastEntryId) { uint32_t parametersMask = 0; @@ -330,6 +332,10 @@ configureRcb (const std::shared_ptr& rs, parametersMask |= RCB_ELEMENT_PURGE_BUF; ClientReportControlBlock_setPurgeBuf (rcb, true); } + else{ + ClientReportControlBlock_setEntryId(rcb,lastEntryId); + parametersMask |= RCB_ELEMENT_ENTRY_ID; + } } else { @@ -369,6 +375,8 @@ configureRcb (const std::shared_ptr& rs, ClientReportControlBlock_setGI (rcb, rs->gi); } + + ClientReportControlBlock_setRptEna (rcb, true); parametersMask |= RCB_ELEMENT_RPT_ENA; @@ -421,7 +429,7 @@ IEC61850ClientConnection::m_configRcb () } uint32_t parametersMask - = configureRcb (rs, rcb, m_client->firstTimeConnect); + = configureRcb (rs, rcb, m_client->firstTimeConnect,lastEntryId); auto connDataSetPair = new std::pair ( @@ -585,6 +593,11 @@ IEC61850ClientConnection::cleanUp () m_controlObjects.clear (); } + if(lastEntryId){ + MmsValue_delete(lastEntryId); + lastEntryId = nullptr; + } + if (!m_connControlPairs.empty ()) { for (auto& cc : m_connControlPairs) From 3fa45f5fefa5b7174d42d6ad9643ff4cfd46198d Mon Sep 17 00:00:00 2001 From: Jude Mingay Date: Fri, 2 Feb 2024 11:48:57 +0000 Subject: [PATCH 08/20] Fixed log level being stuck at debug Signed-off-by: Jude Mingay --- src/iec61850.cpp | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/iec61850.cpp b/src/iec61850.cpp index bffc1e7..9d9af9d 100644 --- a/src/iec61850.cpp +++ b/src/iec61850.cpp @@ -41,23 +41,6 @@ void IEC61850::start () { Iec61850Utility::log_info ("Starting iec61850"); - // LCOV_EXCL_START - switch (m_config->LogLevel ()) - { - case 1: - Logger::getLogger ()->setMinLevel ("debug"); - break; - case 2: - Logger::getLogger ()->setMinLevel ("info"); - break; - case 3: - Logger::getLogger ()->setMinLevel ("warning"); - break; - default: - Logger::getLogger ()->setMinLevel ("error"); - break; - } - // LCOV_EXCL_STOP m_client = new IEC61850Client (this, m_config); From c2307ec3dff6202b54ddad621e957d7f4e6363ac Mon Sep 17 00:00:00 2001 From: Jude Mingay Date: Mon, 5 Feb 2024 11:03:29 +0000 Subject: [PATCH 09/20] Fixed dynamic dataset not being deleted properly Signed-off-by: Jude Mingay --- include/iec61850_client_config.hpp | 1 + src/iec61850_client_connection.cpp | 48 ++++---- tests/test_iec61850_reporting.cpp | 177 +++++++++++++++++++++++++++++ 3 files changed, 203 insertions(+), 23 deletions(-) diff --git a/include/iec61850_client_config.hpp b/include/iec61850_client_config.hpp index ad89b57..71dbbc3 100644 --- a/include/iec61850_client_config.hpp +++ b/include/iec61850_client_config.hpp @@ -26,6 +26,7 @@ FRIEND_TEST (ReportingTest, ReportingUpdateQuality); \ FRIEND_TEST (ReportingTest, ReportingGI); \ FRIEND_TEST (ReportingTest, ReportingSetpointCommand); \ + FRIEND_TEST (ReportingTest, ReconfigureDynamicDataset); \ FRIEND_TEST (ReportingTest, ReportingChangeValueMultipleTimes); \ FRIEND_TEST (SpontDataTest, Polling); \ FRIEND_TEST (SpontDataTest, PollingAllCDC); \ diff --git a/src/iec61850_client_connection.cpp b/src/iec61850_client_connection.cpp index b5ae226..e0c2915 100644 --- a/src/iec61850_client_connection.cpp +++ b/src/iec61850_client_connection.cpp @@ -536,12 +536,24 @@ IEC61850ClientConnection::cleanUp () } } + if (!m_connDataSetDirectoryPairs.empty ()) + { + for (const auto& entry : m_connDataSetDirectoryPairs) + { + LinkedList dataSetDirectory = entry->second; + LinkedList_destroy (dataSetDirectory); + delete entry; + } + m_connDataSetDirectoryPairs.clear (); + } + for(const auto &dataset: m_config->getDatasets()){ if(dataset.second->dynamic){ for(const auto &rcb : m_config->getReportSubscriptions()){ if(rcb.second->datasetRef == dataset.second->datasetRef){ IedClientError error = IED_ERROR_OK; if(m_connection && IedConnection_getState(m_connection) == IED_STATE_CONNECTED){ + ClientReportControlBlock block = IedConnection_getRCBValues (m_connection, &error, rcb.second->rcbRef.c_str (), nullptr); @@ -551,41 +563,31 @@ IEC61850ClientConnection::cleanUp () continue; } + ClientReportControlBlock_setRptEna(block,false); ClientReportControlBlock_setDataSetReference(block, ""); - uint32_t parametersMask = 0; - if(ClientReportControlBlock_hasResvTms(block)){ - parametersMask |= ClientReportControlBlock_getResvTms(block); + + IedConnection_setRCBValues(m_connection,&error,block, RCB_ELEMENT_RPT_ENA | RCB_ELEMENT_DATSET, true); + + if(error!=IED_ERROR_OK){ + m_client->logIedClientError(error,"Remove dynamic dataset " + dataset.second->datasetRef + " from RCB " + rcb.second->rcbRef); } else{ - parametersMask |= ClientReportControlBlock_getResv(block); + Iec61850Utility::log_debug("Update RCB %s", rcb.second->rcbRef.c_str()); } - parametersMask |= ClientReportControlBlock_getTrgOps(block); - parametersMask |= ClientReportControlBlock_getBufTm(block); - parametersMask |= ClientReportControlBlock_getIntgPd(block); - parametersMask |= ClientReportControlBlock_getGI(block); - parametersMask |= ClientReportControlBlock_getRptEna(block); + ClientReportControlBlock_destroy(block); - IedConnection_setRCBValues(m_connection,&error,block, parametersMask, true); - Iec61850Utility::log_debug("Update RCB %s", rcb.second->rcbRef.c_str()); + std::string modifiedDatasetRef = dataset.second->datasetRef; - ClientReportControlBlock_destroy(block); + if(!IedConnection_deleteDataSet(m_connection,&error,modifiedDatasetRef.c_str())){ + m_client->logIedClientError(error, "Delete dynamic dataset " + modifiedDatasetRef); + } } } } + } } - if (!m_connDataSetDirectoryPairs.empty ()) - { - for (const auto& entry : m_connDataSetDirectoryPairs) - { - LinkedList dataSetDirectory = entry->second; - LinkedList_destroy (dataSetDirectory); - delete entry; - } - m_connDataSetDirectoryPairs.clear (); - } - if (!m_controlObjects.empty ()) { for (auto& co : m_controlObjects) diff --git a/tests/test_iec61850_reporting.cpp b/tests/test_iec61850_reporting.cpp index 891ea95..00a71c4 100644 --- a/tests/test_iec61850_reporting.cpp +++ b/tests/test_iec61850_reporting.cpp @@ -120,6 +120,42 @@ static string protocol_config_2 = QUOTE ({ } }); +// PLUGIN DEFAULT PROTOCOL STACK CONF +static string protocol_config_3 = QUOTE ({ + "protocol_stack" : { + "name" : "iec61850client", + "version" : "0.0.1", + "transport_layer" : { + "ied_name" : "IED1", + "connections" : [ { "ip_addr" : "127.0.0.1", "port" : 10002 } ] + }, + "application_layer" : { + "polling_interval" : 0, + "datasets" : [ + { + "dataset_ref" : "simpleIOGenericIO/LLN0.Mags", + "entries" : [ + "simpleIOGenericIO/GGIO1.AnIn1[MX]", + "simpleIOGenericIO/GGIO1.AnIn2[MX]", + "simpleIOGenericIO/GGIO1.AnIn3[MX]", + "simpleIOGenericIO/GGIO1.AnIn4[MX]", + "simpleIOGenericIO/GGIO1.SPCSO1[ST]" + ], + "dynamic" : true + } + ], + "report_subscriptions" : [ + { + "rcb_ref" : "simpleIOGenericIO/LLN0.RP.EventsRCB01", + "dataset_ref" : "simpleIOGenericIO/LLN0.Mags", + "trgops" : [ "data_changed", "quality_changed", "gi" ], + "gi" : false + } + ] + } + } +}); + // PLUGIN DEFAULT EXCHANGED DATA CONF static string exchanged_data = QUOTE ({ @@ -1014,6 +1050,147 @@ TEST_F (ReportingTest, ReportingChangeValueMultipleTimes) } storedReadings.clear (); + IedServer_stop (server); + IedServer_destroy (server); + IedModel_destroy (model); +} + +TEST_F (ReportingTest, ReconfigureDynamicDataset) +{ + iec61850->setJsonConfig (protocol_config, exchanged_data, tls_config); + + IedModel* model = ConfigFileParser_createModelFromConfigFileEx ( + "../tests/data/simpleIO_direct_control.cfg"); + + IedServer server = IedServer_create (model); + + IedServer_start (server, 10002); + iec61850->start (); + + Thread_sleep (1000); + + auto start = std::chrono::high_resolution_clock::now (); + auto timeout = std::chrono::seconds (5); + while (!iec61850->m_client->m_active_connection + || !iec61850->m_client->m_active_connection->m_connection + || IedConnection_getState ( + iec61850->m_client->m_active_connection->m_connection) + != IED_STATE_CONNECTED) + { + auto now = std::chrono::high_resolution_clock::now (); + if (now - start > timeout) + { + IedServer_stop (server); + IedServer_destroy (server); + IedModel_destroy (model); + FAIL () << "Connection not established within timeout"; + } + Thread_sleep (10); + } + + Quality q = 0; + Quality_setValidity (&q, QUALITY_VALIDITY_INVALID); + + IedServer_updateQuality ( + server, + (DataAttribute*)IedModel_getModelNodeByObjectReference ( + model, "simpleIOGenericIO/GGIO1.AnIn1.q"), + q); + + timeout = std::chrono::seconds (3); + start = std::chrono::high_resolution_clock::now (); + while (ingestCallbackCalled != 1) + { + auto now = std::chrono::high_resolution_clock::now (); + if (now - start > timeout) + { + IedServer_stop (server); + IedServer_destroy (server); + IedModel_destroy (model); + FAIL () << "Callback not called within timeout"; + } + Thread_sleep (10); + } + + ASSERT_FALSE (storedReadings.empty ()); + ASSERT_EQ (storedReadings.size (), 1); + Datapoint* commandResponse = storedReadings[0]->getReadingData ()[0]; + verifyDatapoint (commandResponse, "GTIM"); + Datapoint* gtim = getChild (*commandResponse, "GTIM"); + + verifyDatapoint (gtim, "MvTyp"); + Datapoint* MV = getChild (*gtim, "MvTyp"); + + verifyDatapoint (MV, "q"); + Datapoint* qDp = getChild (*MV, "q"); + + std::string expectedValidity = "invalid"; + + verifyDatapoint (qDp, "Validity", &expectedValidity); + + iec61850->stop (); + + iec61850->setJsonConfig (protocol_config_3, exchanged_data, tls_config); + + iec61850->start(); + + Thread_sleep (1000); + + start = std::chrono::high_resolution_clock::now (); + timeout = std::chrono::seconds (5); + while (!iec61850->m_client->m_active_connection + || !iec61850->m_client->m_active_connection->m_connection + || IedConnection_getState ( + iec61850->m_client->m_active_connection->m_connection) + != IED_STATE_CONNECTED) + { + auto now = std::chrono::high_resolution_clock::now (); + if (now - start > timeout) + { + IedServer_stop (server); + IedServer_destroy (server); + IedModel_destroy (model); + FAIL () << "Connection not established within timeout"; + } + Thread_sleep (10); + } + + q = 0; + Quality_setValidity (&q, QUALITY_VALIDITY_INVALID); + + IedServer_updateQuality ( + server, + (DataAttribute*)IedModel_getModelNodeByObjectReference ( + model, "simpleIOGenericIO/GGIO1.SPCSO1.q"), + q); + + timeout = std::chrono::seconds (3); + start = std::chrono::high_resolution_clock::now (); + while (ingestCallbackCalled != 2) + { + auto now = std::chrono::high_resolution_clock::now (); + if (now - start > timeout) + { + IedServer_stop (server); + IedServer_destroy (server); + IedModel_destroy (model); + FAIL () << "Callback not called within timeout"; + } + Thread_sleep (10); + } + + ASSERT_EQ (storedReadings.size (), 2); + + iec61850->stop (); + + delete iec61850; + + for (auto reading : storedReadings) + { + delete reading; + } + storedReadings.clear (); + IedServer_stop (server); IedServer_destroy (server); IedModel_destroy (model); From 2484369982b28d6377c86cded2b16807ca2f6784 Mon Sep 17 00:00:00 2001 From: Jude Mingay Date: Mon, 5 Feb 2024 15:42:13 +0000 Subject: [PATCH 10/20] Added purge buf support Signed-off-by: Jude Mingay --- src/iec61850_client.cpp | 2 +- src/iec61850_client_connection.cpp | 41 ++++++++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/src/iec61850_client.cpp b/src/iec61850_client.cpp index 4881d66..d64f838 100644 --- a/src/iec61850_client.cpp +++ b/src/iec61850_client.cpp @@ -219,7 +219,7 @@ const std::map rootToStrMap IEC61850Client::IEC61850Client (IEC61850* iec61850, IEC61850ClientConfig* iec61850_client_config) - : m_config (iec61850_client_config), m_iec61850 (iec61850) + : m_config (iec61850_client_config), m_iec61850 (iec61850), firstTimeConnect(true) { } diff --git a/src/iec61850_client_connection.cpp b/src/iec61850_client_connection.cpp index 23beecf..a349d5d 100644 --- a/src/iec61850_client_connection.cpp +++ b/src/iec61850_client_connection.cpp @@ -327,6 +327,7 @@ configureRcb (const std::shared_ptr& rs, if (firstTimeConnect) { + Iec61850Utility::log_debug("First time connecting set PurgeBuf to true for %s", rs->rcbRef.c_str()); parametersMask |= RCB_ELEMENT_PURGE_BUF; ClientReportControlBlock_setPurgeBuf (rcb, true); } @@ -398,7 +399,7 @@ IEC61850ClientConnection::m_configRcb () if (error != IED_ERROR_OK) { Iec61850Utility::log_error ( - "Reading data set directory failed!\n"); + "Reading data set directory failed! %s", rs->datasetRef.c_str()); continue; } @@ -434,8 +435,12 @@ IEC61850ClientConnection::m_configRcb () ClientReportControlBlock_getRptId (rcb), reportCallbackFunction, static_cast (connDataSetPair)); + IedConnection_setRCBValues (m_connection, &error, rcb, parametersMask, - true); + true); + + rcb = IedConnection_getRCBValues (m_connection, &error, + rs->rcbRef.c_str (), nullptr); if (clientDataSet) ClientDataSet_destroy (clientDataSet); @@ -490,6 +495,11 @@ IEC61850ClientConnection::m_initialiseControlObjects () auto co = new ControlObjectStruct; co->client = ControlObjectClient_create (def->objRef.c_str (), m_connection); + if(!co->client){ + Iec61850Utility::log_warn ("Failed to create Control ObjectClient %s , %s ", + entry.first.c_str (), def->objRef.c_str ()); + continue; + } co->mode = ControlObjectClient_getControlModel (co->client); co->state = CONTROL_IDLE; co->label = entry.first; @@ -602,6 +612,33 @@ IEC61850ClientConnection::cleanUp () } } + for(const auto &rcb : m_config->getReportSubscriptions()){ + IedClientError error = IED_ERROR_OK; + if(m_connection && IedConnection_getState(m_connection) == IED_STATE_CONNECTED){ + + ClientReportControlBlock block = IedConnection_getRCBValues (m_connection, &error, + rcb.second->rcbRef.c_str (), nullptr); + + if(!block){ + Iec61850Utility::log_debug("RCB %s not found, continue", rcb.second->rcbRef.c_str()); + m_client->logIedClientError(error, "Get RCB in clean up"); + continue; + } + + ClientReportControlBlock_setRptEna(block,false); + + IedConnection_setRCBValues(m_connection,&error,block, RCB_ELEMENT_RPT_ENA | RCB_ELEMENT_DATSET, true); + + if(error!=IED_ERROR_OK){ + m_client->logIedClientError(error,"Disable RCB " + rcb.second->rcbRef); + } + else{ + Iec61850Utility::log_debug("Disabled RCB %s", rcb.second->rcbRef.c_str()); + } + ClientReportControlBlock_destroy(block); + } + } + if (!m_controlObjects.empty ()) { for (auto& co : m_controlObjects) From 3b7d89b8dcbef78d8154dc7d83ddaa928815460f Mon Sep 17 00:00:00 2001 From: Jude Mingay Date: Wed, 7 Feb 2024 12:20:21 +0000 Subject: [PATCH 11/20] Added functionality to send last report entryID after connection loss Signed-off-by: Jude Mingay --- include/iec61850.hpp | 3 +- include/iec61850_client_connection.hpp | 1 - src/iec61850_client.cpp | 5 ++++ src/iec61850_client_connection.cpp | 40 +++++++++++++++----------- 4 files changed, 31 insertions(+), 18 deletions(-) diff --git a/include/iec61850.hpp b/include/iec61850.hpp index 9f628c3..ef41b2b 100644 --- a/include/iec61850.hpp +++ b/include/iec61850.hpp @@ -146,7 +146,8 @@ class IEC61850Client bool terminated); bool firstTimeConnect = true; - + MmsValue* lastEntryId = nullptr; + private: std::shared_ptr > m_connections = nullptr; diff --git a/include/iec61850_client_connection.hpp b/include/iec61850_client_connection.hpp index 11fccc8..5c1a3a8 100644 --- a/include/iec61850_client_connection.hpp +++ b/include/iec61850_client_connection.hpp @@ -150,7 +150,6 @@ class IEC61850ClientConnection bool m_connecting = false; bool m_started = false; bool m_useTls = false; - MmsValue* lastEntryId = nullptr; TLSConfiguration m_tlsConfig = nullptr; diff --git a/src/iec61850_client.cpp b/src/iec61850_client.cpp index d64f838..9a96f4c 100644 --- a/src/iec61850_client.cpp +++ b/src/iec61850_client.cpp @@ -241,6 +241,11 @@ IEC61850Client::stop () delete m_monitoringThread; m_monitoringThread = nullptr; } + + if(lastEntryId){ + MmsValue_delete(lastEntryId); + lastEntryId = nullptr; + } } int diff --git a/src/iec61850_client_connection.cpp b/src/iec61850_client_connection.cpp index 3b3cdfd..8895344 100644 --- a/src/iec61850_client_connection.cpp +++ b/src/iec61850_client_connection.cpp @@ -271,14 +271,24 @@ IEC61850ClientConnection::reportCallbackFunction (void* parameter, MmsValue const* dataSetValues = ClientReport_getDataSetValues (report); - Iec61850Utility::log_debug ("received report for %s with rptId %s\n", + char buf[1024]; + if(ClientReport_getEntryId(report) != nullptr){ + if(con->m_client->lastEntryId !=nullptr){ + MmsValue_delete(con->m_client->lastEntryId); + } + con->m_client->lastEntryId = MmsValue_clone(ClientReport_getEntryId(report)); + if(con->m_client->lastEntryId!=nullptr){ + MmsValue_printToBuffer(con->m_client->lastEntryId,buf,1024); + } + } + + Iec61850Utility::log_debug ("received report for %s with rptId %s entryId %s", ClientReport_getRcbReference (report), - ClientReport_getRptId (report)); + ClientReport_getRptId (report), + con->m_client->lastEntryId ? buf : "NULL"); time_t unixTime = 0; - con->lastEntryId = ClientReport_getEntryId(report); - if (ClientReport_hasTimestamp (report)) { unixTime = ClientReport_getTimestamp (report) / 1000; @@ -324,17 +334,24 @@ configureRcb (const std::shared_ptr& rs, if (isBuffered) { + parametersMask |= RCB_ELEMENT_OPT_FLDS; + ClientReportControlBlock_setOptFlds(rcb,RPT_OPT_REASON_FOR_INCLUSION | RPT_OPT_ENTRY_ID | RPT_OPT_TIME_STAMP| RPT_OPT_DATA_SET); parametersMask |= RCB_ELEMENT_RESV_TMS; ClientReportControlBlock_setResvTms (rcb, 1000); if (firstTimeConnect) { - Iec61850Utility::log_debug("First time connecting set PurgeBuf to true for %s", rs->rcbRef.c_str()); parametersMask |= RCB_ELEMENT_PURGE_BUF; ClientReportControlBlock_setPurgeBuf (rcb, true); } else{ + char buf[1024]; + if(lastEntryId!=nullptr){ + MmsValue_printToBuffer(lastEntryId,buf,1024); + } + Iec61850Utility::log_debug("Reconnecting, send Entry ID %s for RCB %s", lastEntryId != nullptr ? buf : "NULL", rs->rcbRef.c_str()); ClientReportControlBlock_setEntryId(rcb,lastEntryId); + parametersMask |= RCB_ELEMENT_ENTRY_ID; } } @@ -376,8 +393,6 @@ configureRcb (const std::shared_ptr& rs, ClientReportControlBlock_setGI (rcb, rs->gi); } - - ClientReportControlBlock_setRptEna (rcb, true); parametersMask |= RCB_ELEMENT_RPT_ENA; @@ -430,7 +445,7 @@ IEC61850ClientConnection::m_configRcb () } uint32_t parametersMask - = configureRcb (rs, rcb, m_client->firstTimeConnect,lastEntryId); + = configureRcb (rs, rcb, m_client->firstTimeConnect,m_client->lastEntryId); auto connDataSetPair = new std::pair ( @@ -447,9 +462,6 @@ IEC61850ClientConnection::m_configRcb () IedConnection_setRCBValues (m_connection, &error, rcb, parametersMask, true); - rcb = IedConnection_getRCBValues (m_connection, &error, - rs->rcbRef.c_str (), nullptr); - if (clientDataSet) ClientDataSet_destroy (clientDataSet); @@ -635,7 +647,7 @@ IEC61850ClientConnection::cleanUp () ClientReportControlBlock_setRptEna(block,false); - IedConnection_setRCBValues(m_connection,&error,block, RCB_ELEMENT_RPT_ENA | RCB_ELEMENT_DATSET, true); + IedConnection_setRCBValues(m_connection,&error,block, RCB_ELEMENT_RPT_ENA, true); if(error!=IED_ERROR_OK){ m_client->logIedClientError(error,"Disable RCB " + rcb.second->rcbRef); @@ -671,10 +683,6 @@ IEC61850ClientConnection::cleanUp () m_controlObjects.clear (); } - if(lastEntryId){ - MmsValue_delete(lastEntryId); - lastEntryId = nullptr; - } if (!m_connControlPairs.empty ()) { From bb1d8e8399bb7d33b159051fcf4b629ae32229cc Mon Sep 17 00:00:00 2001 From: Jude Mingay Date: Wed, 7 Feb 2024 17:04:44 +0000 Subject: [PATCH 12/20] Removed warning message when using status only control objects Signed-off-by: Jude Mingay --- src/iec61850_client_connection.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/iec61850_client_connection.cpp b/src/iec61850_client_connection.cpp index 8895344..2e61ef8 100644 --- a/src/iec61850_client_connection.cpp +++ b/src/iec61850_client_connection.cpp @@ -505,7 +505,8 @@ IEC61850ClientConnection::m_initialiseControlObjects () continue; IedClientError err; MmsValue* temp = IedConnection_readObject ( - m_connection, &err, def->objRef.c_str (), IEC61850_FC_ST); + m_connection, &err, (def->objRef+".ctlModel").c_str(), IEC61850_FC_ST); + ControlModel model = (ControlModel) MmsValue_toInt32(temp); if (err != IED_ERROR_OK) { m_client->logIedClientError (err, "Initialise control object"); @@ -513,11 +514,13 @@ IEC61850ClientConnection::m_initialiseControlObjects () } MmsValue_delete (temp); auto co = new ControlObjectStruct; - co->client - = ControlObjectClient_create (def->objRef.c_str (), m_connection); + co->client = ControlObjectClient_create (def->objRef.c_str (), m_connection); if(!co->client){ - Iec61850Utility::log_warn ("Failed to create Control ObjectClient %s , %s ", + if(model != CONTROL_MODEL_STATUS_ONLY){ + Iec61850Utility::log_warn ("Failed to create Control ObjectClient %s , %s ", entry.first.c_str (), def->objRef.c_str ()); + } + delete co; continue; } co->mode = ControlObjectClient_getControlModel (co->client); From 0c121c9a6afff8183071c1b0521ed38027e97fd3 Mon Sep 17 00:00:00 2001 From: Jude Mingay Date: Thu, 8 Feb 2024 12:06:40 +0000 Subject: [PATCH 13/20] Added warning message when the buffer overflow bit is set after report synchronization with entryID Signed-off-by: Jude Mingay --- src/iec61850_client_connection.cpp | 6 ++++++ tests/test_iec61850_reporting.cpp | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/iec61850_client_connection.cpp b/src/iec61850_client_connection.cpp index 2e61ef8..23de8e8 100644 --- a/src/iec61850_client_connection.cpp +++ b/src/iec61850_client_connection.cpp @@ -287,6 +287,12 @@ IEC61850ClientConnection::reportCallbackFunction (void* parameter, ClientReport_getRptId (report), con->m_client->lastEntryId ? buf : "NULL"); + if(ClientReport_hasBufOvfl(report) && ClientReport_getBufOvfl(report)){ + Iec61850Utility::log_warn("Buffer overflow bit set for report with rptId %s and entryId %s", + ClientReport_getRptId (report), + con->m_client->lastEntryId ? buf : "NULL"); + } + time_t unixTime = 0; if (ClientReport_hasTimestamp (report)) diff --git a/tests/test_iec61850_reporting.cpp b/tests/test_iec61850_reporting.cpp index 00a71c4..177bb62 100644 --- a/tests/test_iec61850_reporting.cpp +++ b/tests/test_iec61850_reporting.cpp @@ -780,7 +780,7 @@ TEST_F (ReportingTest, ReportingSetpointCommand) delete params[0]; delete[] params; - timeout = std::chrono::seconds (3); + timeout = std::chrono::seconds (5); start = std::chrono::high_resolution_clock::now (); while (ingestCallbackCalled != 2) { From 6d3cb2b4924fd7233adb39488bd633554e81df0b Mon Sep 17 00:00:00 2001 From: Jude Mingay Date: Fri, 9 Feb 2024 18:27:56 +0000 Subject: [PATCH 14/20] Fixed model path in ControlTest.WriteOperations Signed-off-by: Jude Mingay --- tests/test_iec61850_client_control.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_iec61850_client_control.cpp b/tests/test_iec61850_client_control.cpp index f36519e..0e02bda 100644 --- a/tests/test_iec61850_client_control.cpp +++ b/tests/test_iec61850_client_control.cpp @@ -1069,7 +1069,7 @@ TEST_F(ControlTest, StepCommandDirectNormal) { TEST_F(ControlTest, WriteOperations) { iec61850->setJsonConfig(protocol_config, exchanged_data_3 , tls_config); - IedModel* model = ConfigFileParser_createModelFromConfigFileEx("../tests/data/schedulermodel.cfg"); + IedModel* model = ConfigFileParser_createModelFromConfigFileEx("../tests/data/iec61850fledgetest.cfg"); IedServer server = IedServer_create(model); IedServer_start(server,10002); From 72fca5901e9842d0a281e0a38dddd68e4c973941 Mon Sep 17 00:00:00 2001 From: Jude Mingay Date: Mon, 12 Feb 2024 12:40:21 +0000 Subject: [PATCH 15/20] Fixed OSI Selector parsing Signed-off-by: Jude Mingay --- src/iec61850_client_config.cpp | 77 ++++++++++---------- tests/test_iec61850_config.cpp | 126 ++++++++++++++++++++++++++++++++- 2 files changed, 164 insertions(+), 39 deletions(-) diff --git a/src/iec61850_client_config.cpp b/src/iec61850_client_config.cpp index d417135..15091a0 100644 --- a/src/iec61850_client_config.cpp +++ b/src/iec61850_client_config.cpp @@ -585,59 +585,62 @@ IEC61850ClientConfig::parseOsiTSelector (std::string& inputOsiSelector, } OsiSelectorSize -IEC61850ClientConfig::parseOsiSelector (std::string& inputOsiSelector, - uint8_t* selectorValue, - const uint8_t selectorSize) +IEC61850ClientConfig::parseOsiSelector(std::string& inputOsiSelector, uint8_t* selectorValue, const uint8_t selectorSize) { - char* tokenContext = nullptr; - const char* nextToken - = strtok_r (&inputOsiSelector[0], " ,.-", &tokenContext); - uint8_t count = 0; + uint8_t i = 0; - while (nullptr != nextToken) + if (inputOsiSelector.find(',') == std::string::npos) { - if (count >= selectorSize) + std::regex hexPattern("^([0-9a-fA-F]{2})+$"); + if(inputOsiSelector.substr(0, 2) == "0x") inputOsiSelector.erase(0, 2); + if(!std::regex_match(inputOsiSelector, hexPattern)) { - throw ConfigurationException ( - "bad format for 'OSI Selector' (too many bytes)"); + throw ConfigurationException("bad format for 'OSI Selector' (Expected hexadecimal string)"); } - int base = 10; - - if (0 == strncmp (nextToken, "0x", 2)) + if(inputOsiSelector.size()/2 > selectorSize) { - base = 16; + throw ConfigurationException("bad format for 'OSI Selector' (too many bytes)"); } - unsigned long ul = 0; + std::istringstream hex_chars_stream(inputOsiSelector); + uint8_t count = 0; - try + char hex_chars[2]; + + while (hex_chars_stream >> hex_chars[0] >> hex_chars[1]) { - ul = std::stoul (nextToken, nullptr, base); - } - catch (std::invalid_argument&) - { - throw ConfigurationException ( - "bad format for 'OSI Selector' (not a byte)"); - } - catch (std::out_of_range&) - { - throw ConfigurationException ( - "bad format for 'OSI Selector (exceed an int)'"); + char* endptr; + selectorValue[count] = static_cast(strtol(hex_chars, &endptr, 16)); + if (*endptr != 0) + { + throw ConfigurationException("bad format for 'OSI Selector' (not a byte)"); + } + count++; } + return count; + } + else + { + std::replace(inputOsiSelector.begin(), inputOsiSelector.end(), ',', ' '); + std::istringstream ss(inputOsiSelector); + std::string token; - if (ul > 255) + while(std::getline(ss, token, ' ') && i < selectorSize) { - throw ConfigurationException ( - "bad format for 'OSI Selector' (exceed a byte)"); - } + if(token.substr(0, 2) == "0x") token.erase(0, 2); + + std::regex hexPattern("^([0-9a-fA-F]{2})+$"); + if(!std::regex_match(token, hexPattern)) + { + throw ConfigurationException("bad format for 'OSI Selector' (Expected hexadecimal string)"); + } - selectorValue[count] = static_cast (ul); - count++; - nextToken = strtok_r (nullptr, " ,.-", &tokenContext); + selectorValue[i] = static_cast(std::stoul(token, nullptr, 16)); + i++; + } + return i; } - - return count; } void diff --git a/tests/test_iec61850_config.cpp b/tests/test_iec61850_config.cpp index 832ff6b..1f9e6a5 100644 --- a/tests/test_iec61850_config.cpp +++ b/tests/test_iec61850_config.cpp @@ -132,10 +132,10 @@ static string osi_protocol_config = QUOTE({ "remote_ap_title":"1,2,1200,15,3", "remote_ae_qualifier":1, "local_psel":"0x12,0x34,0x56,0x78", - "local_ssel":"0,1,2,3,4", + "local_ssel":"0x04,0x01,0x02,0x03,0x04", "local_tsel":"0x00,0x01,0x02", "remote_psel":"0x87,0x65,0x43,0x21", - "remote_ssel":"0,1", + "remote_ssel":"0x00,0x01", "remote_tsel":"0x00,0x01" } } @@ -1105,4 +1105,126 @@ TEST_F(ConfigTest, ProtocolConfigBuftmIntgpd) { config->importProtocolConfig(wrong_protocol_config_17); ASSERT_TRUE(config->m_protocolConfigComplete); +} + +TEST_F(ConfigTest, TestOSISelector) { + IEC61850ClientConfig* config = new IEC61850ClientConfig(); + + std::string testStr = "0x00,0x01,0x02,0x03"; + uint8_t selectorValue[10]; + + ASSERT_NO_THROW({ + uint8_t parsedBytes = config->parseOsiSelector(testStr, selectorValue, sizeof(selectorValue)); + ASSERT_EQ(parsedBytes, 4); + for(int i = 0; i < parsedBytes; i++) { + ASSERT_EQ(selectorValue[i], i); + } + }); + + testStr = "0x03"; + + ASSERT_NO_THROW({ + uint8_t parsedBytes = config->parseOsiSelector(testStr, selectorValue, sizeof(selectorValue)); + ASSERT_EQ(parsedBytes, 1); + ASSERT_EQ(selectorValue[0], 3); + }); + + testStr = "0x05,0x02"; + + ASSERT_NO_THROW({ + uint8_t parsedBytes = config->parseOsiSelector(testStr, selectorValue, sizeof(selectorValue)); + ASSERT_EQ(parsedBytes, 2); + ASSERT_EQ(selectorValue[0], 5); + ASSERT_EQ(selectorValue[1], 2); + }); + + testStr = "f143125c"; + + ASSERT_NO_THROW({ + uint8_t parsedBytes = config->parseOsiSelector(testStr, selectorValue, sizeof(selectorValue)); + ASSERT_EQ(parsedBytes, 4); + ASSERT_EQ(selectorValue[0],241); + ASSERT_EQ(selectorValue[1],67); + ASSERT_EQ(selectorValue[2],18); + ASSERT_EQ(selectorValue[3],92); + }); + + testStr = "00000001"; + + ASSERT_NO_THROW({ + uint8_t parsedBytes = config->parseOsiSelector(testStr, selectorValue, sizeof(selectorValue)); + ASSERT_EQ(parsedBytes, 4); + ASSERT_EQ(selectorValue[0],0); + ASSERT_EQ(selectorValue[1],0); + ASSERT_EQ(selectorValue[2],0); + ASSERT_EQ(selectorValue[3],1); + }); + + testStr = "123"; + ASSERT_THROW(config->parseOsiSelector(testStr, selectorValue, sizeof(selectorValue)), ConfigurationException); + + testStr = "0x00,0x01,0x02,0x0Z"; + ASSERT_THROW(config->parseOsiSelector(testStr, selectorValue, sizeof(selectorValue)), ConfigurationException); + + testStr = "0a0b0c0d"; + + ASSERT_NO_THROW({ + uint8_t parsedBytes = config->parseOsiSelector(testStr, selectorValue, sizeof(selectorValue)); + ASSERT_EQ(parsedBytes, 4); + ASSERT_EQ(selectorValue[0], 10); + ASSERT_EQ(selectorValue[1], 11); + ASSERT_EQ(selectorValue[2], 12); + ASSERT_EQ(selectorValue[3], 13); + }); + + testStr = "ff"; + + ASSERT_NO_THROW({ + uint8_t parsedBytes = config->parseOsiSelector(testStr, selectorValue, sizeof(selectorValue)); + ASSERT_EQ(parsedBytes, 1); + ASSERT_EQ(selectorValue[0], 255); + }); + + testStr = "AaBfC112"; + + ASSERT_NO_THROW({ + uint8_t parsedBytes = config->parseOsiSelector(testStr, selectorValue, sizeof(selectorValue)); + ASSERT_EQ(parsedBytes, 4); + ASSERT_EQ(selectorValue[0], 170); + ASSERT_EQ(selectorValue[1], 191); + ASSERT_EQ(selectorValue[2], 193); + ASSERT_EQ(selectorValue[3], 18); + }); + + testStr = "01A609C605CC"; + + ASSERT_THROW({ + config->parseOsiSelector(testStr, selectorValue, 4 ); + }, ConfigurationException); + + testStr = "123G56"; + + ASSERT_THROW({ + config->parseOsiSelector(testStr, selectorValue, sizeof(selectorValue)); + }, ConfigurationException); + +} + +TEST_F(ConfigTest, TestCommaSeparatedBytes) { + IEC61850ClientConfig* config = new IEC61850ClientConfig(); + + std::string testStr = "0x00,0x01,0x02,0x03"; + uint8_t selectorValue[4]; + + ASSERT_NO_THROW({ + uint8_t parsedBytes = config->parseOsiSelector(testStr, selectorValue, sizeof(selectorValue)); + ASSERT_EQ(parsedBytes, 4); + for(int i = 0; i < parsedBytes; i++) { + ASSERT_EQ(selectorValue[i], i); + } + }); + + + testStr = "0x00,0x01,0xG2,0x03"; + ASSERT_THROW(config->parseOsiSelector(testStr, selectorValue, sizeof(selectorValue)), ConfigurationException); } \ No newline at end of file From 001947181774b2d192f860ceed3f4828caed0e69 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Mon, 12 Feb 2024 12:46:56 +0000 Subject: [PATCH 16/20] - changed configuration values of trgops to be identical to SCL value Signed-off-by: Michael Zillgith --- src/iec61850_client_config.cpp | 11 +++++------ tests/test_iec61850_config.cpp | 20 ++++++++++---------- tests/test_iec61850_reporting.cpp | 10 +++++----- 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/src/iec61850_client_config.cpp b/src/iec61850_client_config.cpp index 15091a0..cf7dc2f 100644 --- a/src/iec61850_client_config.cpp +++ b/src/iec61850_client_config.cpp @@ -34,12 +34,11 @@ using namespace rapidjson; static const std::unordered_map trgOptions - = { { "data_changed", TRG_OPT_DATA_CHANGED }, - { "quality_changed", TRG_OPT_QUALITY_CHANGED }, - { "data_update", TRG_OPT_DATA_UPDATE }, - { "integrity", TRG_OPT_INTEGRITY }, - { "gi", TRG_OPT_GI }, - { "transient", TRG_OPT_TRANSIENT } }; + = { { "dchg", TRG_OPT_DATA_CHANGED }, + { "qchg", TRG_OPT_QUALITY_CHANGED }, + { "dupd", TRG_OPT_DATA_UPDATE }, + { "period", TRG_OPT_INTEGRITY }, + { "gi", TRG_OPT_GI } }; static const std::unordered_map cdcMap = { { "SpsTyp", SPS }, { "DpsTyp", DPS }, { "BscTyp", BSC }, diff --git a/tests/test_iec61850_config.cpp b/tests/test_iec61850_config.cpp index 1f9e6a5..bbfce7a 100644 --- a/tests/test_iec61850_config.cpp +++ b/tests/test_iec61850_config.cpp @@ -293,8 +293,8 @@ static string wrong_protocol_config_13 = QUOTE({ "rcb_ref" : "simpleIOGenericIO/LLN0.RP.EventsIndexed", "dataset_ref" : "simpleIOGenericIO/LLN0.Events2", "trgops" : [ - "data_changed", - "quality_changed", + "dchg", + "qchg", "gi" ], "gi" : false @@ -325,8 +325,8 @@ static string wrong_protocol_config_14 = QUOTE({ "rcb_ref" : "simpleIOGenericIO/LLN0.RP.EventsIndexed", "dataset_ref" : "simpleIOGenericIO/LLN0.Events2", "trgops" : [ - "data_changed", - "quality_changed", + "dchg", + "qchg", "gi" ], "gi" : false @@ -356,8 +356,8 @@ static string wrong_protocol_config_15 = QUOTE({ { "rcb_ref" : "simpleIOGenericIO/LLN0.RP.EventsIndexed", "trgops" : [ - "data_changed", - "quality_changed", + "dchg", + "qchg", "gi" ], "gi" : false @@ -448,8 +448,8 @@ static string wrong_protocol_config_17 = QUOTE({ "rcb_ref" : "simpleIOGenericIO/LLN0.RP.EventsRCB", "dataset_ref" : "simpleIOGenericIO/LLN0.Mags", "trgops" : [ - "data_changed", - "quality_changed" + "dchg", + "qchg" ], "buftm": 1, "intgpd": 2 @@ -458,8 +458,8 @@ static string wrong_protocol_config_17 = QUOTE({ "rcb_ref" : "simpleIOGenericIO/LLN0.RP.EventsIndexed", "dataset_ref" : "simpleIOGenericIO/LLN0.Events2", "trgops" : [ - "data_changed", - "quality_changed" + "dchg", + "qchg" ], "buftm": 1, "intgpd": 2 diff --git a/tests/test_iec61850_reporting.cpp b/tests/test_iec61850_reporting.cpp index 177bb62..6150dcf 100644 --- a/tests/test_iec61850_reporting.cpp +++ b/tests/test_iec61850_reporting.cpp @@ -51,13 +51,13 @@ static string protocol_config = QUOTE ({ { "rcb_ref" : "simpleIOGenericIO/LLN0.RP.EventsRCB01", "dataset_ref" : "simpleIOGenericIO/LLN0.Mags", - "trgops" : [ "data_changed", "quality_changed", "gi" ], + "trgops" : [ "dchg", "qchg", "gi" ], "gi" : false }, { "rcb_ref" : "simpleIOGenericIO/LLN0.RP.EventsIndexed01", "dataset_ref" : "simpleIOGenericIO/LLN0.Events2", - "trgops" : [ "data_changed", "quality_changed", "gi" ], + "trgops" : [ "dchg", "qchg", "gi" ], "gi" : false } ] @@ -106,13 +106,13 @@ static string protocol_config_2 = QUOTE ({ { "rcb_ref" : "simpleIOGenericIO/LLN0.RP.EventsRCB01", "dataset_ref" : "simpleIOGenericIO/LLN0.Mags", - "trgops" : [ "data_changed", "quality_changed", "gi" ], + "trgops" : [ "dchg", "qchg", "gi" ], "gi" : true }, { "rcb_ref" : "simpleIOGenericIO/LLN0.RP.EventsIndexed01", "dataset_ref" : "simpleIOGenericIO/LLN0.Events2", - "trgops" : [ "data_changed", "quality_changed", "gi" ], + "trgops" : [ "dchg", "qchg", "gi" ], "gi" : true } ] @@ -148,7 +148,7 @@ static string protocol_config_3 = QUOTE ({ { "rcb_ref" : "simpleIOGenericIO/LLN0.RP.EventsRCB01", "dataset_ref" : "simpleIOGenericIO/LLN0.Mags", - "trgops" : [ "data_changed", "quality_changed", "gi" ], + "trgops" : [ "dchg", "qchg", "gi" ], "gi" : false } ] From 164f05902949d555935c5cb1331df461da18a19e Mon Sep 17 00:00:00 2001 From: Jude Mingay Date: Wed, 14 Feb 2024 17:23:41 +0000 Subject: [PATCH 17/20] Improved logger messages and now report subscriptions with no dataset ref are skipped Signed-off-by: Jude Mingay --- src/iec61850_client.cpp | 4 ++-- src/iec61850_client_config.cpp | 6 ++++-- src/iec61850_client_connection.cpp | 2 +- tests/test_iec61850_reporting.cpp | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/iec61850_client.cpp b/src/iec61850_client.cpp index 9a96f4c..7607f6c 100644 --- a/src/iec61850_client.cpp +++ b/src/iec61850_client.cpp @@ -800,7 +800,7 @@ IEC61850Client::m_handleMonitoringData ( Quality quality = extractQuality (mmsvalue, def->spec, attribute); uint64_t ts; - + if (!mmsVal) ts = extractTimestamp (mmsvalue, def->spec, attribute); else @@ -825,7 +825,7 @@ IEC61850Client::extractQuality (MmsValue* mmsvalue, = MmsValue_getSubElement (mmsvalue, varSpec, (char*)"q"); return (!qualityMms && attribute != "q") ? QUALITY_VALIDITY_GOOD - : Quality_fromMmsValue (qualityMms); + : Quality_fromMmsValue ( attribute == "q" ? mmsvalue : qualityMms); } uint64_t diff --git a/src/iec61850_client_config.cpp b/src/iec61850_client_config.cpp index 15091a0..edf80c2 100644 --- a/src/iec61850_client_config.cpp +++ b/src/iec61850_client_config.cpp @@ -321,6 +321,7 @@ IEC61850ClientConfig::importProtocolConfig (const std::string& protocolConfig) } else { + Iec61850Utility::log_error("Report subscription has no RCB ref , skipping"); continue; } @@ -331,7 +332,8 @@ IEC61850ClientConfig::importProtocolConfig (const std::string& protocolConfig) } else { - report->datasetRef = ""; + Iec61850Utility::log_error("Report subscription %s has no Dataset ref , skipping", report->rcbRef.c_str()); + continue; } if (reportVal.HasMember (JSON_TRGOPS) @@ -377,7 +379,7 @@ IEC61850ClientConfig::importProtocolConfig (const std::string& protocolConfig) } else { - Iec61850Utility::log_error ( + Iec61850Utility::log_warn ( "Report %s has no gi value, defaulting to disabled", report->rcbRef.c_str ()); report->gi = false; diff --git a/src/iec61850_client_connection.cpp b/src/iec61850_client_connection.cpp index 23de8e8..192b566 100644 --- a/src/iec61850_client_connection.cpp +++ b/src/iec61850_client_connection.cpp @@ -446,7 +446,7 @@ IEC61850ClientConnection::m_configRcb () if (error != IED_ERROR_OK) { - Iec61850Utility::log_error ("GetRCBValues service error!\n"); + m_client->logIedClientError(error,"GetRCBValues " + rs->rcbRef); continue; } diff --git a/tests/test_iec61850_reporting.cpp b/tests/test_iec61850_reporting.cpp index 177bb62..5b3349f 100644 --- a/tests/test_iec61850_reporting.cpp +++ b/tests/test_iec61850_reporting.cpp @@ -84,7 +84,7 @@ static string protocol_config_2 = QUOTE ({ "simpleIOGenericIO/GGIO1.AnIn2[MX]", "simpleIOGenericIO/GGIO1.AnIn3[MX]", "simpleIOGenericIO/GGIO1.AnIn4[MX]", - "simpleIOGenericIO/GGIO1.SPCSO1[ST]", + "simpleIOGenericIO/GGIO1.SPCSO1.stVal[ST]", "simpleIOGenericIO/GGIO1.SPCSO2[ST]", "simpleIOGenericIO/GGIO1.SPCSO3[ST]", "simpleIOGenericIO/GGIO1.SPCSO4[ST]" From 68e6c01cf8aa2fe3f14e2a86e6f13202a973a784 Mon Sep 17 00:00:00 2001 From: Jude Mingay Date: Thu, 15 Feb 2024 16:55:05 +0000 Subject: [PATCH 18/20] Added support for reporting individual attributes Signed-off-by: Jude Mingay --- include/iec61850.hpp | 2 +- include/iec61850_client_config.hpp | 7 + src/iec61850_client.cpp | 54 ++++++-- src/iec61850_client_config.cpp | 9 +- tests/test_iec61850_reporting.cpp | 202 ++++++++++++++++++++++++++++- 5 files changed, 262 insertions(+), 12 deletions(-) diff --git a/include/iec61850.hpp b/include/iec61850.hpp index ef41b2b..b94145a 100644 --- a/include/iec61850.hpp +++ b/include/iec61850.hpp @@ -180,7 +180,7 @@ class IEC61850Client template Datapoint* m_createDatapoint (const std::string& label, const std::string& objRef, T value, - Quality quality, uint64_t timestampMs); + Quality quality, uint64_t timestampMs, bool hasValue); static int getRootFromCDC (const CDCTYPE cdc); void addQualityDp (Datapoint* cdcDp, Quality quality) const; diff --git a/include/iec61850_client_config.hpp b/include/iec61850_client_config.hpp index 71dbbc3..25cbb95 100644 --- a/include/iec61850_client_config.hpp +++ b/include/iec61850_client_config.hpp @@ -28,6 +28,7 @@ FRIEND_TEST (ReportingTest, ReportingSetpointCommand); \ FRIEND_TEST (ReportingTest, ReconfigureDynamicDataset); \ FRIEND_TEST (ReportingTest, ReportingChangeValueMultipleTimes); \ + FRIEND_TEST (ReportingTest, ReportingIndividualAttributes); \ FRIEND_TEST (SpontDataTest, Polling); \ FRIEND_TEST (SpontDataTest, PollingAllCDC); \ FRIEND_TEST (ControlTest, AnalogueCommandDirectNormal); \ @@ -116,6 +117,12 @@ struct DataExchangeDefinition std::string label; std::string id; MmsVariableSpecification* spec; + bool hasIntValue = true; + union{ + int intVal; + float floatVal; + } lastValue; + bool valueSet = false; }; struct ReportSubscription diff --git a/src/iec61850_client.cpp b/src/iec61850_client.cpp index 7607f6c..981beb8 100644 --- a/src/iec61850_client.cpp +++ b/src/iec61850_client.cpp @@ -207,7 +207,7 @@ const std::map cdcToStrMap = { { SPS, "SpsTyp" }, { DPS, "DpsTyp" }, { BSC, "BscTyp" }, { MV, "MvTyp" }, { SPC, "SpcTyp" }, { DPC, "DpcTyp" }, { APC, "ApcTyp" }, { INC, "IncTyp" }, { INS, "InsTyp" }, - { ENS, "EnsTyp" }, { SPG, "SpgTyp" }, { ASG, "AsgType" }, + { ENS, "EnsTyp" }, { SPG, "SpgTyp" }, { ASG, "AsgTyp" }, { ING, "IngTyp" } }; const std::map rootMap = { { SPS, GTIS }, { DPS, GTIS }, { BSC, GTIC }, { INS, GTIS }, @@ -806,6 +806,19 @@ IEC61850Client::m_handleMonitoringData ( else ts = timestamp; + if(attribute == "q"){ + if(!def->valueSet){ + Iec61850Utility::log_debug("Value for %s not yet set, sending only quality", objRef.c_str()); + datapoints.push_back (m_createDatapoint (label, objRef, 0, quality, timestamp, false)); + cleanUpMmsValue (mmsVal, mmsvalue); + return; + } + else{ + datapoints.push_back (m_createDatapoint (label, objRef, def->hasIntValue ? def->lastValue.intVal : def->lastValue.floatVal, quality, timestamp, true)); + cleanUpMmsValue (mmsVal, mmsvalue); + return; + } + } if (!processDatapoint (type, datapoints, label, objRef, mmsvalue, def->spec, quality, ts, attribute)) { @@ -846,7 +859,7 @@ IEC61850Client::processDatapoint ( const std::string& label, const std::string& objRef, MmsValue* mmsvalue, MmsVariableSpecification* varSpec, Quality quality, uint64_t timestamp, const std::string& attribute) -{ +{ switch (type) { case SPC: @@ -907,8 +920,12 @@ IEC61850Client::processBooleanType ( } } bool value = MmsValue_getBoolean (element); + auto def = m_config->getExchangeDefinitionByObjRef (objRef); + def->lastValue.intVal = (long)value; + def->valueSet = true; + datapoints.push_back ( - m_createDatapoint (label, objRef, (long)value, quality, timestamp)); + m_createDatapoint (label, objRef, (long)value, quality, timestamp, true)); return true; } @@ -954,8 +971,12 @@ IEC61850Client::processBSCType (std::vector& datapoints, long value = MmsValue_toInt32 (posVal); bool transIndVal = MmsValue_getBoolean (transInd); long combinedValue = (value << 1) | (long)transIndVal; + auto def = m_config->getExchangeDefinitionByObjRef (objRef); + def->lastValue.intVal = (long)combinedValue; + def->valueSet = true; + datapoints.push_back ( - m_createDatapoint (label, objRef, combinedValue, quality, timestamp)); + m_createDatapoint (label, objRef, combinedValue, quality, timestamp, true)); return true; } @@ -982,6 +1003,7 @@ IEC61850Client::processAnalogType ( } } + auto def = m_config->getExchangeDefinitionByObjRef (objRef); varSpec = MmsVariableSpecification_getChildSpecificationByName ( varSpec, elementName, nullptr); MmsValue* f = MmsValue_getSubElement (element, varSpec, (char*)"f"); @@ -989,8 +1011,11 @@ IEC61850Client::processAnalogType ( if (f) { double value = MmsValue_toFloat (f); + def->lastValue.floatVal = value; + def->valueSet = true; + datapoints.push_back ( - m_createDatapoint (label, objRef, value, quality, timestamp)); + m_createDatapoint (label, objRef, value, quality, timestamp, true)); return true; } @@ -998,8 +1023,12 @@ IEC61850Client::processAnalogType ( if (i) { long value = MmsValue_toInt32 (i); + def->hasIntValue = true; + def->lastValue.intVal = value; + def->valueSet = true; + datapoints.push_back ( - m_createDatapoint (label, objRef, value, quality, timestamp)); + m_createDatapoint (label, objRef, value, quality, timestamp, true)); return true; } @@ -1030,8 +1059,13 @@ IEC61850Client::processIntegerType ( } } long value = MmsValue_toInt32 (element); + auto def = m_config->getExchangeDefinitionByObjRef (objRef); + + def->lastValue.intVal = (long) value; + def->valueSet = true; + datapoints.push_back ( - m_createDatapoint (label, objRef, value, quality, timestamp)); + m_createDatapoint (label, objRef, value, quality, timestamp,true)); return true; } @@ -1039,7 +1073,7 @@ template Datapoint* IEC61850Client::m_createDatapoint (const std::string& label, const std::string& objRef, T value, - Quality quality, uint64_t timestamp) + Quality quality, uint64_t timestamp, bool hasValue) { auto def = m_config->getExchangeDefinitionByLabel (label); @@ -1052,7 +1086,9 @@ IEC61850Client::m_createDatapoint (const std::string& label, addElementWithValue (rootDp, "Identifier", (std::string)def->label); Datapoint* cdcDp = addElement (rootDp, cdcToStrMap.at (def->cdcType)); - addValueDp (cdcDp, def->cdcType, value); + if(hasValue){ + addValueDp (cdcDp, def->cdcType, value); + } addQualityDp (cdcDp, quality); addTimestampDp (cdcDp, timestamp); diff --git a/src/iec61850_client_config.cpp b/src/iec61850_client_config.cpp index 945f2a1..e72be2c 100644 --- a/src/iec61850_client_config.cpp +++ b/src/iec61850_client_config.cpp @@ -282,8 +282,11 @@ IEC61850ClientConfig::importProtocolConfig (const std::string& protocolConfig) const std::shared_ptr def = getExchangeDefinitionByObjRef (extractedObjRef); - if (def) + if (def){ m_polledDatapoints.erase (extractedObjRef); + Iec61850Utility::log_debug ( + "%s won't be polled", extractedObjRef.c_str()); + } } } } @@ -779,6 +782,10 @@ IEC61850ClientConfig::importExchangeConfig (const std::string& exchangeConfig) def->label = label; def->id = pivot_id; + if(def->cdcType == MV || def->cdcType == APC || def->cdcType == ASG){ + def->hasIntValue = false; + } + m_exchangeDefinitions.insert ({ label, def }); m_exchangeDefinitionsPivotId.insert ({ pivot_id, def }); m_exchangeDefinitionsObjRef.insert ({ objRef, def }); diff --git a/tests/test_iec61850_reporting.cpp b/tests/test_iec61850_reporting.cpp index 0256793..5f33d9b 100644 --- a/tests/test_iec61850_reporting.cpp +++ b/tests/test_iec61850_reporting.cpp @@ -85,7 +85,7 @@ static string protocol_config_2 = QUOTE ({ "simpleIOGenericIO/GGIO1.AnIn3[MX]", "simpleIOGenericIO/GGIO1.AnIn4[MX]", "simpleIOGenericIO/GGIO1.SPCSO1.stVal[ST]", - "simpleIOGenericIO/GGIO1.SPCSO2[ST]", + "simpleIOGenericIO/GGIO1.SPCSO2.q[ST]", "simpleIOGenericIO/GGIO1.SPCSO3[ST]", "simpleIOGenericIO/GGIO1.SPCSO4[ST]" ], @@ -156,6 +156,39 @@ static string protocol_config_3 = QUOTE ({ } }); +static string protocol_config_4 = QUOTE ({ + "protocol_stack" : { + "name" : "iec61850client", + "version" : "0.0.1", + "transport_layer" : { + "ied_name" : "IED1", + "connections" : [ { "ip_addr" : "127.0.0.1", "port" : 10002 } ], + "tls" : false + }, + "application_layer" : { + "polling_interval" : 10, + "datasets" : [ + { + "dataset_ref" : "simpleIOGenericIO/LLN0.Mags", + "entries" : [ + "simpleIOGenericIO/GGIO1.SPCSO1.stVal[ST]", + "simpleIOGenericIO/GGIO1.SPCSO1.q[ST]" + ], + "dynamic" : true + } + ], + "report_subscriptions" : [ + { + "rcb_ref" : "simpleIOGenericIO/LLN0.RP.EventsRCB01", + "dataset_ref" : "simpleIOGenericIO/LLN0.Mags", + "trgops" : [ "dchg", "qchg"], + "gi" : false + } + ] + } + } +}); + // PLUGIN DEFAULT EXCHANGED DATA CONF static string exchanged_data = QUOTE ({ @@ -237,6 +270,22 @@ static string exchanged_data = QUOTE ({ } }); +static string exchanged_data_2 = QUOTE ({ + "exchanged_data" : { + "datapoints" : [ + { + "pivot_id" : "TS1", + "label" : "TS1", + "protocols" : [ { + "name" : "iec61850", + "objref" : "simpleIOGenericIO/GGIO1.SPCSO1", + "cdc" : "SpcTyp" + } ] + } + ] + } +}); + // PLUGIN DEFAULT TLS CONF static string tls_config = QUOTE ({ "tls_conf" : { @@ -1194,4 +1243,155 @@ TEST_F (ReportingTest, ReconfigureDynamicDataset) IedServer_stop (server); IedServer_destroy (server); IedModel_destroy (model); +} + +TEST_F (ReportingTest, ReportingIndividualAttributes) +{ + iec61850->setJsonConfig (protocol_config_4, exchanged_data_2, tls_config); + + IedModel* model = ConfigFileParser_createModelFromConfigFileEx ( + "../tests/data/simpleIO_direct_control.cfg"); + + IedServer server = IedServer_create (model); + + IedServer_start (server, 10002); + iec61850->start (); + + Thread_sleep (1000); + + auto start = std::chrono::high_resolution_clock::now (); + auto timeout = std::chrono::seconds (5); + while (!iec61850->m_client->m_active_connection + || !iec61850->m_client->m_active_connection->m_connection + || IedConnection_getState ( + iec61850->m_client->m_active_connection->m_connection) + != IED_STATE_CONNECTED) + { + auto now = std::chrono::high_resolution_clock::now (); + if (now - start > timeout) + { + IedServer_stop (server); + IedServer_destroy (server); + IedModel_destroy (model); + FAIL () << "Connection not established within timeout"; + } + Thread_sleep (10); + } + + Quality q = 0; + Quality_setValidity (&q, QUALITY_VALIDITY_INVALID); + + IedServer_updateQuality ( + server, + (DataAttribute*)IedModel_getModelNodeByObjectReference ( + model, "simpleIOGenericIO/GGIO1.SPCSO1.q"), + q); + + timeout = std::chrono::seconds (3); + start = std::chrono::high_resolution_clock::now (); + while (ingestCallbackCalled != 1) + { + auto now = std::chrono::high_resolution_clock::now (); + if (now - start > timeout) + { + IedServer_stop (server); + IedServer_destroy (server); + IedModel_destroy (model); + FAIL () << "Callback not called within timeout"; + } + Thread_sleep (10); + } + + ASSERT_FALSE (storedReadings.empty ()); + ASSERT_EQ (storedReadings.size (), 1); + Datapoint* commandResponse = storedReadings[0]->getReadingData ()[0]; + verifyDatapoint (commandResponse, "GTIC"); + Datapoint* gtic = getChild (*commandResponse, "GTIC"); + + verifyDatapoint (gtic, "SpcTyp"); + Datapoint* Spc = getChild (*gtic, "SpcTyp"); + + verifyDatapoint (Spc, "q"); + ASSERT_EQ(getChild(*Spc,"stVal"),nullptr); + + IedServer_updateBooleanAttributeValue ( + server, + (DataAttribute*)IedModel_getModelNodeByObjectReference ( + model, "simpleIOGenericIO/GGIO1.SPCSO1.stVal"), + true); + + timeout = std::chrono::seconds (3); + start = std::chrono::high_resolution_clock::now (); + while (ingestCallbackCalled != 2) + { + auto now = std::chrono::high_resolution_clock::now (); + if (now - start > timeout) + { + IedServer_stop (server); + IedServer_destroy (server); + IedModel_destroy (model); + FAIL () << "Callback not called within timeout"; + } + Thread_sleep (10); + } + + ASSERT_FALSE (storedReadings.empty ()); + ASSERT_EQ (storedReadings.size (), 2); + commandResponse = storedReadings[1]->getReadingData ()[0]; + verifyDatapoint (commandResponse, "GTIC"); + gtic = getChild (*commandResponse, "GTIC"); + + verifyDatapoint (gtic, "SpcTyp"); + Spc = getChild (*gtic, "SpcTyp"); + + int expectedIntVal = 1; + verifyDatapoint (Spc, "stVal",&expectedIntVal); + + q = 0; + Quality_setValidity (&q, QUALITY_VALIDITY_GOOD); + + IedServer_updateQuality ( + server, + (DataAttribute*)IedModel_getModelNodeByObjectReference ( + model, "simpleIOGenericIO/GGIO1.SPCSO1.q"), + q); + + timeout = std::chrono::seconds (3); + start = std::chrono::high_resolution_clock::now (); + while (ingestCallbackCalled != 3) + { + auto now = std::chrono::high_resolution_clock::now (); + if (now - start > timeout) + { + IedServer_stop (server); + IedServer_destroy (server); + IedModel_destroy (model); + FAIL () << "Callback not called within timeout"; + } + Thread_sleep (10); + } + + ASSERT_FALSE (storedReadings.empty ()); + ASSERT_EQ (storedReadings.size (), 3); + commandResponse = storedReadings[2]->getReadingData ()[0]; + verifyDatapoint (commandResponse, "GTIC"); + gtic = getChild (*commandResponse, "GTIC"); + + verifyDatapoint (gtic, "SpcTyp"); + Spc = getChild (*gtic, "SpcTyp"); + + verifyDatapoint (Spc, "stVal",&expectedIntVal); + + iec61850->stop (); + delete iec61850; + + for (auto reading : storedReadings) + { + delete reading; + } + storedReadings.clear (); + + IedServer_stop (server); + IedServer_destroy (server); + IedModel_destroy (model); } \ No newline at end of file From 0a39697f952ff596b02a6f3d07288d43ad114964 Mon Sep 17 00:00:00 2001 From: Jude Mingay Date: Mon, 19 Feb 2024 17:12:24 +0000 Subject: [PATCH 19/20] Changed Control object Pivot roots to GTIS/GTIM in monitoring direction Signed-off-by: Jude Mingay --- src/iec61850_client.cpp | 7 ++++--- tests/test_iec61850_reporting.cpp | 20 ++++++++++---------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/iec61850_client.cpp b/src/iec61850_client.cpp index 981beb8..6909738 100644 --- a/src/iec61850_client.cpp +++ b/src/iec61850_client.cpp @@ -210,9 +210,10 @@ const std::map cdcToStrMap { ENS, "EnsTyp" }, { SPG, "SpgTyp" }, { ASG, "AsgTyp" }, { ING, "IngTyp" } }; const std::map rootMap - = { { SPS, GTIS }, { DPS, GTIS }, { BSC, GTIC }, { INS, GTIS }, - { ENS, GTIS }, { MV, GTIM }, { SPC, GTIC }, { DPC, GTIC }, - { APC, GTIC }, { INC, GTIC } }; + = { { SPS, GTIS }, { DPS, GTIS }, { BSC, GTIS }, { INS, GTIS }, + { ENS, GTIS }, { MV, GTIM }, { SPC, GTIS }, { DPC, GTIS }, + { APC, GTIM }, { INC, GTIS }, { ASG, GTIM }, { SPG, GTIS }, + { ING, GTIS} }; const std::map rootToStrMap = { { GTIM, "GTIM" }, { GTIS, "GTIS" }, { GTIC, "GTIC" } }; diff --git a/tests/test_iec61850_reporting.cpp b/tests/test_iec61850_reporting.cpp index 5f33d9b..f272b2e 100644 --- a/tests/test_iec61850_reporting.cpp +++ b/tests/test_iec61850_reporting.cpp @@ -747,8 +747,8 @@ TEST_F (ReportingTest, ReportingGI) ASSERT_FALSE (storedReadings.empty ()); ASSERT_EQ (storedReadings.size (), 12); Datapoint* commandResponse = storedReadings[0]->getReadingData ()[0]; - verifyDatapoint (commandResponse, "GTIC"); - Datapoint* gtim = getChild (*commandResponse, "GTIC"); + verifyDatapoint (commandResponse, "GTIS"); + Datapoint* gtim = getChild (*commandResponse, "GTIS"); verifyDatapoint (gtim, "SpcTyp"); Datapoint* SPC = getChild (*gtim, "SpcTyp"); @@ -847,8 +847,8 @@ TEST_F (ReportingTest, ReportingSetpointCommand) ASSERT_FALSE (storedReadings.empty ()); ASSERT_EQ (storedReadings.size (), 2); Datapoint* commandResponse = storedReadings[1]->getReadingData ()[0]; - verifyDatapoint (commandResponse, "GTIC"); - Datapoint* gtim = getChild (*commandResponse, "GTIC"); + verifyDatapoint (commandResponse, "GTIS"); + Datapoint* gtim = getChild (*commandResponse, "GTIS"); verifyDatapoint (gtim, "SpcTyp"); Datapoint* SPC = getChild (*gtim, "SpcTyp"); @@ -1305,8 +1305,8 @@ TEST_F (ReportingTest, ReportingIndividualAttributes) ASSERT_FALSE (storedReadings.empty ()); ASSERT_EQ (storedReadings.size (), 1); Datapoint* commandResponse = storedReadings[0]->getReadingData ()[0]; - verifyDatapoint (commandResponse, "GTIC"); - Datapoint* gtic = getChild (*commandResponse, "GTIC"); + verifyDatapoint (commandResponse, "GTIS"); + Datapoint* gtic = getChild (*commandResponse, "GTIS"); verifyDatapoint (gtic, "SpcTyp"); Datapoint* Spc = getChild (*gtic, "SpcTyp"); @@ -1338,8 +1338,8 @@ TEST_F (ReportingTest, ReportingIndividualAttributes) ASSERT_FALSE (storedReadings.empty ()); ASSERT_EQ (storedReadings.size (), 2); commandResponse = storedReadings[1]->getReadingData ()[0]; - verifyDatapoint (commandResponse, "GTIC"); - gtic = getChild (*commandResponse, "GTIC"); + verifyDatapoint (commandResponse, "GTIS"); + gtic = getChild (*commandResponse, "GTIS"); verifyDatapoint (gtic, "SpcTyp"); Spc = getChild (*gtic, "SpcTyp"); @@ -1374,8 +1374,8 @@ TEST_F (ReportingTest, ReportingIndividualAttributes) ASSERT_FALSE (storedReadings.empty ()); ASSERT_EQ (storedReadings.size (), 3); commandResponse = storedReadings[2]->getReadingData ()[0]; - verifyDatapoint (commandResponse, "GTIC"); - gtic = getChild (*commandResponse, "GTIC"); + verifyDatapoint (commandResponse, "GTIS"); + gtic = getChild (*commandResponse, "GTIS"); verifyDatapoint (gtic, "SpcTyp"); Spc = getChild (*gtic, "SpcTyp"); From 31d017f8dd953c9b8d0dd0432f08777367330cf3 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Mon, 27 May 2024 17:38:38 +0100 Subject: [PATCH 20/20] - check for presence of ResvTms to be able to handle ed.1 devices Signed-off-by: Michael Zillgith --- src/iec61850_client_connection.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/iec61850_client_connection.cpp b/src/iec61850_client_connection.cpp index 860bfbb..192d864 100644 --- a/src/iec61850_client_connection.cpp +++ b/src/iec61850_client_connection.cpp @@ -370,8 +370,12 @@ configureRcb (const std::shared_ptr& rs, { parametersMask |= RCB_ELEMENT_OPT_FLDS; ClientReportControlBlock_setOptFlds(rcb,RPT_OPT_REASON_FOR_INCLUSION | RPT_OPT_ENTRY_ID | RPT_OPT_TIME_STAMP| RPT_OPT_DATA_SET); - parametersMask |= RCB_ELEMENT_RESV_TMS; - ClientReportControlBlock_setResvTms (rcb, 1000); + + if (ClientReportControlBlock_hasResvTms(rcb)) + { + parametersMask |= RCB_ELEMENT_RESV_TMS; + ClientReportControlBlock_setResvTms (rcb, 1000); + } if (firstTimeConnect) {