From c327fa07bac8fc96e09696af38bf60b1a109a0ad Mon Sep 17 00:00:00 2001 From: paulbourelly999 <77466294+paulbourelly999@users.noreply.github.com> Date: Thu, 5 Sep 2024 10:50:31 -0400 Subject: [PATCH] Update CDASimAdapter to accomadate changes in SensorConfig file (#640) # PR Details ## Description ### Background (Related changes) This PR was created to accommodate changes in the Sensor Configuration file introduced by this PR (https://github.com/usdot-fhwa-stol/carma-streets/pull/427). To summarize, the sensor configuration file format was changed to allow for reference location to be specified in WGS84 coordinates or cartesian coordinates. For Simulation cartesian coordinates work well since many simulation environments do not have real-world equivalents and cartesian coordinates are easy to pull from different map editors and viewers like [JSOM](https://josm.openstreetmap.de/). For real world deployments however WGS84 coordinates are far easier to use since using cartesian coordinates introduces a dependency on an lanlet2 map for the area. It is far easier to use built in or separate GPS devices to get a reference location for a mounted sensor. #### Old Format of Sensor Configuration JSON: ```json [ { "sensorId": "SomeID", "type": "SemanticLidar", "location": { "x": 1.0, /* in meters */ "y": 2.0, /* in meters */ "z": -3.2 /* in meters */ }, "orientation": { "yaw": 0.0, "pitch": 0.0, "roll": 0.0 } } ] ``` #### New Format of Sensor Configuration JSON: ```json [ { "sensorId": "sensor_1", "type": "SemanticLidar", "ref": { "type": "CARTESIAN", "location": { "x": 1.0, /* in meters */ "y": 2.0, /* in meters */ "z": -3.2 /* in meters */ }, "orientation": { "yaw": 0.0, "pitch": 0.0, "roll": 0.0 } } } ] ``` ### Changes This change in Sensor Configuration file means that we need to change how we read and populate the registration message that goes out from the CDASim Adapter Plugin to CDASim. The content of this registration remains unchanged, we just need to read this information from the new `ref` element in the Sensor Configuration JSON and populate the existing registration. Lastly, we added logic for the registration to ignore any sensor with a WGS84 reference location since we currently only have support ```json { "infrastructureId": "id_123", "location": { "x": 0, "y": 0, "z": 0 }, "rxMessageIpAddress": "127.0.0.1", "rxMessagePort": 8686, "sensors": { "sensorId": "sensor1", "type": "SemanticLidar", "location": { "x": 0.0, "y": 0.0, "z": 0.0 }, "orientation": { "yaw": 0.0, "pitch": 0.0, "roll": 0.0 } }, "simulatedInteractionPort": 7576, "timeSyncPort": 7575 } ``` ## Related Issue [FCP-37 ](https://usdot-carma.atlassian.net/browse/FCP-37) ## Motivation and Context See description ## How Has This Been Tested? Local Integration Testing and unit testing ## Types of changes - [x] Defect fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that cause existing functionality to change) ## Checklist: - [ ] 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. --- src/v2i-hub/CDASimAdapter/README.md | 82 ++++++++++++++++++ .../docs/communication_diagram.png | Bin 0 -> 39948 bytes .../CDASimAdapter/src/CDASimConnection.cpp | 75 +++++++++++++--- .../src/include/CDASimConnection.hpp | 25 ++++-- .../test/TestCDASimConnection.cpp | 16 ++-- src/v2i-hub/CDASimAdapter/test/sensors.json | 46 +++++----- .../sensors_including_invalid_entries.json | 48 ++++++++++ 7 files changed, 250 insertions(+), 42 deletions(-) create mode 100755 src/v2i-hub/CDASimAdapter/README.md create mode 100644 src/v2i-hub/CDASimAdapter/docs/communication_diagram.png create mode 100755 src/v2i-hub/CDASimAdapter/test/sensors_including_invalid_entries.json diff --git a/src/v2i-hub/CDASimAdapter/README.md b/src/v2i-hub/CDASimAdapter/README.md new file mode 100755 index 000000000..2267f3dba --- /dev/null +++ b/src/v2i-hub/CDASimAdapter/README.md @@ -0,0 +1,82 @@ +# CDASim Adapter Plugin Plugin Documentation + +## Introduction + +This Plugin is an adapter for integration with the **CDASim Co-simulation** enviroment. + +## Related Plugins + +A list of plugins related to the CDASim Adapter Plugin. + +### Immediate Forward Plugin + +For RSU Immediate Message Forwarding (IMF) functionality forward J2735 messages to CDASim. + +### Message Receiver Plugin + +The Message Receiver Plugin for receiving incoming J2735 message from CDASim. + +## Configuration/Deployment + +### System Configuration + +This plugin uses several system configuration parameters set as enviroment variables described below. + +**SIMULATION_MODE** – Environment variable for enabling simulation components for V2X-Hub. If set to "true" or "TRUE" simulation components will be enable. Otherwise, simulation components will not be enabled. + +**SIMULATION_IP** – Environment variable for storing IP address of CDASim application. + +**SIMULATION_REGISTRATION_PORT** – Environment variable for storing port on CDASim that handles registration attempts. + +**TIME_SYNC_PORT** – Environment varaible for storing port for receiving time sync messages from CDASim. + +**V2X_PORT** – Environment variable for storing port for receiving v2x messages from CDASim + +**SIM_V2X_PORT** – Environment variable for storing port for sending v2x messages to CDASim + +**V2XHUB_IP** – Environment variable for storing IP address of V2X Hub. + +**INFRASTRUCTURE_ID** – Environment variable for storing infrastructure id of V2X Hub. + +**SENSOR_JSON_FILE_PATH** – Environment variable for storing path to sensor configuration file. This is an optional simulation environment variable that allows for setting up simulated sensor for a V2X-Hub instance. Example file can be found in the **CDASimAdapterPlugin** tests [here](../src/v2i-hub/CDASimAdapter/test/sensors.json). + +### Plugin Configuration + +In addition to the system configuration, the CDASim Adapter also has plugin configuration parameters that can be set via the admin portal. + +**LogLevel**:The log level for this plugin" + +**X**:Cartesian X coordinate in simulated map (in meters). + +**Y**:Cartesian Y coordinate in simulated map (in meters). + +**Z**:Cartesian Z coordinate in simulated map (in meters). + +**MaxConnectionAttempts**:Number of connection attempts CDASimAdapter plugin will execute before failing. Valid values are any integers greater than or equal to 1. Any value less than 1 will result in unlimited connection attemtps. + +**ConnectionSleepTime**:Number of seconds to wait after a failed CDASim connection attempt, before retrying to establish connection. Valid values are equal or greater than 1. Any value less than 1 will be treated as 1. + +## Design + +### Communication + +![Alt text](docs/communication_diagram.png) +The integration with the **CDASim Co-simulation** enviorment consists of 4 major parts: + +**Registration Adapter**: The CDASimAdapter will establish a unique connection with CDASim and provide configuration information about harware like RSUs or Sensors associated with V2X-Hub. + +**Time Adapter**: The CDASimAdapter will consume and broadcast simulation time from the CDASim enviromment to ensure all plugins are using simulation time in place of machine time. + +**Message Adapter**: The CDASimAdapter will forward V2X messages to CDASim for simulated radio broadcast. It will also listen for incoming simulated V2X message recptions for the RSUs it registered with CDASim. + +**Simulation Adapter**: This final part captures any simulated events V2X-Hub needs to listen for. An example is Sensor Detections. When registering a sensor with CDASim, the sensor is created and detections from that sensor need to be forwarded to V2X-Hub. The simulation adapter will listen for these events from CDASim and broadcast them on the TMX message bus for other plugin. + +### Messages + +**Time Sync Message**: A V2X-Hub message that all PluginClientClockAware plugins subscribe to when running V2X-Hub in the simulation environment. This updates the base class clock object to allow for seamless integration of plugins into the simulation environment. + +**Sensor Detected Object**: V2X-Hub's generic message for detection data. This message is generated when the CDASim adapter receives a detection interaction for one of the sensors it registers. + +## Functionality Testing + +Included in this directory is a `scripts` directory that includes scripts to mock a CDASim Server listening for simulation registration events (`udp_socket_server.py`), time steps sent from CDASim (`send_timestep_udp.py`), and simulated detection events sent from CDASim (`send_sim_detected_object_udp.py`). These can be used to test functiontionality associated with the **CDASimAdapter Plugin** diff --git a/src/v2i-hub/CDASimAdapter/docs/communication_diagram.png b/src/v2i-hub/CDASimAdapter/docs/communication_diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..14c1c92c4d2318411714a296302b8be2de48e69b GIT binary patch literal 39948 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.c_str(), json_str.c_str() + json_str.length(), &json_v, &err)) { - PLOG(logERROR) << "Error parsing sensors from string: " << json_str << std::endl; + throw std::invalid_argument("Error parsing JSON from string: " + json_str); } return json_v; } + + Json::Value CDASimConnection::read_sensor_configuration_file(const std::string &file_path) const { + // Sensor Configuration File JSON format + /* { + "sensorId": "sensor_1", + "type": "SemanticLidar", + "ref": { + "type": "CARTESIAN", + "location": { + "x": 1.0, + "y": 2.0, + "z": -3.2 + }, + "orientation": { + "yaw": 0.0, + "pitch": 0.0, + "roll": 0.0 + } + } + } + ] */ + auto sensor_configuration = read_json_file(file_path); + Json::Value sensors_registration; + for ( int index = 0; index < sensor_configuration.size(); ++index ) { + Json::Value sensor; + if ( sensor_configuration[index]["ref"]["type"] != "CARTESIAN" ){ + PLOG(logWARNING) << "Skipping sensor configuration for " + sensor["sensorId"].asString() + ". Invalid ref type! Only CARTESIAN ref type currently supported for CDASim!" ; + continue; + } + sensor["sensorId"] = sensor_configuration[index]["sensorId"]; + sensor["type"] = sensor_configuration[index]["type"]; + sensor["location"] = sensor_configuration[index]["ref"]["location"]; + sensor["orientation"] =sensor_configuration[index]["ref"]["orientation"]; + sensors_registration.append(sensor); + } + /** Sensor Registration JSON Format + [ + { + "sensorId": "SomeID", + "type": "SemanticLidar", + "location": { + "x": 1.0, + "y": 2.0, + "z": -3.2 + }, + "orientation": { + "yaw": 0.0, + "pitch": 0.0, + "roll": 0.0 + } + } + ] + */ + return sensors_registration; + } } diff --git a/src/v2i-hub/CDASimAdapter/src/include/CDASimConnection.hpp b/src/v2i-hub/CDASimAdapter/src/include/CDASimConnection.hpp index 3a9bdf1a4..7899af4f7 100644 --- a/src/v2i-hub/CDASimAdapter/src/include/CDASimConnection.hpp +++ b/src/v2i-hub/CDASimAdapter/src/include/CDASimConnection.hpp @@ -135,17 +135,26 @@ namespace CDASimAdapter { const tmx::utils::Point &location) const; /** - * @brief Read Json file specified by the file path from disk, and convert the json into Json::Value object. - * @param file_path A string of file path in the host machine. + * @brief Helper method to read JSON file and return resulting Json::Value. + * @param file_path A string of file path in the host machine to the JSON file. * @return A Json::Value object. + * @throw std::invalid_argument exception if no file is found or file is not readable for provided path. */ Json::Value read_json_file(const std::string& file_path) const; - /** - * @brief Convert the Json string into Json::Value object. - * @param json_str A JSON string. - * @return A Json::Value object. - */ + /** + * @brief Helper method to convert string JSON content to JSON::Value. + * @param json_str A JSON string content of Sensor Configuration JSON file + * @return A Json::Value object parsed from string. + * @throw std::invalid_argument exception if provided string is not valid JSON + */ Json::Value string_to_json(const std::string &json_str) const; + /** + * @brief Method to read Sensor Configuration JSON file and return Sensor Registration information + * required for Infrastructure Registration to CDASim. + * @param file_path Path to Sensor Configuration file. + * @return Json::Value correspoding to Sensor Registration information for Infrastructure Registration to CDASim. + */ + Json::Value read_sensor_configuration_file(const std::string &file_path) const; std::string _simulation_ip; uint _simulation_registration_port; @@ -171,6 +180,8 @@ namespace CDASimAdapter { FRIEND_TEST(TestCDASimConnection, get_handshake_json_no_sensor_config); FRIEND_TEST(TestCDASimConnection, read_json_file); FRIEND_TEST(TestCDASimConnection, string_to_json); + FRIEND_TEST(TestCDASimConnection, read_sensor_configuration_file); + }; diff --git a/src/v2i-hub/CDASimAdapter/test/TestCDASimConnection.cpp b/src/v2i-hub/CDASimAdapter/test/TestCDASimConnection.cpp index 710dc49d8..80a47cc2f 100644 --- a/src/v2i-hub/CDASimAdapter/test/TestCDASimConnection.cpp +++ b/src/v2i-hub/CDASimAdapter/test/TestCDASimConnection.cpp @@ -101,20 +101,26 @@ namespace CDASimAdapter { TEST_F(TestCDASimConnection, read_json_file) { - auto sensorJsonV = connection->read_json_file("Invalid_file_path" ); - ASSERT_TRUE(sensorJsonV.empty()); + EXPECT_THROW(connection->read_json_file("Invalid_file_path" ), std::invalid_argument); std::ifstream in_strm; in_strm.open(sensors_file_path, std::ifstream::binary); if(in_strm.is_open()) { - sensorJsonV = connection->read_json_file(sensors_file_path ); + auto sensorJsonV = connection->read_json_file(sensors_file_path ); ASSERT_FALSE(sensorJsonV.empty()); } } TEST_F(TestCDASimConnection, string_to_json) { - auto sensorJsonV = connection->string_to_json("Invalid Json"); - ASSERT_TRUE(sensorJsonV.empty()); + EXPECT_THROW(connection->string_to_json("Invalid Json"), std::invalid_argument); + } + + TEST_F(TestCDASimConnection, read_sensor_configuration_file) { + auto sensor_registration = connection->read_sensor_configuration_file("../../CDASimAdapter/test/sensors_including_invalid_entries.json"); + EXPECT_EQ(2, sensor_registration.size()); + EXPECT_EQ("SomeID", sensor_registration[0]["sensorId"].asString()); + EXPECT_EQ("SomeID2", sensor_registration[1]["sensorId"].asString()); + } } \ No newline at end of file diff --git a/src/v2i-hub/CDASimAdapter/test/sensors.json b/src/v2i-hub/CDASimAdapter/test/sensors.json index 985496f8d..3f7cb1094 100755 --- a/src/v2i-hub/CDASimAdapter/test/sensors.json +++ b/src/v2i-hub/CDASimAdapter/test/sensors.json @@ -2,29 +2,35 @@ { "sensorId": "SomeID", "type": "SemanticLidar", - "location": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "orientation": { - "yaw": 0.0, - "pitch": 0.0, - "roll": 0.0 - } + "ref": { + "type": "CARTESIAN", + "location": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "orientation": { + "yaw": 0.0, + "pitch": 0.0, + "roll": 0.0 + } + } }, { "sensorId": "SomeID2", "type": "SemanticLidar", - "location": { - "x": 1.0, - "y": 2.0, - "z": 0.0 - }, - "orientation": { - "yaw": 23.0, - "pitch": 0.0, - "roll": 0.0 - } + "ref": { + "type": "CARTESIAN", + "location": { + "x": 1.0, + "y": 2.0, + "z": 0.0 + }, + "orientation": { + "yaw": 23.0, + "pitch": 0.0, + "roll": 0.0 + } + } } ] \ No newline at end of file diff --git a/src/v2i-hub/CDASimAdapter/test/sensors_including_invalid_entries.json b/src/v2i-hub/CDASimAdapter/test/sensors_including_invalid_entries.json new file mode 100755 index 000000000..edf290686 --- /dev/null +++ b/src/v2i-hub/CDASimAdapter/test/sensors_including_invalid_entries.json @@ -0,0 +1,48 @@ +[ + { + "sensorId": "SomeID", + "type": "SemanticLidar", + "ref": { + "type": "CARTESIAN", + "location": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "orientation": { + "yaw": 0.0, + "pitch": 0.0, + "roll": 0.0 + } + } + }, + { + "sensorId": "sensor_1", + "type": "SemanticLidar", + "ref": { + "type": "WGS84", + "location": { + "latitude": 38.9549716548523, + "longitude": -77.14935313519123, + "altitude": 5.5 + } + } + }, + { + "sensorId": "SomeID2", + "type": "SemanticLidar", + "ref": { + "type": "CARTESIAN", + "location": { + "x": 1.0, + "y": 2.0, + "z": 0.0 + }, + "orientation": { + "yaw": 23.0, + "pitch": 0.0, + "roll": 0.0 + } + } + } +] \ No newline at end of file