From d747a98a383de0161233a8b823d587fbe7012482 Mon Sep 17 00:00:00 2001 From: dan Date: Fri, 10 Nov 2023 19:43:58 +0000 Subject: [PATCH] address comments and add unit test --- src/tmx/TmxUtils/src/RSU_MIB_4_1.h | 69 +++++++++++ .../RSUHealthMonitorPlugin/CMakeLists.txt | 22 +++- .../RSUHealthMonitorPlugin/manifest.json | 6 +- .../src/RSUHealthMonitorPlugin.cpp | 111 ++++------------- .../src/RSUHealthMonitorPlugin.h | 41 ++----- .../src/RSUHealthMonitorWorker.cpp | 112 ++++++++++++++++++ .../src/RSUHealthMonitorWorker.h | 76 ++++++++++++ .../RSUHealthMonitorPlugin/test/main.cpp | 8 ++ .../test/test_RSUHealthMonitorWorker.cpp | 40 +++++++ 9 files changed, 364 insertions(+), 121 deletions(-) create mode 100644 src/tmx/TmxUtils/src/RSU_MIB_4_1.h create mode 100644 src/v2i-hub/RSUHealthMonitorPlugin/src/RSUHealthMonitorWorker.cpp create mode 100644 src/v2i-hub/RSUHealthMonitorPlugin/src/RSUHealthMonitorWorker.h create mode 100644 src/v2i-hub/RSUHealthMonitorPlugin/test/main.cpp create mode 100644 src/v2i-hub/RSUHealthMonitorPlugin/test/test_RSUHealthMonitorWorker.cpp diff --git a/src/tmx/TmxUtils/src/RSU_MIB_4_1.h b/src/tmx/TmxUtils/src/RSU_MIB_4_1.h new file mode 100644 index 000000000..f88bd96a7 --- /dev/null +++ b/src/tmx/TmxUtils/src/RSU_MIB_4_1.h @@ -0,0 +1,69 @@ +#pragma once +namespace tmx +{ + namespace utils + { + namespace rsu41 + { + namespace mib + { + namespace oid + { + /** + * @brief This header file contains a subset of RSU MIB definition from https://github.com/certificationoperatingcouncil/COC_TestSpecs/blob/master/AppNotes/RSU/RSU-MIB.txt + */ + // Contains the ID given to this RSU. + static constexpr const char *RSU_ID_OID = "1.0.15628.4.1.17.4.0"; + + // Contains the version of this MIB supported by this RSU, e.g. rsuMIB 4.1 rev201812060000Z + static constexpr const char *RSU_MIB_VERSION = "1.0.15628.4.1.17.1.0"; + + // Contains the version of firmware running on this RSU. + static constexpr const char *RSU_FIRMWARE_VERSION = "1.0.15628.4.1.17.2.0"; + + // Contains the name of the manufacturer of this RSU. + static constexpr const char *RSU_MANUFACTURER = "1.0.15628.4.1.17.5.0"; + + // Contains GPS NMEA GPGGA output string. + static constexpr const char *RSU_GPS_OUTPUT_STRING = "1.0.15628.4.1.8.5.0"; + + // Immediate Forward Message Index + static constexpr const char *RSU_IFM_INDEX = "1.0.15628.4.1.5.1.1.0"; + + // Immediate Forward Message PSID. + static constexpr const char *RSU_IFM_PSID = "1.0.15628.4.1.5.1.2.0"; + + // Immediate Forward Message DSRC Message ID + static constexpr const char *RSU_IFM_DSRC_MSG_ID = "1.0.15628.4.1.5.1.3.0"; + + // Immediate Forward Message Transmit Mode + static constexpr const char *RSU_IFM_TX_MODE = "1.0.15628.4.1.5.1.4.0"; + + // DSRC channel set for Immediate Forward Message transmit + static constexpr const char *RSU_IFM_TX_CHANNEL = "1.0.15628.4.1.5.1.5.0"; + + // Set this bit to enable transmission of the message 0=off, 1=on + static constexpr const char *RSU_IFM_ENABLE = "1.0.15628.4.1.5.1.6.0"; + + // Create (4) or Destroy (6) row entry + static constexpr const char *RSU_IFM_STATUS = "1.0.15628.4.1.5.1.7.0"; + + // Specifies the current mode of operation of the RSU and provides capability to transition the device into a new mode, e.g. from the current mode to off, etc + static constexpr const char *RSU_MODE = "1.0.15628.4.1.99.0"; + + /* + SYNTAX INTEGER { + bothOp (0), --both Continuous and Alternating modes are operational + altOp (1), --Alternating mode is operational, + --Continuous mode is not operational + contOp (2), --Continuous mode is operational, + --Alternating mode is not operational + noneOp (3) --neither Continuous nor Alternating mode is operational + */ + static constexpr const char *RSU_CHAN_STATUS = "1.0.15628.4.1.19.1.0"; + } // namespace oid + } // namespace mib + } // namespace rsu41 + + } +} \ No newline at end of file diff --git a/src/v2i-hub/RSUHealthMonitorPlugin/CMakeLists.txt b/src/v2i-hub/RSUHealthMonitorPlugin/CMakeLists.txt index 995f8254c..fa0a6816b 100755 --- a/src/v2i-hub/RSUHealthMonitorPlugin/CMakeLists.txt +++ b/src/v2i-hub/RSUHealthMonitorPlugin/CMakeLists.txt @@ -10,4 +10,24 @@ find_library(libasn1c .) BuildTmxPlugin() TARGET_INCLUDE_DIRECTORIES(${PROJECT_NAME} PUBLIC ${NETSNMP_INCLUDE_DIRS}) -TARGET_LINK_LIBRARIES(${PROJECT_NAME} PUBLIC tmxutils ${NETSNMPAGENT} ${NETSNMPMIBS} ${NETSNMP} ${NETSNMP_LIBRARIES} jsoncpp NemaTode) \ No newline at end of file +TARGET_LINK_LIBRARIES(${PROJECT_NAME} PUBLIC tmxutils ${NETSNMPAGENT} ${NETSNMPMIBS} ${NETSNMP} ${NETSNMP_LIBRARIES} jsoncpp NemaTode) + +############# +## Testing ## +############# +enable_testing() +include_directories(${PROJECT_SOURCE_DIR}/src) +add_library(${PROJECT_NAME}_lib src/RSUHealthMonitorWorker.cpp) +target_link_libraries(${PROJECT_NAME}_lib PUBLIC ${TMXAPI_LIBRARIES} + ${ASN_J2735_LIBRARIES} + ${MYSQL_LIBRARIES} + ${MYSQLCPPCONN_LIBRARIES} + tmxutils + NemaTode + ${UUID_LIBRARY}) +set(BINARY ${PROJECT_NAME}_test) +file(GLOB_RECURSE TEST_SOURCES LIST_DIRECTORIES false test/*.h test/*.cpp) +set(SOURCES ${TEST_SOURCES} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/test) +add_executable(${BINARY} ${TEST_SOURCES}) +add_test(NAME ${BINARY} COMMAND ${BINARY}) +target_link_libraries(${BINARY} PUBLIC ${PROJECT_NAME}_lib gtest NemaTode) \ No newline at end of file diff --git a/src/v2i-hub/RSUHealthMonitorPlugin/manifest.json b/src/v2i-hub/RSUHealthMonitorPlugin/manifest.json index b41ae7ff2..690b3c4e9 100755 --- a/src/v2i-hub/RSUHealthMonitorPlugin/manifest.json +++ b/src/v2i-hub/RSUHealthMonitorPlugin/manifest.json @@ -43,9 +43,9 @@ "description":"SNMP Security level" }, { - "key":"RSUOIDConfigMap", - "default":"{\"RSUOIDConfig\":[{\"RsuField\":\"rsuGpsOutputString\",\"OID\":\"iso.0.15628.4.1.8.5.0\",\"Required\":false},{\"RsuField\":\"rsuID\",\"OID\":\"iso.0.15628.4.1.17.4.0\",\"Required\":false},{\"RsuField\":\"rsuMibVersion\",\"OID\":\"iso.0.15628.4.1.17.1.0\",\"Required\":false},{\"RsuField\":\"rsuFirmwareVersion\",\"OID\":\"iso.0.15628.4.1.17.2.0\",\"Required\":false},{\"RsuField\":\"rsuManufacturer\",\"OID\":\"iso.0.15628.4.1.17.5.0\",\"Required\":false},{\"RsuField\":\"rsuIFMIndex\",\"OID\":\"iso.3.6.1.2.1.1.7.0\",\"Required\":false},{\"RsuField\":\"rsuIFMPsid\",\"OID\":\"iso.3.6.1.2.1.1.7.0\",\"Required\":false},{\"RsuField\":\"rsuIFMDsrcMsgId\",\"OID\":\"iso.3.6.1.2.1.1.7.0\",\"Required\":false},{\"RsuField\":\"rsuIFMTxMode\",\"OID\":\"iso.3.6.1.2.1.1.7.0\",\"Required\":false},{\"RsuField\":\"rsuIFMTxChannel\",\"OID\":\"iso.3.6.1.2.1.1.7.0\",\"Required\":false},{\"RsuField\":\"rsuIFMEnable\",\"OID\":\"iso.3.6.1.2.1.1.7.0\",\"Required\":false},{\"RsuField\":\"rsuIFMStatus\",\"OID\":\"iso.3.6.1.2.1.1.7.0\",\"Required\":false},{\"RsuField\":\"rsuMode\",\"OID\":\"iso.0.15628.4.1.99.0\",\"Required\":false}]}", - "description":"OID (Object Identifier) uniquely identify managed objects in a MIB database." + "key":"RSUMIBVersion", + "default":"RSU4.1", + "description":"THe version of RSU MIB (Management Information Base). E.G. RSU4.1 or RSU1218" } ] } \ No newline at end of file diff --git a/src/v2i-hub/RSUHealthMonitorPlugin/src/RSUHealthMonitorPlugin.cpp b/src/v2i-hub/RSUHealthMonitorPlugin/src/RSUHealthMonitorPlugin.cpp index 8f2dd7103..699c31248 100755 --- a/src/v2i-hub/RSUHealthMonitorPlugin/src/RSUHealthMonitorPlugin.cpp +++ b/src/v2i-hub/RSUHealthMonitorPlugin/src/RSUHealthMonitorPlugin.cpp @@ -8,6 +8,7 @@ namespace RSUHealthMonitor RSUHealthMonitorPlugin::RSUHealthMonitorPlugin(std::string name) : PluginClient(name) { + _rsuWorker = std::make_shared(); UpdateConfigSettings(); // Send SNMP call to RSU periodically at configurable interval. std::thread rsuStatus_t(&RSUHealthMonitorPlugin::PeriodicRSUStatusReq, this); @@ -25,44 +26,17 @@ namespace RSUHealthMonitor GetConfigValue("AuthPassPhrase", _authPassPhrase); GetConfigValue("SecurityUser", _securityUser); GetConfigValue("SecurityLevel", _securityLevel); - - // Update the OID to RSU field mapping - string rsuOIDMapJsonStr; - GetConfigValue("RSUOIDConfigMap", rsuOIDMapJsonStr); - UpdateRSUOIDConfig(rsuOIDMapJsonStr); - } - - void RSUHealthMonitorPlugin::UpdateRSUOIDConfig(string &json_str) - { - - if (json_str.length() == 0) - { - PLOG(logERROR) << "Error updating RSU OID config due to JSON is empty."; - return; - } - try + GetConfigValue("RSUMIBVersion", _rsuMIBVersionStr); + boost::trim_left(_rsuMIBVersionStr); + boost::trim_right(_rsuMIBVersionStr); + // Support RSU MIB version 4.1 + if (boost::iequals(_rsuMIBVersionStr, RSU4_1_str)) { - ptree pt; - istringstream iss(json_str); - read_json(iss, pt); - - // Clear the RSU OID mapping variable - _rsuOIDConfigMap.clear(); - BOOST_FOREACH (ptree::value_type &child, pt.get_child("RSUOIDConfig")) - { - // Array elements have no names. - assert(child.first.empty()); - RSUOIDConfig config; - config.field = child.second.get("RsuField"); - config.oid = child.second.get("OID"); - config.required = child.second.get("Required"); - // Add RSU OID to the map - _rsuOIDConfigMap.push_back(config); - } + _rsuMibVersion = RSUMIB_4_1; } - catch (const std::exception &e) + else { - PLOG(logERROR) << "Error updating RSU OID config " << e.what(); + PLOG(logERROR) << "Unknow RSU Mib version: " + _rsuMIBVersionStr; } } @@ -75,10 +49,10 @@ namespace RSUHealthMonitor void RSUHealthMonitorPlugin::PeriodicRSUStatusReq() { while (true) - { + { try { - //SNMP call to get RSU status + // Periodic SNMP call to get RSU status based on RSU MIB version 4.1 auto rsuStatusJson = getRSUstatus(); // Broadcast the RSU status info when there are RSU responses. if (!rsuStatusJson.empty()) @@ -89,7 +63,7 @@ namespace RSUHealthMonitor rsuStatusFields.push_back(field); } // Only broadcast RSU status when all required fields are present. - if (isAllRequiredFieldsPresent(rsuStatusFields)) + if (_rsuWorker && _rsuWorker->isAllRequiredFieldsPresent(_rsuMibVersion, rsuStatusFields)) { Json::FastWriter fasterWirter; string json_str = fasterWirter.write(rsuStatusJson); @@ -111,13 +85,18 @@ namespace RSUHealthMonitor Json::Value RSUHealthMonitorPlugin::getRSUstatus() { - if (_rsuOIDConfigMap.size() == 0) + if (!_rsuWorker) { - PLOG(logERROR) << "RSU status update call failed due to RSUOIDConfigMap is empty!"; + PLOG(logERROR) << "RSU status update call failed due to fail to initialize RSU worker!"; + return Json::nullValue; + } + + auto rsuStatusConfigTbl = _rsuWorker->GetRSUStatusConfig(_rsuMibVersion); + if (rsuStatusConfigTbl.size() == 0) + { + PLOG(logERROR) << "RSU status update call failed due to RSU stataus config table is empty!"; return Json::nullValue; } - PLOG(logDEBUG) << "RSU status update call at every " << _interval << " seconds!\n"; - // Create SNMP client and use SNMP V3 protocol PLOG(logINFO) << "Update SNMP client: RSU IP: " << _rsuIp << ", RSU port: " << _snmpPort << ", User: " << _securityUser << ", auth pass phrase: " << _authPassPhrase << ", security level: " << _securityLevel; @@ -130,7 +109,7 @@ namespace RSUHealthMonitor Json::Value rsuStatuJson; // Sending RSU SNMP call for each field as each field has its own OID. - for (auto &config : _rsuOIDConfigMap) + for (auto &config : rsuStatusConfigTbl) { try { @@ -138,11 +117,10 @@ namespace RSUHealthMonitor snmp_response_obj responseVal; auto success = _snmpClientPtr->process_snmp_request(config.oid, request_type::GET, responseVal); if (!success) - { // If any snmp request failed, stop any furthur snmp requests using the same current snmp session as the next OID will not be created. break; - } - else if (success && responseVal.type == snmp_response_obj::response_type::INTEGER) + + if (success && responseVal.type == snmp_response_obj::response_type::INTEGER) { rsuStatuJson[config.field] = responseVal.val_int; } @@ -153,7 +131,7 @@ namespace RSUHealthMonitor // Proess GPS nmea string if (boost::iequals("rsuGpsOutputString", config.field)) { - auto gps = ParseGPS(response_str); + auto gps = _rsuWorker->ParseRSUGPS(response_str); rsuStatuJson["rsuGpsOutputStringLatitude"] = gps.begin()->first; rsuStatuJson["rsuGpsOutputStringLongitude"] = gps.begin()->second; } @@ -171,47 +149,8 @@ namespace RSUHealthMonitor return rsuStatuJson; } - bool RSUHealthMonitorPlugin::isAllRequiredFieldsPresent(vector fields) - { - bool isAllPresent = true; - for (auto &config : _rsuOIDConfigMap) - { - if (config.required && std::find(fields.begin(), fields.end(), config.field) == fields.end()) - { - isAllPresent = false; - PLOG(logWARNING) << "No broadcast as required field " << config.field << " is not present!"; - } - } - return isAllPresent; - } - - std::map RSUHealthMonitorPlugin::ParseGPS(const std::string &gps_nmea_data) - { - std::map result; - nmea::NMEAParser parser; - nmea::GPSService gps(parser); - try - { - parser.readLine(gps_nmea_data); - std::stringstream ss; - ss << std::setprecision(8) << std::fixed << gps.fix.latitude << std::endl; - auto latitude_str = ss.str(); - std::stringstream sss; - sss << std::setprecision(8) << std::fixed << gps.fix.longitude << std::endl; - auto longitude_str = sss.str(); - result.insert({std::stod(latitude_str), std::stod(longitude_str)}); - PLOG(logDEBUG) << "Parse GPS NMEA string: " << gps_nmea_data << ". Result (Latitude, Longitude): (" << latitude_str << "," << longitude_str << ")"; - } - catch (nmea::NMEAParseError &e) - { - fprintf(stderr, "Error:%s\n", e.message.c_str()); - } - return result; - } - RSUHealthMonitorPlugin::~RSUHealthMonitorPlugin() { - _rsuOIDConfigMap.clear(); } } // namespace RSUHealthMonitor diff --git a/src/v2i-hub/RSUHealthMonitorPlugin/src/RSUHealthMonitorPlugin.h b/src/v2i-hub/RSUHealthMonitorPlugin/src/RSUHealthMonitorPlugin.h index dcba45a2a..a0cf40a31 100755 --- a/src/v2i-hub/RSUHealthMonitorPlugin/src/RSUHealthMonitorPlugin.h +++ b/src/v2i-hub/RSUHealthMonitorPlugin/src/RSUHealthMonitorPlugin.h @@ -1,17 +1,11 @@ -#ifndef RSUHEALTHMONITORLUGIN_H_ -#define RSUHEALTHMONITORLUGIN_H_ +#pragma once #include "PluginClient.h" -#include -#include -#include -#include #include "SNMPClient.h" #include #include "RSUStatusMessage.h" -#include -#include +#include "RSUHealthMonitorWorker.h" using namespace tmx::utils; using namespace std; @@ -20,13 +14,6 @@ using namespace boost::property_tree; namespace RSUHealthMonitor { - struct RSUOIDConfig - { - string field; - string oid; - bool required; // Indicate whether this field is required to before broadcasting the RSU status. - }; - class RSUHealthMonitorPlugin : public PluginClient { private: @@ -37,7 +24,11 @@ namespace RSUHealthMonitor string _authPassPhrase; string _securityUser; string _securityLevel; - vector _rsuOIDConfigMap; + string _rsuMIBVersionStr; + RSUMibVersion _rsuMibVersion; + const char *RSU4_1_str = "RSU4.1"; + const char *RSU1218_str = "RSU1218"; + std::shared_ptr _rsuWorker; const long SEC_TO_MICRO = 1000000; // std::shared_ptr _snmpClientPtr; /** @@ -50,20 +41,10 @@ namespace RSUHealthMonitor */ void PeriodicRSUStatusReq(); /** - * @brief Sending SNMP requests to get info for each field in the _rsuOIDConfigMap, and return the RSU status in JSON + * @brief Sending SNMP requests to get info for each field in the RSUStatusConfigTable, and return the RSU status in JSON + * Use RSU Status configuration table include RSU field, OIDs, and whether fields are required or optional */ Json::Value getRSUstatus(); - /** - * @brief Parse NMEA GPS sentense and return GPS related data - * @param gps_nmea_data NMEA GPS sentense - * @return map A map of latitude and longitude - */ - std::map ParseGPS(const std::string &gps_nmea_data); - /** - * @brief determine if all required fields in the RSU config map _rsuOIDConfigMap present in the input fields - * @return True if all required fields found. Otherwise, false. - */ - bool isAllRequiredFieldsPresent(vector fields); public: RSUHealthMonitorPlugin(std::string name); @@ -72,6 +53,4 @@ namespace RSUHealthMonitor void OnConfigChanged(const char *key, const char *value); }; -} // namespace RSUHealthMonitorPlugin - -#endif \ No newline at end of file +} // namespace RSUHealthMonitorPlugin \ No newline at end of file diff --git a/src/v2i-hub/RSUHealthMonitorPlugin/src/RSUHealthMonitorWorker.cpp b/src/v2i-hub/RSUHealthMonitorPlugin/src/RSUHealthMonitorWorker.cpp new file mode 100644 index 000000000..5fa32b24c --- /dev/null +++ b/src/v2i-hub/RSUHealthMonitorPlugin/src/RSUHealthMonitorWorker.cpp @@ -0,0 +1,112 @@ +#include "RSUHealthMonitorWorker.h" + +namespace RSUHealthMonitor +{ + + RSUHealthMonitorWorker::RSUHealthMonitorWorker() + { + _RSUSTATUSConfigMapPtr = make_shared>(); + // Current only support RSU MIB version 4.1. Other future supported versions will be inserted here. + RSUStatusConfigTable rsuRstatusTable = constructRsuStatusConfigTable(RSUMIB_4_1); + _RSUSTATUSConfigMapPtr->insert({RSUMIB_4_1, rsuRstatusTable}); + } + + RSUStatusConfigTable RSUHealthMonitorWorker::constructRsuStatusConfigTable(const RSUMibVersion &mibVersion) + { + RSUStatusConfigTable rsuStatusTbl; + // Populate custome defined RSU Status table with RSU MIB version 4.1. + if (mibVersion == RSUMIB_4_1) + { + RSUFieldOIDStruct rsuID = {"rsuID", RSU_ID_OID, true}; + rsuStatusTbl.push_back(rsuID); + + RSUFieldOIDStruct rsuMibVersion = {"rsuMibVersion", RSU_MIB_VERSION, true}; + rsuStatusTbl.push_back(rsuMibVersion); + + RSUFieldOIDStruct rsuFirmwareVersion = {"rsuFirmwareVersion", RSU_FIRMWARE_VERSION, true}; + rsuStatusTbl.push_back(rsuFirmwareVersion); + + RSUFieldOIDStruct rsuManufacturer = {"rsuManufacturer", RSU_MANUFACTURER, true}; + rsuStatusTbl.push_back(rsuManufacturer); + + RSUFieldOIDStruct rsuGpsOutputString = {"rsuGpsOutputString", RSU_GPS_OUTPUT_STRING, true}; + rsuStatusTbl.push_back(rsuGpsOutputString); + + RSUFieldOIDStruct rsuIFMIndex = {"rsuIFMIndex", RSU_IFM_INDEX, false}; + rsuStatusTbl.push_back(rsuIFMIndex); + + RSUFieldOIDStruct rsuIFMPsid = {"rsuIFMPsid", RSU_IFM_PSID, false}; + rsuStatusTbl.push_back(rsuIFMPsid); + + RSUFieldOIDStruct rsuIFMDsrcMsgId = {"rsuIFMDsrcMsgId", RSU_IFM_DSRC_MSG_ID, false}; + rsuStatusTbl.push_back(rsuIFMDsrcMsgId); + + RSUFieldOIDStruct rsuIFMTxMode = {"rsuIFMTxMode", RSU_IFM_INDEX, false}; + rsuStatusTbl.push_back(rsuIFMTxMode); + + RSUFieldOIDStruct rsuIFMTxChannel = {"rsuIFMTxChannel", RSU_IFM_TX_CHANNEL, false}; + rsuStatusTbl.push_back(rsuIFMTxChannel); + + RSUFieldOIDStruct rsuIFMEnable = {"rsuIFMEnable", RSU_IFM_ENABLE, false}; + rsuStatusTbl.push_back(rsuIFMEnable); + + RSUFieldOIDStruct rsuIFMStatus = {"rsuIFMStatus", RSU_IFM_STATUS, false}; + rsuStatusTbl.push_back(rsuIFMStatus); + + RSUFieldOIDStruct rsuMode = {"rsuMode", RSU_MODE, true}; + rsuStatusTbl.push_back(rsuMode); + + RSUFieldOIDStruct rsuChanStatus = {"rsuChanStatus", RSU_CHAN_STATUS, true}; + rsuStatusTbl.push_back(rsuChanStatus); + } + return rsuStatusTbl; + } + + bool RSUHealthMonitorWorker::isAllRequiredFieldsPresent(const RSUMibVersion &mibVersion, const vector &fields) + { + bool isAllPresent = true; + for (auto &config : GetRSUStatusConfig(mibVersion)) + { + if (config.required && find(fields.begin(), fields.end(), config.field) == fields.end()) + { + isAllPresent = false; + PLOG(logWARNING) << "No broadcast as required field " << config.field << " is not present!"; + } + } + return isAllPresent; + } + + RSUStatusConfigTable RSUHealthMonitorWorker::GetRSUStatusConfig(const RSUMibVersion& mibVersion) + { + return _RSUSTATUSConfigMapPtr->at(mibVersion); + } + + std::map RSUHealthMonitorWorker::ParseRSUGPS(const std::string &gps_nmea_data) + { + std::map result; + nmea::NMEAParser parser; + nmea::GPSService gps(parser); + try + { + parser.readLine(gps_nmea_data); + std::stringstream ss; + ss << std::setprecision(8) << std::fixed << gps.fix.latitude << std::endl; + auto latitude_str = ss.str(); + std::stringstream sss; + sss << std::setprecision(8) << std::fixed << gps.fix.longitude << std::endl; + auto longitude_str = sss.str(); + result.insert({std::stod(latitude_str), std::stod(longitude_str)}); + PLOG(logDEBUG) << "Parse GPS NMEA string: " << gps_nmea_data << ". Result (Latitude, Longitude): (" << latitude_str << "," << longitude_str << ")"; + } + catch (nmea::NMEAParseError &e) + { + fprintf(stderr, "Error:%s\n", e.message.c_str()); + } + return result; + } + + RSUHealthMonitorWorker::~RSUHealthMonitorWorker() + { + _RSUSTATUSConfigMapPtr->clear(); + } +} \ No newline at end of file diff --git a/src/v2i-hub/RSUHealthMonitorPlugin/src/RSUHealthMonitorWorker.h b/src/v2i-hub/RSUHealthMonitorPlugin/src/RSUHealthMonitorWorker.h new file mode 100644 index 000000000..bd2d9e324 --- /dev/null +++ b/src/v2i-hub/RSUHealthMonitorPlugin/src/RSUHealthMonitorWorker.h @@ -0,0 +1,76 @@ +#pragma once +#include +#include "RSU_MIB_4_1.h" +#include +#include +#include +#include +#include +#include "PluginLog.h" + +using namespace std; +using namespace tmx::utils; +using namespace tmx::utils::rsu41::mib::oid; + +namespace RSUHealthMonitor +{ + enum RSUMibVersion + { + RSUMIB_4_1 = 0, + RSUMIB_1218 = 1 + }; + + struct RSUFieldOIDStruct + { + string field; + string oid; + bool required; // Indicate whether this field is required to before broadcasting the RSU status. + }; + + /** + * RSUStatusTable is custome defined RSU status information. + * The fields are the subset of fields from RSU MIB definition https://github.com/certificationoperatingcouncil/COC_TestSpecs/blob/master/AppNotes/RSU/RSU-MIB.txt + */ + using RSUStatusConfigTable = vector; + + class RSUHealthMonitorWorker + { + private: + // A map of RSU MIB version used and RSUStatusTable + shared_ptr> _RSUSTATUSConfigMapPtr; + + /** + * @brief Poupate the RSU status table with the specified version of OIDs and fields. + * Private: Only allow to initialze the RSU STATUS MAP once + * @param mibVersion specified + * @return RSUStatusTable the self defined RSU status https://usdot-carma.atlassian.net/wiki/spaces/WFD2/pages/2640740360/RSU+Health+Monitor+Plugin+Design + */ + RSUStatusConfigTable constructRsuStatusConfigTable(const RSUMibVersion &mibVersion); + + public: + // Populate the RSU Status Table with predefined fields and their mapping OIDs in constructor + RSUHealthMonitorWorker(); + + // Access to the RSU status table based in the RSU MIB version provided + RSUStatusConfigTable GetRSUStatusConfig(const RSUMibVersion &mibVersion); + + /** + * @brief determine if all required fields in the RSU config map _RSUSTATUSConfigMapPtr present in the input fields + * Use _RSUSTATUSConfigMapPtr RSU status config map that defines all fields and whether the fields are required. + * @param RSUMibVersion RSU MIB version + * @param vector Input RSU fields to verify + * @return True if all required fields found. Otherwise, false. + */ + bool isAllRequiredFieldsPresent(const RSUMibVersion &mibVersion, const vector &fields); + + /** + * @brief Parse NMEA GPS sentense and return GPS related data + * @param gps_nmea_data NMEA GPS sentense + * @return map A map of latitude and longitude + */ + std::map ParseRSUGPS(const std::string &gps_nmea_data); + + // Clear the RSU status map in destructor + ~RSUHealthMonitorWorker(); + }; +} // namespace RSUHealthMonitor diff --git a/src/v2i-hub/RSUHealthMonitorPlugin/test/main.cpp b/src/v2i-hub/RSUHealthMonitorPlugin/test/main.cpp new file mode 100644 index 000000000..ba7cd2667 --- /dev/null +++ b/src/v2i-hub/RSUHealthMonitorPlugin/test/main.cpp @@ -0,0 +1,8 @@ + +#include + +int main(int argc, char **argv) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/src/v2i-hub/RSUHealthMonitorPlugin/test/test_RSUHealthMonitorWorker.cpp b/src/v2i-hub/RSUHealthMonitorPlugin/test/test_RSUHealthMonitorWorker.cpp new file mode 100644 index 000000000..760569323 --- /dev/null +++ b/src/v2i-hub/RSUHealthMonitorPlugin/test/test_RSUHealthMonitorWorker.cpp @@ -0,0 +1,40 @@ +#include "RSUHealthMonitorWorker.h" +#include + +namespace RSUHealthMonitor +{ + class test_RSUHealthMonitorWorker : public ::testing::Test + { + public: + std::shared_ptr _rsuWorker = std::make_shared(); + }; + + TEST_F(test_RSUHealthMonitorWorker, GetRSUStatusConfig) + { + RSUStatusConfigTable rsuStatusConfigTbl = _rsuWorker->GetRSUStatusConfig(RSUMIB_4_1); + ASSERT_EQ(14, rsuStatusConfigTbl.size()); + } + + TEST_F(test_RSUHealthMonitorWorker, isAllRequiredFieldsPresent) + { + vector requiredFields = {"rsuID", "rsuMibVersion", "rsuFirmwareVersion", "rsuManufacturer", "rsuGpsOutputString", "rsuMode", "rsuChanStatus"}; + ASSERT_TRUE(_rsuWorker->isAllRequiredFieldsPresent(RSUMIB_4_1, requiredFields)); + } + + TEST_F(test_RSUHealthMonitorWorker, ParseRSUGPS) + { + std::string gps_nmea_data = "$GPGGA,142440.00,3857.3065,N,07708.9734,W,2,18,0.65,86.18,M,-34.722,M,,*62"; + auto gps_map = _rsuWorker->ParseRSUGPS(gps_nmea_data); + ASSERT_EQ(1, gps_map.size()); + double expected_latitude = 38.9551; + double expected_longitude = -77.1496; + for (auto itr = gps_map.begin(); itr != gps_map.end(); itr++) + { + ASSERT_NEAR(expected_latitude, itr->first, 0.001); + ASSERT_NEAR(expected_longitude, itr->second, 0.001); + } + std::string invalid_gps_nmea_data = "$*GPGGA,invalid"; + auto gps_map_invalid = _rsuWorker->ParseRSUGPS(invalid_gps_nmea_data); + ASSERT_EQ(0, gps_map_invalid.size()); + } +} \ No newline at end of file