diff --git a/src/tmx/Messages/test/Main.cpp b/src/tmx/Messages/test/Main.cpp new file mode 100644 index 000000000..5c1d740bb --- /dev/null +++ b/src/tmx/Messages/test/Main.cpp @@ -0,0 +1,23 @@ +/** + * Copyright (C) 2024 LEIDOS. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +#include + +int main(int argc, char **argv) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/src/tmx/Messages/test/TestTimeSyncMessage.cpp b/src/tmx/Messages/test/TestTimeSyncMessage.cpp new file mode 100644 index 000000000..a853ed2b2 --- /dev/null +++ b/src/tmx/Messages/test/TestTimeSyncMessage.cpp @@ -0,0 +1,10 @@ +#include +#include "TimeSyncMessage.h" +namespace tmx::messages { + + TEST(TestTimeSyncMessage, to_string) { + TimeSyncMessage msg(20, 30); + std::string json = "{ \"timestep\":20, \"seq\":30}"; + EXPECT_EQ( json, msg.to_string()); + } +} \ No newline at end of file diff --git a/src/tmx/TmxUtils/test/MockSNMPClient.h b/src/tmx/TmxUtils/src/MockSNMPClient.h similarity index 100% rename from src/tmx/TmxUtils/test/MockSNMPClient.h rename to src/tmx/TmxUtils/src/MockSNMPClient.h diff --git a/src/tmx/TmxUtils/src/MockUdpServer.h b/src/tmx/TmxUtils/src/MockUdpServer.h index 533965711..0197f54bb 100644 --- a/src/tmx/TmxUtils/src/MockUdpServer.h +++ b/src/tmx/TmxUtils/src/MockUdpServer.h @@ -24,6 +24,6 @@ namespace tmx::utils { MOCK_METHOD(std::string, GetAddress, (), (const, override)); MOCK_METHOD(int, Receive, (char *msg, size_t maxSize), (override)); MOCK_METHOD(int, GetSocket, (), (override, const)); + MOCK_METHOD(std::string, stringTimedReceive, (int maxWait_ms), (override)); }; - } \ No newline at end of file diff --git a/src/tmx/TmxUtils/src/PluginClientClockAware.cpp b/src/tmx/TmxUtils/src/PluginClientClockAware.cpp index 4cf87fe6b..cf08e3b43 100644 --- a/src/tmx/TmxUtils/src/PluginClientClockAware.cpp +++ b/src/tmx/TmxUtils/src/PluginClientClockAware.cpp @@ -14,16 +14,16 @@ namespace tmx::utils { clock = std::make_shared(_simulation_mode); if (_simulation_mode) { AddMessageFilter(this, &PluginClientClockAware::HandleTimeSyncMessage); - + SubscribeToMessages(); } } void PluginClientClockAware::HandleTimeSyncMessage(tmx::messages::TimeSyncMessage &msg, routeable_message &routeableMsg ) { - PLOG(logDEBUG) << "Message Received " << msg.to_string() << std::endl; - this->getClock()->update( msg.get_timestep() ); - if (sim::is_simulation_mode() ) { + if (_simulation_mode ) { + PLOG(logDEBUG) << "Message Received " << msg.to_string() << std::endl; + clock->update( msg.get_timestep() ); SetStatus(Key_Simulation_Time_Step, Clock::ToUtcPreciseTimeString(msg.get_timestep())); } } @@ -39,4 +39,9 @@ namespace tmx::utils { return _simulation_mode; } + std::shared_ptr PluginClientClockAware::getClock() const { + clock->wait_for_initialization(); // Blocks until first call to update when in sim mode. + return clock; + } + } \ No newline at end of file diff --git a/src/tmx/TmxUtils/src/PluginClientClockAware.h b/src/tmx/TmxUtils/src/PluginClientClockAware.h index 974f55060..41512809a 100644 --- a/src/tmx/TmxUtils/src/PluginClientClockAware.h +++ b/src/tmx/TmxUtils/src/PluginClientClockAware.h @@ -33,15 +33,14 @@ class PluginClientClockAware : public PluginClient { protected: /** - * @brief Method for child classes to use to retrieve the clock object and get the simulation or real time. - * @return + * @brief Method for child classes to use to retrieve the clock object and get the simulation or real time. + * In simulation mode method will block until first time update has been received. + * @return clock */ - inline std::shared_ptr getClock() const { - return clock; - } + std::shared_ptr getClock() const ; void OnStateChange(IvpPluginState state) override; - + bool isSimulationMode() const; diff --git a/src/tmx/TmxUtils/src/SNMPClient.cpp b/src/tmx/TmxUtils/src/SNMPClient.cpp index e3398ebbd..1cbb53853 100644 --- a/src/tmx/TmxUtils/src/SNMPClient.cpp +++ b/src/tmx/TmxUtils/src/SNMPClient.cpp @@ -165,27 +165,17 @@ namespace tmx::utils PLOG(logINFO) << "Response request status: " << status << " (=" << (status == STAT_SUCCESS ? "SUCCESS" : "FAILED") << ")"; // Check GET response - if (status == STAT_SUCCESS && response && response->errstat == SNMP_ERR_NOERROR && request_type == request_type::GET) + if (status == STAT_SUCCESS && response && response->errstat == SNMP_ERR_NOERROR ) { - for (auto vars = response->variables; vars; vars = vars->next_variable) - { - // Get value of variable depending on ASN.1 type - // Variable could be a integer, string, bitstring, ojbid, counter : defined here https://github.com/net-snmp/net-snmp/blob/master/include/net-snmp/types.h - // get Integer value - if (vars->type == ASN_INTEGER && vars->val.integer) - { - val.type = snmp_response_obj::response_type::INTEGER; - val.val_int = *vars->val.integer; - } - else if (vars->type == ASN_OCTET_STR && vars->val.string) - { - size_t str_len = vars->val_len; - for (size_t i = 0; i < str_len; ++i) - { - val.val_string.push_back(vars->val.string[i]); - } - val.type = snmp_response_obj::response_type::STRING; - } + if ( request_type == request_type::GET ) { + process_snmp_get_response(val, *response); + } + else if( request_type == request_type::SET){ + process_snmp_set_response(val, input_oid); + } + else { + log_error(status, request_type, response); + return false; } } else @@ -208,6 +198,42 @@ namespace tmx::utils return port_; } + void snmp_client::process_snmp_get_response(snmp_response_obj &val, const snmp_pdu &response) const { + for (auto vars = response.variables; vars; vars = vars->next_variable) + { + // Get value of variable depending on ASN.1 type + // Variable could be a integer, string, bitstring, ojbid, counter : defined here https://github.com/net-snmp/net-snmp/blob/master/include/net-snmp/types.h + // get Integer value + if (vars->type == ASN_INTEGER && vars->val.integer) + { + val.type = snmp_response_obj::response_type::INTEGER; + val.val_int = *vars->val.integer; + } + else if (vars->type == ASN_OCTET_STR && vars->val.string) + { + size_t str_len = vars->val_len; + for (size_t i = 0; i < str_len; ++i) + { + val.val_string.push_back(vars->val.string[i]); + } + val.type = snmp_response_obj::response_type::STRING; + } + } + } + + void snmp_client::process_snmp_set_response( const snmp_response_obj &val, const std::string &input_oid) const { + if(val.type == snmp_response_obj::response_type::INTEGER){ + FILE_LOG(logDEBUG) << "Success in SET for OID: " << input_oid << " Value: " << val.val_int << std::endl; + } + + else if(val.type == snmp_response_obj::response_type::STRING){ + FILE_LOG(logDEBUG) << "Success in SET for OID: " << input_oid << " Value:" << std::endl; + for(auto data : val.val_string){ + FILE_LOG(logDEBUG) << data ; + } + } + } + void snmp_client::log_error(const int &status, const request_type &request_type, const snmp_pdu *response) const { diff --git a/src/tmx/TmxUtils/src/SNMPClient.h b/src/tmx/TmxUtils/src/SNMPClient.h index 75eaa9360..f36646a53 100644 --- a/src/tmx/TmxUtils/src/SNMPClient.h +++ b/src/tmx/TmxUtils/src/SNMPClient.h @@ -68,6 +68,18 @@ namespace tmx::utils int snmp_version_ = 3; // default to 3 since previous versions not compatable currently /*Time after which the the snmp request times out*/ int timeout_ = 10000; + /** + * @brief Helper method for populating snmp_respons_obj with SNMP get response. + * @param val response object + * @param response pdu + */ + void process_snmp_get_response(snmp_response_obj &val, const snmp_pdu &response) const; + /** + * @brief Helper method for logging successful SNMP set responses + * @param val response object + * @param input_oid OID + */ + void process_snmp_set_response( const snmp_response_obj &val, const std::string &input_oid) const; public: /** @brief Constructor for Traffic Signal Controller Service client. diff --git a/src/tmx/TmxUtils/test/J2735MessageTest.cpp b/src/tmx/TmxUtils/test/J2735MessageTest.cpp index 1bc605656..609cf456d 100644 --- a/src/tmx/TmxUtils/test/J2735MessageTest.cpp +++ b/src/tmx/TmxUtils/test/J2735MessageTest.cpp @@ -814,7 +814,7 @@ TEST_F(J2735MessageTest, EncodeSDSM) tmx::messages::SdsmEncodedMessage SdsmEncodeMessage; auto _sdsmMessage = new tmx::messages::SdsmMessage(message); tmx::messages::MessageFrameMessage frame_msg(_sdsmMessage->get_j2735_data()); - SdsmEncodeMessage.set_data(TmxJ2735EncodedMessage::encode_j2735_message>(frame_msg)); + SdsmEncodeMessage.set_data(TmxJ2735EncodedMessage::encode_j2735_message>(frame_msg)); free(message); free(frame_msg.get_j2735_data().get()); ASSERT_EQ(41, SdsmEncodeMessage.get_msgId()); diff --git a/src/tmx/TmxUtils/test/test_SNMPClient.cpp b/src/tmx/TmxUtils/test/test_SNMPClient.cpp index 3e775945c..55871477d 100644 --- a/src/tmx/TmxUtils/test/test_SNMPClient.cpp +++ b/src/tmx/TmxUtils/test/test_SNMPClient.cpp @@ -1,7 +1,7 @@ -#include "MockSNMPClient.h" -#include "gtest/gtest.h" -#include "RSU_MIB_4_1.h" +#include +#include +#include using namespace tmx::utils; using namespace std; diff --git a/src/v2i-hub/MUSTSensorDriverPlugin/src/MUSTSensorDriverPlugin.cpp b/src/v2i-hub/MUSTSensorDriverPlugin/src/MUSTSensorDriverPlugin.cpp index 946d67869..024430b4c 100644 --- a/src/v2i-hub/MUSTSensorDriverPlugin/src/MUSTSensorDriverPlugin.cpp +++ b/src/v2i-hub/MUSTSensorDriverPlugin/src/MUSTSensorDriverPlugin.cpp @@ -23,10 +23,6 @@ namespace MUSTSensorDriverPlugin { MUSTSensorDriverPlugin::MUSTSensorDriverPlugin(const string &name): PluginClientClockAware(name) { mustSensorPacketReceiverThread = std::make_unique(std::chrono::milliseconds(5)); - if (PluginClientClockAware::isSimulationMode()) { - PLOG(tmx::utils::logINFO) << "Simulation mode on " << std::endl; - SubscribeToMessages(); - } } void MUSTSensorDriverPlugin::OnStateChange(IvpPluginState state) { diff --git a/src/v2i-hub/MapPlugin/src/MapPlugin.cpp b/src/v2i-hub/MapPlugin/src/MapPlugin.cpp index 7ee8d4ae7..884a02217 100644 --- a/src/v2i-hub/MapPlugin/src/MapPlugin.cpp +++ b/src/v2i-hub/MapPlugin/src/MapPlugin.cpp @@ -132,8 +132,7 @@ namespace MapPlugin { std::unique_ptr msg; int activeAction = -1; - // wait for the clock to be initialized - getClock()->wait_for_initialization(); + while (_plugin->state != IvpPluginState_error) { if (_isMapFileNew) { diff --git a/src/v2i-hub/SpatPlugin/CMakeLists.txt b/src/v2i-hub/SpatPlugin/CMakeLists.txt index bdff7a0b8..fa987d581 100644 --- a/src/v2i-hub/SpatPlugin/CMakeLists.txt +++ b/src/v2i-hub/SpatPlugin/CMakeLists.txt @@ -1,44 +1,20 @@ -PROJECT ( SpatPlugin VERSION 7.6.0 LANGUAGES CXX ) - -SET (TMX_PLUGIN_NAME "SPAT") - -FIND_PACKAGE (XercesC REQUIRED) -FIND_PACKAGE (NetSNMP REQUIRED) -FIND_PACKAGE (carma-clock REQUIRED) - -BuildTmxPlugin () - -TARGET_INCLUDE_DIRECTORIES ( ${PROJECT_NAME} PUBLIC ${XercesC_INCLUDE_DIRS} ${NETSNMP_INCLUDE_DIRS}) -TARGET_LINK_LIBRARIES ( ${PROJECT_NAME} tmxutils ::carma-clock rdkafka++ jsoncpp ${XercesC_LIBRARY} ${NETSNMP_LIBRARIES}) - -################################ -# GTest -################################ -enable_testing() - -add_library(${PROJECT_NAME}_spat_lib src/NTCIP1202.cpp src/signalController.cpp src/PedestrianDetectionForSPAT.cpp) -target_include_directories( ${PROJECT_NAME}_spat_lib PUBLIC - ${PROJECT_SOURCE_DIR}/src - ${XercesC_INCLUDE_DIRS} ${NETSNMP_INCLUDE_DIRS}) -target_link_libraries(${PROJECT_NAME}_spat_lib PUBLIC - tmxutils ::carma-clock ${XercesC_LIBRARY} ${NETSNMP_LIBRARIES}) +project( SpatPlugin VERSION 7.6.0 LANGUAGES CXX ) +set(TMX_PLUGIN_NAME "SPAT") +set(CMAKE_CXX_STANDARD 17) +find_package(carma-clock REQUIRED) +BuildTmxPlugin() +target_link_libraries(${PROJECT_NAME} tmxutils ::carma-clock) +add_library(${PROJECT_NAME}_lib src/NTCIP1202.cpp + src/SignalControllerConnection.cpp) +target_link_libraries(${PROJECT_NAME}_lib PUBLIC tmxutils ::carma-clock ) ############# ## Testing ## ############# +enable_testing() 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) - +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}_spat_lib ${TMXAPI_LIBRARIES} - ${ASN_J2735_LIBRARIES} - ${MYSQL_LIBRARIES} - ${MYSQLCPPCONN_LIBRARIES} - tmxutils - ${UUID_LIBRARY} - gtest) \ No newline at end of file +target_include_directories(${BINARY} PUBLIC src/) +target_link_libraries(${BINARY} PUBLIC ${PROJECT_NAME}_lib gtest) \ No newline at end of file diff --git a/src/v2i-hub/SpatPlugin/README.md b/src/v2i-hub/SpatPlugin/README.md new file mode 100644 index 000000000..f106d1fb3 --- /dev/null +++ b/src/v2i-hub/SpatPlugin/README.md @@ -0,0 +1,82 @@ +# SPaT Plugin Documentation + +## Introduction + +The SPaT Plugin is responsible for receiving information from the Traffic Signal Controller (TSC or SC) necessary for broadcasting Signal Phase and Timing (SPaT) messages. This includes querying any SNMP objects to determine TSC state and listen for any broadcast SPaT information from the TSC. + +## Related Plugins + +A list of plugins related to the SPaT Plugin. + +### Immediate Forward Plugin + +For RSU Immediate Message Forwarding (IMF) functionality forward SPaT (Signal Phase and Timing Messages). + +### Map Plugin + +The SPaT and MAP messages are strongly related in that MAP messages are required to be able to understand SPaT messages. Map +messages offer data about which signal groups impact which lanes in a given intersection. + +## Configuration/Deployment + +This plugin has several configuration parameters. Below these are listed out as together with descriptions on how to set them. + +**Intersection_ID**: The intersection id for SPAT generated by this plugin (Note Only used in SPAT MODE = BINARY). + +**Intersection_Name**: The intersection name for SPAT generated by this plugin (Note Only used in SPAT MODE = BINARY). + +**SignalGroupMapping**: JSON data defining a list of SignalGroups and phases (Note Only used in SPAT MODE = BINARY). + +**Local_IP**: The IPv4 address of the local computer for receiving Traffic Signal Controller Broadcast Messages. + +**Local_UDP_Port**: The local UDP port for reception of Traffic Signal Controller Broadcast Messages from the TSC. + +**TSC_IP**: The IPv4 address of the destination Traffic Signal Controller (TSC). + +**TSC_SNMP_Port**: The destination port on the Traffic Signal Controller (TSC) for SNMP NTCIP 1202 communication." + +**TSC_SNMP_Community**: The SNMP Community used for sending SNMP NTCIP 1202 communication to Traffic Signal Controller (TSC). Please refer TSC vendor documentation for SNMP Community. + +**SPAT_Mode**: The format of received SPAT from Traffic Signal Controller (TSC). Acceptance values are BINARY and J2735_HEX. +> [!NOTE] +> **J2735_HEX** is a new added SPAT format. If your TSC is able to send UPER SPAT directly to an RSU, this is the format in which it is being sent. Below is a screen shot of J2735_HEX SPaT via TCP Dump +![Alt text](docs/hex_tcpdump.png) + +## Design + +![Alt text](docs/spat_plugin_design.png) +The diagram above illustrates roughly how the SPaT Plugin functions. The SPaT Plugin is able to get and set Traffic Signal Controller (TSC or SC) configuration via SNMP requests. The defined objects and behaviour are standardize via NTCIP 1202, the National Transportation Communications for ITS Protocol Object Definitions for Actuated Signal Controllers (ASC) Interface. It also receives live Signal Phase and Timing Data from the TSC via UDP packets. Using this information, the SPaT plugin generates J2725 SPaT messags which are eventually forwarded to and Road Side Unit (RSU) radio for broadcast to actors at or near the intersection. + +### Messages + +**SPAT**: This message contains information from the traffic signal controller about Signal Phase and Timing (SPaT). To use this information for vehicle control the MAP message is also required for mapping signal phase to lanes in an intersection. + +## Functionality Testing + +Testing the functionality of a configured instance of the SPaT plugin requires access to a Traffic Signal Controller (TSC) or Virtual Traffic Signal Controller. + +1) Configure the TSC to broadcast SPAT to an open port your edge device. + 1) Take note of the format of this data. This can be done by consulting the TSC manual or by inspecting it using `tcpdump -i any port -X`.**BINARY** payloads will be complete human unreadable collection of bytes and **J2735_HEX** will include human readable header information like `Type=SPAT`, `PSID=0x8002`, and a `Payload` followed by string HEX of the UPER encoded J2735 SPaT message. +2) Inspect the configurations of your TSC to determine Signal Group to phase mapping and the SNMP port. This can be found either at the **Channel Table** or the **Load Switch Configuration** on the TSC. Using these configurations create your signal group mapping JSON (Example shown below) +```json + +{"SignalGroups": + [ + {"SignalGroupId":1,"Phase":1,"Type":"vehicle"}, + {"SignalGroupId":2,"Phase":2,"Type":"vehicle"}, + {"SignalGroupId":3,"Phase":3,"Type":"vehicle"}, + {"SignalGroupId":4,"Phase":4,"Type":"vehicle"}, + {"SignalGroupId":5,"Phase":5,"Type":"vehicle"}, + {"SignalGroupId":6,"Phase":6,"Type":"vehicle"}, + {"SignalGroupId":7,"Phase":7,"Type":"vehicle"}, + {"SignalGroupId":8,"Phase":8,"Type":"vehicle"}, + {"SignalGroupId":9,"Phase":2,"Type":"pedestrian"}, + {"SignalGroupId":10,"Phase":4,"Type":"pedestrian"}, + {"SignalGroupId":11,"Phase":6,"Type":"pedestrian"}, + {"SignalGroupId":12,"Phase":8,"Type":"pedestrian"} + ] +} +``` + +3) Configure your SPaT Plugin with the information gathers in steps 1 and 2. +4) Check the Status and Messages tabs on the plugin after enabling it. Once enabled the SPAT Plugin should be receive SPAT messages at 10Hz (visible in the Messages tab) and have a status of "CONNECTED" \ No newline at end of file diff --git a/src/v2i-hub/SpatPlugin/docs/hex_tcpdump.png b/src/v2i-hub/SpatPlugin/docs/hex_tcpdump.png new file mode 100644 index 000000000..3e3991b9b Binary files /dev/null and b/src/v2i-hub/SpatPlugin/docs/hex_tcpdump.png differ diff --git a/src/v2i-hub/SpatPlugin/docs/spat_plugin_design.png b/src/v2i-hub/SpatPlugin/docs/spat_plugin_design.png new file mode 100644 index 000000000..de85b4ffe Binary files /dev/null and b/src/v2i-hub/SpatPlugin/docs/spat_plugin_design.png differ diff --git a/src/v2i-hub/SpatPlugin/manifest.json b/src/v2i-hub/SpatPlugin/manifest.json index 2d8b957f4..1297658a9 100644 --- a/src/v2i-hub/SpatPlugin/manifest.json +++ b/src/v2i-hub/SpatPlugin/manifest.json @@ -1,6 +1,6 @@ { "name":"SPAT", - "description":"Plugin that reads PTLM data from a configuration file, receives live data from the signal controller, and publishes a J2735 SPAT message.", + "description":"The SPaT plugin receives live Signal Phase and Timing data from the Traffic Signal Controller and publishes a J2735 SPAT message.", "version":"@PROJECT_VERSION@", "exeLocation":"/bin/SpatPlugin", "coreIpAddr":"127.0.0.1", @@ -10,48 +10,53 @@ "type":"J2735", "subtype":"SPAT-P", "description":"Signal Phase and Timing (SPAT) status for the signalized intersection." - }, - { - "type":"SIGCONT", - "subtype":"ACT", - "description":"Current signal controller action" } ], "configuration":[ { "key":"Intersection_Id", "default":"1", - "description":"The intersection id for SPAT generated by this plugin." + "description":"The intersection id for SPAT generated by this plugin (Note Only used in SPAT MODE = BINARY)." }, { "key":"Intersection_Name", "default":"Intersection", - "description":"The intersection name for SPAT generated by this plugin." + "description":"The intersection name for SPAT generated by this plugin (Note Only used in SPAT MODE = BINARY)." }, { "key":"SignalGroupMapping", "default":"{\"SignalGroups\":[{\"SignalGroupId\":1,\"Phase\":1,\"Type\":\"vehicle\"},{\"SignalGroupId\":2,\"Phase\":2,\"Type\":\"vehicle\"},{\"SignalGroupId\":3,\"Phase\":3,\"Type\":\"vehicle\"},{\"SignalGroupId\":4,\"Phase\":4,\"Type\":\"vehicle\"},{\"SignalGroupId\":5,\"Phase\":5,\"Type\":\"vehicle\"},{\"SignalGroupId\":6,\"Phase\":6,\"Type\":\"vehicle\"},{\"SignalGroupId\":7,\"Phase\":7,\"Type\":\"vehicle\"},{\"SignalGroupId\":8,\"Phase\":8,\"Type\":\"vehicle\"},{\"SignalGroupId\":22,\"Phase\":2,\"Type\":\"pedestrian\"},{\"SignalGroupId\":24,\"Phase\":4,\"Type\":\"pedestrian\"},{\"SignalGroupId\":26,\"Phase\":6,\"Type\":\"pedestrian\"},{\"SignalGroupId\":28,\"Phase\":8,\"Type\":\"pedestrian\"}]}", - "description":"JSON data defining a list of SignalGroups and phases." + "description":"JSON data defining a list of SignalGroups and phases (Note Only used in SPAT MODE = BINARY)." }, { "key":"Local_IP", - "default":"", + "default":"127.0.0.1", "description":"The IPv4 address of the local computer for receiving Traffic Signal Controller Broadcast Messages." }, { "key":"Local_UDP_Port", - "default":"local port", + "default":"6053", "description":"The local UDP port for reception of Traffic Signal Controller Broadcast Messages from the TSC." }, { "key":"TSC_IP", - "default":"", + "default":"127.0.0.1", "description":"The IPv4 address of the destination Traffic Signal Controller (TSC)." }, { - "key":"TSC_Remote_SNMP_Port", - "default":"", - "description":"The destination port on the Traffic Signal Controller (TSC) for SNMP NTCIP communication." + "key":"TSC_SNMP_Port", + "default":"5050", + "description":"The destination port on the Traffic Signal Controller (TSC) for SNMP NTCIP 1202 communication." + }, + { + "key":"TSC_SNMP_Community", + "default":"public", + "description":"The SNMP Community used for sending SNMP NTCIP 1202 communication to Traffic Signal Controller (TSC). Please refer TSC vendor documentation for SNMP Community." + }, + { + "key":"SPAT_Mode", + "default":"BINARY", + "description":"The format of received SPAT from Traffic Signal Controller (TSC). Acceptance values are BINARY and J2735_HEX." }, { "key":"LogLevel", diff --git a/src/v2i-hub/SpatPlugin/src/NTCIP1202.cpp b/src/v2i-hub/SpatPlugin/src/NTCIP1202.cpp index 77cac309c..0e8773629 100644 --- a/src/v2i-hub/SpatPlugin/src/NTCIP1202.cpp +++ b/src/v2i-hub/SpatPlugin/src/NTCIP1202.cpp @@ -1,20 +1,20 @@ -/* - * NTCIP1202.cpp +/** + * Copyright (C) 2024 LEIDOS. * - * Created on: Apr 3, 2017 - * Author: ivp + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. */ -#include -#include #include "NTCIP1202.h" -#include -#include -#include -#include -#include -#include -#include using namespace std; using namespace boost::property_tree; @@ -38,13 +38,13 @@ void Ntcip1202::setSignalGroupMappingList(string json) ptree root; read_json(ss, root); - for(auto & signalGroup : root.get_child("SignalGroups")) + for( const auto &[key, value]: root.get_child("SignalGroups")) { - int signalGroupId = signalGroup.second.get("SignalGroupId"); - int phaseNumber = signalGroup.second.get("Phase", 0); - string typeName = signalGroup.second.get("Type"); + int signalGroupId = value.get("SignalGroupId"); + int phaseNumber = value.get("Phase", 0); + string typeName = value.get("Type"); - PLOG(logDEBUG) <<"signalGroupId: "< lock(_spat_lock); std::memcpy(&ntcip1202Data, buff, numBytes); @@ -186,27 +185,10 @@ bool Ntcip1202::isPhaseFlashing() void Ntcip1202::printDebug() { - //printf("phase %d spatVehMinTimeToChange: %02x\r\n",1, ntcip1202Data.phaseTimes[1].spatVehMinTimeToChange); - - //printf("header: %02x\r\n", ntcip1202Data.header); - //printf("phases: %02x\r\n", ntcip1202Data.numOfPhases); - - /*for(int i=0; i<16; i++) - { - printf("phase %d number: %02x\r\n",i, ntcip1202Data.phaseTimes[i].phaseNumber); - - printf("phase %d spatVehMinTimeToChange: %02x\r\n",i, ntcip1202Data.phaseTimes[i].spatVehMinTimeToChange); - printf("phase %d spatVehMaxTimeToChange: %02x\r\n",i, ntcip1202Data.phaseTimes[i].spatVehMaxTimeToChange); - printf("phase %d spatPedMinTimeToChange: %02x\r\n",i, ntcip1202Data.phaseTimes[i].spatPedMinTimeToChange); - printf("phase %d spatPedMaxTimeToChange: %02x\r\n",i, ntcip1202Data.phaseTimes[i].spatPedMaxTimeToChange); - printf("phase %d spatOvlpMinTimeToChange: %02x\r\n",i, ntcip1202Data.phaseTimes[i].spatOvlpMinTimeToChange); - printf("phase %d spatOvpMaxTimeToChange: %02x\r\n",i, ntcip1202Data.phaseTimes[i].spatOvpMaxTimeToChange); - } -*/ for(int i=0; i<16; i++) { int phaseNum = i+1; - PLOG(logDEBUG3) << "Phase " << phaseNum << + PLOG(logDEBUG) << "Phase " << phaseNum << ", Green " << getPhaseGreensStatus(phaseNum) << ", Yellow " << getPhaseYellowStatus(phaseNum) << ", Red " << getPhaseRedStatus(phaseNum) << @@ -216,9 +198,9 @@ void Ntcip1202::printDebug() } } -bool Ntcip1202::ToJ2735r41SPAT(SPAT* spat, char* intersectionName, IntersectionID_t intersectionId) +void Ntcip1202::ToJ2735SPAT(SPAT* spat, unsigned long msEpoch , const std::string &intersectionName, IntersectionID_t intersectionId) { - time_t epochSec = clock->nowInSeconds(); + time_t epochSec = msEpoch/1000; struct tm utctime; gmtime_r( &epochSec, &utctime ); @@ -227,11 +209,9 @@ bool Ntcip1202::ToJ2735r41SPAT(SPAT* spat, char* intersectionName, IntersectionI long minOfYear = utctime.tm_min + (utctime.tm_hour * 60) + (utctime.tm_yday * 24 * 60); // Calculate the millisecond of the minute - auto epochMs = clock->nowInMilliseconds(); + auto epochMs = msEpoch; long msOfMin = 1000 * (epochSec % 60) + (epochMs % 1000); - std::lock_guard lock(_spat_lock); - ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_SPAT, spat); #if SAEJ2735_SPEC < 63 @@ -242,9 +222,9 @@ bool Ntcip1202::ToJ2735r41SPAT(SPAT* spat, char* intersectionName, IntersectionI intersection->name = (DescriptiveName_t *) calloc(1, sizeof(DescriptiveName_t)); - intersection->name->size = strlen(intersectionName); - intersection->name->buf = (uint8_t *) calloc(1, strlen(intersectionName)); - memcpy(intersection->name->buf, intersectionName, strlen(intersectionName)); + intersection->name->size = intersectionName.length(); + intersection->name->buf = (uint8_t *) calloc(1, intersectionName.length()); + memcpy(intersection->name->buf, intersectionName.c_str(), intersectionName.length()); intersection->id.id = intersectionId; intersection->revision = (MsgCount_t) 1; @@ -276,7 +256,7 @@ bool Ntcip1202::ToJ2735r41SPAT(SPAT* spat, char* intersectionName, IntersectionI MovementState *movement = (MovementState *) calloc(1, sizeof(MovementState)); movement->signalGroup = it->SignalGroupId; - populateVehicleSignalGroup(movement, phase); + populateVehicleSignalGroup(movement, phase, msEpoch); ASN_SEQUENCE_ADD(&intersection->states.list, movement); } @@ -293,7 +273,7 @@ bool Ntcip1202::ToJ2735r41SPAT(SPAT* spat, char* intersectionName, IntersectionI MovementState *movement = (MovementState *) calloc(1, sizeof(MovementState)); movement->signalGroup = it->SignalGroupId; - populatePedestrianSignalGroup(movement, phase); + populatePedestrianSignalGroup(movement, phase, msEpoch); ASN_SEQUENCE_ADD(&intersection->states.list, movement); } @@ -309,7 +289,7 @@ bool Ntcip1202::ToJ2735r41SPAT(SPAT* spat, char* intersectionName, IntersectionI MovementState *movement = (MovementState *) calloc(1, sizeof(MovementState)); movement->signalGroup = it->SignalGroupId; - populateOverlapSignalGroup(movement, phase); + populateOverlapSignalGroup(movement, phase, msEpoch); ASN_SEQUENCE_ADD(&intersection->states.list, movement); } @@ -320,12 +300,9 @@ bool Ntcip1202::ToJ2735r41SPAT(SPAT* spat, char* intersectionName, IntersectionI } ASN_SEQUENCE_ADD(&(spat->intersections.list), intersection); - - - return true; } -void Ntcip1202::populateVehicleSignalGroup(MovementState *movement, int phase) +void Ntcip1202::populateVehicleSignalGroup(MovementState *movement, int phase, unsigned long msEpoch) { MovementEvent *stateTimeSpeed = (MovementEvent *) calloc(1, sizeof(MovementEvent)); @@ -334,10 +311,7 @@ void Ntcip1202::populateVehicleSignalGroup(MovementState *movement, int phase) if(getPhaseRedStatus(phase)) { - PLOG(logDEBUG3) << "Phase " << phase << - " Red " << getPhaseRedStatus(phase) << - ", isFlashing " << isFlashing << - ", forceFlashing " << forceFlashing ; + PLOG(logDEBUG3) << "Phase " << phase << " Red " << getPhaseRedStatus(phase) << ", isFlashing " << isFlashing << ", forceFlashing " << forceFlashing ; if(isFlashing) stateTimeSpeed->eventState = MovementPhaseState_stop_Then_Proceed; else @@ -364,30 +338,20 @@ void Ntcip1202::populateVehicleSignalGroup(MovementState *movement, int phase) } stateTimeSpeed->timing = (TimeChangeDetails * ) calloc(1, sizeof(TimeChangeDetails)); - stateTimeSpeed->timing->minEndTime = getAdjustedTime(getVehicleMinTime(phase)); + stateTimeSpeed->timing->minEndTime = getAdjustedTime(getVehicleMinTime(phase),msEpoch); if (getVehicleMaxTime(phase) > 0) { stateTimeSpeed->timing->maxEndTime = (TimeMark_t *) calloc(1, sizeof(TimeMark_t)); - *(stateTimeSpeed->timing->maxEndTime) = getAdjustedTime(getVehicleMaxTime(phase)); + *(stateTimeSpeed->timing->maxEndTime) = getAdjustedTime(getVehicleMaxTime(phase), msEpoch); } //we only get a phase number 1-16 from ped detect, assume its a ped phase -// if(getSpatPedestrianDetect(phase)) -// { -// movement->maneuverAssistList = (ManeuverAssistList *) calloc(1, sizeof(ManeuverAssistList)); -// ConnectionManeuverAssist *pedDetect = (ConnectionManeuverAssist *) calloc(1, sizeof(ConnectionManeuverAssist)); -// pedDetect->connectionID = 0; -// pedDetect->pedBicycleDetect = (PedestrianBicycleDetect_t *) calloc(1, sizeof(PedestrianBicycleDetect_t)); -// -// *(pedDetect->pedBicycleDetect) = 1; -// ASN_SEQUENCE_ADD(&movement->maneuverAssistList->list, pedDetect); -// } ASN_SEQUENCE_ADD(&movement->state_time_speed.list, stateTimeSpeed); } -void Ntcip1202::populatePedestrianSignalGroup(MovementState *movement, int phase) +void Ntcip1202::populatePedestrianSignalGroup(MovementState *movement, int phase, unsigned long msEpoch) { MovementEvent *stateTimeSpeed = (MovementEvent *) calloc(1, sizeof(MovementEvent)); @@ -409,12 +373,12 @@ void Ntcip1202::populatePedestrianSignalGroup(MovementState *movement, int phase } stateTimeSpeed->timing = (TimeChangeDetails * ) calloc(1, sizeof(TimeChangeDetails)); - stateTimeSpeed->timing->minEndTime = getAdjustedTime(getPedMinTime(phase)); + stateTimeSpeed->timing->minEndTime = getAdjustedTime(getPedMinTime(phase), msEpoch); if (ntcip1202Data.phaseTimes[phase].spatPedMaxTimeToChange > 0) { stateTimeSpeed->timing->maxEndTime = (TimeMark_t *) calloc(1, sizeof(TimeMark_t)); - *(stateTimeSpeed->timing->maxEndTime) = getAdjustedTime(getPedMaxTime(phase)); + *(stateTimeSpeed->timing->maxEndTime) = getAdjustedTime(getPedMaxTime(phase), msEpoch); } if(getSpatPedestrianDetect(phase)) @@ -430,7 +394,7 @@ void Ntcip1202::populatePedestrianSignalGroup(MovementState *movement, int phase ASN_SEQUENCE_ADD(&movement->state_time_speed.list, stateTimeSpeed); } -void Ntcip1202::populateOverlapSignalGroup(MovementState *movement, int phase) +void Ntcip1202::populateOverlapSignalGroup(MovementState *movement, int phase, unsigned long msEpoch) { MovementEvent *stateTimeSpeed = (MovementEvent *) calloc(1, sizeof(MovementEvent)); @@ -465,12 +429,12 @@ void Ntcip1202::populateOverlapSignalGroup(MovementState *movement, int phase) } stateTimeSpeed->timing = (TimeChangeDetails * ) calloc(1, sizeof(TimeChangeDetails)); - stateTimeSpeed->timing->minEndTime = getAdjustedTime(getOverlapMinTime(phase)); + stateTimeSpeed->timing->minEndTime = getAdjustedTime(getOverlapMinTime(phase), msEpoch); if (getOverlapMaxTime(phase) > 0) { stateTimeSpeed->timing->maxEndTime = (TimeMark_t *) calloc(1, sizeof(TimeMark_t)); - *(stateTimeSpeed->timing->maxEndTime) = getAdjustedTime(getOverlapMaxTime(phase)); + *(stateTimeSpeed->timing->maxEndTime) = getAdjustedTime(getOverlapMaxTime(phase), msEpoch); } //we only get a phase number 1-16 from ped detect, assume its a ped phase @@ -518,13 +482,13 @@ int Ntcip1202::getPedestrianSignalGroupForPhase(int phase) return signalGroupId; } -long Ntcip1202::getAdjustedTime(unsigned int offset_tenthofSec) +long Ntcip1202::getAdjustedTime(unsigned int offset_tenthofSec, unsigned long msEpoch) const { // generate J2735 TimeMark which is: // Tenths of a second in the current or next hour // In units of 1/10th second from UTC time // first get new time is absolute milliseconds - auto epochMs = clock->nowInMilliseconds() + (offset_tenthofSec * 100); + auto epochMs = msEpoch + (offset_tenthofSec * 100); // get minute and second of hour from UTC time time_t epochSec = epochMs / 1000; struct tm utctime; diff --git a/src/v2i-hub/SpatPlugin/src/NTCIP1202.h b/src/v2i-hub/SpatPlugin/src/NTCIP1202.h index b3bc9a549..2ebb279a8 100644 --- a/src/v2i-hub/SpatPlugin/src/NTCIP1202.h +++ b/src/v2i-hub/SpatPlugin/src/NTCIP1202.h @@ -1,19 +1,35 @@ -/* - * NTCIP1202.h +/** + * Copyright (C) 2024 LEIDOS. * - * Created on: Apr 3, 2017 - * Author: ivp + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. */ -#ifndef SRC_NTCIP1202_H_ -#define SRC_NTCIP1202_H_ +#pragma once #include #include #include +#include +#include +#include +#include +#include +#include -#include "carma-clock/carma_clock.h" +#include +#include +#include using namespace std; @@ -68,8 +84,6 @@ struct SignalGroupMapping class Ntcip1202 { public: - inline explicit Ntcip1202(std::shared_ptr clock) : - clock(clock) {}; void setSignalGroupMappingList(string json); void copyBytesIntoNtcip1202(char* buff, int numBytes); @@ -100,32 +114,29 @@ class Ntcip1202 uint16_t getOverlapMinTime(int phaseNumber); uint16_t getOverlapMaxTime(int phaseNumber); - long getAdjustedTime(unsigned int offset); + long getAdjustedTime(unsigned int offset_tenthofSec, unsigned long msEpoch) const; bool isFlashingStatus(); bool isPhaseFlashing(); - bool ToJ2735r41SPAT(SPAT* spat, char* intersectionName, IntersectionID_t intersectionId); + void ToJ2735SPAT(SPAT* spat, unsigned long msEpoch , const std::string &intersectionName, IntersectionID_t intersectionId); void printDebug(); private: - std::shared_ptr clock; Ntcip1202Ext ntcip1202Data; std::map _phaseToIndexMapping; - std::mutex _spat_lock; list signalGroupMappingList; int getVehicleSignalGroupForPhase(int phase); int getPedestrianSignalGroupForPhase(int phase); - void populateVehicleSignalGroup(MovementState *movement, int phase); - void populatePedestrianSignalGroup(MovementState *movement, int phase); - void populateOverlapSignalGroup(MovementState *movement, int phase); + void populateVehicleSignalGroup(MovementState *movement, int phase, unsigned long msEpoch); + void populatePedestrianSignalGroup(MovementState *movement, int phase, unsigned long msEpoch); + void populateOverlapSignalGroup(MovementState *movement, int phase, unsigned long msEpoch); }; -#endif /* SRC_NTCIP1202_H_ */ diff --git a/src/v2i-hub/SpatPlugin/src/NTCIP1202OIDs.h b/src/v2i-hub/SpatPlugin/src/NTCIP1202OIDs.h new file mode 100644 index 000000000..1d4789f1d --- /dev/null +++ b/src/v2i-hub/SpatPlugin/src/NTCIP1202OIDs.h @@ -0,0 +1,29 @@ +/** + * Copyright (C) 2024 LEIDOS. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +#pragma once + +#include +/** + * @brief Contains OIDs (Object Identifier) described in NTCIP 1202 V3 (National Transporation Communication for ITS Protocol Object + * Definitions for Actuated Signal Controllers (ASC) Interface) used for SPaT Plugin functionality. + */ +namespace NTCIP1202V2{ + /** + * @brief OID for ENABLE_SPAT + */ + static const std::string ENABLE_SPAT_OID = "1.3.6.1.4.1.1206.3.5.2.9.44.1.0"; +} \ No newline at end of file diff --git a/src/v2i-hub/SpatPlugin/src/PedestrianDetectionForSPAT.cpp b/src/v2i-hub/SpatPlugin/src/PedestrianDetectionForSPAT.cpp deleted file mode 100644 index 235618446..000000000 --- a/src/v2i-hub/SpatPlugin/src/PedestrianDetectionForSPAT.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include "PedestrianDetectionForSPAT.h" - -#include - -using namespace tmx::messages; - -void PedestrianDetectionForSPAT::updateEncodedSpat(SpatEncodedMessage & spatEncodedMsg, - std::shared_ptr _spatMessage, - const std::string & currentPedLanes) const -{ - // Add pedestrian lanes with active detections and clear the rest - auto spat = _spatMessage->get_j2735_data(); - // check for valid SPAT and also if ped lanes is filled in - if (spat && spat->intersections.list.array && spat->intersections.list.count > 0 && currentPedLanes.length()) { - // parse ped zone string into set of int values - std::vector zones; - { - std::vector zoneList(currentPedLanes.length()); - std::copy(currentPedLanes.begin(), currentPedLanes.end(), zoneList.begin()); - char *restOfString = nullptr; - auto c = strtok_r(zoneList.data(), ",", &restOfString); - - while (c != nullptr) { - zones.push_back(strtol(c, nullptr, 0)); - c = strtok_r(nullptr, ",", &restOfString); - } - } - - if (!zones.empty()) { - ManeuverAssistList *&mas = spat->intersections.list.array[0]->maneuverAssistList; - mas = (ManeuverAssistList *) calloc(1, sizeof(ManeuverAssistList)); - std::sort(zones.begin(), zones.end()); - // add a connection maneuver for each ped zone and set the ped detect flag - std::for_each(zones.begin(), zones.end(), [&mas](LaneConnectionID_t connectionId) - { - auto connectionManManeuverAssist = (ConnectionManeuverAssist *) calloc(1, sizeof(ConnectionManeuverAssist)); - connectionManManeuverAssist->connectionID = connectionId; - connectionManManeuverAssist->pedBicycleDetect = (PedestrianBicycleDetect_t *) calloc(1, sizeof(PedestrianBicycleDetect_t)); - *(connectionManManeuverAssist->pedBicycleDetect) = 1; - ASN_SEQUENCE_ADD(mas, connectionManManeuverAssist); - } - ); - } - } - - MessageFrameMessage frame(_spatMessage->get_j2735_data()); - spatEncodedMsg.set_data(TmxJ2735EncodedMessage::encode_j2735_message>(frame)); - //Free the memory allocated for MessageFrame - free(frame.get_j2735_data().get()); -} diff --git a/src/v2i-hub/SpatPlugin/src/PedestrianDetectionForSPAT.h b/src/v2i-hub/SpatPlugin/src/PedestrianDetectionForSPAT.h deleted file mode 100644 index 3d9ed440f..000000000 --- a/src/v2i-hub/SpatPlugin/src/PedestrianDetectionForSPAT.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include - -/** - * A helper class to add pedestrian detection elements to a SPAT message. -*/ -class PedestrianDetectionForSPAT -{ -public: - void updateEncodedSpat(tmx::messages::SpatEncodedMessage & spatEncodedMsg, - std::shared_ptr _spatMessage, - const std::string & currentPedLanes) const; -}; diff --git a/src/v2i-hub/SpatPlugin/src/SignalControllerConnection.cpp b/src/v2i-hub/SpatPlugin/src/SignalControllerConnection.cpp new file mode 100644 index 000000000..d5579886e --- /dev/null +++ b/src/v2i-hub/SpatPlugin/src/SignalControllerConnection.cpp @@ -0,0 +1,87 @@ +/** + * Copyright (C) 2024 LEIDOS. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +#include "SignalControllerConnection.h" + +namespace SpatPlugin { + + SignalControllerConnection::SignalControllerConnection(const std::string &localIp, unsigned int localPort, const std::string &signalGroupMapping, const std::string &scIp, unsigned int scSNMPPort, const std::string &scSNMPCommunity, const std::string &intersectionName, unsigned int intersectionId) : spatPacketReceiver(std::make_shared(localIp, localPort)) ,scSNMPClient(std::make_shared(scIp, scSNMPPort ,scSNMPCommunity, "", "", "")), signalGroupMapping(signalGroupMapping), intersectionName(intersectionName), intersectionId(intersectionId) { + + }; + bool SignalControllerConnection::initializeSignalControllerConnection(bool enable_spat) const { + // TODO : Update to more generic TSC Initialization process that simply follows NTCIP 1202 version guidelines. Also + // set intersection ID in J2735_HEX SPAT Mode. The HEX payload should include a intersection ID. + bool status = true; + if (enable_spat) + { + // For binary SPAT a value of 2 enables original SPAT binary broadcast on the TSC and a value of 6 enables original SPAT plugin additional Pedestrian Information. + // NOTE: Pedestrian information is untested. + tmx::utils::snmp_response_obj enable_spat_resp; + enable_spat_resp.val_int = 2; + enable_spat_resp.type = tmx::utils::snmp_response_obj::response_type::INTEGER; + status = status && scSNMPClient->process_snmp_request(NTCIP1202V2::ENABLE_SPAT_OID, tmx::utils::request_type::SET, enable_spat_resp); + } + + return status; + }; + + void SignalControllerConnection::receiveBinarySPAT(const std::shared_ptr &spat, uint64_t timeMs ) const { + FILE_LOG(tmx::utils::logDEBUG) << "Receiving binary SPAT ..." << std::endl; + char buf[SPAT_BINARY_BUFFER_SIZE]; + auto numBytes = spatPacketReceiver->TimedReceive(buf, SPAT_BINARY_BUFFER_SIZE, UDP_SERVER_TIMEOUT_MS); + if (numBytes > 0) + { + // Convert Binary buffer to SPAT pointer + Ntcip1202 ntcip1202; + ntcip1202.setSignalGroupMappingList(this->signalGroupMapping); + ntcip1202.copyBytesIntoNtcip1202(buf, numBytes); + ntcip1202.ToJ2735SPAT(spat.get(),timeMs, intersectionName, intersectionId); + if (tmx::utils::FILELog::ReportingLevel() >= tmx::utils::logDEBUG) + { + xer_fprint(stdout, &asn_DEF_SPAT, spat.get()); + } + } + else { + throw tmx::utils::UdpServerRuntimeError("UDP Server error occured or socket time out."); + } + } + + void SignalControllerConnection::receiveUPERSPAT(std::shared_ptr &spatEncoded_ptr) const { + FILE_LOG(tmx::utils::logDEBUG1) << "Receiving J2725 HEX SPAT ..." << std::endl; + auto payload = spatPacketReceiver->stringTimedReceive( UDP_SERVER_TIMEOUT_MS ); + auto index = payload.find("Payload="); + if ( index != std::string::npos ) { + // Retreive hex string payload + auto hex = payload.substr(index + 8); + // Remove new lines and empty space + hex.erase(std::remove(hex.begin(), hex.end(), '\n'), hex.end()); + hex.erase(std::remove(hex.begin(), hex.end(), ' '), hex.end()); + FILE_LOG(tmx::utils::logDEBUG1) << "Reading HEX String " << hex << std::endl; + // Convert to byte stream + tmx::byte_stream bytes = tmx::byte_stream_decode(hex); + // Read SpateEncodedMessage from bytes + tmx::messages::J2735MessageFactory myFactory; + spatEncoded_ptr.reset(dynamic_cast(myFactory.NewMessage(bytes))); + if (tmx::utils::FILELog::ReportingLevel() >= tmx::utils::logDEBUG) + { + xer_fprint(stdout, &asn_DEF_SPAT, spatEncoded_ptr->decode_j2735_message().get_j2735_data().get()); + } + } + else { + throw tmx::TmxException("Could not find UPER Payload in received SPAT UDP Packet!"); + } + } + +} \ No newline at end of file diff --git a/src/v2i-hub/SpatPlugin/src/SignalControllerConnection.h b/src/v2i-hub/SpatPlugin/src/SignalControllerConnection.h new file mode 100644 index 000000000..8370d8f58 --- /dev/null +++ b/src/v2i-hub/SpatPlugin/src/SignalControllerConnection.h @@ -0,0 +1,104 @@ +/** + * Copyright (C) 2024 LEIDOS. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "NTCIP1202.h" +#include "NTCIP1202OIDs.h" + + + +namespace SpatPlugin { + /** + * @brief Class to represent Traffic Signal Controller (TSC or SC) connection. Includes a UDP Server for listening for + * SPaT data broadcast from TSC. Also includes an SNMP Client for request or setting status of SNMP objects described + * in NTCIP 1202 (Object Definitions for Actuated Signal Controllers). + */ + class SignalControllerConnection + { + private: + /** + * @brief UDP Server for receiving SPaT packets from TSC. + */ + std::shared_ptr spatPacketReceiver; + /** + * @brief SNMP Client for requesting or setting status of SNMP Objects on + * TSC. + */ + std::shared_ptr scSNMPClient; + /** + * @brief String that describes phase to signal group mapping configured on + * the TSC. TODO: Remove in place of SNMP requests on tables. + */ + std::string signalGroupMapping; + /** + * @brief String name of intersection in SPaT messages. + */ + std::string intersectionName; + /** + * @brief Numeric identifier for intersection in SPaT messages. + */ + unsigned int intersectionId; + + const static unsigned int SPAT_BINARY_BUFFER_SIZE = 1000; + + const static unsigned int UDP_SERVER_TIMEOUT_MS = 1000; + + friend class TestSignalControllerConnection; + + public: + /** + * @brief Constructor for Signal Controller Connection. + * @param localIp IP address of device connecting to signal controller. This will be the IP of the UDP Server listening for SPaT data. + * @param localPort port on which to listen for SPaT data. + * @param signalGroupMapping JSON mapping of phases to signal groups + * @param scIp IP address of TSC + * @param scSNMPPort port of SNMP Server on TSC + * @param intersectionName Name of intersection + * @param intersectionID Intersection ID. + */ + SignalControllerConnection(const std::string &localIp, unsigned int localPort, const std::string &signalGroupMapping, const std::string &scIp, unsigned int scSNMPPort, const std::string &scSNMPCommunity, const std::string &intersectionName, unsigned int intersectionID); + + /** + * @brief Method attempts to send SNMP SET requests to initialize the TSC. NOTE: Some of the + * OIDs in called in this initialize method may not be exposed by a TSC depending on which + * version of the NTCIP 1202 standard they are complaint with. To avoid failures please use + * the bool flags to indicate which OIDs need to be set for initialization. + * @param enable_spat bool flag on whether to attempt to set enable spat to true (NOT available for NTCIP 1202 versions >= 3 ) + * @return true if successful and false if not. + */ + bool initializeSignalControllerConnection(bool enable_spat) const; + /** + * @brief Method to receive SPaT data in binary format from TSC. + * @param spat an empty SPaT pointer to which the SPAT data will be written. + * @param timeMs current time in ms from epoch to use for message timestamp. + */ + void receiveBinarySPAT(const std::shared_ptr &spat, uint64_t timeMs) const; + /** + * @brief Method to receive SPaT data in UPER Hex format from TSC. + * @param spatEncoded_ptr Empty SpatEncodedMessage to which the UPER encoded SPaT data will be written. + */ + void receiveUPERSPAT(std::shared_ptr &spatEncoded_ptr) const; + }; +} \ No newline at end of file diff --git a/src/v2i-hub/SpatPlugin/src/SpatPlugin.cpp b/src/v2i-hub/SpatPlugin/src/SpatPlugin.cpp index c47a4863f..1a7b9ca96 100644 --- a/src/v2i-hub/SpatPlugin/src/SpatPlugin.cpp +++ b/src/v2i-hub/SpatPlugin/src/SpatPlugin.cpp @@ -1,6 +1,20 @@ +/** + * Copyright (C) 2024 LEIDOS. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ #include "SpatPlugin.h" -#include using namespace std; using namespace tmx::messages; @@ -8,170 +22,131 @@ using namespace tmx::utils; namespace SpatPlugin { -SpatPlugin::SpatPlugin(string name) : - PluginClientClockAware(name), sc(getClock()), intersectionId(0) { - AddMessageFilter(this, &SpatPlugin::HandlePedestrianDetection); - SubscribeToMessages(); -} - -SpatPlugin::~SpatPlugin() { - -} - -void SpatPlugin::UpdateConfigSettings() { - - GetConfigValue("SignalGroupMapping", signalGroupMappingJson, &data_lock); - GetConfigValue("Local_IP", localIp, &data_lock); - GetConfigValue("Local_UDP_Port", localUdpPort, &data_lock); - GetConfigValue("TSC_IP", tscIp, &data_lock); - GetConfigValue("TSC_Remote_SNMP_Port", tscRemoteSnmpPort, - &data_lock); - GetConfigValue("Intersection_Name", intersectionName, - &data_lock); - GetConfigValue("Intersection_Id", intersectionId, &data_lock); - - isConfigurationLoaded = true; -} - -void SpatPlugin::OnConfigChanged(const char *key, const char *value) { - PluginClient::OnConfigChanged(key, value); - UpdateConfigSettings(); -} - -void SpatPlugin::OnStateChange(IvpPluginState state) { - PluginClientClockAware::OnStateChange(state); - - if (state == IvpPluginState_registered) { - UpdateConfigSettings(); + SpatPlugin::SpatPlugin(const std::string &name) :PluginClientClockAware(name) { + spatReceiverThread = std::make_unique(std::chrono::milliseconds(5)); } -} -void SpatPlugin::HandlePedestrianDetection(PedestrianMessage &pedMsg, routeable_message &routeableMsg) { - lock_guard lock(data_lock); - _pedMessage = pedMsg; -} - -int SpatPlugin::Main() { - - int iCounter = 0; - - PLOG(logINFO) << "Waiting for clock initialization"; - - // wait for the clock to be initialized and record the time when it is ready - getClock()->wait_for_initialization(); - auto nextSpatTime = getClock()->nowInMilliseconds(); - PLOG(logINFO) << "Initial nextSpatTime=" << nextSpatTime; - - try { - while (_plugin->state != IvpPluginState_error) { - // wait to send next message - if (isConfigurationLoaded) { - if (!isConfigured) { - - usleep(200000); + SpatPlugin::~SpatPlugin() { - int action = sc.getActionNumber(); - - /*pthread_mutex_lock(&gSettingsMutex); - std::cout << "Get PTLM file specified by configuration settings" << std::endl; - std::string ptlmFile = GetPtlmFile(action); - pthread_mutex_unlock(&gSettingsMutex); - */ - //if (!ptlmFile.empty()) - //{ - _actionNumber = action; - - // sc.spat_message_mutex does not need locked because the thread is not running yet. - - { - std::lock_guard lock(data_lock); - string ptlm = ""; - sc.setConfigs(localIp, localUdpPort, tscIp, - tscRemoteSnmpPort, ptlm, intersectionName, - intersectionId); - } - // Start the signal controller thread. - sc.Start(signalGroupMappingJson); - // Give the spatdata pointer to the message class - //smr41.setSpatData(sc.getSpatData()); + } - isConfigured = true; - //} + void SpatPlugin::UpdateConfigSettings() { + + if (this->IsPluginState(IvpPluginState_registered)) { + std::string signal_group_mapping_json; + std::string ip_address; + unsigned int port; + std::string signal_controller_ip; + unsigned int signal_controller_snmp_port; + std::string signal_controller_snmp_community; + std::string intersection_name; + unsigned int intersection_id; + GetConfigValue("SignalGroupMapping", signal_group_mapping_json, &data_lock); + GetConfigValue("Local_IP", ip_address, &data_lock); + GetConfigValue("Local_UDP_Port", port, &data_lock); + GetConfigValue("TSC_IP", signal_controller_ip, &data_lock); + GetConfigValue("TSC_SNMP_Port", signal_controller_snmp_port,&data_lock); + GetConfigValue("TSC_SNMP_Community", signal_controller_snmp_community,&data_lock); + + GetConfigValue("Intersection_Name", intersection_name,&data_lock); + GetConfigValue("Intersection_Id", intersection_id, &data_lock); + GetConfigValue("SPAT_Mode", spatMode, &data_lock); + + if (scConnection) { + scConnection.reset(new SignalControllerConnection(ip_address, port, signal_group_mapping_json, signal_controller_ip, signal_controller_snmp_port, signal_controller_snmp_community ,intersection_name, intersection_id)); + } + else { + scConnection = std::make_unique(ip_address, port, signal_group_mapping_json, signal_controller_ip, signal_controller_snmp_port,signal_controller_snmp_community, intersection_name, intersection_id); + } + // Only enable spat broadcast in simulation mode. TFHRC TSCs do not expose this OID so calls to it will fail in hardware deployment + auto connected = scConnection->initializeSignalControllerConnection(PluginClientClockAware::isSimulationMode()); + if ( connected ) { + SetStatus(keyConnectionStatus, "IDLE"); + try { + spatReceiverThread->AddPeriodicTick([this]() + { + this->processSpat(); + if (!this->isConnected) { + SetStatus(keyConnectionStatus, "CONNECTED"); + this->isConnected = true; + } + }, // end of lambda expression + std::chrono::milliseconds(5) + ); + + spatReceiverThread->Start(); } + catch (const TmxException &e) { + PLOG(tmx::utils::logERROR) << "Encountered error " << e.what() << " during SPAT Processing." << std::endl + << e.GetBacktrace(); + SetStatus(keyConnectionStatus, "DISCONNECTED"); + this->isConnected = false; - // SPaT must be sent exactly every 100 ms. So adjust for how long it took to do the last send. - nextSpatTime += 100; - getClock()->sleep_until(nextSpatTime); - - iCounter++; - - bool messageSent = false; - - // Update PTLM file if the action number has changed. - int actionNumber = sc.getActionNumber(); - if (_actionNumber != actionNumber) { - _actionNumber = actionNumber; - - //pthread_mutex_lock(&gSettingsMutex); - //std::string ptlmFile = GetPtlmFile(_actionNumber); - //pthread_mutex_unlock(&gSettingsMutex); - - /*if (!ptlmFile.empty()) - { - pthread_mutex_lock(&sc.spat_message_mutex); - sc.updatePtlmFile(ptlmFile.c_str()); - pthread_mutex_unlock(&sc.spat_message_mutex); - }*/ } - if (sc.getIsConnected()) { - SetStatus("TSC Connection", "Connected"); - - // Add pedestrian detection - string pedZones; - { - lock_guard lock(data_lock); - pedZones = _pedMessage.get_DetectionZones(); - } - if (!pedZones.empty()) { - PLOG(logDEBUG) << "Pedestrians detected in lanes " << pedZones; - } - - SpatEncodedMessage spatEncodedMsg; - sc.getEncodedSpat(&spatEncodedMsg, pedZones); - - spatEncodedMsg.set_flags(IvpMsgFlags_RouteDSRC); - spatEncodedMsg.addDsrcMetadata(0x8002); - - //PLOG(logDEBUG) << spatEncodedMsg; + } + else { + PLOG(tmx::utils::logERROR) << "Traffic Signal Controller at " << signal_controller_ip << ":" << signal_controller_snmp_port << " failed!"; + SetStatus(keyConnectionStatus, "DISCONNECTED"); + this->isConnected = false; - BroadcastMessage(static_cast(spatEncodedMsg)); + } + } + } - if (iCounter % 20 == 0) { - iCounter = 0; - // Action Number - IvpMessage *actionMsg = ivpSigCont_createMsg( - sc.getActionNumber()); - if (actionMsg != NULL) { - ivp_broadcastMessage(_plugin, actionMsg); - ivpMsg_destroy(actionMsg); - } + void SpatPlugin::processSpat() { + if (this->scConnection ) { + PLOG(tmx::utils::logDEBUG) << "Processing SPAT ... " << std::endl; + try { + + if (spatMode == "J2735_HEX") { + auto spatEncoded_ptr = std::make_shared(); + scConnection->receiveUPERSPAT(spatEncoded_ptr); + spatEncoded_ptr->set_flags(IvpMsgFlags_RouteDSRC); + spatEncoded_ptr->addDsrcMetadata(tmx::messages::api::msgPSID::signalPhaseAndTimingMessage_PSID); + auto rMsg = dynamic_cast(spatEncoded_ptr.get()); + BroadcastMessage(*rMsg); + } + else { + if ( spatMode != "BINARY"){ + PLOG(tmx::utils::logWARNING) << spatMode << " is an unsupport SPAT MODE. Defaulting to BINARY. Supported options are BINARY and J2735_HEX"; } - - } else { - SetStatus("TSC Connection", "Disconnected"); + auto spat_ptr = std::make_shared(); + PLOG(logDEBUG) << "Starting SPaT Receiver ..."; + scConnection->receiveBinarySPAT(spat_ptr, PluginClientClockAware::getClock()->nowInMilliseconds()); + tmx::messages::SpatMessage _spatMessage(spat_ptr); + auto spatEncoded_ptr = std::make_shared(); + spatEncoded_ptr->initialize(_spatMessage,"", 0U, IvpMsgFlags_RouteDSRC); + spatEncoded_ptr->addDsrcMetadata(tmx::messages::api::msgPSID::signalPhaseAndTimingMessage_PSID); + auto rMsg = dynamic_cast(spatEncoded_ptr.get()); + BroadcastMessage(*rMsg); } } + catch (const UdpServerRuntimeError &e) { + PLOG(tmx::utils::logWARNING) << "Encountered UDP Server Runtime Error" << e.what() << " attempting to process SPAT." << std::endl + << e.GetBacktrace(); + } + catch (const tmx::TmxException &e) { + PLOG(tmx::utils::logERROR) << "Encountered Tmx Exception " << e.what() << " attempting to process SPAT." << std::endl + << e.GetBacktrace(); + skippedMessages++; + SetStatus(keySkippedMessages, skippedMessages); + } + } - } catch (exception &ex) { - stringstream ss; - ss << "SpatPlugin terminating from unhandled exception: " << ex.what(); + } + void SpatPlugin::OnConfigChanged(const char *key, const char *value) { + PluginClient::OnConfigChanged(key, value); + UpdateConfigSettings(); + } - ivp_addEventLog(_plugin, IvpLogLevel_error, ss.str().c_str()); - std::terminate(); + void SpatPlugin::OnStateChange(IvpPluginState state) { + PluginClientClockAware::OnStateChange(state); + + if (state == IvpPluginState_registered) { + UpdateConfigSettings(); + } } - return EXIT_SUCCESS; -} } /* End namespace SpatPlugin */ diff --git a/src/v2i-hub/SpatPlugin/src/SpatPlugin.h b/src/v2i-hub/SpatPlugin/src/SpatPlugin.h index 0d0b9987f..bb3bc9182 100644 --- a/src/v2i-hub/SpatPlugin/src/SpatPlugin.h +++ b/src/v2i-hub/SpatPlugin/src/SpatPlugin.h @@ -1,78 +1,101 @@ -/* - * SpatPlugin.h +/** + * Copyright (C) 2024 LEIDOS. * - * Created on: April 20, 2017 - * Author: zinkg + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. */ - -#ifndef SPATPLUGIN_H_ -#define SPATPLUGIN_H_ +#pragma once #include #include #include #include #include -#include "PluginClientClockAware.h" -#include "UdpClient.h" -#include "signalController.h" - -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include "SignalControllerConnection.h" namespace SpatPlugin { - -class SpatPlugin: public tmx::utils::PluginClientClockAware { - -public: - - SpatPlugin(std::string name); - virtual ~SpatPlugin(); - virtual int Main(); - - void HandlePedestrianDetection(tmx::messages::PedestrianMessage &pedMsg, tmx::routeable_message &routeableMsg); - -protected: - - void UpdateConfigSettings(); - - // Virtual method overrides. - void OnConfigChanged(const char *key, const char *value); - void OnStateChange(IvpPluginState state); - - -private: - - - unsigned char derEncoded[4000]; - unsigned int derEncodedBytes = 0; - - SignalController sc; - int _actionNumber = -1; - - std::mutex data_lock; - std::string localIp; - std::string localUdpPort; - std::string tscIp; - std::string tscRemoteSnmpPort; - std::string signalGroupMappingJson; - - std::string intersectionName; - - int intersectionId; - - bool isConfigurationLoaded = false; - bool isConfigured = false; - - bool encodeSpat(); - bool createUPERframe_DERencoded_msg(); - - tmx::messages::PedestrianMessage _pedMessage; -}; + /** + * @brief The SPaT Plugin is responsible for receiving information from the Traffic Signal Controller (TSC or SC) necessary + * for broadcasting Signal Phase and Timing (SPaT) messages. This includes querying any SNMP objects to determine + * TSC state and listen for any broadcast SPaT information from the TSC. + */ + class SpatPlugin: public tmx::utils::PluginClientClockAware { + + public: + /** + * @brief Plugin Constructor. + * @param name Plugin Name. + */ + explicit SpatPlugin(const std::string &name); + /** + * @brief Plugin Destructor + */ + virtual ~SpatPlugin(); + + + protected: + /** + * @brief Method to update plugin after configuration settings have changed. + */ + void UpdateConfigSettings(); + + // Virtual method overrides. + void OnConfigChanged(const char *key, const char *value) override; + void OnStateChange(IvpPluginState state) override; + + private: + /** + * @brief Mutex for thread safety for configuration parameters. + */ + std::mutex data_lock; + /** + * @brief Thread timer used to periodically consume broadcast SPaT + * data from the TSC . + */ + std::unique_ptr spatReceiverThread; + /** + * @brief TSC Connection. + */ + std::unique_ptr scConnection; + /** + * @brief String describing the expected format of received SPaT data. + */ + std::string spatMode = ""; + /** + * @brief Key for state object describing TSC Connection Status. + */ + const char* keyConnectionStatus = "Connection Status"; + /** + * @brief Key for counting the number of received packets from TSC that + * have been skipped due to errors. + */ + const char* keySkippedMessages = "Skipped Messages"; + /** + * @brief Count of received packets from the TSC that have been skipped due to + * errors. + */ + uint skippedMessages = 0; + /** + * @brief Bool flag for TSC connection status. + */ + bool isConnected = false; + /** + * @brief Method to receive and process TSC broadcast SPaT data. + */ + void processSpat(); + }; } /* namespace SpatPlugin */ -#endif /* SPATPLUGIN_H_ */ diff --git a/src/v2i-hub/SpatPlugin/src/signalController.cpp b/src/v2i-hub/SpatPlugin/src/signalController.cpp deleted file mode 100644 index c75ef757a..000000000 --- a/src/v2i-hub/SpatPlugin/src/signalController.cpp +++ /dev/null @@ -1,289 +0,0 @@ -/* - ============================================================================ - Name : snmpClient.cpp - Author : William Gibbs - Version : - Copyright : Battelle - Description : Query Signal Controller and populate the SPaT message - ============================================================================ - */ -#include -#include -#include -#include - -#include -#include -#ifndef __CYGWIN__ -#include -#endif -#include -#include -#include - -#include - -#include "signalController.h" - -#include "NTCIP1202.h" -#include "PedestrianDetectionForSPAT.h" - -using namespace tmx::messages; -using namespace tmx::utils; -using namespace std; - -SignalController::~SignalController() { - SNMPCloseSession(); -} - -void SignalController::Start(std::string signalGroupMappingJson) -{ - _signalGroupMappingJson = signalGroupMappingJson; - - // Create mutex for the Spat message - pthread_mutex_init(&spat_message_mutex, nullptr); - // launch update thread - sigcon_thread_id = boost::thread(&SignalController::start_signalController, this); - // test code - normalstate = 0x01; - crossstate = 0x04; -} - -// get sockaddr, IPv4 or IPv6: -void *SignalController::get_in_addr(struct sockaddr *sa) -{ - if (sa->sa_family == AF_INET) { - return &(((struct sockaddr_in*)sa)->sin_addr); - } - - return &(((struct sockaddr_in6*)sa)->sin6_addr); -} - -void SignalController::setConfigs(std::string localIp, std::string localUdpPort, std::string tscIp, std::string tscRemoteSnmpPort, std::string ptlmFile, std::string intersectionName, int intersectionId) -{ - _localIp = strdup(localIp.c_str()); - _localUdpPort = strdup(localUdpPort.c_str()); - _intersectionId = intersectionId; - _intersectionName = strdup(intersectionName.c_str()); - _tscIp = tscIp; - _tscRemoteSnmpPort = stoi(tscRemoteSnmpPort); -} - -void SignalController::start_signalController() -{ -#ifndef __CYGWIN__ - prctl(PR_SET_NAME, "SpatGenSC", 0, 0, 0); -#endif - - pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,nullptr); - pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,nullptr); - - int maxDataSize = 1000; - - int sockfd, numbytes; - char buf[maxDataSize]; - struct addrinfo hints, *servinfo; - int rv; - struct timeval tv; - int on = 1; - int errnoVal; - - //Enable SPAT - // 0 = disable - // 2 = enable SPAT - // 6 = enable SPAT wit pedestrian data - PLOG(logINFO) << "Enable SPAT Sent"; - SNMPSet("1.3.6.1.4.1.1206.3.5.2.9.44.1.0", 2); - SNMPCloseSession(); - - // Create UDP Socket - memset(&hints, 0, sizeof hints); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_DGRAM; - hints.ai_protocol = IPPROTO_UDP; - hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; - - EthernetIsConnected = 0; - IsReceiving = 0; - - while (1) { - PLOG(logDEBUG) << "Top of While Loop"; - if ((rv = getaddrinfo(_localIp, _localUdpPort, &hints, &servinfo)) != 0) { - PLOG(logERROR) << "Getaddrinfo Failed " << _localIp << " " << _localUdpPort << ". Exiting thread!!!"; - return; - } - PLOG(logDEBUG) << "Getting Socket"; - if ((sockfd = socket(servinfo->ai_family, servinfo->ai_socktype, servinfo->ai_protocol)) == -1) { - PLOG(logERROR) << "Get Socket Failed " << _localIp << " " << _localUdpPort << ". Exiting thread!!!"; - return; - } - - rv = setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)); - - // Set the socket to time out on reads if no data comes in during the timeout value then the socket will close and - // then try to re-open during the normal execution - // Wait up to 10 seconds. - tv.tv_sec = 10; - tv.tv_usec = 0; - setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO,(struct timeval *)&tv,sizeof(struct timeval)); - - if (bind(sockfd,servinfo->ai_addr,servinfo->ai_addrlen)==-1) { - PLOG(logERROR) << "Could not bind to Socket " << _localIp << " " << _localUdpPort << ". Exiting thread!!!"; - return; - } - - if (servinfo == nullptr) { - PLOG(logERROR) << "Could not connect"; - EthernetIsConnected = 0; - } - else { - PLOG(logDEBUG) << "Connected"; - EthernetIsConnected = 1; - } - - if (EthernetIsConnected) { - //printf("Signal Controller UDP Client Connected to %s:%s\n",_localIp, _localUdpPort); - freeaddrinfo(servinfo); // all done with this structure -// client_socket = sockfd; - // Receive Packets and process until disconnected - while(EthernetIsConnected) { - //printf("Signal Controller ethernet connected, reading data\n"); - numbytes = recv(sockfd, buf, maxDataSize-1, 0); - errnoVal = errno; - //printf("Signal Controller read %d bytes\n", numbytes); - //TODO - Check the start byte for 0xcd, then check for len of 245. - //TODO - store in temp space if less than 245, send only from 0xcd (byte 0) to byte 245 to new processing function - if ((numbytes == -1) || (numbytes == 0)){ - if (numbytes == 0 || errnoVal == EAGAIN || errnoVal == EWOULDBLOCK) { - PLOG(logINFO) << "Signal Controller Timed out"; - } else { - PLOG(logINFO) << "Signal Controller Client closed"; - EthernetIsConnected = 0; - close(sockfd); - } - IsReceiving = 0; - } - else { - - IsReceiving = 1; - pthread_mutex_lock(&spat_message_mutex); - auto ntcip1202 = std::make_shared(clock); - ntcip1202->setSignalGroupMappingList(_signalGroupMappingJson); - //printf("Signal Controller calling ntcip1202 copyBytesIntoNtcip1202"); - ntcip1202->copyBytesIntoNtcip1202(buf, numbytes); - - //printf("Signal Controller calling ntcip1202 ToJ2735r41SPAT"); - SPAT *_spat = (SPAT *) calloc(1, sizeof(SPAT)); - ntcip1202->ToJ2735r41SPAT(_spat, _intersectionName, _intersectionId); - - //printf("Signal Controller calling _spatMessage set_j2735_data\n"); - //_spatMessage.set_j2735_data(_spat); - if (_spatMessage != nullptr) - { - _spatMessage = nullptr; - } - _spatMessage = std::make_shared(_spat); - - pthread_mutex_unlock(&spat_message_mutex); - PLOG(logDEBUG) << *_spatMessage; - } - } - } - sleep(3); - } -} - -void SignalController::getEncodedSpat(SpatEncodedMessage* spatEncodedMsg, std::string currentPedLanes) -{ - pthread_mutex_lock(&spat_message_mutex); - - if (_spatMessage != nullptr) { - PedestrianDetectionForSPAT pedDetect; - pedDetect.updateEncodedSpat(*spatEncodedMsg, _spatMessage, currentPedLanes); - } - - pthread_mutex_unlock(&spat_message_mutex); - -} - -int SignalController::getIsConnected() -{ - return EthernetIsConnected && IsReceiving; -} - -int SignalController::getActionNumber() -{ - return 1;//sd.actionNumber; -} - -void SignalController::SNMPOpenSession() -{ - //check for valid TSC info - if (_tscIp == "" || _tscRemoteSnmpPort == 0) - return; - //open snmp session - snmp_sess_init(&_session_info); - string peername = _tscIp; - peername.append(":"); - peername.append(to_string(_tscRemoteSnmpPort)); - _session_info.peername = (char*)peername.c_str(); - _session_info.version = SNMP_VERSION_1; - _session_info.community = (u_char*)"public"; - _session_info.community_len = strlen("public"); - _session = snmp_open(&_session_info); - if (_session) - _snmpSessionOpen = true; -} - -void SignalController::SNMPCloseSession() -{ - //close session - if (_snmpSessionOpen) - snmp_close(_session); - _snmpSessionOpen = false; -} - -bool SignalController::SNMPSet(string targetOid, int32_t value) -{ - return SNMPSet(targetOid, ASN_INTEGER, (const void *)&value, sizeof(value)); -} - -bool SignalController::SNMPSet(string targetOid, u_char type, const void *value, size_t len) -{ - struct snmp_pdu *pdu; - struct snmp_pdu *response; - oid anOID[MAX_OID_LEN]; - size_t anOID_len = MAX_OID_LEN; - int status; - bool rc = true; - - //check is snmp session open - if (!_snmpSessionOpen) - { - SNMPOpenSession(); - if (!_snmpSessionOpen) - return false; - _snmpDestinationChanged = false; - } - //check destination change - if (_snmpDestinationChanged) - { - SNMPCloseSession(); - SNMPOpenSession(); - if (!_snmpSessionOpen) - return false; - _snmpDestinationChanged = false; - } - - pdu = snmp_pdu_create(SNMP_MSG_SET); - read_objid(targetOid.c_str(), anOID, &anOID_len); - snmp_pdu_add_variable(pdu, anOID, anOID_len, type, value, len); - status = snmp_synch_response(_session, pdu, &response); - if (status != STAT_SUCCESS || response->errstat != SNMP_ERR_NOERROR) - rc = false; - if (response) - snmp_free_pdu(response); - - return rc; -} - diff --git a/src/v2i-hub/SpatPlugin/src/signalController.h b/src/v2i-hub/SpatPlugin/src/signalController.h deleted file mode 100644 index a31fdce69..000000000 --- a/src/v2i-hub/SpatPlugin/src/signalController.h +++ /dev/null @@ -1,77 +0,0 @@ -/* - * snmpClient.h - * - * Created on: Aug 22, 2014 - * Author: gibbsw - */ - -#ifndef SIGNALCONTROLLER_H_ -#define SIGNALCONTROLLER_H_ - -#include -#include - -#include - -#include -#include - -#include "carma-clock/carma_clock.h" - -class SignalController -{ - public: - inline explicit SignalController(std::shared_ptr clock) : - clock(clock) {}; - ~SignalController(); - - void Start(std::string signalGroupMappingJson); - void spat_load(); - void start_signalController(); - int getActionNumber(); - void setConfigs(std::string ip, std::string udpPort, std::string snmpIP, std::string snmpPort, std::string ptlmFile, std::string intersectionName, int intersectionId); - void updatePtlmFile(const char* ptlmFile); - int getIsConnected(); - - //int getDerEncodedSpat(unsigned char* derEncodedBuffer); - - void getEncodedSpat(tmx::messages::SpatEncodedMessage* spatEncodedMsg, std::string currentPedLanes = ""); - - pthread_mutex_t spat_message_mutex; - boost::thread sigcon_thread_id; - - void SNMPOpenSession(); - void SNMPCloseSession(); - bool SNMPSet(std::string targetOid, int32_t value); - bool SNMPSet(std::string targetOid, u_char type, const void *value, size_t len); - - private: - void *get_in_addr(struct sockaddr *); - - std::shared_ptr clock; - - // Local IP address and UDP port for reception of SPAT dSPaTDataata from the TSC. - char* _localIp; - char* _localUdpPort; - char* _intersectionName; - int _intersectionId; - std::string _tscIp; - uint32_t _tscRemoteSnmpPort; - - std::string _signalGroupMappingJson; - std::shared_ptr _spatMessage; - int counter; - unsigned long normalstate; - unsigned long crossstate; - int EthernetIsConnected; - int IsReceiving; - - //snmp - struct snmp_session _session_info; - struct snmp_session *_session{NULL}; - bool _snmpSessionOpen{false}; - bool _snmpDestinationChanged{false}; - -}; - -#endif /* SIGNALCONTROLLER_H_ */ diff --git a/src/v2i-hub/SpatPlugin/src/utils/PerformanceTimer.h b/src/v2i-hub/SpatPlugin/src/utils/PerformanceTimer.h deleted file mode 100644 index b7952aa69..000000000 --- a/src/v2i-hub/SpatPlugin/src/utils/PerformanceTimer.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * PerformanceTimer.h - * - * Created on: Dec 5, 2014 - * Author: ivp - */ - -#ifndef PERFORMANCETIMER_H_ -#define PERFORMANCETIMER_H_ - -#include - -class PerformanceTimer -{ -public: - // Returns the current high-resolution system time in UTC. - static boost::posix_time::ptime Now() { return boost::posix_time::microsec_clock::universal_time(); } - - // Construct and start the timer. - PerformanceTimer() : _startTime( Now() ) {}; - - // Reset the timer to the current time. - void Reset() { _startTime = Now(); } - - // Returns the elapsed time. - boost::posix_time::time_duration const Elapsed() - { - return Now() - _startTime; - } - -private: - // The time of class construction or when last Reset(). - boost::posix_time::ptime _startTime; -}; - -#endif /* PERFORMANCETIMER_H_ */ diff --git a/src/v2i-hub/SpatPlugin/test/Main.cpp b/src/v2i-hub/SpatPlugin/test/Main.cpp index 75163d417..5c1d740bb 100644 --- a/src/v2i-hub/SpatPlugin/test/Main.cpp +++ b/src/v2i-hub/SpatPlugin/test/Main.cpp @@ -1,8 +1,17 @@ -/* - * Main.cpp +/** + * Copyright (C) 2024 LEIDOS. * - * Created on: May 10, 2016 - * Author: ivp + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. */ #include diff --git a/src/v2i-hub/SpatPlugin/test/test_NTCIP1202.cpp b/src/v2i-hub/SpatPlugin/test/TestNTCIP1202.cpp similarity index 56% rename from src/v2i-hub/SpatPlugin/test/test_NTCIP1202.cpp rename to src/v2i-hub/SpatPlugin/test/TestNTCIP1202.cpp index cc9c671a8..ff30ba42a 100644 --- a/src/v2i-hub/SpatPlugin/test/test_NTCIP1202.cpp +++ b/src/v2i-hub/SpatPlugin/test/TestNTCIP1202.cpp @@ -1,25 +1,30 @@ - +/** + * Copyright (C) 2024 LEIDOS. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ #include #include #include +#include using namespace fwha_stol::lib::time; TEST(NTCIP1202Test, copyBytesIntoNtcip1202) { - DescriptiveName_t *update_to_intersection_name = (DescriptiveName_t *)calloc(1, sizeof(DescriptiveName_t)); - char *my_string = (char*) "test intersection name"; - stringstream ss; - update_to_intersection_name->buf = reinterpret_cast(my_string); - ss << update_to_intersection_name->buf; - ASSERT_EQ(ss.str(), "test intersection name"); - - IntersectionReferenceID_t *update_to_intersection_id = (IntersectionReferenceID_t *)calloc(1, sizeof(IntersectionReferenceID_t)); - update_to_intersection_id->id = 9012; + uint64_t tsMsec = 1677775434400; - auto clock = std::make_shared(); - clock->wait_for_initialization(); - auto ntcip1202_p = std::make_shared(clock); + auto ntcip1202_p = std::make_shared(); unsigned int raw_data[] = {4294967245, 16, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 118, 0, 118, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 4294967208, 0, 4294967208, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 118, 0, 118, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 4294967208, 0, 4294967208, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4294967295, 4294967261, 0, 0, 0, 34, 4294967295, 4294967295, 0, 0, 0, 0, 4294967295, 4294967295, 0, 0, 0, 0, 0, 0, 0, 0, 4294967168, 0, 8, 103, 1, 10, 4294967237, 0, 0}; int numBytes = sizeof(raw_data)/sizeof(unsigned int); char buf[ numBytes] = {}; @@ -32,52 +37,38 @@ TEST(NTCIP1202Test, copyBytesIntoNtcip1202) ntcip1202_p->copyBytesIntoNtcip1202(buf, numBytes); SPAT *spat_ptr = (SPAT *)calloc(1, sizeof(SPAT)); - ntcip1202_p->ToJ2735r41SPAT(spat_ptr, reinterpret_cast(update_to_intersection_name->buf), update_to_intersection_id->id); + ntcip1202_p->ToJ2735SPAT(spat_ptr,tsMsec, "test intersection name", 9012); + ASSERT_EQ(3, spat_ptr->intersections.list.array[0]->states.list.array[0]->state_time_speed.list.array[0]->eventState); + free(spat_ptr); } -TEST(NTCIP1202Test, ToJ2735r41SPAT) +TEST(NTCIP1202Test, ToJ2735SPAT) { - auto clock = std::make_shared(); - clock->wait_for_initialization(); - auto ntcip1202_p = std::make_shared(clock); - SPAT *spat_ptr = (SPAT *)calloc(1, sizeof(SPAT)); + uint64_t tsMsec = 1677775434400; - char *my_string = (char*)"test intersection name"; - stringstream ss; - - DescriptiveName_t *update_to_intersection_name = (DescriptiveName_t *)calloc(1, sizeof(DescriptiveName_t)); - update_to_intersection_name->buf = reinterpret_cast(my_string); - ss << update_to_intersection_name->buf; - ASSERT_EQ(ss.str(), "test intersection name"); - - IntersectionReferenceID_t *update_to_intersection_id = (IntersectionReferenceID_t *)calloc(1, sizeof(IntersectionReferenceID_t)); - update_to_intersection_id->id = 9012; - - - bool transform_status = ntcip1202_p->ToJ2735r41SPAT(spat_ptr, reinterpret_cast(update_to_intersection_name->buf), update_to_intersection_id->id); + auto ntcip1202_p = std::make_shared(); + SPAT *spat_ptr = (SPAT *)calloc(1, sizeof(SPAT)); + ntcip1202_p->ToJ2735SPAT(spat_ptr, tsMsec, "test intersection name", 9012); auto _spatMessage = std::make_shared(spat_ptr); auto spat = _spatMessage->get_j2735_data(); - ASSERT_EQ(transform_status, true); } TEST(NTCIP1202Test, TestAdjustedTime) { - auto clock = std::make_shared(true); - // 1677775434 = 2023-02-03 16:43:54 - timeStampMilliseconds tsMsec = ((uint64_t)1677775434 * 1000) + 400; + // 1677775434400 = 2023-02-03 16:43:54.400 + uint64_t tsMsec = 1677775434400; auto baseTenthsOfSeconds = 43 * 600 + 54 * 10 + 4; - clock->update(tsMsec); - auto ntcip1202_p = std::make_shared(clock); - auto result = ntcip1202_p->getAdjustedTime(0); + auto ntcip1202_p = std::make_shared(); + auto result = ntcip1202_p->getAdjustedTime(0, tsMsec); EXPECT_EQ(baseTenthsOfSeconds, result); - result = ntcip1202_p->getAdjustedTime(46); + result = ntcip1202_p->getAdjustedTime(46, tsMsec); EXPECT_EQ(baseTenthsOfSeconds + 46, result); // cross minute boundary - result = ntcip1202_p->getAdjustedTime(200); + result = ntcip1202_p->getAdjustedTime(200, tsMsec); EXPECT_EQ(baseTenthsOfSeconds + 200, result); // cross hour boundary - result = ntcip1202_p->getAdjustedTime(10200); + result = ntcip1202_p->getAdjustedTime(10200, tsMsec); EXPECT_EQ((baseTenthsOfSeconds + 10200) % 36000, result); } \ No newline at end of file diff --git a/src/v2i-hub/SpatPlugin/test/TestSignalControllerConnection.cpp b/src/v2i-hub/SpatPlugin/test/TestSignalControllerConnection.cpp new file mode 100644 index 000000000..a95b53374 --- /dev/null +++ b/src/v2i-hub/SpatPlugin/test/TestSignalControllerConnection.cpp @@ -0,0 +1,614 @@ +/** + * Copyright (C) 2024 LEIDOS. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +#include +#include +#include +#include +#include +#include + +using testing::_; +using testing::Action; +using testing::ByRef; +using testing::DoDefault; +using testing::Return; +using testing::SetArgPointee; +using testing::SetArgReferee; +using testing::SetArrayArgument; +using testing::Throw; +namespace SpatPlugin { + class TestSignalControllerConnection : public ::testing::Test + { + public: + TestSignalControllerConnection() { + + } + void SetUp() { + std::string signalGroupMapping = R"( + {"SignalGroups": + [ + {"SignalGroupId":1,"Phase":1,"Type":"vehicle"}, + {"SignalGroupId":2,"Phase":2,"Type":"vehicle"}, + {"SignalGroupId":3,"Phase":3,"Type":"vehicle"}, + {"SignalGroupId":4,"Phase":4,"Type":"vehicle"}, + {"SignalGroupId":5,"Phase":5,"Type":"vehicle"}, + {"SignalGroupId":6,"Phase":6,"Type":"vehicle"}, + {"SignalGroupId":7,"Phase":7,"Type":"vehicle"}, + {"SignalGroupId":8,"Phase":8,"Type":"vehicle"}, + {"SignalGroupId":9,"Phase":2,"Type":"pedestrian"}, + {"SignalGroupId":10,"Phase":4,"Type":"pedestrian"}, + {"SignalGroupId":11,"Phase":6,"Type":"pedestrian"}, + {"SignalGroupId":12,"Phase":8,"Type":"pedestrian"} + ] + } + )"; + signalControllerConnection = std::make_unique("127.0.0.1", 5000, signalGroupMapping, "", 5020,"administrator", "someIntersection", 9001); + mockSnmpClient = std::make_shared("127.0.0.1", 6045, "administrator", "", "", ""); + mockUdpServer = std::make_shared(); + signalControllerConnection->scSNMPClient = mockSnmpClient; + signalControllerConnection->spatPacketReceiver = mockUdpServer; + } + + std::vector read_binary_file(std::string name) + { + std::ifstream file(name.c_str(), std::ios::binary); + std::vector buf; + + if (!file.good()) + { + throw runtime_error("Could not open file " + name); + } + + file.unsetf(std::ios::skipws); + file.seekg(0, std::ios::end); + const size_t size = file.tellg(); + + file.seekg(0, std::ios::beg); + buf.resize(size); + file.read(buf.data(), size); + file.close(); + + return buf; + } + + std::shared_ptr mockSnmpClient; + std::shared_ptr mockUdpServer; + + std::unique_ptr signalControllerConnection; + }; + + TEST_F(TestSignalControllerConnection, initialize) { + tmx::utils::snmp_response_obj enable_spat; + enable_spat.type = tmx::utils::snmp_response_obj::response_type::INTEGER; + enable_spat.val_int = 2; + EXPECT_CALL(*mockSnmpClient, process_snmp_request(NTCIP1202V2::ENABLE_SPAT_OID, tmx::utils::request_type::SET, enable_spat)).WillOnce(testing::DoAll(SetArgReferee<2>(enable_spat), Return(true))); + EXPECT_TRUE(signalControllerConnection->initializeSignalControllerConnection(true)); + } + + TEST_F(TestSignalControllerConnection, receiveBinarySPAT) { + auto spat_binary_buf = read_binary_file("../../SpatPlugin/test/test_spat_binaries/spat_1721238398773.bin"); + EXPECT_CALL(*mockUdpServer, TimedReceive(_, 1000, 1000)).WillOnce(testing::DoAll(SetArrayArgument<0>(spat_binary_buf.begin(), spat_binary_buf.end()), Return(spat_binary_buf.size()))); + auto spat = std::make_shared(); + signalControllerConnection->receiveBinarySPAT(spat, 1721238398773); + /** + * + + + someIntersection + + 9001 + + 1 + + 0000000000000000 + + 286186 + 38773 + + + 1 + + + + + 28027 + 21522 + + + + + + 2 + + + + + 27987 + 21522 + + + + + + 9 + + + + + 27987 + + + + + + 3 + + + + + 28027 + 21522 + + + + + + 4 + + + + + 28027 + 21522 + + + + + + 10 + + + + + 28027 + + + + + + 5 + + + + + 28027 + 21522 + + + + + + 6 + + + + + 27987 + 21522 + + + + + + 11 + + + + + 27987 + + + + + + 0 + + + + + + 7 + + + + + 28027 + 21522 + + + + + + 8 + + + + + 28027 + 21522 + + + + + + 12 + + + + + 28027 + + + + + + 0 + + + + + + + + + */ + EXPECT_EQ(9001, spat->intersections.list.array[0]->id.id); + EXPECT_EQ(286186, *spat->intersections.list.array[0]->moy); + EXPECT_EQ(38773, *spat->intersections.list.array[0]->timeStamp); + // Signal Group 1 + EXPECT_EQ(1, spat->intersections.list.array[0]->states.list.array[0]->signalGroup); + EXPECT_EQ(3, spat->intersections.list.array[0]->states.list.array[0]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(28027, spat->intersections.list.array[0]->states.list.array[0]->state_time_speed.list.array[0]->timing->minEndTime); + EXPECT_EQ(21522, *spat->intersections.list.array[0]->states.list.array[0]->state_time_speed.list.array[0]->timing->maxEndTime); + // Signal Group 2 + EXPECT_EQ(2, spat->intersections.list.array[0]->states.list.array[1]->signalGroup); + EXPECT_EQ(6, spat->intersections.list.array[0]->states.list.array[1]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(27987, spat->intersections.list.array[0]->states.list.array[1]->state_time_speed.list.array[0]->timing->minEndTime); + EXPECT_EQ(21522, *spat->intersections.list.array[0]->states.list.array[1]->state_time_speed.list.array[0]->timing->maxEndTime); + // Signal Group 9 + EXPECT_EQ(9, spat->intersections.list.array[0]->states.list.array[2]->signalGroup); + EXPECT_EQ(3, spat->intersections.list.array[0]->states.list.array[2]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(27987, spat->intersections.list.array[0]->states.list.array[2]->state_time_speed.list.array[0]->timing->minEndTime); + // Signal Group 3 + EXPECT_EQ(3, spat->intersections.list.array[0]->states.list.array[3]->signalGroup); + EXPECT_EQ(3, spat->intersections.list.array[0]->states.list.array[3]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(28027, spat->intersections.list.array[0]->states.list.array[3]->state_time_speed.list.array[0]->timing->minEndTime); + EXPECT_EQ(21522, *spat->intersections.list.array[0]->states.list.array[3]->state_time_speed.list.array[0]->timing->maxEndTime); + // Signal Group 4 + EXPECT_EQ(4, spat->intersections.list.array[0]->states.list.array[4]->signalGroup); + EXPECT_EQ(3, spat->intersections.list.array[0]->states.list.array[4]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(28027, spat->intersections.list.array[0]->states.list.array[4]->state_time_speed.list.array[0]->timing->minEndTime); + EXPECT_EQ(21522, *spat->intersections.list.array[0]->states.list.array[4]->state_time_speed.list.array[0]->timing->maxEndTime); + // // Signal Group 10 + EXPECT_EQ(10, spat->intersections.list.array[0]->states.list.array[5]->signalGroup); + EXPECT_EQ(3, spat->intersections.list.array[0]->states.list.array[5]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(28027, spat->intersections.list.array[0]->states.list.array[5]->state_time_speed.list.array[0]->timing->minEndTime); + // // Signal Group 5 + EXPECT_EQ(5, spat->intersections.list.array[0]->states.list.array[6]->signalGroup); + EXPECT_EQ(3, spat->intersections.list.array[0]->states.list.array[6]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(28027, spat->intersections.list.array[0]->states.list.array[6]->state_time_speed.list.array[0]->timing->minEndTime); + EXPECT_EQ(21522, *spat->intersections.list.array[0]->states.list.array[6]->state_time_speed.list.array[0]->timing->maxEndTime); + // Signal Group 6 + EXPECT_EQ(6, spat->intersections.list.array[0]->states.list.array[7]->signalGroup); + EXPECT_EQ(6, spat->intersections.list.array[0]->states.list.array[7]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(27987, spat->intersections.list.array[0]->states.list.array[7]->state_time_speed.list.array[0]->timing->minEndTime); + EXPECT_EQ(21522, *spat->intersections.list.array[0]->states.list.array[7]->state_time_speed.list.array[0]->timing->maxEndTime); + // Signal Group 11 + EXPECT_EQ(11, spat->intersections.list.array[0]->states.list.array[8]->signalGroup); + EXPECT_EQ(3, spat->intersections.list.array[0]->states.list.array[8]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(27987, spat->intersections.list.array[0]->states.list.array[8]->state_time_speed.list.array[0]->timing->minEndTime); + // Signal Group 7 + EXPECT_EQ(7, spat->intersections.list.array[0]->states.list.array[9]->signalGroup); + EXPECT_EQ(3, spat->intersections.list.array[0]->states.list.array[9]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(28027, spat->intersections.list.array[0]->states.list.array[9]->state_time_speed.list.array[0]->timing->minEndTime); + EXPECT_EQ(21522, *spat->intersections.list.array[0]->states.list.array[9]->state_time_speed.list.array[0]->timing->maxEndTime); + // Signal Group 8 + EXPECT_EQ(8, spat->intersections.list.array[0]->states.list.array[10]->signalGroup); + EXPECT_EQ(3, spat->intersections.list.array[0]->states.list.array[10]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(28027, spat->intersections.list.array[0]->states.list.array[10]->state_time_speed.list.array[0]->timing->minEndTime); + EXPECT_EQ(21522, *spat->intersections.list.array[0]->states.list.array[10]->state_time_speed.list.array[0]->timing->maxEndTime); + // Signal Group 12 + EXPECT_EQ(12, spat->intersections.list.array[0]->states.list.array[11]->signalGroup); + EXPECT_EQ(3, spat->intersections.list.array[0]->states.list.array[11]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(28027, spat->intersections.list.array[0]->states.list.array[11]->state_time_speed.list.array[0]->timing->minEndTime); + } + TEST_F(TestSignalControllerConnection, receiveBinarySPATException) { + EXPECT_CALL(*mockUdpServer, TimedReceive(_, 1000, 1000)).WillOnce(testing::DoAll( Return(0))); + auto spat = std::make_shared(); + EXPECT_THROW(signalControllerConnection->receiveBinarySPAT(spat, 1721238398773), tmx::utils::UdpServerRuntimeError); + } + + TEST_F(TestSignalControllerConnection, receiveUPERSPAT) { + std::string uper_hex = R"( + Version=0.7 + Type=SPAT + PSID=0x8002 + Priority=7 + TxMode=CONT + TxChannel=172 + TxInterval=0 + DeliveryStart= + DeliveryStop= + Signature=True + Encryption=False + Payload=00136b4457f20180000000208457f2c7c20b0010434162bc650001022a0b0be328000c10d058af194000808682c578ca00050434162bc650003022a0b0be328001c10d058af194001008682c578ca000904341617c650005021a0b15e328002c10d0585f194001808682c578ca00 + )"; + EXPECT_CALL(*mockUdpServer, stringTimedReceive(1000)).WillOnce(testing::DoAll(Return(uper_hex))); + + /** + * + 284658 + + + + 0 + + 0 + + 0000001000001000 + + 284658 + 51138 + + + 1 + + + + + 11351 + 36000 + + + + + + 2 + + + + + 11311 + 36000 + + + + + + 3 + + + + + 11351 + 36000 + + + + + + 4 + + + + + 11351 + 36000 + + + + + + 5 + + + + + 11351 + 36000 + + + + + + 6 + + + + + 11311 + 36000 + + + + + + 7 + + + + + 11351 + 36000 + + + + + + 8 + + + + + 11351 + 36000 + + + + + + 9 + + + + + 11311 + 36000 + + + + + + 10 + + + + + 11351 + 36000 + + + + + + 11 + + + + + 11311 + 36000 + + + + + + 12 + + + + + 11351 + 36000 + + + + + + + + + */ + auto spatEncoded_ptr = std::make_shared(); + signalControllerConnection->receiveUPERSPAT(spatEncoded_ptr); + auto spat = spatEncoded_ptr->decode_j2735_message().get_j2735_data(); + EXPECT_EQ(284658L, *spat->timeStamp); + EXPECT_EQ(0, spat->intersections.list.array[0]->id.id); + EXPECT_EQ(284658L, *spat->intersections.list.array[0]->moy); + EXPECT_EQ(51138, *spat->intersections.list.array[0]->timeStamp); + // Signal Group 1 + EXPECT_EQ(1, spat->intersections.list.array[0]->states.list.array[0]->signalGroup); + EXPECT_EQ(3, spat->intersections.list.array[0]->states.list.array[0]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(11351, spat->intersections.list.array[0]->states.list.array[0]->state_time_speed.list.array[0]->timing->minEndTime); + EXPECT_EQ(36000, *spat->intersections.list.array[0]->states.list.array[0]->state_time_speed.list.array[0]->timing->maxEndTime); + // Signal Group 2 + EXPECT_EQ(2, spat->intersections.list.array[0]->states.list.array[1]->signalGroup); + EXPECT_EQ(5, spat->intersections.list.array[0]->states.list.array[1]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(11311, spat->intersections.list.array[0]->states.list.array[1]->state_time_speed.list.array[0]->timing->minEndTime); + EXPECT_EQ(36000, *spat->intersections.list.array[0]->states.list.array[1]->state_time_speed.list.array[0]->timing->maxEndTime); + // Signal Group 3 + EXPECT_EQ(3, spat->intersections.list.array[0]->states.list.array[2]->signalGroup); + EXPECT_EQ(3, spat->intersections.list.array[0]->states.list.array[2]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(11351, spat->intersections.list.array[0]->states.list.array[2]->state_time_speed.list.array[0]->timing->minEndTime); + EXPECT_EQ(36000, *spat->intersections.list.array[0]->states.list.array[2]->state_time_speed.list.array[0]->timing->maxEndTime); + // Signal Group 4 + EXPECT_EQ(4, spat->intersections.list.array[0]->states.list.array[3]->signalGroup); + EXPECT_EQ(3, spat->intersections.list.array[0]->states.list.array[3]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(11351, spat->intersections.list.array[0]->states.list.array[3]->state_time_speed.list.array[0]->timing->minEndTime); + EXPECT_EQ(36000, *spat->intersections.list.array[0]->states.list.array[3]->state_time_speed.list.array[0]->timing->maxEndTime); + // Signal Group 5 + EXPECT_EQ(5, spat->intersections.list.array[0]->states.list.array[4]->signalGroup); + EXPECT_EQ(3, spat->intersections.list.array[0]->states.list.array[4]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(11351, spat->intersections.list.array[0]->states.list.array[4]->state_time_speed.list.array[0]->timing->minEndTime); + EXPECT_EQ(36000, *spat->intersections.list.array[0]->states.list.array[4]->state_time_speed.list.array[0]->timing->maxEndTime); + // Signal Group 6 + EXPECT_EQ(6, spat->intersections.list.array[0]->states.list.array[5]->signalGroup); + EXPECT_EQ(5, spat->intersections.list.array[0]->states.list.array[5]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(11311, spat->intersections.list.array[0]->states.list.array[5]->state_time_speed.list.array[0]->timing->minEndTime); + EXPECT_EQ(36000, *spat->intersections.list.array[0]->states.list.array[5]->state_time_speed.list.array[0]->timing->maxEndTime); + // Signal Group 7 + EXPECT_EQ(7, spat->intersections.list.array[0]->states.list.array[6]->signalGroup); + EXPECT_EQ(3, spat->intersections.list.array[0]->states.list.array[6]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(11351, spat->intersections.list.array[0]->states.list.array[6]->state_time_speed.list.array[0]->timing->minEndTime); + EXPECT_EQ(36000, *spat->intersections.list.array[0]->states.list.array[6]->state_time_speed.list.array[0]->timing->maxEndTime); + // Signal Group 8 + EXPECT_EQ(8, spat->intersections.list.array[0]->states.list.array[7]->signalGroup); + EXPECT_EQ(3, spat->intersections.list.array[0]->states.list.array[7]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(11351, spat->intersections.list.array[0]->states.list.array[7]->state_time_speed.list.array[0]->timing->minEndTime); + EXPECT_EQ(36000, *spat->intersections.list.array[0]->states.list.array[7]->state_time_speed.list.array[0]->timing->maxEndTime); + // Signal Group 9 + EXPECT_EQ(9, spat->intersections.list.array[0]->states.list.array[8]->signalGroup); + EXPECT_EQ(3, spat->intersections.list.array[0]->states.list.array[8]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(11311, spat->intersections.list.array[0]->states.list.array[8]->state_time_speed.list.array[0]->timing->minEndTime); + EXPECT_EQ(36000, *spat->intersections.list.array[0]->states.list.array[8]->state_time_speed.list.array[0]->timing->maxEndTime); + // Signal Group 10 + EXPECT_EQ(10, spat->intersections.list.array[0]->states.list.array[9]->signalGroup); + EXPECT_EQ(3, spat->intersections.list.array[0]->states.list.array[9]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(11351, spat->intersections.list.array[0]->states.list.array[9]->state_time_speed.list.array[0]->timing->minEndTime); + EXPECT_EQ(36000, *spat->intersections.list.array[0]->states.list.array[9]->state_time_speed.list.array[0]->timing->maxEndTime); + // Signal Group 11 + EXPECT_EQ(11, spat->intersections.list.array[0]->states.list.array[10]->signalGroup); + EXPECT_EQ(3, spat->intersections.list.array[0]->states.list.array[10]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(11311, spat->intersections.list.array[0]->states.list.array[10]->state_time_speed.list.array[0]->timing->minEndTime); + EXPECT_EQ(36000, *spat->intersections.list.array[0]->states.list.array[10]->state_time_speed.list.array[0]->timing->maxEndTime); + // Signal Group 12 + EXPECT_EQ(12, spat->intersections.list.array[0]->states.list.array[11]->signalGroup); + EXPECT_EQ(3, spat->intersections.list.array[0]->states.list.array[11]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(11351, spat->intersections.list.array[0]->states.list.array[11]->state_time_speed.list.array[0]->timing->minEndTime); + EXPECT_EQ(36000, *spat->intersections.list.array[0]->states.list.array[11]->state_time_speed.list.array[0]->timing->maxEndTime); + } + + TEST_F(TestSignalControllerConnection, receiveUPERSPATException) { + std::string without_paylod = R"( + Version=0.7 + Type=SPAT + PSID=0x8002 + Priority=7 + TxMode=CONT + TxChannel=172 + TxInterval=0 + DeliveryStart= + DeliveryStop= + Signature=True + Encryption=False + )"; + EXPECT_CALL(*mockUdpServer, stringTimedReceive(1000)).WillOnce(testing::DoAll(Return(without_paylod))); + + auto spatEncoded_ptr = std::make_shared(); + EXPECT_THROW(signalControllerConnection->receiveUPERSPAT(spatEncoded_ptr), tmx::TmxException); + } +} \ No newline at end of file diff --git a/src/v2i-hub/SpatPlugin/test/testPedestrianDetectionForSPAT.cpp b/src/v2i-hub/SpatPlugin/test/testPedestrianDetectionForSPAT.cpp deleted file mode 100644 index e404307b2..000000000 --- a/src/v2i-hub/SpatPlugin/test/testPedestrianDetectionForSPAT.cpp +++ /dev/null @@ -1,80 +0,0 @@ -#include -#include - -using namespace tmx::messages; - -static std::shared_ptr createSPAT() { - auto spatPtr = std::make_shared(); - auto intersectionState = (IntersectionState *)calloc(1, sizeof(IntersectionState)); - intersectionState->id.id = 1; - ASN_SEQUENCE_ADD(&(spatPtr->intersections), intersectionState); - - for (int i = 1; i <= 2; i++) { - auto movementState = (MovementState *)calloc(1, sizeof(MovementState)); - movementState->signalGroup = i; - ASN_SEQUENCE_ADD(&(intersectionState->states), movementState); - - auto movementEvent = (MovementEvent* )calloc(1, sizeof(MovementEvent)); - movementEvent->eventState = - i % 2 == 0 ? MovementPhaseState_protected_Movement_Allowed : MovementPhaseState_stop_And_Remain; - ASN_SEQUENCE_ADD(&(movementState->state_time_speed), movementEvent); - } - return spatPtr; -} - -TEST(PedestrianDetectionForSPAT, updateEncodedSpat_testIncompleteSpat) -{ - PedestrianDetectionForSPAT pedSPAT; - SpatEncodedMessage spatEncoded; - // create incomplete message - auto spatPtr = std::make_shared(); - auto spatMessage = std::make_shared(spatPtr); - bool execptionCaught = false; - try { - pedSPAT.updateEncodedSpat(spatEncoded, spatMessage, ""); - } catch (std::exception & e) { - // should not encode but should get here - execptionCaught = true; - } - EXPECT_EQ(execptionCaught, true); -} - -TEST(PedestrianDetectionForSPAT, updateEncodedSpat) -{ - PedestrianDetectionForSPAT pedSPAT; - // set up a J2735 SPAT to use and add to - auto spatPtr = createSPAT(); - - // first encode the message as is - tmx::messages::SpatEncodedMessage spatEncoded; - tmx::messages::MessageFrameMessage frame(spatPtr); - spatEncoded.set_data(tmx::messages::TmxJ2735EncodedMessage::encode_j2735_message>(frame)); - auto originalSpatHex = spatEncoded.get_payload_str(); - EXPECT_STRNE(originalSpatHex.c_str(), ""); - - // test once with no ped zones and ensure it is the same - { - SpatEncodedMessage spatEncoded; - auto spatMessage = std::make_shared(spatPtr); - pedSPAT.updateEncodedSpat(spatEncoded, spatMessage, ""); - EXPECT_STREQ(spatEncoded.get_payload_str().c_str(), originalSpatHex.c_str()); - } - - // test with ped zones to see that it updates - { - SpatEncodedMessage spatEncoded; - auto spatMessage = std::make_shared(spatPtr); - pedSPAT.updateEncodedSpat(spatEncoded, spatMessage, "2"); - // check hex is not equal to orginal - EXPECT_STRNE(spatEncoded.get_payload_str().c_str(), originalSpatHex.c_str()); - // check that the ped detect was added - ASSERT_EQ(spatPtr->intersections.list.count, 1); - ASSERT_EQ(spatPtr->intersections.list.array[0]->states.list.count, 2); - EXPECT_EQ(spatPtr->intersections.list.array[0]->states.list.array[0]->signalGroup, 1); - EXPECT_EQ(spatPtr->intersections.list.array[0]->states.list.array[1]->signalGroup, 2); - ASSERT_NE(spatPtr->intersections.list.array[0]->maneuverAssistList, nullptr); - EXPECT_NE(spatPtr->intersections.list.array[0]->maneuverAssistList->list.count, 0); - ASSERT_NE(spatPtr->intersections.list.array[0]->maneuverAssistList->list.array[0]->pedBicycleDetect, nullptr); - EXPECT_EQ(*(spatPtr->intersections.list.array[0]->maneuverAssistList->list.array[0]->pedBicycleDetect), 1); - } -} diff --git a/src/v2i-hub/SpatPlugin/test/test_spat_binaries/spat_1721238398773.bin b/src/v2i-hub/SpatPlugin/test/test_spat_binaries/spat_1721238398773.bin new file mode 100644 index 000000000..694c2e4b5 Binary files /dev/null and b/src/v2i-hub/SpatPlugin/test/test_spat_binaries/spat_1721238398773.bin differ