diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index fe5ecef7..ba3eb807 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -2,7 +2,8 @@ name: Docker build on: pull_request: types: [opened, synchronize, reopened] - jobs: docker: uses: usdot-fhwa-stol/actions/.github/workflows/docker.yml@main + with: + runner: ubuntu-latest-16-cores diff --git a/.github/workflows/dockerhub.yml b/.github/workflows/dockerhub.yml index abdd8134..09bf8bbf 100644 --- a/.github/workflows/dockerhub.yml +++ b/.github/workflows/dockerhub.yml @@ -2,8 +2,8 @@ name: Docker Hub build on: push: branches: - - "develop" - - "master" + - develop + - master - "release/*" tags: - "carma-system-*" @@ -14,3 +14,5 @@ jobs: secrets: DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} + with: + runner: ubuntu-latest-16-cores diff --git a/README.md b/README.md index d19a2b73..423087f9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -| CI Build Status | Docker Build | Docker Hub Push | Sonar Code Quality | +| Docker Image Build (develop) | Docker Image Build (release) | Sonar Code Quality | Sonar Workflow | |----------------------|---------------------|---------------------|---------------------| -|[![CI](https://github.com/usdot-fhwa-stol/carma-simulation/actions/workflows/ci.yml/badge.svg)](https://github.com/usdot-fhwa-stol/carma-simulation/actions/workflows/ci.yml) | [![Docker](https://github.com/usdot-fhwa-stol/carma-simulation/actions/workflows/docker.yml/badge.svg)](https://github.com/usdot-fhwa-stol/carma-simulation/actions/workflows/docker.yml) | [![Docker Hub](https://github.com/usdot-fhwa-stol/carma-simulation/actions/workflows/dockerhub.yml/badge.svg)](https://github.com/usdot-fhwa-stol/carma-simulation/actions/workflows/dockerhub.yml) | [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=usdot-fhwa-stol_carma-simulation&metric=alert_status)](https://sonarcloud.io/dashboard?id=usdot-fhwa-stol_carma-simulation)| +|[![Docker Hub build](https://github.com/usdot-fhwa-stol/cdasim/actions/workflows/dockerhub.yml/badge.svg?branch=develop)](https://github.com/usdot-fhwa-stol/cdasim/actions/workflows/dockerhub.yml) | [![Docker Hub build](https://github.com/usdot-fhwa-stol/cdasim/actions/workflows/dockerhub.yml/badge.svg?branch=master)](https://github.com/usdot-fhwa-stol/cdasim/actions/workflows/dockerhub.yml) | [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=usdot-fhwa-stol_carma-simulation&metric=alert_status)](https://sonarcloud.io/dashboard?id=usdot-fhwa-stol_carma-simulation)| [![CI: Run tests](https://github.com/usdot-fhwa-stol/cdasim/actions/workflows/ci.yml/badge.svg)](https://github.com/usdot-fhwa-stol/cdasim/actions/workflows/ci.yml)| # CARMASimulation This repository will host the CARMA Everything-in-the-Loop Co-Simulation tool. This XiL simulation platform will support CARLA and SUMO simulation environments as a co-simulation using the MOSAIC framework to facilitate coordination and data exchange. This co-simulation environment will also utilize NS-3 to simulate the communications used by the C-ADS systems. diff --git a/co-simulation/bundle/pom.xml b/co-simulation/bundle/pom.xml index 024b663c..f83b3d30 100644 --- a/co-simulation/bundle/pom.xml +++ b/co-simulation/bundle/pom.xml @@ -144,6 +144,11 @@ mosaic-infrastructure ${mosaic.version} + + org.eclipse.mosaic + mosaic-carma-cloud + ${mosaic.version} + @@ -262,4 +267,4 @@ - \ No newline at end of file + diff --git a/co-simulation/bundle/src/assembly/resources/etc/logback.xml b/co-simulation/bundle/src/assembly/resources/etc/logback.xml index 4ab190bd..1cd6764d 100755 --- a/co-simulation/bundle/src/assembly/resources/etc/logback.xml +++ b/co-simulation/bundle/src/assembly/resources/etc/logback.xml @@ -98,6 +98,13 @@ %date %-5level %C{0}:%line - %msg%n + + UTF-8 + ${logDirectory}/CarmaCloud.log + + %date %-5level %C{0}:%line - %msg%n + + UTF-8 ${logDirectory}/Environment.log @@ -209,6 +216,10 @@ + + + + diff --git a/co-simulation/bundle/src/assembly/resources/etc/runtime.json b/co-simulation/bundle/src/assembly/resources/etc/runtime.json index 4350f1eb..28c175e1 100644 --- a/co-simulation/bundle/src/assembly/resources/etc/runtime.json +++ b/co-simulation/bundle/src/assembly/resources/etc/runtime.json @@ -121,6 +121,20 @@ ], "javaClasspathEntries": [] }, + { + "id": "carma-cloud", + "classname": "org.eclipse.mosaic.fed.carmacloud.ambassador.CarmaCloudMessageAmbassador", + "configuration": "carma-cloud_config.json", + "priority": 50, + "host": "local", + "port": 0, + "deploy": false, + "start": false, + "subscriptions": [ + "V2xMessageReception" + ], + "javaClasspathEntries": [] + }, { "id": "mapping", diff --git a/co-simulation/bundle/src/assembly/resources/scenarios/Town04_test/infrastructure/infrastructure_config.json b/co-simulation/bundle/src/assembly/resources/scenarios/Town04/carma-cloud/carma-cloud_config.json similarity index 100% rename from co-simulation/bundle/src/assembly/resources/scenarios/Town04_test/infrastructure/infrastructure_config.json rename to co-simulation/bundle/src/assembly/resources/scenarios/Town04/carma-cloud/carma-cloud_config.json diff --git a/co-simulation/bundle/src/assembly/resources/scenarios/Town04/scenario_config.json b/co-simulation/bundle/src/assembly/resources/scenarios/Town04/scenario_config.json index 2103391b..03a52f54 100755 --- a/co-simulation/bundle/src/assembly/resources/scenarios/Town04/scenario_config.json +++ b/co-simulation/bundle/src/assembly/resources/scenarios/Town04/scenario_config.json @@ -34,7 +34,8 @@ "sumo": true, "carla": true, "carma": true, - "infrastructure": true + "infrastructure": true, + "carma-cloud": true } } diff --git a/co-simulation/bundle/src/assembly/resources/scenarios/Town04_test/application/Town04.db b/co-simulation/bundle/src/assembly/resources/scenarios/Town04_carma_cloud/application/Town04.db similarity index 100% rename from co-simulation/bundle/src/assembly/resources/scenarios/Town04_test/application/Town04.db rename to co-simulation/bundle/src/assembly/resources/scenarios/Town04_carma_cloud/application/Town04.db diff --git a/co-simulation/bundle/src/assembly/resources/scenarios/Town04_test/carla/bridge.sh b/co-simulation/bundle/src/assembly/resources/scenarios/Town04_carma_cloud/carla/bridge.sh similarity index 100% rename from co-simulation/bundle/src/assembly/resources/scenarios/Town04_test/carla/bridge.sh rename to co-simulation/bundle/src/assembly/resources/scenarios/Town04_carma_cloud/carla/bridge.sh diff --git a/co-simulation/bundle/src/assembly/resources/scenarios/Town04_test/carla/carla_config.json b/co-simulation/bundle/src/assembly/resources/scenarios/Town04_carma_cloud/carla/carla_config.json similarity index 63% rename from co-simulation/bundle/src/assembly/resources/scenarios/Town04_test/carla/carla_config.json rename to co-simulation/bundle/src/assembly/resources/scenarios/Town04_carma_cloud/carla/carla_config.json index aff6f191..7f16c9ba 100644 --- a/co-simulation/bundle/src/assembly/resources/scenarios/Town04_test/carla/carla_config.json +++ b/co-simulation/bundle/src/assembly/resources/scenarios/Town04_carma_cloud/carla/carla_config.json @@ -1,7 +1,7 @@ { "updateInterval": 100, "carlaUE4Path": "/opt/carla/", - "bridgePath": "/opt/carma-simulation/scenarios/Town04_test/carla; bridge.sh", + "bridgePath": "/opt/carma-simulation/scenarios/Town04_carma_cloud/carla; bridge.sh", "carlaConnectionPort": 8913, "carlaCDASimAdapterUrl":"http://127.0.0.1:8090/RPC2" } diff --git a/co-simulation/bundle/src/assembly/resources/scenarios/Town04_carma_cloud/carma-cloud/carma-cloud_config.json b/co-simulation/bundle/src/assembly/resources/scenarios/Town04_carma_cloud/carma-cloud/carma-cloud_config.json new file mode 100644 index 00000000..196145f7 --- /dev/null +++ b/co-simulation/bundle/src/assembly/resources/scenarios/Town04_carma_cloud/carma-cloud/carma-cloud_config.json @@ -0,0 +1,3 @@ +{ + "updateInterval": 100 +} diff --git a/co-simulation/bundle/src/assembly/resources/scenarios/Town04_test/carma/carma_config.json b/co-simulation/bundle/src/assembly/resources/scenarios/Town04_carma_cloud/carma/carma_config.json similarity index 100% rename from co-simulation/bundle/src/assembly/resources/scenarios/Town04_test/carma/carma_config.json rename to co-simulation/bundle/src/assembly/resources/scenarios/Town04_carma_cloud/carma/carma_config.json diff --git a/co-simulation/bundle/src/assembly/resources/scenarios/Town04_carma_cloud/infrastructure/infrastructure_config.json b/co-simulation/bundle/src/assembly/resources/scenarios/Town04_carma_cloud/infrastructure/infrastructure_config.json new file mode 100644 index 00000000..196145f7 --- /dev/null +++ b/co-simulation/bundle/src/assembly/resources/scenarios/Town04_carma_cloud/infrastructure/infrastructure_config.json @@ -0,0 +1,3 @@ +{ + "updateInterval": 100 +} diff --git a/co-simulation/bundle/src/assembly/resources/scenarios/Town04_test/mapping/mapping_config.json b/co-simulation/bundle/src/assembly/resources/scenarios/Town04_carma_cloud/mapping/mapping_config.json similarity index 100% rename from co-simulation/bundle/src/assembly/resources/scenarios/Town04_test/mapping/mapping_config.json rename to co-simulation/bundle/src/assembly/resources/scenarios/Town04_carma_cloud/mapping/mapping_config.json diff --git a/co-simulation/bundle/src/assembly/resources/scenarios/Town04_test/ns3/ns3_config.json b/co-simulation/bundle/src/assembly/resources/scenarios/Town04_carma_cloud/ns3/ns3_config.json similarity index 100% rename from co-simulation/bundle/src/assembly/resources/scenarios/Town04_test/ns3/ns3_config.json rename to co-simulation/bundle/src/assembly/resources/scenarios/Town04_carma_cloud/ns3/ns3_config.json diff --git a/co-simulation/bundle/src/assembly/resources/scenarios/Town04_test/ns3/ns3_federate_config.xml b/co-simulation/bundle/src/assembly/resources/scenarios/Town04_carma_cloud/ns3/ns3_federate_config.xml similarity index 100% rename from co-simulation/bundle/src/assembly/resources/scenarios/Town04_test/ns3/ns3_federate_config.xml rename to co-simulation/bundle/src/assembly/resources/scenarios/Town04_carma_cloud/ns3/ns3_federate_config.xml diff --git a/co-simulation/bundle/src/assembly/resources/scenarios/Town04_test/output/output_config.xml b/co-simulation/bundle/src/assembly/resources/scenarios/Town04_carma_cloud/output/output_config.xml similarity index 100% rename from co-simulation/bundle/src/assembly/resources/scenarios/Town04_test/output/output_config.xml rename to co-simulation/bundle/src/assembly/resources/scenarios/Town04_carma_cloud/output/output_config.xml diff --git a/co-simulation/bundle/src/assembly/resources/scenarios/Town04_test/scenario_config.json b/co-simulation/bundle/src/assembly/resources/scenarios/Town04_carma_cloud/scenario_config.json similarity index 93% rename from co-simulation/bundle/src/assembly/resources/scenarios/Town04_test/scenario_config.json rename to co-simulation/bundle/src/assembly/resources/scenarios/Town04_carma_cloud/scenario_config.json index 2103391b..03a52f54 100755 --- a/co-simulation/bundle/src/assembly/resources/scenarios/Town04_test/scenario_config.json +++ b/co-simulation/bundle/src/assembly/resources/scenarios/Town04_carma_cloud/scenario_config.json @@ -34,7 +34,8 @@ "sumo": true, "carla": true, "carma": true, - "infrastructure": true + "infrastructure": true, + "carma-cloud": true } } diff --git a/co-simulation/bundle/src/assembly/resources/scenarios/Town04_test/sumo/Town04.net.xml b/co-simulation/bundle/src/assembly/resources/scenarios/Town04_carma_cloud/sumo/Town04.net.xml similarity index 100% rename from co-simulation/bundle/src/assembly/resources/scenarios/Town04_test/sumo/Town04.net.xml rename to co-simulation/bundle/src/assembly/resources/scenarios/Town04_carma_cloud/sumo/Town04.net.xml diff --git a/co-simulation/bundle/src/assembly/resources/scenarios/Town04_test/sumo/Town04.rou.xml b/co-simulation/bundle/src/assembly/resources/scenarios/Town04_carma_cloud/sumo/Town04.rou.xml similarity index 100% rename from co-simulation/bundle/src/assembly/resources/scenarios/Town04_test/sumo/Town04.rou.xml rename to co-simulation/bundle/src/assembly/resources/scenarios/Town04_carma_cloud/sumo/Town04.rou.xml diff --git a/co-simulation/bundle/src/assembly/resources/scenarios/Town04_test/sumo/Town04.sumocfg b/co-simulation/bundle/src/assembly/resources/scenarios/Town04_carma_cloud/sumo/Town04.sumocfg similarity index 100% rename from co-simulation/bundle/src/assembly/resources/scenarios/Town04_test/sumo/Town04.sumocfg rename to co-simulation/bundle/src/assembly/resources/scenarios/Town04_carma_cloud/sumo/Town04.sumocfg diff --git a/co-simulation/bundle/src/assembly/resources/scenarios/Town04_test/sumo/detector.xml b/co-simulation/bundle/src/assembly/resources/scenarios/Town04_carma_cloud/sumo/detector.xml similarity index 100% rename from co-simulation/bundle/src/assembly/resources/scenarios/Town04_test/sumo/detector.xml rename to co-simulation/bundle/src/assembly/resources/scenarios/Town04_carma_cloud/sumo/detector.xml diff --git a/co-simulation/bundle/src/assembly/resources/scenarios/Town04_test/sumo/sumo_config.json b/co-simulation/bundle/src/assembly/resources/scenarios/Town04_carma_cloud/sumo/sumo_config.json similarity index 100% rename from co-simulation/bundle/src/assembly/resources/scenarios/Town04_test/sumo/sumo_config.json rename to co-simulation/bundle/src/assembly/resources/scenarios/Town04_carma_cloud/sumo/sumo_config.json diff --git a/co-simulation/fed/mosaic-carma-cloud/pom.xml b/co-simulation/fed/mosaic-carma-cloud/pom.xml new file mode 100644 index 00000000..4c71b10c --- /dev/null +++ b/co-simulation/fed/mosaic-carma-cloud/pom.xml @@ -0,0 +1,105 @@ + + + + 4.0.0 + + + org.eclipse.mosaic + mosaic-parent + 22.1-SNAPSHOT + ../../pom.xml + + + mosaic-carma-cloud + CARMA Cloud Ambassador + + + + org.eclipse.mosaic + mosaic-rti-api + ${mosaic.version} + + + org.eclipse.mosaic + mosaic-objects + ${mosaic.version} + + + org.eclipse.mosaic + mosaic-objects + ${mosaic.version} + test-jar + test + + + gov.dot.fhwa.saxton + mosaic-carma-utils + ${mosaic.version} + + + org.eclipse.mosaic + mosaic-interactions + ${mosaic.version} + + + org.eclipse.mosaic + mosaic-utils + ${mosaic.version} + test-jar + test + + + org.eclipse.mosaic + mosaic-geomath + ${mosaic.version} + test-jar + test + + + ch.qos.logback + logback-classic + + + junit + junit + 4.11 + test + + + org.eclipse.mosaic + mosaic-application + 22.1-SNAPSHOT + compile + + + jakarta.xml.bind + jakarta.xml.bind-api + 2.3.2 + + + org.glassfish.jaxb + jaxb-runtime + 2.3.2 + + + + + + skip-carma-cloud-tests + + + + + + maven-surefire-plugin + + true + + + + + + + + diff --git a/co-simulation/fed/mosaic-carma-cloud/src/main/java/org/eclipse/mosaic/fed/carmacloud/ambassador/CarmaCloudInstance.java b/co-simulation/fed/mosaic-carma-cloud/src/main/java/org/eclipse/mosaic/fed/carmacloud/ambassador/CarmaCloudInstance.java new file mode 100644 index 00000000..0dc5333e --- /dev/null +++ b/co-simulation/fed/mosaic-carma-cloud/src/main/java/org/eclipse/mosaic/fed/carmacloud/ambassador/CarmaCloudInstance.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2023 LEIDOS. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file 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. + */ +package org.eclipse.mosaic.fed.carmacloud.ambassador; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.URL; +import java.net.HttpURLConnection; +import com.google.gson.Gson; +import gov.dot.fhwa.saxton.TimeSyncMessage; + + +/** + * CarmaCloudInstance class represents a physical instance of an + * CARMA Cloud in the simulated environment. + * It contains information about the instance such as its id and target address + */ +public class CarmaCloudInstance +{ + // unique simulation identifier for the CARMA Cloud instance + private final String carmaCloudId; + // URL endpoint where to send simulation time sync messages + private final String carmaCloudUrl; + + + /** + * Constructor for CarmaCloudInstance + * + * @param sId the ID of the CARMA Cloud instance. + * @param sUrl the receive time synchronization message port of the infrastructure. + */ + public CarmaCloudInstance(String sId, String sUrl) + { + carmaCloudId = sId; + carmaCloudUrl = sUrl; + } + + + /** + * Returns the URL endpoint of the CARMA Cloud instance + * + * @return String URL endpoint of the CARMA Cloud instance + */ + public String getCarmaCloudUrl() + { + return carmaCloudUrl; + } + + + /** + * Returns the ID of the CARMA Cloud instance + * + * @return String the ID of the CARMA Cloud instance + */ + public String getCarmaCloudId() + { + return carmaCloudId; + } + + + /** + * Sends time sync data to the CARMA Cloud Instance + * + * @param oMsg the JSON time sync state to transmit + * @throws IOException if there is an issue with the underlying URL connection + */ + public void sendTimeSyncMsg(TimeSyncMessage oMsg) + throws IOException + { + HttpURLConnection oHttp = (HttpURLConnection)new URL(carmaCloudUrl).openConnection(); + oHttp.setRequestMethod("POST"); + oHttp.setDoOutput(true); + try (DataOutputStream oOut = new DataOutputStream(oHttp.getOutputStream())) + { + oOut.write(new Gson().toJson(oMsg).getBytes()); + } + if (oHttp.getResponseCode() != 200) + throw new IOException(String.format("CARMA Cloud failure %d %s", oHttp.getResponseCode(), oHttp.getResponseMessage())); + } +} diff --git a/co-simulation/fed/mosaic-carma-cloud/src/main/java/org/eclipse/mosaic/fed/carmacloud/ambassador/CarmaCloudInstanceManager.java b/co-simulation/fed/mosaic-carma-cloud/src/main/java/org/eclipse/mosaic/fed/carmacloud/ambassador/CarmaCloudInstanceManager.java new file mode 100644 index 00000000..94e55253 --- /dev/null +++ b/co-simulation/fed/mosaic-carma-cloud/src/main/java/org/eclipse/mosaic/fed/carmacloud/ambassador/CarmaCloudInstanceManager.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2023 LEIDOS. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file 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. + */ + +package org.eclipse.mosaic.fed.carmacloud.ambassador; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import gov.dot.fhwa.saxton.TimeSyncMessage; + + +/** + * Session management class for Infrastructure instances communicating with + * MOSAIC. + * + * This class is responsible for managing instances of CARMA Cloud registered + * with the MOSAIC system. It provides methods for registering new instances, + * checking if instances are registered, and storing and retrieving instances + * from a map. + */ +public class CarmaCloudInstanceManager +{ + private final Map managedInstances = new HashMap<>(); + private final Logger log = LoggerFactory.getLogger(this.getClass()); + + + /** + * Register a new CARMA Cloud instance with the MOSAIC system. + * + * This method takes a CarmaCloudRegistrationMessage, converts it to a + * CarmaCloudInstance, and adds it to the managedInstances map + * if it is not already present. + * + * @param registration The CarmaCloudRegistrationMessage to be registered. + * + */ + public void onNewRegistration(CarmaCloudRegistrationMessage registration) + { + if (!managedInstances.containsKey(registration.getId())) + { + CarmaCloudInstance tmp = new CarmaCloudInstance(registration.getId(), registration.getUrl()); + managedInstances.put(registration.getId(), tmp); + } + else + log.warn("Registration message received for already registered CARMA Cloud with ID: {}", registration.getId()); + } + + + /** + * This function is used to send out encoded time step update to all registered + * instances the manager has on the managed instances map + * + * @param message This time message is used to store current seq and time step + * from the ambassador side + * @throws IOException + */ + public void onTimeStepUpdate(TimeSyncMessage message) + throws IOException + { + if (managedInstances.isEmpty()) + log.debug("There are no registered instances"); + else + { + log.debug("onTimeStepUpdate instance count {}", managedInstances.size()); + for (CarmaCloudInstance currentInstance : managedInstances.values()) + { + currentInstance.sendTimeSyncMsg(message); + log.debug("Sent time message to CARMA-Cloud " + message.toString()); + } + } + } + + + /** + * Returns Map of managed CARMA Cloud instances with CARMA Cloud ID as the + * String Key. + * + * @return map of managed CARMA Cloud instances. + */ + public Map getManagedInstances() + { + return managedInstances; + } +} diff --git a/co-simulation/fed/mosaic-carma-cloud/src/main/java/org/eclipse/mosaic/fed/carmacloud/ambassador/CarmaCloudMessageAmbassador.java b/co-simulation/fed/mosaic-carma-cloud/src/main/java/org/eclipse/mosaic/fed/carmacloud/ambassador/CarmaCloudMessageAmbassador.java new file mode 100644 index 00000000..3bc42cc1 --- /dev/null +++ b/co-simulation/fed/mosaic-carma-cloud/src/main/java/org/eclipse/mosaic/fed/carmacloud/ambassador/CarmaCloudMessageAmbassador.java @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2023 LEIDOS. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file 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. + */ + +package org.eclipse.mosaic.fed.carmacloud.ambassador; + +import java.io.IOException; +import java.util.List; + +import org.eclipse.mosaic.fed.carmacloud.configuration.CarmaCloudConfiguration; +import org.eclipse.mosaic.interactions.communication.V2xMessageReception; +import org.eclipse.mosaic.lib.util.objects.ObjectInstantiation; +import org.eclipse.mosaic.rti.api.AbstractFederateAmbassador; +import org.eclipse.mosaic.rti.api.IllegalValueException; +import org.eclipse.mosaic.rti.api.Interaction; +import org.eclipse.mosaic.rti.api.InternalFederateException; +import org.eclipse.mosaic.rti.api.parameters.AmbassadorParameter; +import org.eclipse.mosaic.rti.TIME; + +import gov.dot.fhwa.saxton.TimeSyncMessage; + +/** + * Implementation of a {@link AbstractFederateAmbassador} for CarmaCloud + * message ambassador. + */ +public class CarmaCloudMessageAmbassador extends AbstractFederateAmbassador +{ + /** + * Simulation time. + */ + long currentSimulationTime; + + /** + * CarmaCloudMessageAmbassador configuration file. + */ + CarmaCloudConfiguration carmaCloudConfiguration; + + private CarmaCloudRegistrationReceiver carmaCloudRegistrationReceiver; + private final CarmaCloudInstanceManager carmaCloudInstanceManager = new CarmaCloudInstanceManager(); + private int timeSyncSeq; + + + /** + * Create a new {@link CarmaCloudMessageAmbassador} object. + * + * @param ambassadorParameter includes parameters for the + * CarmaCloudMessageAmbassador. + */ + public CarmaCloudMessageAmbassador(AmbassadorParameter ambassadorParameter) throws RuntimeException + { + super(ambassadorParameter); + try // load configuration file + { + carmaCloudConfiguration = new ObjectInstantiation<>(CarmaCloudConfiguration.class, log) + .readFile(ambassadorParameter.configuration); + } + catch (InstantiationException e) + { + log.error("Configuration object could not be instantiated: ", e); + } + + log.info("The update interval of CARMA Cloud message ambassador is {}.", carmaCloudConfiguration.updateInterval); + + if (carmaCloudConfiguration.updateInterval <= 0L) + { + throw new RuntimeException("Invalid update interval for CARMA Cloud message ambassador, should be > 0."); + } + log.info("CARMA Cloud message ambassador is generated."); + } + + + /** + * This method is called to tell the federate the start time and the end time. + * + * @param startTime Start time of the simulation run in nanoseconds. + * @param endTime End time of the simulation run in nanoseconds. + * @throws InternalFederateException Exception is thrown if an error is occurred + * while execute of a federate. + */ + @Override + public void initialize(long startTime, long endTime) + throws InternalFederateException + { + super.initialize(startTime, endTime); + currentSimulationTime = startTime; + + carmaCloudRegistrationReceiver = new CarmaCloudRegistrationReceiver(); + carmaCloudRegistrationReceiver.init(); + new Thread(carmaCloudRegistrationReceiver).start(); + + try + { + rti.requestAdvanceTime(currentSimulationTime, 0, (byte) 1); + } + catch (IllegalValueException e) + { + log.error("Error during advanceTime request", e); + throw new InternalFederateException(e); + } + } + + + /** + * This method is called by the AbstractFederateAmbassador when the RTI grants a + * time advance to the federate.Any unprocessed interactions are forwarded to + the federate using the processInteraction method before this call is made. + * + * @param time The timestamp (in nanoseconds) indicating the time to which the federate can + * advance its local time. + * @throws InternalFederateException + */ + @Override + public synchronized void processTimeAdvanceGrant(long time) + throws InternalFederateException + { + // Process the time advance only if the time is equal or greater than the next + // simulation time step + log.debug("Process time advance grant from {} to {}.", currentSimulationTime, time); + if (time < currentSimulationTime) + { + return; + } + + currentSimulationTime = time; + try + { + // Handle any new carmaCloud registration requests + List newRegistrations = carmaCloudRegistrationReceiver.getReceivedMessages(); + for (CarmaCloudRegistrationMessage reg : newRegistrations) + { + log.info("Processing new registration request for {}.", reg.getId()); + // Store new instance registration to carmaCloud instance manager + carmaCloudInstanceManager.onNewRegistration(reg); + } + + timeSyncSeq += 1; + // nanoseconds to milliseconds for CarmaCloudTimeMessage + TimeSyncMessage timeSyncMessage = new TimeSyncMessage(currentSimulationTime / 1000000L, timeSyncSeq); + carmaCloudInstanceManager.onTimeStepUpdate(timeSyncMessage); + + // Advance the simulation time + currentSimulationTime += carmaCloudConfiguration.updateInterval* TIME.MILLI_SECOND; + + // Request the next time advance from the RTI + log.debug("Requesting timestep updated to {}.", currentSimulationTime); + rti.requestAdvanceTime(currentSimulationTime, 0, (byte) 2); + } + catch (IllegalValueException e) + { + throw new InternalFederateException(e); + } + catch (IOException e) + { + log.error("Error during updating timestep :", e); + } + } + + + /** + * Return whether this federate is time constrained. Is set if the federate is + * sensitive towards the correct ordering of events. The federate ambassador + * will ensure that the message processing happens in time stamp order. If set + * to false, interactions will be processed in receive order. + * + * @return {@code true} if this federate is time constrained, else + * {@code false}. + */ + @Override + public boolean isTimeConstrained() + { + return true; + } + + + /** + * Return whether this federate is time regulating. Is set if the federate + * influences other federates and can prevent them from advancing their local + * time. + * + * @return {@code true} if this federate is time regulating, {@code false} else. + */ + @Override + public boolean isTimeRegulating() + { + return false; + } + + + /** + * Test helper function to cleanup sockets and threads. + */ + protected void close() + { + carmaCloudRegistrationReceiver.stop(); + } +} diff --git a/co-simulation/fed/mosaic-carma-cloud/src/main/java/org/eclipse/mosaic/fed/carmacloud/ambassador/CarmaCloudRegistrationMessage.java b/co-simulation/fed/mosaic-carma-cloud/src/main/java/org/eclipse/mosaic/fed/carmacloud/ambassador/CarmaCloudRegistrationMessage.java new file mode 100644 index 00000000..82c5c25a --- /dev/null +++ b/co-simulation/fed/mosaic-carma-cloud/src/main/java/org/eclipse/mosaic/fed/carmacloud/ambassador/CarmaCloudRegistrationMessage.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2023 LEIDOS. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file 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. + */ +package org.eclipse.mosaic.fed.carmacloud.ambassador; + + +/** + * A message to be sent by CARMA Cloud instance when it registers with the + * carmacloud-mosaic ambassador + */ +public class CarmaCloudRegistrationMessage +{ + // unique simulation identifier for the CARMA Cloud instance + private final String id; + // URL endpoint where to send simulation time sync messages + private final String url; + + + /** + * Constructor for a CarmaCloudRegistrationMessage + * + * @param sId the ID of the CARMA Cloud instance. + * @param sUrl the receive time synchronization message port of the infrastructure. + */ + public CarmaCloudRegistrationMessage(String sId, String sUrl) + { + id = sId; + url = sUrl; + } + + + /** + * Returns the URL endpoint of the CARMA Cloud instance + * + * @return String URL endpoint of the CARMA Cloud instance + */ + public String getUrl() + { + return url; + } + + + /** + * Returns the ID of the CARMA Cloud instance + * + * @return String the ID of the CARMA Cloud instance + */ + public String getId() + { + return id; + } + + + @Override + public String toString() + { + return String.format("CarmaCloudRegistrationMessage id %s url %s", id, url); + } +} diff --git a/co-simulation/fed/mosaic-carma-cloud/src/main/java/org/eclipse/mosaic/fed/carmacloud/ambassador/CarmaCloudRegistrationReceiver.java b/co-simulation/fed/mosaic-carma-cloud/src/main/java/org/eclipse/mosaic/fed/carmacloud/ambassador/CarmaCloudRegistrationReceiver.java new file mode 100644 index 00000000..1bc0834d --- /dev/null +++ b/co-simulation/fed/mosaic-carma-cloud/src/main/java/org/eclipse/mosaic/fed/carmacloud/ambassador/CarmaCloudRegistrationReceiver.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2023 LEIDOS. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file 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. + */ + +package org.eclipse.mosaic.fed.carmacloud.ambassador; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.concurrent.atomic.AtomicBoolean; + +import com.google.gson.Gson; +import java.io.DataInputStream; +import java.net.ServerSocket; +import java.net.Socket; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Worker thread Runnable for operating a listen socket to receive outbound + * CARMA Cloud Messages from CARMA Cloud instances + * This {@link Runnable} instance will operate a UDP socket to subscribe to + * packets from a CARMA Cloud adapter. + * Upon receiving a packet, it will be queued for the primary thread + * to process the data once it ticks to a simulation processing step + */ +public class CarmaCloudRegistrationReceiver implements Runnable +{ + private static final int LISTEN_PORT = 1617; + private final AtomicBoolean running = new AtomicBoolean(false); + private final Logger log = LoggerFactory.getLogger(this.getClass()); + private final Queue rxQueue = new LinkedList<>(); + private ServerSocket srvr; + + + /** + * Initialize the listen socket for messages from the CARMA Cloud instance adapter + * + * @throws RuntimeException if socket instantiation fails + */ + public void init() throws RuntimeException + { + try + { + srvr = new ServerSocket(LISTEN_PORT); + } + catch (Exception oEx) + { + throw new RuntimeException("server socket instantiation failure", oEx); + } + } + + + /** + * The main method of the worker thread. Listens for incoming messages and + * queues them for processing on the primary thread. + */ + @Override + public void run() + { + running.set(true); + try + { + while (running.get()) + { + Socket oSock = srvr.accept(); + DataInputStream oIn = new DataInputStream(oSock.getInputStream()); + + // parse message + CarmaCloudRegistrationMessage parsedMessage = + new Gson().fromJson(oIn.readUTF(), CarmaCloudRegistrationMessage.class); + + // Enqueue message for processing on main thread + synchronized (rxQueue) + { + rxQueue.add(parsedMessage); + } + log.info("New CARMA Cloud instance '{}' received with CARMA Cloud Registration Receiver.", parsedMessage.getId()); + } + } + catch (Exception oEx) + { + log.error("Error occurred", oEx); + } + } + + + /** + * Stop the runnable instance and close the listen socket. + */ + public void stop() + { + running.set(false); + if (srvr != null) + { + try + { + srvr.close(); + } + catch (Exception oEx) + { + log.error("Error occurred", oEx); + } + } + } + + + /** + * Query the current buffer of outbound messages. Clears the currently stored + * buffer once called. Thread-safe. + * + * @return The list of received outbound message from all Infrastructure Device + * instances since last call of this method + */ + public List getReceivedMessages() + { + List output = new ArrayList<>(); + synchronized (rxQueue) + { + output.addAll(rxQueue); + rxQueue.clear(); + } + return output; + } +} diff --git a/co-simulation/fed/mosaic-carma-cloud/src/main/java/org/eclipse/mosaic/fed/carmacloud/configuration/CarmaCloudConfiguration.java b/co-simulation/fed/mosaic-carma-cloud/src/main/java/org/eclipse/mosaic/fed/carmacloud/configuration/CarmaCloudConfiguration.java new file mode 100644 index 00000000..bbdaf761 --- /dev/null +++ b/co-simulation/fed/mosaic-carma-cloud/src/main/java/org/eclipse/mosaic/fed/carmacloud/configuration/CarmaCloudConfiguration.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2021 Old Dominion University. All rights reserved. + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package org.eclipse.mosaic.fed.carmacloud.configuration; + +import java.io.Serializable; +import com.google.gson.annotations.JsonAdapter; +import org.eclipse.mosaic.lib.util.gson.TimeFieldAdapter; + + +/** + * The CARMA Cloud Message Ambassador configuration class. + */ +public class CarmaCloudConfiguration implements Serializable +{ + private static final long serialVersionUID = 1705520136000000000L; + + + @JsonAdapter(TimeFieldAdapter.LegacyMilliSeconds.class) + public Long updateInterval = 100L; +} diff --git a/co-simulation/fed/mosaic-carma-cloud/src/test/java/org/eclipse/mosaic/fed/carmacloud/ambassador/CarmaCloudInstanceManagerTest.java b/co-simulation/fed/mosaic-carma-cloud/src/test/java/org/eclipse/mosaic/fed/carmacloud/ambassador/CarmaCloudInstanceManagerTest.java new file mode 100644 index 00000000..98300e91 --- /dev/null +++ b/co-simulation/fed/mosaic-carma-cloud/src/test/java/org/eclipse/mosaic/fed/carmacloud/ambassador/CarmaCloudInstanceManagerTest.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2023 LEIDOS. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file 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. + */ + +package org.eclipse.mosaic.fed.carmacloud.ambassador; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import java.io.IOException; +import java.util.Map; +import org.junit.Before; +import org.junit.Test; + +import gov.dot.fhwa.saxton.TimeSyncMessage; + + + +public class CarmaCloudInstanceManagerTest { + + private CarmaCloudInstanceManager manager; + private CarmaCloudInstance instance1; + private static final String carmacloudId = "carma-cloud"; + private static final String carmacloudUrl = "carma-cloud-url"; + + @Before + public void setUp() throws Exception { + manager = new CarmaCloudInstanceManager(); + instance1 = mock(CarmaCloudInstance.class); + } + + @Test + public void testOnNewRegistration() { + // Set up the registration object + CarmaCloudRegistrationMessage registration = new CarmaCloudRegistrationMessage(carmacloudId, carmacloudUrl); + + // Call the onNewRegistration method with the registration object + manager.onNewRegistration(registration); + + // Verify that the infrastructure instance was added to the manager + assertFalse(manager.getManagedInstances().isEmpty()); + assertNotNull(manager.getManagedInstances().get(carmacloudId)); + } + + @Test + public void testOnTimeStepUpdate() throws IOException { + // replace registered CARMA Cloud instance with mock instance + manager.getManagedInstances().put(carmacloudId, instance1); + + TimeSyncMessage message = new TimeSyncMessage(999L, 11); + manager.onTimeStepUpdate(message); + // Verify that all instances sendTimeSyncMsgs was called. + verify(instance1).sendTimeSyncMsg(message); + } +} diff --git a/co-simulation/fed/mosaic-carma-cloud/src/test/java/org/eclipse/mosaic/fed/carmacloud/ambassador/CarmaCloudInstanceTest.java b/co-simulation/fed/mosaic-carma-cloud/src/test/java/org/eclipse/mosaic/fed/carmacloud/ambassador/CarmaCloudInstanceTest.java new file mode 100644 index 00000000..5f6d74e5 --- /dev/null +++ b/co-simulation/fed/mosaic-carma-cloud/src/test/java/org/eclipse/mosaic/fed/carmacloud/ambassador/CarmaCloudInstanceTest.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2023 LEIDOS. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file 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. + */ +package org.eclipse.mosaic.fed.carmacloud.ambassador; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.BufferedInputStream; +import java.net.InetSocketAddress; +import com.sun.net.httpserver.HttpServer; +import com.sun.net.httpserver.HttpContext; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; + +import com.google.gson.Gson; +import java.io.IOException; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + + + +import gov.dot.fhwa.saxton.TimeSyncMessage; + +public class CarmaCloudInstanceTest { + + private CarmaCloudInstance instance; + private StringBuilder messageBuf = new StringBuilder(); + private HttpServer oSrvr; + + class TimeSyncHandler implements HttpHandler { + @Override + public void handle(HttpExchange oExch) throws IOException { + int nChar; + BufferedInputStream oIn = new BufferedInputStream(oExch.getRequestBody()); + while ((nChar = oIn.read()) >= 0) + messageBuf.append((char)nChar); + + oExch.sendResponseHeaders(200, -1L); + oExch.close(); + } + } + + @Before + public void setUp() throws IOException { + instance = new CarmaCloudInstance("carma-cloud", "http://localhost:8080/carmacloud/simulation"); + oSrvr = HttpServer.create(new InetSocketAddress("localhost", 8080), 0); + HttpContext oCtx = oSrvr.createContext("/carmacloud/simulation"); + oCtx.setHandler(new TimeSyncHandler()); + oSrvr.start(); + } + + @After + public void tearDown() throws IOException { + oSrvr.stop(0); + } + + @Test + public void testGetterSetterConstructor() { + // Test getters and constructor for setting and retrieving class members + assertEquals("carma-cloud", instance.getCarmaCloudId()); + assertEquals("http://localhost:8080/carmacloud/simulation", instance.getCarmaCloudUrl()); + } + + @Test + public void testSendTimeSyncMsg() throws IOException { + // Test SendTimeSyncMsg method + TimeSyncMessage test_msg = new TimeSyncMessage(999L, 11); + instance.sendTimeSyncMsg(test_msg); + + assertTrue(messageBuf.length() > 0); + if (messageBuf.length() > 0) + { + TimeSyncMessage parsedMessage = new Gson().fromJson(messageBuf.toString(), TimeSyncMessage.class); + assertEquals(999L, parsedMessage.getTimestep()); + assertEquals(11, parsedMessage.getSeq()); + } + } +} diff --git a/co-simulation/fed/mosaic-carma-cloud/src/test/java/org/eclipse/mosaic/fed/carmacloud/ambassador/CarmaCloudMessageAmbassadorTest.java b/co-simulation/fed/mosaic-carma-cloud/src/test/java/org/eclipse/mosaic/fed/carmacloud/ambassador/CarmaCloudMessageAmbassadorTest.java new file mode 100644 index 00000000..bde0faa1 --- /dev/null +++ b/co-simulation/fed/mosaic-carma-cloud/src/test/java/org/eclipse/mosaic/fed/carmacloud/ambassador/CarmaCloudMessageAmbassadorTest.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2023 LEIDOS. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file 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. + */ + +package org.eclipse.mosaic.fed.carmacloud.ambassador; + +import org.eclipse.mosaic.lib.util.junit.TestFileRule; +import org.eclipse.mosaic.rti.api.IllegalValueException; +import org.eclipse.mosaic.rti.api.InternalFederateException; +import org.eclipse.mosaic.rti.api.Interaction; +import org.eclipse.mosaic.rti.api.RtiAmbassador; +import org.eclipse.mosaic.rti.api.parameters.AmbassadorParameter; +import org.eclipse.mosaic.rti.api.parameters.FederateDescriptor; +import org.eclipse.mosaic.rti.config.CLocalHost; +import org.eclipse.mosaic.rti.TIME; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import org.mockito.internal.util.reflection.FieldSetter; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.RuleChain; +import org.junit.rules.TemporaryFolder; + + +/** + * Tests for {@link CarmaCloudMessageAmbassador}. + */ +public class CarmaCloudMessageAmbassadorTest { + + private final TemporaryFolder temporaryFolder = new TemporaryFolder(); + + private final TestFileRule testFileRule = new TestFileRule(temporaryFolder).basedir("carmacloud"); + + @Rule + public RuleChain chain = RuleChain.outerRule(temporaryFolder).around(testFileRule); + + /** + * {@link RtiAmbassador} mock. + */ + private RtiAmbassador rtiMock; + + private CarmaCloudMessageAmbassador ambassador; + /** + * {@link CarmaCloudInstanceManager} mock. + */ + private CarmaCloudInstanceManager instanceManagerMock; + /** + * {@link CarmaCloudRegistrationReceiver} mock. + */ + private CarmaCloudRegistrationReceiver receiverMock; + + private ArrayList registrationMessages; + + @Before + public void setUp() throws IOException, NoSuchFieldException, InternalFederateException, IllegalValueException { + // Initialize Mocks + rtiMock = mock(RtiAmbassador.class); + FederateDescriptor handleMock = mock(FederateDescriptor.class); + instanceManagerMock = mock(CarmaCloudInstanceManager.class); + receiverMock = mock(CarmaCloudRegistrationReceiver.class); + File workingDir = temporaryFolder.getRoot(); + CLocalHost testHostConfig = new CLocalHost(); + testHostConfig.workingDirectory = workingDir.getAbsolutePath(); + + // Mock methods + when(handleMock.getHost()).thenReturn(testHostConfig); + when(handleMock.getId()).thenReturn("carmacloud"); + CarmaCloudRegistrationMessage message = new CarmaCloudRegistrationMessage("carmacloud-id", "carmacloud-url"); + registrationMessages = new ArrayList<>(); + registrationMessages.add(message); + when(receiverMock.getReceivedMessages()).thenReturn(registrationMessages); + + // Set mocks as ambassador members through reflection or setters + ambassador = new CarmaCloudMessageAmbassador(new AmbassadorParameter("carmacloud", + temporaryFolder.newFile("carmacloud/carma-cloud_config.json"))); + + ambassador.setRtiAmbassador(rtiMock); + ambassador.setFederateDescriptor(handleMock); + FieldSetter.setField(ambassador, ambassador.getClass().getDeclaredField("carmaCloudRegistrationReceiver"), receiverMock); + FieldSetter.setField(ambassador, ambassador.getClass().getDeclaredField("carmaCloudInstanceManager"), instanceManagerMock); + } + + @Test + public void testInitialize() throws InternalFederateException, IllegalValueException { + // Test initialize method + ambassador.initialize(0, 100 * TIME.SECOND); + verify(rtiMock, times(1)).requestAdvanceTime(0L, 0L, ((byte) 1)); + // cleanup threads and sockets + ambassador.close(); + } + + @Test + public void testProcessTimeAdvanceGrant() throws InternalFederateException, IllegalValueException, NoSuchFieldException, SecurityException + { + //Test processTimeAdvanceGrant for CARMA Cloud Registration + ambassador.processTimeAdvanceGrant(100000000L); + // Verify received messages were attempted to be pulled from CARMA Cloud Registration Receiver mock + verify(receiverMock, times(1)).getReceivedMessages(); + verify(rtiMock, times(1)).requestAdvanceTime(200000000L, 0L, ((byte) 2)); + } +} diff --git a/co-simulation/fed/mosaic-carma-cloud/src/test/java/org/eclipse/mosaic/fed/carmacloud/ambassador/CarmaCloudRegistrationMessageTest.java b/co-simulation/fed/mosaic-carma-cloud/src/test/java/org/eclipse/mosaic/fed/carmacloud/ambassador/CarmaCloudRegistrationMessageTest.java new file mode 100644 index 00000000..b48449b4 --- /dev/null +++ b/co-simulation/fed/mosaic-carma-cloud/src/test/java/org/eclipse/mosaic/fed/carmacloud/ambassador/CarmaCloudRegistrationMessageTest.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 LEIDOS. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file 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. + */ +package org.eclipse.mosaic.fed.carmacloud.ambassador; + +import static org.junit.Assert.assertEquals; +import org.junit.Test; + +public class CarmaCloudRegistrationMessageTest { + @Test + public void testGetterSettersConstructor() { + // Test Constructor + CarmaCloudRegistrationMessage message = new CarmaCloudRegistrationMessage( + "carma-cloud", + "http://someaddress:8080/carmacloud/simulation"); + + // Test Getter + assertEquals("carma-cloud", message.getId()); + assertEquals("http://someaddress:8080/carmacloud/simulation", message.getUrl() ); + } +} diff --git a/co-simulation/fed/mosaic-carma-cloud/src/test/java/org/eclipse/mosaic/fed/carmacloud/ambassador/CarmaCloudRegistrationReceiverTest.java b/co-simulation/fed/mosaic-carma-cloud/src/test/java/org/eclipse/mosaic/fed/carmacloud/ambassador/CarmaCloudRegistrationReceiverTest.java new file mode 100644 index 00000000..2a0341a6 --- /dev/null +++ b/co-simulation/fed/mosaic-carma-cloud/src/test/java/org/eclipse/mosaic/fed/carmacloud/ambassador/CarmaCloudRegistrationReceiverTest.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2023 LEIDOS. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file 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. + */ + + package org.eclipse.mosaic.fed.carmacloud.ambassador; + + import static org.junit.Assert.assertEquals; + import static org.mockito.Mockito.mock; + import static org.mockito.Mockito.when; + import org.mockito.internal.util.reflection.FieldSetter; + + import java.io.ByteArrayInputStream; + import java.io.ByteArrayOutputStream; + import java.io.DataOutputStream; + import java.net.InetAddress; + import java.net.InetSocketAddress; + import java.net.ServerSocket; + import java.net.Socket; + import java.util.List; + + import org.junit.After; + import org.junit.Before; + import org.junit.Test; + + public class CarmaCloudRegistrationReceiverTest { + + @Test + public void testMessageReceive() throws Exception { + // Define a test message in JSON format + String json = "{\"id\":\"carma-cloud\",\"url\":\"http://someaddress:8080/carmacloud/simulation\"}"; + ByteArrayOutputStream oBytes = new ByteArrayOutputStream(); + DataOutputStream oOut = new DataOutputStream(oBytes); + oOut.writeUTF(json); + oOut.close(); // flush contents + + ServerSocket MockServer = mock(ServerSocket.class); + Socket MockSock = mock(Socket.class); + + // mock socket server, socket, and inputstream + when(MockServer.accept()).thenReturn(MockSock); + when(MockSock.getInputStream()).thenReturn(new ByteArrayInputStream(oBytes.toByteArray())); + + // Setup the registration receiver + CarmaCloudRegistrationReceiver receiver = new CarmaCloudRegistrationReceiver(); + FieldSetter.setField(receiver, receiver.getClass().getDeclaredField("srvr"), MockServer); + receiver.run(); // multi-threading not needed here + + // Verify that the message was received correctly + List msgs = receiver.getReceivedMessages(); + assertEquals(1, msgs.size()); + + CarmaCloudRegistrationMessage msg = msgs.get(0); + assertEquals("carma-cloud", msg.getId()); + assertEquals("http://someaddress:8080/carmacloud/simulation", msg.getUrl()); + } + } + diff --git a/co-simulation/fed/mosaic-carma/src/main/java/org/eclipse/mosaic/fed/carma/ambassador/CarmaInstanceManager.java b/co-simulation/fed/mosaic-carma/src/main/java/org/eclipse/mosaic/fed/carma/ambassador/CarmaInstanceManager.java index 59aaee9d..e53d049b 100644 --- a/co-simulation/fed/mosaic-carma/src/main/java/org/eclipse/mosaic/fed/carma/ambassador/CarmaInstanceManager.java +++ b/co-simulation/fed/mosaic-carma/src/main/java/org/eclipse/mosaic/fed/carma/ambassador/CarmaInstanceManager.java @@ -96,10 +96,11 @@ public V2xMessageTransmission onV2XMessageTx(InetAddress sourceAddr, CarmaV2xMes sender.getCarlaRoleName(), sender.getLocation()).viaChannel(AdHocChannel.CCH); // TODO: Get maximum broadcast radius from configuration file. MessageRouting routing = messageRoutingBuilder.geoBroadCast(new GeoCircle(sender.getLocation(), 300)); - log.debug("Generating V2XMessageTransmission interaction sim time: {}, sender id: {}, location: {}, payload: {}", + log.debug("Generating V2XMessageTransmission interaction sim time: {}, sender id: {}, location: {}, type: {}, payload: {}", time, sender.getCarmaVehicleId(), sender.getLocation(), + txMsg.getType(), txMsg.getPayload() ); return new V2xMessageTransmission( time, new ExternalV2xMessage(routing, diff --git a/co-simulation/fed/mosaic-carma/src/main/java/org/eclipse/mosaic/fed/carma/ambassador/CarmaMessageAmbassador.java b/co-simulation/fed/mosaic-carma/src/main/java/org/eclipse/mosaic/fed/carma/ambassador/CarmaMessageAmbassador.java index eec5c94f..5215000c 100644 --- a/co-simulation/fed/mosaic-carma/src/main/java/org/eclipse/mosaic/fed/carma/ambassador/CarmaMessageAmbassador.java +++ b/co-simulation/fed/mosaic-carma/src/main/java/org/eclipse/mosaic/fed/carma/ambassador/CarmaMessageAmbassador.java @@ -174,6 +174,7 @@ public synchronized void processTimeAdvanceGrant(long time) throws InternalFeder List> newMessages = v2xMessageReceiver.getReceivedMessages(); for (Tuple msg : newMessages) { V2xMessageTransmission msgInt = carmaInstanceManager.onV2XMessageTx(msg.getA(), msg.getB(), currentSimulationTime); + log.debug("Generated a message with ID: {}", msgInt.getMessageId()); SimulationKernel.SimulationKernel.getV2xMessageCache().putItem(currentSimulationTime, msgInt.getMessage()); rti.triggerInteraction(msgInt); } diff --git a/co-simulation/pom.xml b/co-simulation/pom.xml index 8844c0c0..cb71b377 100644 --- a/co-simulation/pom.xml +++ b/co-simulation/pom.xml @@ -72,6 +72,7 @@ fed/mosaic-carla fed/mosaic-carma fed/mosaic-infrastructure + fed/mosaic-carma-cloud test/mosaic-integration-tests diff --git a/docker/checkout.sh b/docker/checkout.sh deleted file mode 100755 index 06391349..00000000 --- a/docker/checkout.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/bin/bash - -# Copyright (C) 2018-2020 LEIDOS. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not -# use this file 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. - -# CARMA packages checkout script -# Optional argument to set the root checkout directory with no ending '/' default is '~' - -set -ex - -dir=~ -while [[ $# -gt 0 ]]; do - arg="$1" - case $arg in - -d|--develop) - BRANCH=develop - shift - ;; - -r|--root) - dir=$2 - shift - shift - ;; - esac -done - -if [[ "$BRANCH" = "develop" ]]; then - git clone https://github.com/usdot-fhwa-stol/carma-msgs.git ~/src/carma-msgs --branch $BRANCH --depth 1 - git clone https://github.com/usdot-fhwa-stol/carma-utils.git ~/src/carma-utils --branch $BRANCH --depth 1 -else - git clone https://github.com/usdot-fhwa-stol/carma-msgs.git ${dir}/src/carma-msgs --branch carma-system-4.5.0 --depth 1 - git clone https://github.com/usdot-fhwa-stol/carma-utils.git ${dir}/src/carma-utils --branch carma-system-4.5.0 --depth 1 -fi