Skip to content

Commit

Permalink
Vh 1216 fix spat plugin segfault simulation (#621)
Browse files Browse the repository at this point in the history
<!-- Thanks for the contribution, this is awesome. -->

# PR Details
## Description
This pull request updates the SPAT plugin to fix a number of issues with
the legacy implementation.

- Overly complex legacy code in SPaT Plugin
- Reduced duplication using existing classes for SNMP Client and UDP
Server. Previous implementation created custom SNMP Client and UDP
server.
- Reduced use of raw pointers to mitigate the possibility of memory
leaks.
- Improved documentation for SPaT plugin.
- Added unit testing 
- Added functionality to consume J2735 UPER HEX SPaT from signal
controller.


Additionally to help provide these improvements in the SPaT plugin,
several improvements were made in the TMX Utils functionality.
- Fix SNMP Client to have working SNMP Set implementation (allows reused
of existing client instead of standalone SNMP client implementation for
SPaT plugin)
- Improvement PluginClientClockAware base class to automatically
subscribe to TimeSync messages and getter for clock to wait for clock
initialization. These improvements removed all simulation specific
implementation from inheriting classes including the SpatPlugin

<!--- Describe your changes in detail -->

## Related Issue
[VH-1216](https://usdot-carma.atlassian.net/browse/VH-1216) 
<!--- This project only accepts pull requests related to open issues -->
<!--- If suggesting a new feature or change, please discuss it in an
issue first -->
<!--- If fixing a bug, there should be an issue describing it with steps
to reproduce -->
<!--- Please link to the issue here: -->

## Motivation and Context
Fix SPaT plugin for simulation and improve overall usability and
maintainability.
<!--- Why is this change required? What problem does it solve? -->

## How Has This Been Tested?
Tested using virtual signal controller for both binary and hex spat
payloads.
Also tested in sim mode using the following steps
1) Deploy Virtual Signal Controller.
2) Deploy V2X-Hub in SIM MODE using Dev Container deployment
3) Run `src/v2i-hub/CDASimAdapter/scripts/send_timestep_udp.py` script
to send periodic time sync messages.
4) Enable SPaT Plugin 
<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, and the tests you ran
to -->
<!--- see how your change affects other areas of the code, etc. -->

## Types of changes

<!--- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->

- [x] Defect fix (non-breaking change that fixes an issue)
- [x] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that cause existing functionality
to change)

## Checklist:

<!--- Go over all the following points, and put an `x` in all the boxes
that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're
here to help! -->

- [ ] I have added any new packages to the sonar-scanner.properties file
- [x] My change requires a change to the documentation.
- [x] I have updated the documentation accordingly.
- [x] I have read the **CONTRIBUTING** document.
[V2XHUB Contributing
Guide](https://github.com/usdot-fhwa-OPS/V2X-Hub/blob/develop/Contributing.md)
- [x] I have added tests to cover my changes.
- [x] All new and existing tests passed.


[VH-1216]:
https://usdot-carma.atlassian.net/browse/VH-1216?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
  • Loading branch information
paulbourelly999 authored Jul 31, 2024
1 parent 2b9f38f commit 2303a49
Show file tree
Hide file tree
Showing 34 changed files with 1,387 additions and 993 deletions.
23 changes: 23 additions & 0 deletions src/tmx/Messages/test/Main.cpp
Original file line number Diff line number Diff line change
@@ -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 <gtest/gtest.h>

int main(int argc, char **argv)
{
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
10 changes: 10 additions & 0 deletions src/tmx/Messages/test/TestTimeSyncMessage.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#include <gtest/gtest.h>
#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());
}
}
File renamed without changes.
2 changes: 1 addition & 1 deletion src/tmx/TmxUtils/src/MockUdpServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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));
};

}
13 changes: 9 additions & 4 deletions src/tmx/TmxUtils/src/PluginClientClockAware.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,16 @@ namespace tmx::utils {
clock = std::make_shared<CarmaClock>(_simulation_mode);
if (_simulation_mode) {
AddMessageFilter<tmx::messages::TimeSyncMessage>(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()));
}
}
Expand All @@ -39,4 +39,9 @@ namespace tmx::utils {
return _simulation_mode;
}

std::shared_ptr<fwha_stol::lib::time::CarmaClock> PluginClientClockAware::getClock() const {
clock->wait_for_initialization(); // Blocks until first call to update when in sim mode.
return clock;
}

}
11 changes: 5 additions & 6 deletions src/tmx/TmxUtils/src/PluginClientClockAware.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<fwha_stol::lib::time::CarmaClock> getClock() const {
return clock;
}
std::shared_ptr<fwha_stol::lib::time::CarmaClock> getClock() const ;

void OnStateChange(IvpPluginState state) override;

bool isSimulationMode() const;


Expand Down
66 changes: 46 additions & 20 deletions src/tmx/TmxUtils/src/SNMPClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
{

Expand Down
12 changes: 12 additions & 0 deletions src/tmx/TmxUtils/src/SNMPClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion src/tmx/TmxUtils/test/J2735MessageTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<SignalRequestMessage>::encode_j2735_message<codec::uper<MessageFrameMessage>>(frame_msg));
SdsmEncodeMessage.set_data(TmxJ2735EncodedMessage<SdsmMessage>::encode_j2735_message<codec::uper<MessageFrameMessage>>(frame_msg));
free(message);
free(frame_msg.get_j2735_data().get());
ASSERT_EQ(41, SdsmEncodeMessage.get_msgId());
Expand Down
6 changes: 3 additions & 3 deletions src/tmx/TmxUtils/test/test_SNMPClient.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

#include "MockSNMPClient.h"
#include "gtest/gtest.h"
#include "RSU_MIB_4_1.h"
#include <MockSNMPClient.h>
#include <gtest/gtest.h>
#include <RSU_MIB_4_1.h>

using namespace tmx::utils;
using namespace std;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,6 @@ namespace MUSTSensorDriverPlugin {
MUSTSensorDriverPlugin::MUSTSensorDriverPlugin(const string &name): PluginClientClockAware(name)
{
mustSensorPacketReceiverThread = std::make_unique<tmx::utils::ThreadTimer>(std::chrono::milliseconds(5));
if (PluginClientClockAware::isSimulationMode()) {
PLOG(tmx::utils::logINFO) << "Simulation mode on " << std::endl;
SubscribeToMessages();
}
}

void MUSTSensorDriverPlugin::OnStateChange(IvpPluginState state) {
Expand Down
3 changes: 1 addition & 2 deletions src/v2i-hub/MapPlugin/src/MapPlugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,7 @@ namespace MapPlugin {
std::unique_ptr<tmx::messages::MapDataEncodedMessage> msg;
int activeAction = -1;

// wait for the clock to be initialized
getClock()->wait_for_initialization();


while (_plugin->state != IvpPluginState_error) {
if (_isMapFileNew) {
Expand Down
50 changes: 13 additions & 37 deletions src/v2i-hub/SpatPlugin/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
target_include_directories(${BINARY} PUBLIC src/)
target_link_libraries(${BINARY} PUBLIC ${PROJECT_NAME}_lib gtest)
82 changes: 82 additions & 0 deletions src/v2i-hub/SpatPlugin/README.md
Original file line number Diff line number Diff line change
@@ -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 <open_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"
Binary file added src/v2i-hub/SpatPlugin/docs/hex_tcpdump.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 2303a49

Please sign in to comment.