diff --git a/co-simulation/fed/mosaic-application/pom.xml b/co-simulation/fed/mosaic-application/pom.xml index ddeb0881..f1cadd9f 100644 --- a/co-simulation/fed/mosaic-application/pom.xml +++ b/co-simulation/fed/mosaic-application/pom.xml @@ -54,6 +54,12 @@ ${mosaic.version} test-jar test + + + org.eclipse.mosaic + mosaic-application + + diff --git a/co-simulation/fed/mosaic-carma-messenger/pom.xml b/co-simulation/fed/mosaic-carma-messenger/pom.xml new file mode 100644 index 00000000..78d8c6bb --- /dev/null +++ b/co-simulation/fed/mosaic-carma-messenger/pom.xml @@ -0,0 +1,138 @@ + + + + 4.0.0 + + + org.eclipse.mosaic + mosaic-parent + 22.1-SNAPSHOT + ../../pom.xml + + + mosaic-carma-messenger + CARMA Messenger Ambassador + + + + org.eclipse.mosaic + mosaic-rti-api + ${mosaic.version} + + + org.eclipse.mosaic + mosaic-objects + ${mosaic.version} + + + gov.dot.fhwa.saxton + mosaic-carma-utils + ${mosaic.version} + + + com.google.code.gson + gson + 2.8.9 + + + 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 + + + org.eclipse.mosaic + mosaic-objects + ${mosaic.version} + test-jar + test + + + ch.qos.logback + logback-classic + + + junit + junit + 4.11 + test + + + org.eclipse.mosaic + mosaic-application + 22.1-SNAPSHOT + compile + + + javax.xml.bind + jaxb-api + 2.3.1 + + + commons-codec + commons-codec + 1.15 + + + jakarta.xml.bind + jakarta.xml.bind-api + 2.3.2 + + + org.glassfish.jaxb + jaxb-runtime + 2.3.2 + + + org.eclipse.mosaic + mosaic-utils + ${mosaic.version} + compile + + + org.eclipse.mosaic + mosaic-carma + 22.1-SNAPSHOT + compile + + + org.eclipse.mosaic + mosaic-common-utils + 22.1-SNAPSHOT + compile + + + + + + skip-carma-messenger-tests + + + + + + maven-surefire-plugin + + true + + + + + + + + diff --git a/co-simulation/fed/mosaic-carma-messenger/src/main/java/org/eclipse/mosaic/fed/carmamessenger/ambassador/CarmaMessengerInstance.java b/co-simulation/fed/mosaic-carma-messenger/src/main/java/org/eclipse/mosaic/fed/carmamessenger/ambassador/CarmaMessengerInstance.java new file mode 100644 index 00000000..1134e8db --- /dev/null +++ b/co-simulation/fed/mosaic-carma-messenger/src/main/java/org/eclipse/mosaic/fed/carmamessenger/ambassador/CarmaMessengerInstance.java @@ -0,0 +1,63 @@ +/* + * 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.carmamessenger.ambassador; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.InetAddress; + +import org.eclipse.mosaic.lib.CommonUtil.ambassador.CommonInstance; + + +public class CarmaMessengerInstance extends CommonInstance{ + + private String messengerEmergencyState; + private int rxBridgeMessagePort; + + public CarmaMessengerInstance(String carmaMessengerVehicleId, String sumoRoleName, InetAddress targetAddress, int v2xPort, int timeSyncPort, String messengerEmergencyState, int rxBridgeMessagePort) { + super(carmaMessengerVehicleId, sumoRoleName, targetAddress, v2xPort, timeSyncPort); + this.messengerEmergencyState = messengerEmergencyState; + this.rxBridgeMessagePort = rxBridgeMessagePort; + } + /** + * Carma Messenger Emergency state + */ + + public String getMessengerEmergencyState() { + return messengerEmergencyState; + } + + public void setMessengerEmergencyState(String messengerEmergencyState) { + this.messengerEmergencyState = messengerEmergencyState; + } + + public int getRxBridgeMessagePort() { + return rxBridgeMessagePort; + } + + public void setRxBridgeMessagePort(int rxBridgeMessagePort) { + this.rxBridgeMessagePort = rxBridgeMessagePort; + } + + public void sendVehStatusMsgs(byte[] data) throws IOException { + if (super.rxMsgsSocket == null) { + throw new IllegalStateException("Attempted to send data before opening socket"); + } + + DatagramPacket packet = new DatagramPacket(data, data.length, super.getTargetAddress(), rxBridgeMessagePort); + super.rxMsgsSocket.send(packet); + } +} diff --git a/co-simulation/fed/mosaic-carma-messenger/src/main/java/org/eclipse/mosaic/fed/carmamessenger/ambassador/CarmaMessengerInstanceManager.java b/co-simulation/fed/mosaic-carma-messenger/src/main/java/org/eclipse/mosaic/fed/carmamessenger/ambassador/CarmaMessengerInstanceManager.java new file mode 100644 index 00000000..cbc930c5 --- /dev/null +++ b/co-simulation/fed/mosaic-carma-messenger/src/main/java/org/eclipse/mosaic/fed/carmamessenger/ambassador/CarmaMessengerInstanceManager.java @@ -0,0 +1,111 @@ +/* + * 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.carmamessenger.ambassador; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; + +import org.eclipse.mosaic.lib.CommonUtil.ambassador.CommonInstanceManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.Gson; + +import gov.dot.fhwa.saxton.TimeSyncMessage; + +public class CarmaMessengerInstanceManager extends CommonInstanceManager{ + + private static final int BRIDGE_TARGET_PORT = 5500; + private final Logger log = LoggerFactory.getLogger(this.getClass()); + + /** + * Callback to invoked when a new CARMA Platform instance registers with the mosaic-carma ambassador for the first time + * @param registration The new instance's registration data + */ + public void onNewRegistration(CarmaMessengerRegistrationMessage registration) { + super.setTargetPort(5600); + if (!managedInstances.containsKey(registration.getVehicleRole())) { + try { + newCarmaMessengerInstance( + registration.getVehicleId(), + registration.getVehicleRole(), + InetAddress.getByName(registration.getRxMessageIpAddress()), + registration.getRxMessagePort(), + registration.getRxTimeSyncPort(), + registration.getMessengerEmergencyState(), + registration.getRxBridgeMessagePort() + ); + } catch (UnknownHostException e) { + throw new RuntimeException(e); + } + } else { + // log warning + log.warn("Received duplicate registration for vehicle " + registration.getVehicleRole()); + } + } + + /** + * This function is used to send out encoded timestep 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 timestep + * from the ambassador side + * @throws IOException + */ + @Override + public void onTimeStepUpdate(TimeSyncMessage message) throws IOException { + if (managedInstances.size() == 0) { + log.debug("There are no registered instances"); + } + else { + Gson gson = new Gson(); + byte[] bytes = gson.toJson(message).getBytes(); + for (CarmaMessengerInstance currentInstance : managedInstances.values()) { + log.debug("Sending CARMA Messenger instance {} at {}:{} time sync message for time {}!" , + currentInstance.getVehicleId(), + currentInstance.getTargetAddress(), + currentInstance.getTimeSyncPort(), + currentInstance.getMessengerEmergencyState(), + message.getTimestep()); + currentInstance.sendTimeSyncMsg(bytes); + } + } + } + + + /** + * Helper function to configure a new CARMA Platform instance object upon registration + * @param carmaMessengerVehId The CARMA Platform vehicle ID (e.g. it's license plate number) + * @param sumoRoleName The Role Name associated with the CARMA Platform's ego vehicle in CARLA + * @param targetAddress The IP address to which received simulated V2X messages should be sent + * @param v2xPort The port to which received simulated V2X messages should be sent + * @param timeSyncPort The port to which to send time sync messages. + */ + private void newCarmaMessengerInstance(String carmaMessengerVehId, String sumoRoleName, InetAddress targetAddress, int v2xPort, int timeSyncPort, String messengerEmergencyState, int rxBridgeMessagePort) { + CarmaMessengerInstance tmp = new CarmaMessengerInstance(carmaMessengerVehId, sumoRoleName, targetAddress, v2xPort, timeSyncPort, messengerEmergencyState, rxBridgeMessagePort); + try { + tmp.bind(); + log.info("New CARMA Messenger instance '{}' registered with CARMA Instance Manager.", sumoRoleName); + } catch (IOException e) { + log.error("Failed to bind CARMA Messenger instance with ID '{}' to its RX message socket: {}", + sumoRoleName, e.getMessage()); + log.error("Stack trace:", e); + throw new RuntimeException(e); + } + managedInstances.put(sumoRoleName, tmp); + } +} diff --git a/co-simulation/fed/mosaic-carma-messenger/src/main/java/org/eclipse/mosaic/fed/carmamessenger/ambassador/CarmaMessengerMessageAmbassador.java b/co-simulation/fed/mosaic-carma-messenger/src/main/java/org/eclipse/mosaic/fed/carmamessenger/ambassador/CarmaMessengerMessageAmbassador.java new file mode 100644 index 00000000..ff93d4fe --- /dev/null +++ b/co-simulation/fed/mosaic-carma-messenger/src/main/java/org/eclipse/mosaic/fed/carmamessenger/ambassador/CarmaMessengerMessageAmbassador.java @@ -0,0 +1,52 @@ +/* + * 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.carmamessenger.ambassador; + +import org.eclipse.mosaic.lib.CommonUtil.ambassador.CommonMessageAmbassador; +import org.eclipse.mosaic.lib.CommonUtil.configuration.CommonConfiguration; +import org.eclipse.mosaic.lib.util.objects.ObjectInstantiation; +import org.eclipse.mosaic.rti.api.parameters.AmbassadorParameter; + +public class CarmaMessengerMessageAmbassador extends CommonMessageAmbassador{ + + + /** + * Create a new {@link CarmaMessengerMessageAmbassador} object. + * + * @param ambassadorParameter includes parameters for the + * CarmaMessageAmbassador. + */ + public CarmaMessengerMessageAmbassador(AmbassadorParameter ambassadorParameter) { + super(ambassadorParameter, CarmaMessengerRegistrationMessage.class, CommonConfiguration.class); + + try { + // Read the CARMA message ambassador configuration file + commonConfiguration = new ObjectInstantiation<>(CommonConfiguration.class, log) + .readFile(ambassadorParameter.configuration); + } catch (InstantiationException e) { + log.error("Configuration object could not be instantiated: ", e); + } + + log.info("The update interval of CARMA message ambassador is " + commonConfiguration.updateInterval + " ."); + + // Check the CARMA update interval + if (commonConfiguration.updateInterval <= 0) { + throw new RuntimeException("Invalid update interval for CARMA message ambassador, should be >0."); + } + log.info("CARMA message ambassador is generated."); + } + +} diff --git a/co-simulation/fed/mosaic-carma-messenger/src/main/java/org/eclipse/mosaic/fed/carmamessenger/ambassador/CarmaMessengerRegistrationMessage.java b/co-simulation/fed/mosaic-carma-messenger/src/main/java/org/eclipse/mosaic/fed/carmamessenger/ambassador/CarmaMessengerRegistrationMessage.java new file mode 100644 index 00000000..4516f15c --- /dev/null +++ b/co-simulation/fed/mosaic-carma-messenger/src/main/java/org/eclipse/mosaic/fed/carmamessenger/ambassador/CarmaMessengerRegistrationMessage.java @@ -0,0 +1,55 @@ +/* + * 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.carmamessenger.ambassador; + +import org.eclipse.mosaic.lib.CommonUtil.ambassador.CommonRegistrationMessage; + +public class CarmaMessengerRegistrationMessage extends CommonRegistrationMessage{ + + private String messengerEmergencyState; + private int rxBridgeMessagePort; + + public CarmaMessengerRegistrationMessage(String carmaMessengerVehicleId, String sumoVehicleRole, String rxMessageIpAddress, + int rxMessagePort, int rxTimeSyncPort, String messengerEmergencyState, int rxBridgeMessagePort) { + super(carmaMessengerVehicleId, sumoVehicleRole, rxMessageIpAddress, rxMessagePort, rxTimeSyncPort); + + this.messengerEmergencyState = messengerEmergencyState; + this.rxBridgeMessagePort = rxBridgeMessagePort; + } + + public String getMessengerEmergencyState() { + return messengerEmergencyState; + } + + public void setMessengerEmergencyState(String messengerEmergencyState) { + this.messengerEmergencyState = messengerEmergencyState; + } + + public int getRxBridgeMessagePort(){ + return rxBridgeMessagePort; + } + + public void setRxBridgeMessagePort(int rxBridgeMessagePort){ + this.rxBridgeMessagePort = rxBridgeMessagePort; + } + + @Override + public String toString() { + return "CarmaMessengerRegistrationMessage [carmaMessengerVehicleId=" + super.getVehicleId() + ", sumoVehicleRole=" + super.getVehicleRole() + + ", rxMessageIpAddress=" + super.getRxMessageIpAddress() + ", rxMessagePort=" + super.getRxMessagePort() + + ", rxTimeSyncPort=" + super.getRxTimeSyncPort() + ", MessengerEmergencyState=" + messengerEmergencyState + ", rxBridgeMessagePort=" + rxBridgeMessagePort +"]"; + } +} diff --git a/co-simulation/fed/mosaic-carma-messenger/src/main/java/org/eclipse/mosaic/fed/carmamessenger/ambassador/CarmaMessengerRegistrationReceiver.java b/co-simulation/fed/mosaic-carma-messenger/src/main/java/org/eclipse/mosaic/fed/carmamessenger/ambassador/CarmaMessengerRegistrationReceiver.java new file mode 100644 index 00000000..57ee5b9c --- /dev/null +++ b/co-simulation/fed/mosaic-carma-messenger/src/main/java/org/eclipse/mosaic/fed/carmamessenger/ambassador/CarmaMessengerRegistrationReceiver.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.carmamessenger.ambassador; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.util.LinkedList; +import java.util.Queue; + +import org.eclipse.mosaic.lib.CommonUtil.ambassador.CommonRegistrationReceiver; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.Gson; + +public class CarmaMessengerRegistrationReceiver extends CommonRegistrationReceiver{ + + public CarmaMessengerRegistrationReceiver(Class type) { + super(type); + //TODO Auto-generated constructor stub + } + + private Queue rxQueue = new LinkedList<>(); + private DatagramSocket listenSocket = null; + private static final int listenPort = 1715; + private boolean running = true; + private static final int UDP_MTU = 1535; + private final Logger log = LoggerFactory.getLogger(this.getClass()); + + + + @Override + public void run() { + byte[] buf = new byte[UDP_MTU]; + while (running) { + DatagramPacket msg = new DatagramPacket(buf, buf.length); + try { + listenSocket.receive(msg); + } catch (IOException e) { + throw new RuntimeException(e); + } + + // parse message + String receivedPacket = new String(msg.getData(), 0, msg.getLength()); + log.debug("Registration JSON received: {}", receivedPacket); + Gson gson = new Gson(); + CarmaMessengerRegistrationMessage parsedMessage = gson.fromJson(receivedPacket, CarmaMessengerRegistrationMessage.class); + + // Enqueue message for processing on main thread + synchronized (rxQueue) { + log.info("New CARMA messenger instance '{}' received with CARMA messenger Registration Receiver.", parsedMessage.getVehicleId()); + rxQueue.add(parsedMessage); + } + } + } + +} diff --git a/co-simulation/fed/mosaic-carma-messenger/src/test/java/org/eclipse/mosaic/fed/carmamessenger/ambassador/CarmaMessengerInstanceManagerTest.java b/co-simulation/fed/mosaic-carma-messenger/src/test/java/org/eclipse/mosaic/fed/carmamessenger/ambassador/CarmaMessengerInstanceManagerTest.java new file mode 100644 index 00000000..e5b5d8ab --- /dev/null +++ b/co-simulation/fed/mosaic-carma-messenger/src/test/java/org/eclipse/mosaic/fed/carmamessenger/ambassador/CarmaMessengerInstanceManagerTest.java @@ -0,0 +1,105 @@ +/* + * 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.carmamessenger.ambassador; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.mosaic.lib.junit.IpResolverRule; +import org.eclipse.mosaic.lib.objects.addressing.IpResolver; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import static org.mockito.Mockito.mock; +import org.mockito.internal.util.reflection.FieldSetter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CarmaMessengerInstanceManagerTest { + private CarmaMessengerInstanceManager manager; + private CarmaMessengerInstance instance1; + private CarmaMessengerInstance instance2; + private CarmaMessengerInstance instance3; + private final String sampleMessage = + "Version=0.7\n" + + "Type=BSM\n" + + "PSID=0020\n" + + "Priority=6\n" + + "TxMode=ALT\n" + + "TxChannel=172\n" + + "TxInterval=0\n" + + "DeliveryStart=\n" + + "DeliveryStop=\n" + + "Signature=False\n" + + "Encryption=False\n" + + "Payload=00142500400000000f0e35a4e900eb49d20000007fffffff8ffff080fdfa1fa1007fff8000960fa0\n"; + private final Logger log = LoggerFactory.getLogger(this.getClass()); + + /** + * Rule to initialize {@link IpResolver} Singleton. + */ + @Rule + public IpResolverRule ipResolverRule = new IpResolverRule(); + + @Before + public void setUp() throws Exception { + manager = new CarmaMessengerInstanceManager(); + instance1 = mock(CarmaMessengerInstance.class); + instance2 = mock(CarmaMessengerInstance.class); + instance3 = mock(CarmaMessengerInstance.class); + Map managedInstances = new HashMap<>(); + managedInstances.put("instance1", instance1); + managedInstances.put("instance2", instance2); + managedInstances.put("instance3", instance3); + + // Set private instance field to mock using reflection + FieldSetter.setField(manager, manager.getClass().getSuperclass().getDeclaredField("managedInstances"), managedInstances); + } + + @Test + public void testOnNewRegistration() { + // Set up the registration object + String infrastructureId = "infrastructure-123"; + int rxMessagePort = 1234; + int timeSyncPort = 5678; + String ipAddressString = "127.0.0.1"; + String emergencyState = "MockState"; + int rxBridgeMessagePort = 5600; + + // Mock the behavior of the registration object + CarmaMessengerRegistrationMessage registration = new CarmaMessengerRegistrationMessage( + infrastructureId, + infrastructureId, + ipAddressString, + rxMessagePort, + timeSyncPort, + emergencyState, + rxBridgeMessagePort); + // Ensure checkIfRegistered returns false for infrastructure ID before registering + assertFalse( manager.checkIfRegistered(infrastructureId) ); + + // Call the onNewRegistration method with the mocked registration object + manager.onNewRegistration(registration); + + // Verify that the infrastructure instance was added to the manager + assertTrue( manager.checkIfRegistered(infrastructureId) ); + // Ensure checkIfRegistered returns false for other Ids + assertFalse( manager.checkIfRegistered(infrastructureId + "something") ); + } + +} diff --git a/co-simulation/fed/mosaic-carma-messenger/src/test/java/org/eclipse/mosaic/fed/carmamessenger/ambassador/CarmaMessengerInstanceTest.java b/co-simulation/fed/mosaic-carma-messenger/src/test/java/org/eclipse/mosaic/fed/carmamessenger/ambassador/CarmaMessengerInstanceTest.java new file mode 100644 index 00000000..04f0cf0b --- /dev/null +++ b/co-simulation/fed/mosaic-carma-messenger/src/test/java/org/eclipse/mosaic/fed/carmamessenger/ambassador/CarmaMessengerInstanceTest.java @@ -0,0 +1,89 @@ +/* + * 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.carmamessenger.ambassador; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import org.mockito.internal.util.reflection.FieldSetter; + +public class CarmaMessengerInstanceTest{ + /** + * Mock Datagram socket + */ + private DatagramSocket socket; + private CarmaMessengerInstance instance; + /** + * Mock InetAddress + */ + private InetAddress address; + + @Before + public void setup() throws NoSuchFieldException { + //init mocks + address = mock(InetAddress.class); + socket = mock(DatagramSocket.class); + + //init infra instance + instance = new CarmaMessengerInstance( + "MockID", + "MockRolename", + address, + 3456, + 7890, + "MockState", + 5600 + ); + FieldSetter.setField(instance, instance.getClass().getSuperclass().getDeclaredField("rxMsgsSocket"), socket); + } + + @Test + public void testGetterSetterConstructor() { + assertEquals("MockState", instance.getMessengerEmergencyState()); + assertEquals(5600, instance.getRxBridgeMessagePort()); + instance.setMessengerEmergencyState("NewState"); + assertEquals("NewState", instance.getMessengerEmergencyState()); + instance.setRxBridgeMessagePort(5700); + assertEquals(5700, instance.getRxBridgeMessagePort()); + } + + @Test + public void testSendVehStatusMsg() throws IOException { + // Test SendV2xMsg method + String test_msg = "test message"; + instance.sendVehStatusMsgs(test_msg.getBytes()); + // ArgumentCaptor to capture parameters passed to mock on method calls + ArgumentCaptor packet = ArgumentCaptor.forClass(DatagramPacket.class); + // Verify socket.send(DatagramPacket packet) is called and capture packet + // parameter + verify(socket, times(1)).send(packet.capture()); + + // Verify parameter members + assertArrayEquals(test_msg.getBytes(), packet.getValue().getData()); + assertEquals(instance.getRxBridgeMessagePort(), packet.getValue().getPort()); + assertEquals(address, packet.getValue().getAddress()); + } +} diff --git a/co-simulation/fed/mosaic-carma-messenger/src/test/java/org/eclipse/mosaic/fed/carmamessenger/ambassador/CarmaMessengerMessageAmbassadorTest.java b/co-simulation/fed/mosaic-carma-messenger/src/test/java/org/eclipse/mosaic/fed/carmamessenger/ambassador/CarmaMessengerMessageAmbassadorTest.java new file mode 100644 index 00000000..82376c04 --- /dev/null +++ b/co-simulation/fed/mosaic-carma-messenger/src/test/java/org/eclipse/mosaic/fed/carmamessenger/ambassador/CarmaMessengerMessageAmbassadorTest.java @@ -0,0 +1,84 @@ +/* + * 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.carmamessenger.ambassador; + +import java.io.File; +import java.io.IOException; + +import org.eclipse.mosaic.lib.util.junit.TestFileRule; +import org.eclipse.mosaic.rti.TIME; +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.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.RuleChain; +import org.junit.rules.TemporaryFolder; +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; + +public class CarmaMessengerMessageAmbassadorTest { + private final TemporaryFolder temporaryFolder = new TemporaryFolder(); + + private final TestFileRule testFileRule = new TestFileRule(temporaryFolder).basedir("carma") + .with("carma_config.json", "/carma_config.json"); + + @Rule + public RuleChain chain = RuleChain.outerRule(temporaryFolder).around(testFileRule); + + private RtiAmbassador rtiMock; + + private CarmaMessengerMessageAmbassador ambassador; + + @Before + public void setup() throws IOException { + + rtiMock = mock(RtiAmbassador.class); + + FederateDescriptor handleMock = mock(FederateDescriptor.class); + + File workingDir = temporaryFolder.getRoot(); + + CLocalHost testHostConfig = new CLocalHost(); + + testHostConfig.workingDirectory = workingDir.getAbsolutePath(); + + when(handleMock.getHost()).thenReturn(testHostConfig); + + when(handleMock.getId()).thenReturn("carma"); + + ambassador = new CarmaMessengerMessageAmbassador( + new AmbassadorParameter("carma", testFileRule.get("carma_config.json"))); + + ambassador.setRtiAmbassador(rtiMock); + + ambassador.setFederateDescriptor(handleMock); + } + + @Test + public void initialize() throws Throwable { + + // RUN + ambassador.initialize(0, 100 * TIME.SECOND); + // ASSERT + verify(rtiMock, times(1)).requestAdvanceTime(eq(0L), eq(0L), eq((byte) 1)); + } +} diff --git a/co-simulation/fed/mosaic-carma-messenger/src/test/java/org/eclipse/mosaic/fed/carmamessenger/ambassador/CarmaMessengerRegistrationMessageTest.java b/co-simulation/fed/mosaic-carma-messenger/src/test/java/org/eclipse/mosaic/fed/carmamessenger/ambassador/CarmaMessengerRegistrationMessageTest.java new file mode 100644 index 00000000..7c111f4d --- /dev/null +++ b/co-simulation/fed/mosaic-carma-messenger/src/test/java/org/eclipse/mosaic/fed/carmamessenger/ambassador/CarmaMessengerRegistrationMessageTest.java @@ -0,0 +1,44 @@ +/* + * 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.carmamessenger.ambassador; + +import static org.junit.Assert.assertEquals; +import org.junit.Test; + +public class CarmaMessengerRegistrationMessageTest { + @Test + public void testGetterSettersConstructor() { + + CarmaMessengerRegistrationMessage message = new CarmaMessengerRegistrationMessage( + "MockID", + "MockRole", + "127.0.0.1", + 5678, + 1234, + "MockState", + 5600); + // Test Getter + assertEquals("MockState", message.getMessengerEmergencyState()); + assertEquals(5600, message.getRxBridgeMessagePort()); + + message.setMessengerEmergencyState("NewState"); + message.setRxBridgeMessagePort(5700); + + + assertEquals("NewState", message.getMessengerEmergencyState()); + assertEquals(5700, message.getRxBridgeMessagePort()); + } +} diff --git a/co-simulation/fed/mosaic-carma-messenger/src/test/resources/carma_config.json b/co-simulation/fed/mosaic-carma-messenger/src/test/resources/carma_config.json new file mode 100644 index 00000000..f12b548c --- /dev/null +++ b/co-simulation/fed/mosaic-carma-messenger/src/test/resources/carma_config.json @@ -0,0 +1,25 @@ +{ + "updateInterval": 100, + "carmaVehicles":[ +{ + "routeID": "0", + "lane": 1, + "position": 0, + "departSpeed": 0, + "vehicleType": "vehicle.chevrolet.impala", + "applications":["org.eclipse.mosaic.app.tutorial.VehicleCommunicationApp"], + "geoPosition": { + "latitude": 52.579272059028646, + "longitude": 13.467165499469328 + }, + "projectedPosition": + { + "x": 501.62, + "y": 116.95 + }, + + "heading": 24.204351784500364, + "slope": 0.0 +}], +"senderCarmaVehicleId":"carma_0" +} diff --git a/co-simulation/fed/mosaic-carma/pom.xml b/co-simulation/fed/mosaic-carma/pom.xml index 155576a3..e4ec0832 100644 --- a/co-simulation/fed/mosaic-carma/pom.xml +++ b/co-simulation/fed/mosaic-carma/pom.xml @@ -97,6 +97,12 @@ jaxb-runtime 2.3.2 + + org.eclipse.mosaic + mosaic-common-utils + 22.1-SNAPSHOT + compile + diff --git a/co-simulation/fed/mosaic-carma/src/main/java/org/eclipse/mosaic/fed/carma/ambassador/CarmaInstance.java b/co-simulation/fed/mosaic-carma/src/main/java/org/eclipse/mosaic/fed/carma/ambassador/CarmaInstance.java index 04523ac6..c922586c 100644 --- a/co-simulation/fed/mosaic-carma/src/main/java/org/eclipse/mosaic/fed/carma/ambassador/CarmaInstance.java +++ b/co-simulation/fed/mosaic-carma/src/main/java/org/eclipse/mosaic/fed/carma/ambassador/CarmaInstance.java @@ -16,117 +16,17 @@ package org.eclipse.mosaic.fed.carma.ambassador; -import org.eclipse.mosaic.lib.geo.GeoPoint; +import org.eclipse.mosaic.lib.CommonUtil.ambassador.CommonInstance; -import java.io.IOException; -import java.net.DatagramPacket; -import java.net.DatagramSocket; import java.net.InetAddress; /** * Connection manager and data object to associate with a single CARMA Platform instance in XIL */ -public class CarmaInstance { - private String carmaVehicleId; - private String carlaRoleName; - private DatagramSocket rxMsgsSocket = null; - - private InetAddress targetAddress; - private int v2xPort; - private int timeSyncPort; - private GeoPoint location = GeoPoint.ORIGO; +public class CarmaInstance extends CommonInstance{ public CarmaInstance(String carmaVehicleId, String carlaRoleName, InetAddress targetAddress, int v2xPort, int timeSyncPort ) { - this.carmaVehicleId = carmaVehicleId; - this.carlaRoleName = carlaRoleName; - this.targetAddress = targetAddress; - this.v2xPort = v2xPort; - this.timeSyncPort = timeSyncPort; - } - - public InetAddress getTargetAddress() { - return targetAddress; - } - - public void setTargetAddress(InetAddress targetAddress) { - this.targetAddress = targetAddress; - } - - public void setLocation(GeoPoint location) { - this.location = location; - } - - public GeoPoint getLocation() { - return this.location; - } - - - public int getV2xPort() { - return v2xPort; - } - - public void setV2xPort(int v2xPort) { - this.v2xPort = v2xPort; - } - - public int getTimeSyncPort() { - return timeSyncPort; - } - - public void setTimeSyncPort(int timeSyncPort) { - this.timeSyncPort = timeSyncPort; + super(carmaVehicleId, carlaRoleName, targetAddress, v2xPort, timeSyncPort); } - /** - * Sends the V2X message to the CARMA Platform communications interface configured at construction time. - * @param data The binary data to transmit - * @throws IOException If there is an issue with the underlying socket object or methods - */ - public void sendV2xMsgs(byte[] data) throws IOException { - if (rxMsgsSocket == null) { - throw new IllegalStateException("Attempted to send data before opening socket"); - } - - DatagramPacket packet = new DatagramPacket(data, data.length, targetAddress, v2xPort); - - rxMsgsSocket.send(packet); - } - /** - * Sends the time sync messages to the CARMA Platform to synchronize ros clock with simulation clock. - * @param data The binary data encoding of json time sync message - * @throws IOException If there is an issue with the underlying socket object or methods - */ - public void sendTimeSyncMsg(byte[] data) throws IOException { - if (rxMsgsSocket == null) { - throw new IllegalStateException("Attempted to send data before opening socket"); - } - - DatagramPacket packet = new DatagramPacket(data, data.length, targetAddress, timeSyncPort); - - rxMsgsSocket.send(packet); - } - - /** - * Connects the sockt to receive messages from the CARMA Platform instance - * @throws IOException If the socket creation fails - */ - public void bind() throws IOException { - rxMsgsSocket = new DatagramSocket(); - } - - public String getCarmaVehicleId() { - return carmaVehicleId; - } - - public void setCarmaVehicleId(String carmaVehicleId) { - this.carmaVehicleId = carmaVehicleId; - } - - public String getCarlaRoleName() { - return carlaRoleName; - } - - public void setCarlaRoleName(String carlaRoleName) { - this.carlaRoleName = carlaRoleName; - } } 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 e53d049b..a0442386 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 @@ -16,156 +16,20 @@ package org.eclipse.mosaic.fed.carma.ambassador; -import gov.dot.fhwa.saxton.CarmaV2xMessage; -import gov.dot.fhwa.saxton.TimeSyncMessage; - -import org.eclipse.mosaic.interactions.communication.V2xMessageTransmission; -import org.eclipse.mosaic.interactions.traffic.VehicleUpdates; -import org.eclipse.mosaic.lib.enums.AdHocChannel; -import org.eclipse.mosaic.lib.geo.GeoCircle; -import org.eclipse.mosaic.lib.objects.addressing.AdHocMessageRoutingBuilder; -import org.eclipse.mosaic.lib.objects.v2x.ExternalV2xContent; -import org.eclipse.mosaic.lib.objects.v2x.ExternalV2xMessage; -import org.eclipse.mosaic.lib.objects.v2x.MessageRouting; -import org.eclipse.mosaic.lib.objects.vehicle.VehicleData; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.gson.Gson; - import java.io.IOException; import java.net.InetAddress; -import java.net.UnknownHostException; -import java.util.HashMap; -import java.util.Map; + +import org.eclipse.mosaic.lib.CommonUtil.ambassador.CommonInstanceManager; /** * Session management class for CARMA Platform instances communicating with MOSAIC */ -public class CarmaInstanceManager { - private Map managedInstances = new HashMap<>(); - - // TODO: Verify actual port for CARMA Platform NS-3 adapter - private static final int TARGET_PORT = 5374; - private final Logger log = LoggerFactory.getLogger(this.getClass()); - - /** - * Callback to invoked when a new CARMA Platform instance registers with the mosaic-carma ambassador for the first time - * @param registration The new instance's registration data - */ - public void onNewRegistration(CarmaRegistrationMessage registration) { - if (!managedInstances.containsKey(registration.getCarlaVehicleRole())) { - try { - newCarmaInstance( - registration.getCarmaVehicleId(), - registration.getCarlaVehicleRole(), - InetAddress.getByName(registration.getRxMessageIpAddress()), - registration.getRxMessagePort(), - registration.getRxTimeSyncPort() - ); - } catch (UnknownHostException e) { - throw new RuntimeException(e); - } - } else { - // log warning - log.warn("Received duplicate registration for vehicle " + registration.getCarlaVehicleRole()); - } - } - /** - * Method to be invoked when CARMA Ambassador receives a V2X message from CARMA Platform. Creates - * V2xMessageTransmission interaction to be sent on the MOSIAC RTI. - * @param sourceAddr the ip address of the CARMA Platform instance that sent the message. - * @param txMsg The V2X Message received. - * @param time The timestamp at which the interaction occurs. - * @throws IllegalStateException if sourceAddr does not match any address in the managed instances. - */ - public V2xMessageTransmission onV2XMessageTx(InetAddress sourceAddr, CarmaV2xMessage txMsg, long time) { - CarmaInstance sender = null; - // Find the CarmaInstance with sourceAddr. - for (CarmaInstance ci : managedInstances.values()) { - if (ci.getTargetAddress().equals(sourceAddr)) { - sender = ci; - break; - } - } - // Unregistered instance attempting to send messages - if (sender == null) { - throw new IllegalStateException("Unregistered CARMA Platform instance attempting to send messages via MOSAIC"); - } - AdHocMessageRoutingBuilder messageRoutingBuilder = new AdHocMessageRoutingBuilder( - 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: {}, type: {}, payload: {}", - time, - sender.getCarmaVehicleId(), - sender.getLocation(), - txMsg.getType(), - txMsg.getPayload() - ); - return new V2xMessageTransmission( time, new ExternalV2xMessage(routing, - new ExternalV2xContent( time, sender.getLocation(), txMsg.getPayload()))); - } - - /** - * Callback to be invoked when the simulation emits a VehicleUpdates event, used to track the location of CARMA - * Platform instances in this manager. - * @param vui The vehicle update information - */ - public void onVehicleUpdates(VehicleUpdates vui) { - for (VehicleData veh : vui.getUpdated()) { - if (managedInstances.containsKey(veh.getName())) { - managedInstances.get(veh.getName()).setLocation(veh.getPosition()); - } - } +public class CarmaInstanceManager extends CommonInstanceManager{ + + public CarmaInstanceManager(){ + setTargetPort(5374); } - /** - * Callback to be invoked when CARMA Platform receives a V2X Message from the NS-3 simulation - * @param rxMsg The V2X Message received - * @param rxVehicleId The Host ID of the vehicle receiving the data - * @throws RuntimeException If the socket used to communicate with the platform experiences failure - */ - public void onV2XMessageRx(byte[] rxMsg, String rxVehicleId) { - if (!managedInstances.containsKey(rxVehicleId)) { - return; - } - - CarmaInstance carma = managedInstances.get(rxVehicleId); - try { - carma.sendV2xMsgs(rxMsg); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - /** - * This function is used to send out encoded timestep 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 timestep - * from the ambassador side - * @throws IOException - */ - public void onTimeStepUpdate(TimeSyncMessage message) throws IOException { - if (managedInstances.size() == 0) { - log.debug("There are no registered instances"); - } - else { - Gson gson = new Gson(); - byte[] bytes = gson.toJson(message).getBytes(); - for (CarmaInstance currentInstance : managedInstances.values()) { - log.debug("Sending CARMA Platform instance {} at {}:{} time sync message for time {}!" , - currentInstance.getCarmaVehicleId(), - currentInstance.getTargetAddress(), - currentInstance.getTimeSyncPort(), - message.getTimestep()); - currentInstance.sendTimeSyncMsg(bytes); - } - } - } - - /** * Helper function to configure a new CARMA Platform instance object upon registration * @param carmaVehId The CARMA Platform vehicle ID (e.g. it's license plate number) @@ -174,7 +38,8 @@ public void onTimeStepUpdate(TimeSyncMessage message) throws IOException { * @param v2xPort The port to which received simulated V2X messages should be sent * @param timeSyncPort The port to which to send time sync messages. */ - private void newCarmaInstance(String carmaVehId, String carlaRoleName, InetAddress targetAddress, int v2xPort, int timeSyncPort) { + @Override + protected void newCommonInstance(String carmaVehId, String carlaRoleName, InetAddress targetAddress, int v2xPort, int timeSyncPort) { CarmaInstance tmp = new CarmaInstance(carmaVehId, carlaRoleName, targetAddress, v2xPort, timeSyncPort); try { tmp.bind(); @@ -188,13 +53,4 @@ private void newCarmaInstance(String carmaVehId, String carlaRoleName, InetAddre managedInstances.put(carlaRoleName, tmp); } - /** - * External helper function to allow the ambassador to check if a given vehicle ID is a registered CARMA Platform - * instance - * @param mosiacVehicleId The id to check - * @return True if managed by this object (e.g., is a registered CARMA Platform vehicle). false o.w. - */ - public boolean checkIfRegistered(String mosiacVehicleId) { - return managedInstances.keySet().contains(mosiacVehicleId); - } } 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 5215000c..ddbcb1c3 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 @@ -13,61 +13,18 @@ package org.eclipse.mosaic.fed.carma.ambassador; -import gov.dot.fhwa.saxton.CarmaV2xMessage; -import gov.dot.fhwa.saxton.CarmaV2xMessageReceiver; -import gov.dot.fhwa.saxton.TimeSyncMessage; - -import org.eclipse.mosaic.fed.application.ambassador.SimulationKernel; import org.eclipse.mosaic.fed.carma.configuration.CarmaConfiguration; -import org.eclipse.mosaic.interactions.application.CarmaV2xMessageReception; -import org.eclipse.mosaic.interactions.communication.AdHocCommunicationConfiguration; -import org.eclipse.mosaic.interactions.communication.V2xMessageReception; -import org.eclipse.mosaic.interactions.communication.V2xMessageTransmission; -import org.eclipse.mosaic.interactions.mapping.advanced.ExternalVehicleRegistration; -import org.eclipse.mosaic.interactions.traffic.VehicleUpdates; -import org.eclipse.mosaic.lib.enums.AdHocChannel; -import org.eclipse.mosaic.lib.enums.DriveDirection; -import org.eclipse.mosaic.lib.geo.CartesianPoint; -import org.eclipse.mosaic.lib.geo.GeoPoint; -import org.eclipse.mosaic.lib.misc.Tuple; -import org.eclipse.mosaic.lib.objects.addressing.IpResolver; -import org.eclipse.mosaic.lib.objects.communication.AdHocConfiguration; -import org.eclipse.mosaic.lib.objects.communication.InterfaceConfiguration; -import org.eclipse.mosaic.lib.objects.road.IRoadPosition; -import org.eclipse.mosaic.lib.objects.road.SimpleRoadPosition; -import org.eclipse.mosaic.lib.objects.v2x.ExternalV2xMessage; -import org.eclipse.mosaic.lib.objects.v2x.V2xMessage; -import org.eclipse.mosaic.lib.objects.vehicle.*; -import org.eclipse.mosaic.lib.objects.vehicle.sensor.DistanceSensor; -import org.eclipse.mosaic.lib.objects.vehicle.sensor.RadarSensor; +import org.eclipse.mosaic.lib.CommonUtil.ambassador.CommonMessageAmbassador; import org.eclipse.mosaic.lib.util.objects.ObjectInstantiation; -import org.eclipse.mosaic.rti.TIME; 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 javax.xml.bind.DatatypeConverter; - -import java.io.IOException; -import java.net.Inet4Address; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - +import gov.dot.fhwa.saxton.CarmaV2xMessageReceiver; /** * Implementation of a {@link AbstractFederateAmbassador} for CARMA message * ambassador. */ -public class CarmaMessageAmbassador extends AbstractFederateAmbassador { - - /** - * Simulation time. - */ - long currentSimulationTime; +public class CarmaMessageAmbassador extends CommonMessageAmbassador { /** * CarmaMessageAmbassador configuration file. @@ -89,7 +46,7 @@ public class CarmaMessageAmbassador extends AbstractFederateAmbassador { * CarmaMessageAmbassador. */ public CarmaMessageAmbassador(AmbassadorParameter ambassadorParameter) { - super(ambassadorParameter); + super(ambassadorParameter, CarmaRegistrationMessage.class, CarmaConfiguration.class); try { // Read the CARMA message ambassador configuration file @@ -107,294 +64,4 @@ public CarmaMessageAmbassador(AmbassadorParameter ambassadorParameter) { } log.info("CARMA 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 nano seconds. - * @param endTime End time of the simulation run in nano seconds. - * @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; - try { - rti.requestAdvanceTime(currentSimulationTime, 0, (byte) 1); - } catch (IllegalValueException e) { - log.error("Error during advanceTime request", e); - throw new InternalFederateException(e); - } - - // Initialize listener socket and thread for CARMA Registration messages - carmaRegistrationReceiver = new CarmaRegistrationReceiver(); - carmaRegistrationReceiver.init(); - registrationRxBackgroundThread = new Thread(carmaRegistrationReceiver); - registrationRxBackgroundThread.start(); - - // Initialize listener socket and thread for CARMA NS-3 Adapter messages - v2xMessageReceiver = new CarmaV2xMessageReceiver(); - v2xMessageReceiver.init(); - v2xRxBackgroundThread = new Thread(v2xMessageReceiver); - v2xRxBackgroundThread.start(); - } - - /** - * This method is called by the AbstractFederateAmbassador when a time advance - * has been granted by the RTI. Before this call is placed, any unprocessed - * interaction is forwarded to the federate using the processInteraction method. - * - * @param time The timestamp towards which the federate can advance it local - * time. - */ - @Override - public synchronized void processTimeAdvanceGrant(long time) throws InternalFederateException { - - if (time < currentSimulationTime) { - // process time advance only if time is equal or greater than the next - // simulation time step - return; - } - log.info("Carma message ambassador processing timestep to {}.", time); - - try { - List newRegistrations = carmaRegistrationReceiver.getReceivedMessages(); - for (CarmaRegistrationMessage reg : newRegistrations) { - carmaInstanceManager.onNewRegistration(reg); - onDsrcRegistrationRequest(reg.getCarlaVehicleRole()); - } - // Set current simulation time to most recent time update - currentSimulationTime = time; - if (currentSimulationTime == 0) { - // For the first timestep, clear the message receive queues. - v2xMessageReceiver.getReceivedMessages(); // Automatically empties the queues. - } else { - 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); - } - } - // Time Syncmessage in nano seconds - TimeSyncMessage timeSyncMessage = new TimeSyncMessage(currentSimulationTime, timeSyncSeq); - carmaInstanceManager.onTimeStepUpdate(timeSyncMessage); - // Increment time - currentSimulationTime += carmaConfiguration.updateInterval * TIME.MILLI_SECOND; - timeSyncSeq += 1; - - rti.requestAdvanceTime(currentSimulationTime, 0, (byte) 2); - } catch (IllegalValueException e) { - log.error("Error during advanceTime(" + time + ")", e); - throw new InternalFederateException(e); - } catch (UnknownHostException e) { - log.error("Error during advanceTime(" + time + ")", e); - throw new InternalFederateException(e); - } catch (IOException e) { - log.error("Error during advanceTime(" + time + ")", e); - throw new InternalFederateException(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 true; - } - - /** - * This method processes the interactions. - * - * @param interaction The interaction that can be processed. - * @throws InternalFederateException Exception is thrown if an error is occurred - * while execute of a federate. - */ - @Override - public void processInteraction(Interaction interaction) throws InternalFederateException { - String type = interaction.getTypeId(); - long interactionTime = interaction.getTime(); - log.trace("Process interaction with type '{}' at time: {}", type, interactionTime); - if (interaction.getTypeId().equals(V2xMessageReception.TYPE_ID)) { - receiveV2xReceptionInteraction((V2xMessageReception) interaction); - } - if (interaction.getTypeId().equals(CarmaV2xMessageReception.TYPE_ID)) { - receiveInteraction((CarmaV2xMessageReception) interaction); - } - if (interaction.getTypeId().equals(VehicleUpdates.TYPE_ID)) { - receiveVehicleUpdateInteraction((VehicleUpdates) interaction); - } - } - - private synchronized void receiveVehicleUpdateInteraction(VehicleUpdates interaction) { - carmaInstanceManager.onVehicleUpdates(interaction); - } - - /** - * Helper function to retrieve previously transmitted messages by ID from the buffer - * @param id The id of the message to return - * @return The {@link V2xMessage} object if the id exists in the buffer, null o.w. - */ - private V2xMessage lookupV2xMsgIdInBuffer(int id) { - return SimulationKernel.SimulationKernel.getV2xMessageCache().getItem(id); - } - - /** - * Callback to be invoked when the network simulator determines that a simulated radio has received a V2X message - * @param interaction The v2x message receipt data - */ - private synchronized void receiveV2xReceptionInteraction(V2xMessageReception interaction) { - String carlaRoleName = interaction.getReceiverName(); - if (!carmaInstanceManager.checkIfRegistered(carlaRoleName)) { - // Abort early as we only are concerned with CARMA Platform vehicles - return; - } - log.info("Processing V2X message reception event for " + interaction.getReceiverName() + " of msg id " + interaction.getMessageId()); - - int messageId = interaction.getMessageId(); - V2xMessage msg = lookupV2xMsgIdInBuffer(messageId); - - if (msg != null && msg instanceof ExternalV2xMessage) { - ExternalV2xMessage msg2 = (ExternalV2xMessage) msg; - carmaInstanceManager.onV2XMessageRx(DatatypeConverter.parseHexBinary(msg2.getMessage()), carlaRoleName); - log.info("Sending V2X message reception event for " + interaction.getReceiverName() + " of msg id " + interaction.getMessageId() + " of size " + msg2.getPayLoad().getBytes().length); - } else { - log.warn("Message with id " + interaction.getMessageId() + " received by " + interaction.getReceiverName() + " is no longer in the message buffer to be retrieved! Message transmission failed!!!"); - } - } - - private void onDsrcRegistrationRequest(String vehicleId) throws UnknownHostException { - ExternalVehicleRegistration tempRegistration = new ExternalVehicleRegistration( - currentSimulationTime, - vehicleId, - "carma", - null, - new VehicleType("carma")); - - try { - // Trigger RTI interaction to MOSAIC to exchange the Ad-Hoc configuration - this.rti.triggerInteraction(tempRegistration); - } catch (InternalFederateException | IllegalValueException e) { - // Log error message if there was an issue with the RTI interaction - log.error(e.getMessage()); - } - - VehicleSignals tmpSignals = new VehicleSignals( - false, - false, - false, - false, - false); - - VehicleEmissions tmpEmissions = new VehicleEmissions( - new Emissions( - 0.0, - 0.0, - 0.0, - 0.0, - 0.0 - ), - new Emissions( - 0.0, - 0.0, - 0.0, - 0.0, - 0.0 - )); - - VehicleBatteryState tmpBattery = new VehicleBatteryState("", currentSimulationTime); - IRoadPosition tmpPos = new SimpleRoadPosition("", 0, 0.0, 0.0); - VehicleSensors tmpSensors = new VehicleSensors( - new DistanceSensor(0.0, - 0.0, - 0.0, - 0.0), - new RadarSensor(0.0)); - VehicleConsumptions tmpConsumptions = new VehicleConsumptions( - new Consumptions(0.0, 0.0), - new Consumptions(0.0, 0.0)); - VehicleData tmpVehicle = new VehicleData.Builder(currentSimulationTime, vehicleId) - .position(GeoPoint.ORIGO, CartesianPoint.ORIGO) - .movement(0.0, 0.0, 0.0) - .consumptions(tmpConsumptions) - .emissions(tmpEmissions) - .electric(tmpBattery) - .laneArea("") - .sensors(tmpSensors) - .road(tmpPos) - .signals(tmpSignals) - .orientation(DriveDirection.FORWARD, 0.0, 0.0) - .stopped(false) - .route("") - .create(); - VehicleUpdates tempUpdates = new VehicleUpdates( - currentSimulationTime, - new ArrayList<>(Arrays.asList(tmpVehicle)), - new ArrayList<>(), - new ArrayList<>()); - - try { - // Trigger RTI interaction to MOSAIC to exchange the Ad-Hoc configuration - this.rti.triggerInteraction(tempUpdates); - } catch (InternalFederateException | IllegalValueException e) { - // Log error message if there was an issue with the RTI interaction - log.error(e.getMessage()); - } - - - // Create an InterfaceConfiguration object to represent the configuration of the - // Ad-Hoc interface - // TODO: Replace the transmit power of the ad-hoc interface (in dBm) if necessary - // TODO: Replace the communication range of the ad-hoc interface (in meters) if necessary - Inet4Address vehAddress = IpResolver.getSingleton().registerHost(vehicleId); - log.info("Assigned registered comms device " + vehicleId + " with IP address " + vehAddress.toString()); - InterfaceConfiguration interfaceConfig = new InterfaceConfiguration.Builder(AdHocChannel.CCH) - .ip(vehAddress) - .subnet(IpResolver.getSingleton().getNetMask()) - .power(50) - .radius(300.0) - .create(); - - // Create an AdHocConfiguration object to associate the Ad-Hoc interface - // configuration with the infrastructure instance's ID - AdHocConfiguration adHocConfig = new AdHocConfiguration.Builder(vehicleId) - .addInterface(interfaceConfig) - .create(); - - // Create an AdHocCommunicationConfiguration object to specify the time and - // Ad-Hoc configuration for exchange with another vehicle or component - AdHocCommunicationConfiguration communicationConfig = new AdHocCommunicationConfiguration(currentSimulationTime, - adHocConfig); - log.info("Communications comms device " + vehicleId + " with IP address " + vehAddress.toString() + " success!"); - try { - // Trigger RTI interaction to MOSAIC to exchange the Ad-Hoc configuration - this.rti.triggerInteraction(communicationConfig); - } catch (InternalFederateException | IllegalValueException e) { - // Log error message if there was an issue with the RTI interaction - log.error(e.getMessage()); - } - } } diff --git a/co-simulation/fed/mosaic-carma/src/main/java/org/eclipse/mosaic/fed/carma/ambassador/CarmaRegistrationMessage.java b/co-simulation/fed/mosaic-carma/src/main/java/org/eclipse/mosaic/fed/carma/ambassador/CarmaRegistrationMessage.java index 84310c16..9094e23d 100644 --- a/co-simulation/fed/mosaic-carma/src/main/java/org/eclipse/mosaic/fed/carma/ambassador/CarmaRegistrationMessage.java +++ b/co-simulation/fed/mosaic-carma/src/main/java/org/eclipse/mosaic/fed/carma/ambassador/CarmaRegistrationMessage.java @@ -16,11 +16,12 @@ package org.eclipse.mosaic.fed.carma.ambassador; +import org.eclipse.mosaic.lib.CommonUtil.ambassador.CommonRegistrationMessage; /** * JSON compatible message to be sent by CARMA Platform when it registers with * the carma-mosaic ambassador */ -public class CarmaRegistrationMessage { +public class CarmaRegistrationMessage extends CommonRegistrationMessage{ private String carmaVehicleId; private String carlaVehicleRole; private String rxMessageIpAddress; @@ -29,6 +30,7 @@ public class CarmaRegistrationMessage { public CarmaRegistrationMessage(String carmaVehicleId, String carlaVehicleRole, String rxMessageIpAddress, int rxMessagePort, int rxTimeSyncPort) { + super(carmaVehicleId, carlaVehicleRole, rxMessageIpAddress, rxMessagePort, rxTimeSyncPort); this.carmaVehicleId = carmaVehicleId; this.carlaVehicleRole = carlaVehicleRole; this.rxMessageIpAddress = rxMessageIpAddress; @@ -36,46 +38,6 @@ public CarmaRegistrationMessage(String carmaVehicleId, String carlaVehicleRole, this.rxTimeSyncPort = rxTimeSyncPort; } - public String getCarmaVehicleId() { - return carmaVehicleId; - } - - public void setCarmaVehicleId(String carmaVehicleId) { - this.carmaVehicleId = carmaVehicleId; - } - - public String getCarlaVehicleRole() { - return carlaVehicleRole; - } - - public void setCarlaVehicleRole(String carlaVehicleRole) { - this.carlaVehicleRole = carlaVehicleRole; - } - - public String getRxMessageIpAddress() { - return rxMessageIpAddress; - } - - public void setRxMessageIpAddress(String rxMessageIpAddress) { - this.rxMessageIpAddress = rxMessageIpAddress; - } - - public int getRxMessagePort() { - return rxMessagePort; - } - - public void setRxMessagePort(int rxMessagePort) { - this.rxMessagePort = rxMessagePort; - } - - public int getRxTimeSyncPort() { - return rxTimeSyncPort; - } - - public void setRxTimeSyncPort(int rxTimeSyncPort) { - this.rxTimeSyncPort = rxTimeSyncPort; - } - @Override public String toString() { return "CarmaRegistrationMessage [carmaVehicleId=" + carmaVehicleId + ", carlaVehicleRole=" + carlaVehicleRole diff --git a/co-simulation/fed/mosaic-carma/src/main/java/org/eclipse/mosaic/fed/carma/ambassador/CarmaRegistrationReceiver.java b/co-simulation/fed/mosaic-carma/src/main/java/org/eclipse/mosaic/fed/carma/ambassador/CarmaRegistrationReceiver.java index 3e995a65..6e7b8d98 100644 --- a/co-simulation/fed/mosaic-carma/src/main/java/org/eclipse/mosaic/fed/carma/ambassador/CarmaRegistrationReceiver.java +++ b/co-simulation/fed/mosaic-carma/src/main/java/org/eclipse/mosaic/fed/carma/ambassador/CarmaRegistrationReceiver.java @@ -20,14 +20,10 @@ import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; -import java.net.SocketException; -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; -import java.util.Queue; + import com.google.gson.Gson; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; + +import org.eclipse.mosaic.lib.CommonUtil.ambassador.CommonRegistrationReceiver; /** * Worker thread Runnable for operating a listen socket to receive outbound V2X Messages from CARMA Platform instances @@ -35,25 +31,17 @@ * adapter. Upon receiving a packet, it will be enqueued for the primary thread to process the data once it ticks to a * simulation processing step */ -public class CarmaRegistrationReceiver implements Runnable { - private Queue rxQueue = new LinkedList<>(); +public class CarmaRegistrationReceiver extends CommonRegistrationReceiver{ + + public CarmaRegistrationReceiver(Class type) { + super(type); + } + private DatagramSocket listenSocket = null; private static final int listenPort = 1515; private boolean running = true; private static final int UDP_MTU = 1535; - private final Logger log = LoggerFactory.getLogger(this.getClass()); - /** - * Initialize the listen socket for messages from the CARMA Platform NS-3 Adapter - * @throws RuntimeException iff socket instantiation fails - */ - public void init() { - try { - listenSocket = new DatagramSocket(listenPort); - } catch (SocketException e) { - throw new RuntimeException(e); - } - } @Override public void run() { @@ -74,35 +62,9 @@ public void run() { // Enqueue message for processing on main thread synchronized (rxQueue) { - log.info("New CARMA instance '{}' received with CARMA Registration Receiver.", parsedMessage.getCarmaVehicleId()); + log.info("New CARMA instance '{}' received with CARMA Registration Receiver.", parsedMessage.getVehicleId()); rxQueue.add(parsedMessage); } } } - - /** - * Stop the runnable instance - */ - public void stop() { - if (listenSocket != null) { - listenSocket.close(); - } - - running = false; - } - - /** - * 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 CARMA Platform 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/src/main/java/org/eclipse/mosaic/fed/carma/configuration/CarmaConfiguration.java b/co-simulation/fed/mosaic-carma/src/main/java/org/eclipse/mosaic/fed/carma/configuration/CarmaConfiguration.java index a7fb62cf..207e752d 100644 --- a/co-simulation/fed/mosaic-carma/src/main/java/org/eclipse/mosaic/fed/carma/configuration/CarmaConfiguration.java +++ b/co-simulation/fed/mosaic-carma/src/main/java/org/eclipse/mosaic/fed/carma/configuration/CarmaConfiguration.java @@ -13,32 +13,13 @@ package org.eclipse.mosaic.fed.carma.configuration; -import org.eclipse.mosaic.lib.util.gson.TimeFieldAdapter; -import com.google.gson.annotations.JsonAdapter; -import java.io.Serializable; -import java.util.List; +import org.eclipse.mosaic.lib.CommonUtil.configuration.CommonConfiguration; /** * The CARMA Message Ambassador configuration class. */ -public class CarmaConfiguration implements Serializable { +public class CarmaConfiguration extends CommonConfiguration { private static final long serialVersionUID = 1479294781446446539L; - /** - * The time step that the CARMA message ambassador advances time. The default - * value is 1000 (1s). Unit: [ms]. - */ - @JsonAdapter(TimeFieldAdapter.LegacyMilliSeconds.class) - public Long updateInterval = 1000L; - - /** - * Configruation for CARMA vehicles. - */ - public List carmaVehicles; - - /** - * ID of CARMA vehicle that sends external messages. - */ - public String senderCarmaVehicleId; } diff --git a/co-simulation/fed/mosaic-carma/src/main/java/org/eclipse/mosaic/fed/carma/configuration/CarmaVehicleConfiguration.java b/co-simulation/fed/mosaic-carma/src/main/java/org/eclipse/mosaic/fed/carma/configuration/CarmaVehicleConfiguration.java index f6b8bc77..067ce7a6 100644 --- a/co-simulation/fed/mosaic-carma/src/main/java/org/eclipse/mosaic/fed/carma/configuration/CarmaVehicleConfiguration.java +++ b/co-simulation/fed/mosaic-carma/src/main/java/org/eclipse/mosaic/fed/carma/configuration/CarmaVehicleConfiguration.java @@ -13,63 +13,12 @@ package org.eclipse.mosaic.fed.carma.configuration; -import java.util.List; - -import org.eclipse.mosaic.lib.geo.GeoPoint; -import org.eclipse.mosaic.lib.geo.CartesianPoint; +import org.eclipse.mosaic.lib.CommonUtil.configuration.CommonVehicleConfiguration; /** * Define CARMA vehicle information */ -public class CarmaVehicleConfiguration { - - /** - * The route ID on which the vehicle will be spawned. - */ - public String routeID; - - /** - * The lane on which the vehicle will be spawned. - */ - public int lane; - - /** - * Position within the route where the vehicle should be spawned. - */ - public double position; - - /** - * The speed at which the vehicle is supposed to depart. - */ - public double departSpeed; - - /** - * The vehicle type - */ - public String vehicleType; - - /** - * Specify the applications to be used for this vehicle. - */ - public List applications; - - /** - * The geo position at which the vehicle is currently located. - */ - public GeoPoint geoPosition; - - /** - * The projected position at which currently the vehicle is located. - */ - public CartesianPoint projectedPosition; - - /** - * Vehicle heading in degrees - */ - public Double heading; +public class CarmaVehicleConfiguration extends CommonVehicleConfiguration { - /** - * The slope of vehicle in degrees - */ - public double slope; + } diff --git a/co-simulation/fed/mosaic-carma/src/test/java/org/eclipse/mosaic/fed/carma/ambassador/CarmaInstanceManagerTest.java b/co-simulation/fed/mosaic-carma/src/test/java/org/eclipse/mosaic/fed/carma/ambassador/CarmaInstanceManagerTest.java index a7f84ccd..07412908 100644 --- a/co-simulation/fed/mosaic-carma/src/test/java/org/eclipse/mosaic/fed/carma/ambassador/CarmaInstanceManagerTest.java +++ b/co-simulation/fed/mosaic-carma/src/test/java/org/eclipse/mosaic/fed/carma/ambassador/CarmaInstanceManagerTest.java @@ -15,20 +15,8 @@ */ package org.eclipse.mosaic.fed.carma.ambassador; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - import java.io.IOException; -import java.net.DatagramSocket; import java.net.InetAddress; -import java.util.Arrays; import java.util.HashMap; import java.util.Map; @@ -36,12 +24,17 @@ import org.eclipse.mosaic.lib.geo.GeoPoint; import org.eclipse.mosaic.lib.junit.IpResolverRule; import org.eclipse.mosaic.lib.objects.addressing.IpResolver; -import org.eclipse.mosaic.lib.objects.v2x.ExternalV2xContent; -import org.eclipse.mosaic.lib.objects.v2x.ExternalV2xMessage; -import org.eclipse.mosaic.lib.objects.v2x.V2xMessage; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import org.mockito.internal.util.reflection.FieldSetter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -89,7 +82,7 @@ public void setUp() throws Exception { managedInstances.put("instance3", instance3); // Set private instance field to mock using reflection - FieldSetter.setField(manager, manager.getClass().getDeclaredField("managedInstances"), managedInstances); + FieldSetter.setField(manager, manager.getClass().getSuperclass().getDeclaredField("managedInstances"), managedInstances); } @Test @@ -139,7 +132,7 @@ public void testOnTimeStepUpdateWithoutRegisteredIntstances() throws NoSuchField Map managedInstances = new HashMap<>(); // Set private instance field to mock using reflection - FieldSetter.setField(manager, manager.getClass().getDeclaredField("managedInstances"), managedInstances); + FieldSetter.setField(manager, manager.getClass().getSuperclass().getDeclaredField("managedInstances"), managedInstances); TimeSyncMessage message = new TimeSyncMessage(300, 3); manager.onTimeStepUpdate(message); @@ -162,7 +155,7 @@ public void testonV2XMessageTx() { // Register host with IpResolver singleton IpResolver.getSingleton().registerHost("veh_0"); // Set CarlaRoleName to veh_0 to macth registered host - when(instance1.getCarlaRoleName()).thenReturn("veh_0"); + when(instance1.getRoleName()).thenReturn("veh_0"); // Set location to origin when(instance1.getLocation()).thenReturn(GeoPoint.ORIGO); @@ -184,7 +177,7 @@ public void testonV2XMessageTxUnregisteredCarmaPlatform() { when(instance2.getTargetAddress()).thenReturn(address2); when(instance3.getTargetAddress()).thenReturn(address3); IpResolver.getSingleton().registerHost("veh_0"); - when(instance1.getCarlaRoleName()).thenReturn("veh_0"); + when(instance1.getRoleName()).thenReturn("veh_0"); when(instance1.getLocation()).thenReturn(GeoPoint.ORIGO); // Attempt to create V2X Message Transmission for unregistered address. // Throws IllegalStateException diff --git a/co-simulation/fed/mosaic-carma/src/test/java/org/eclipse/mosaic/fed/carma/ambassador/CarmaInstanceTest.java b/co-simulation/fed/mosaic-carma/src/test/java/org/eclipse/mosaic/fed/carma/ambassador/CarmaInstanceTest.java index 5c69adc6..b3f17709 100644 --- a/co-simulation/fed/mosaic-carma/src/test/java/org/eclipse/mosaic/fed/carma/ambassador/CarmaInstanceTest.java +++ b/co-simulation/fed/mosaic-carma/src/test/java/org/eclipse/mosaic/fed/carma/ambassador/CarmaInstanceTest.java @@ -15,21 +15,20 @@ */ package org.eclipse.mosaic.fed.carma.ambassador; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import org.eclipse.mosaic.lib.geo.GeoPoint; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import org.mockito.internal.util.reflection.FieldSetter; import com.google.gson.Gson; @@ -78,20 +77,20 @@ public void setup() throws NoSuchFieldException { 3456, 5667); // Set private instance field to mock using reflection - FieldSetter.setField(instance, instance.getClass().getDeclaredField("rxMsgsSocket"), socket); + FieldSetter.setField(instance, instance.getClass().getSuperclass().getDeclaredField("rxMsgsSocket"), socket); } @Test public void testGetterSetterConstructor() { // Test getters and constructor for setting and retrieving class members - assertEquals("SomeID", instance.getCarmaVehicleId()); + assertEquals("SomeID", instance.getVehicleId()); assertEquals( GeoPoint.ORIGO, instance.getLocation()); assertEquals(3456, instance.getV2xPort()); assertEquals(5667, instance.getTimeSyncPort()); // Test Setter - instance.setCarmaVehicleId("DifferentID"); - assertEquals("DifferentID", instance.getCarmaVehicleId()); + instance.setVehicleId("DifferentID"); + assertEquals("DifferentID", instance.getVehicleId()); instance.setTimeSyncPort(4321); assertEquals(4321, instance.getTimeSyncPort()); instance.setV2xPort(5678); diff --git a/co-simulation/fed/mosaic-carma/src/test/java/org/eclipse/mosaic/fed/carma/ambassador/CarmaRegistrationMessageTest.java b/co-simulation/fed/mosaic-carma/src/test/java/org/eclipse/mosaic/fed/carma/ambassador/CarmaRegistrationMessageTest.java index 0ead758a..b41bae33 100644 --- a/co-simulation/fed/mosaic-carma/src/test/java/org/eclipse/mosaic/fed/carma/ambassador/CarmaRegistrationMessageTest.java +++ b/co-simulation/fed/mosaic-carma/src/test/java/org/eclipse/mosaic/fed/carma/ambassador/CarmaRegistrationMessageTest.java @@ -30,19 +30,19 @@ public void testGetterSettersConstructor() { 5678, 8642); // Test Getter - assertEquals("ID", message.getCarmaVehicleId()); - assertEquals("role", message.getCarlaVehicleRole()); + assertEquals("ID", message.getVehicleId()); + assertEquals("role", message.getVehicleRole()); assertEquals("127.0.0.1", message.getRxMessageIpAddress()); assertEquals(5678, message.getRxMessagePort()); assertEquals(8642, message.getRxTimeSyncPort()); message.setRxMessagePort(7777); message.setRxTimeSyncPort(6666); - message.setCarmaVehicleId("SOMEID"); - message.setCarlaVehicleRole("SOMEROLL"); + message.setVehicleId("SOMEID"); + message.setVehicleRole("SOMEROLL"); message.setRxMessageIpAddress("someIP"); - assertEquals("SOMEID", message.getCarmaVehicleId()); - assertEquals("SOMEROLL", message.getCarlaVehicleRole()); + assertEquals("SOMEID", message.getVehicleId()); + assertEquals("SOMEROLL", message.getVehicleRole()); assertEquals("someIP", message.getRxMessageIpAddress()); assertEquals(7777, message.getRxMessagePort()); diff --git a/co-simulation/fed/mosaic-carma/src/test/java/org/eclipse/mosaic/fed/carma/configuration/CarmaConfigurationTest.java b/co-simulation/fed/mosaic-carma/src/test/java/org/eclipse/mosaic/fed/carma/configuration/CarmaConfigurationTest.java index ad6cd629..f30ba04c 100644 --- a/co-simulation/fed/mosaic-carma/src/test/java/org/eclipse/mosaic/fed/carma/configuration/CarmaConfigurationTest.java +++ b/co-simulation/fed/mosaic-carma/src/test/java/org/eclipse/mosaic/fed/carma/configuration/CarmaConfigurationTest.java @@ -13,15 +13,18 @@ package org.eclipse.mosaic.fed.carma.configuration; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; +import java.io.InputStreamReader; +import java.lang.reflect.Type; import org.eclipse.mosaic.lib.geo.MutableCartesianPoint; import org.eclipse.mosaic.lib.geo.MutableGeoPoint; -import org.eclipse.mosaic.lib.util.objects.ObjectInstantiation; - +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import org.junit.Test; +import com.google.common.reflect.TypeToken; +import com.google.gson.Gson; + /** * Test for {@link CarmaConfiguration}. * @@ -44,20 +47,20 @@ public void readConfig_assertProperties() throws InstantiationException { // ASSERT assertNotNull(carmaConfiguration); // assert that configuration is created assertEquals(Long.valueOf(100L), carmaConfiguration.updateInterval); - assertEquals("0", carmaConfiguration.carmaVehicles.get(0).routeID); - assertEquals(1, carmaConfiguration.carmaVehicles.get(0).lane); - assertEquals(Double.valueOf(0.0D), carmaConfiguration.carmaVehicles.get(0).position, 0.001); - assertEquals(Double.valueOf(0.0D), carmaConfiguration.carmaVehicles.get(0).departSpeed, 0.001); - assertEquals("vehicle.chevrolet.impala", carmaConfiguration.carmaVehicles.get(0).vehicleType); + assertEquals("0", carmaConfiguration.Vehicles.get(0).routeID); + assertEquals(1, carmaConfiguration.Vehicles.get(0).lane); + assertEquals(Double.valueOf(0.0D), carmaConfiguration.Vehicles.get(0).position, 0.001); + assertEquals(Double.valueOf(0.0D), carmaConfiguration.Vehicles.get(0).departSpeed, 0.001); + assertEquals("vehicle.chevrolet.impala", carmaConfiguration.Vehicles.get(0).vehicleType); assertEquals("org.eclipse.mosaic.app.tutorial.VehicleCommunicationApp", - carmaConfiguration.carmaVehicles.get(0).applications.get(0)); + carmaConfiguration.Vehicles.get(0).applications.get(0)); assertEquals(new MutableGeoPoint(52.579272059028646, 13.467165499469328), - carmaConfiguration.carmaVehicles.get(0).geoPosition); + carmaConfiguration.Vehicles.get(0).geoPosition); assertEquals(new MutableCartesianPoint(501.62, 116.95, 0.0), - carmaConfiguration.carmaVehicles.get(0).projectedPosition); - assertEquals(Double.valueOf(24.204351784500364D), carmaConfiguration.carmaVehicles.get(0).heading); - assertEquals(Double.valueOf(0.0), carmaConfiguration.carmaVehicles.get(0).slope, 0.001); - assertEquals("carma_0", carmaConfiguration.senderCarmaVehicleId); + carmaConfiguration.Vehicles.get(0).projectedPosition); + assertEquals(Double.valueOf(24.204351784500364D), carmaConfiguration.Vehicles.get(0).heading); + assertEquals(Double.valueOf(0.0), carmaConfiguration.Vehicles.get(0).slope, 0.001); + assertEquals("carma_0", carmaConfiguration.senderVehicleId); } /** @@ -70,7 +73,8 @@ public void readConfig_assertProperties() throws InstantiationException { * deserialization/instantiation. */ private CarmaConfiguration getCarmaConfiguration(String filePath) throws InstantiationException { - return new ObjectInstantiation<>(CarmaConfiguration.class).read(getClass().getResourceAsStream(filePath)); + Type type = new TypeToken() {}.getType(); + return new Gson().fromJson(new InputStreamReader(getClass().getResourceAsStream(filePath)), type); } } \ No newline at end of file diff --git a/co-simulation/fed/mosaic-carma/src/test/resources/carma_config.json b/co-simulation/fed/mosaic-carma/src/test/resources/carma_config.json index f12b548c..b40a8049 100644 --- a/co-simulation/fed/mosaic-carma/src/test/resources/carma_config.json +++ b/co-simulation/fed/mosaic-carma/src/test/resources/carma_config.json @@ -1,6 +1,6 @@ { "updateInterval": 100, - "carmaVehicles":[ + "Vehicles":[ { "routeID": "0", "lane": 1, @@ -21,5 +21,5 @@ "heading": 24.204351784500364, "slope": 0.0 }], -"senderCarmaVehicleId":"carma_0" +"senderVehicleId":"carma_0" } diff --git a/co-simulation/lib/mosaic-common-utils/pom.xml b/co-simulation/lib/mosaic-common-utils/pom.xml new file mode 100644 index 00000000..9c0af4dc --- /dev/null +++ b/co-simulation/lib/mosaic-common-utils/pom.xml @@ -0,0 +1,112 @@ + + + 4.0.0 + + + org.eclipse.mosaic + mosaic-parent + 22.1-SNAPSHOT + ../../pom.xml + + + mosaic-common-utils + Common Utilities + + + + com.google.code.gson + gson + + + org.eclipse.mosaic + mosaic-rti-api + ${mosaic.version} + + + org.eclipse.mosaic + mosaic-geomath + ${mosaic.version} + + + com.google.guava + guava + + + org.apache.commons + commons-configuration2 + + + + commons-jxpath + commons-jxpath + runtime + + + commons-cli + commons-cli + + + org.leadpony.justify + justify + + + com.ibm.icu + icu4j + + + + + org.apache.johnzon + johnzon-core + compile + + + gov.dot.fhwa.saxton + mosaic-carma-utils + ${mosaic.version} + + + org.eclipse.mosaic + mosaic-application + ${mosaic.version} + + + org.eclipse.mosaic + mosaic-objects + ${mosaic.version} + + + org.eclipse.mosaic + mosaic-objects + ${mosaic.version} + test-jar + test + + + org.eclipse.mosaic + mosaic-utils + ${mosaic.version} + test-jar + test + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + test-jar + + + + + + + + + diff --git a/co-simulation/lib/mosaic-common-utils/src/main/java/org/eclipse/mosaic/lib/CommonUtil/ambassador/CommonInstance.java b/co-simulation/lib/mosaic-common-utils/src/main/java/org/eclipse/mosaic/lib/CommonUtil/ambassador/CommonInstance.java new file mode 100644 index 00000000..b438c3b0 --- /dev/null +++ b/co-simulation/lib/mosaic-common-utils/src/main/java/org/eclipse/mosaic/lib/CommonUtil/ambassador/CommonInstance.java @@ -0,0 +1,128 @@ +/* + * 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.lib.CommonUtil.ambassador; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; + +import org.eclipse.mosaic.lib.geo.GeoPoint; + +public class CommonInstance { + + private String VehicleId; + private String RoleName; + private InetAddress targetAddress; + private int v2xPort; + private int timeSyncPort; + private GeoPoint location = GeoPoint.ORIGO; + protected DatagramSocket rxMsgsSocket = null; + + public CommonInstance(String VehicleId, String RoleName, InetAddress targetAddress, int v2xPort, int timeSyncPort){ + this.VehicleId = VehicleId; + this.RoleName = RoleName; + this.targetAddress = targetAddress; + this.v2xPort = v2xPort; + this.timeSyncPort = timeSyncPort; + } + + public String getVehicleId() { + return VehicleId; + } + + public void setVehicleId(String VehicleId) { + this.VehicleId = VehicleId; + } + + public String getRoleName() { + return RoleName; + } + + public void setRoleName(String RoleName) { + this.RoleName = RoleName; + } + + public InetAddress getTargetAddress() { + return targetAddress; + } + + public void setTargetAddress(InetAddress targetAddress) { + this.targetAddress = targetAddress; + } + + public int getV2xPort() { + return v2xPort; + } + + public void setV2xPort(int v2xPort) { + this.v2xPort = v2xPort; + } + + public int getTimeSyncPort() { + return timeSyncPort; + } + + public void setTimeSyncPort(int timeSyncPort) { + this.timeSyncPort = timeSyncPort; + } + + public void setLocation(GeoPoint location) { + this.location = location; + } + + public GeoPoint getLocation() { + return this.location; + } + + /** + * Sends the V2X message to the CARMA Platform communications interface configured at construction time. + * @param data The binary data to transmit + * @throws IOException If there is an issue with the underlying socket object or methods + */ + public void sendV2xMsgs(byte[] data) throws IOException { + if (rxMsgsSocket == null) { + throw new IllegalStateException("Attempted to send data before opening socket"); + } + + DatagramPacket packet = new DatagramPacket(data, data.length, targetAddress, v2xPort); + + rxMsgsSocket.send(packet); + } + + /** + * Sends the time sync messages to the CARMA Platform to synchronize ros clock with simulation clock. + * @param data The binary data encoding of json time sync message + * @throws IOException If there is an issue with the underlying socket object or methods + */ + public void sendTimeSyncMsg(byte[] data) throws IOException { + if (rxMsgsSocket == null) { + throw new IllegalStateException("Attempted to send data before opening socket"); + } + + DatagramPacket packet = new DatagramPacket(data, data.length, targetAddress, timeSyncPort); + + rxMsgsSocket.send(packet); + } + + /** + * Connects the sockt to receive messages from the CARMA Platform instance + * @throws IOException If the socket creation fails + */ + public void bind() throws IOException { + rxMsgsSocket = new DatagramSocket(); + } +} diff --git a/co-simulation/lib/mosaic-common-utils/src/main/java/org/eclipse/mosaic/lib/CommonUtil/ambassador/CommonInstanceManager.java b/co-simulation/lib/mosaic-common-utils/src/main/java/org/eclipse/mosaic/lib/CommonUtil/ambassador/CommonInstanceManager.java new file mode 100644 index 00000000..a19ff08e --- /dev/null +++ b/co-simulation/lib/mosaic-common-utils/src/main/java/org/eclipse/mosaic/lib/CommonUtil/ambassador/CommonInstanceManager.java @@ -0,0 +1,159 @@ +/* + * 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.lib.CommonUtil.ambassador; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.mosaic.interactions.communication.V2xMessageTransmission; +import org.eclipse.mosaic.interactions.traffic.VehicleUpdates; +import org.eclipse.mosaic.lib.enums.AdHocChannel; +import org.eclipse.mosaic.lib.geo.GeoCircle; +import org.eclipse.mosaic.lib.objects.addressing.AdHocMessageRoutingBuilder; +import org.eclipse.mosaic.lib.objects.v2x.ExternalV2xContent; +import org.eclipse.mosaic.lib.objects.v2x.ExternalV2xMessage; +import org.eclipse.mosaic.lib.objects.v2x.MessageRouting; +import org.eclipse.mosaic.lib.objects.vehicle.VehicleData; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.Gson; + +import gov.dot.fhwa.saxton.CarmaV2xMessage; +import gov.dot.fhwa.saxton.TimeSyncMessage; + + +public class CommonInstanceManager { + protected Map managedInstances = new HashMap<>(); + + private int TARGET_PORT = 5374; + protected final Logger log = LoggerFactory.getLogger(this.getClass()); + + public void onNewRegistration(R registration) { + if (!managedInstances.containsKey(registration.getVehicleRole())) { + try { + newCommonInstance( + registration.getVehicleId(), + registration.getVehicleRole(), + InetAddress.getByName(registration.getRxMessageIpAddress()), + registration.getRxMessagePort(), + registration.getRxTimeSyncPort() + ); + } catch (UnknownHostException e) { + throw new RuntimeException(e); + } + } else { + // log warning + log.warn("Received duplicate registration for vehicle " + registration.getVehicleRole()); + } + } + + protected void newCommonInstance(String VehId, String RoleName, InetAddress targetAddress, int v2xPort, int timeSyncPort) { + CommonInstance tmp = new CommonInstance(VehId, RoleName, targetAddress, v2xPort, timeSyncPort); + try { + tmp.bind(); + log.info("New Common instance '{}' registered with CARMA Instance Manager.", RoleName); + } catch (IOException e) { + log.error("Failed to bind Common instance with ID '{}' to its RX message socket: {}", + RoleName, e.getMessage()); + log.error("Stack trace:", e); + throw new RuntimeException(e); + } + managedInstances.put(RoleName, (T) tmp); + } + + public boolean checkIfRegistered(String mosiacVehicleId) { + return managedInstances.keySet().contains(mosiacVehicleId); + } + + public V2xMessageTransmission onV2XMessageTx(InetAddress sourceAddr, CarmaV2xMessage txMsg, long time) { + T sender = null; + // Find the CarmaInstance with sourceAddr. + for (T ci : managedInstances.values()) { + if (ci.getTargetAddress().equals(sourceAddr)) { + sender = ci; + break; + } + } + // Unregistered instance attempting to send messages + if (sender == null) { + throw new IllegalStateException("Unregistered Common instance attempting to send messages via MOSAIC"); + } + AdHocMessageRoutingBuilder messageRoutingBuilder = new AdHocMessageRoutingBuilder( + sender.getRoleName(), 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: {}, type: {}, payload: {}", + time, + sender.getVehicleId(), + sender.getLocation(), + txMsg.getType(), + txMsg.getPayload() + ); + return new V2xMessageTransmission( time, new ExternalV2xMessage(routing, + new ExternalV2xContent( time, sender.getLocation(), txMsg.getPayload()))); + } + + public void onV2XMessageRx(byte[] rxMsg, String rxVehicleId) { + if (!managedInstances.containsKey(rxVehicleId)) { + return; + } + + T common = managedInstances.get(rxVehicleId); + try { + common.sendV2xMsgs(rxMsg); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public void onTimeStepUpdate(TimeSyncMessage message) throws IOException { + if (managedInstances.size() == 0) { + log.debug("There are no registered instances"); + } + else { + Gson gson = new Gson(); + byte[] bytes = gson.toJson(message).getBytes(); + for (T currentInstance : managedInstances.values()) { + log.debug("Sending Common instance {} at {}:{} time sync message for time {}!" , + currentInstance.getVehicleId(), + currentInstance.getTargetAddress(), + currentInstance.getTimeSyncPort(), + message.getTimestep()); + currentInstance.sendTimeSyncMsg(bytes); + } + } + } + + public int getTargetPort(){ + return TARGET_PORT; + } + + public void setTargetPort(int targetPort){ + this.TARGET_PORT = targetPort; + } + + public void onVehicleUpdates(VehicleUpdates vui) { + for (VehicleData veh : vui.getUpdated()) { + if (managedInstances.containsKey(veh.getName())) { + managedInstances.get(veh.getName()).setLocation(veh.getPosition()); + } + } + } +} diff --git a/co-simulation/lib/mosaic-common-utils/src/main/java/org/eclipse/mosaic/lib/CommonUtil/ambassador/CommonMessageAmbassador.java b/co-simulation/lib/mosaic-common-utils/src/main/java/org/eclipse/mosaic/lib/CommonUtil/ambassador/CommonMessageAmbassador.java new file mode 100644 index 00000000..a5b7df8f --- /dev/null +++ b/co-simulation/lib/mosaic-common-utils/src/main/java/org/eclipse/mosaic/lib/CommonUtil/ambassador/CommonMessageAmbassador.java @@ -0,0 +1,349 @@ +/* + * 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.lib.CommonUtil.ambassador; + +import java.io.IOException; +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.xml.bind.DatatypeConverter; + +import org.eclipse.mosaic.fed.application.ambassador.SimulationKernel; +import org.eclipse.mosaic.interactions.application.CarmaV2xMessageReception; +import org.eclipse.mosaic.interactions.communication.AdHocCommunicationConfiguration; +import org.eclipse.mosaic.interactions.communication.V2xMessageReception; +import org.eclipse.mosaic.interactions.communication.V2xMessageTransmission; +import org.eclipse.mosaic.interactions.mapping.advanced.ExternalVehicleRegistration; +import org.eclipse.mosaic.interactions.traffic.VehicleUpdates; +import org.eclipse.mosaic.lib.CommonUtil.configuration.CommonConfiguration; +import org.eclipse.mosaic.lib.enums.AdHocChannel; +import org.eclipse.mosaic.lib.enums.DriveDirection; +import org.eclipse.mosaic.lib.geo.CartesianPoint; +import org.eclipse.mosaic.lib.geo.GeoPoint; +import org.eclipse.mosaic.lib.misc.Tuple; +import org.eclipse.mosaic.lib.objects.addressing.IpResolver; +import org.eclipse.mosaic.lib.objects.communication.AdHocConfiguration; +import org.eclipse.mosaic.lib.objects.communication.InterfaceConfiguration; +import org.eclipse.mosaic.lib.objects.road.IRoadPosition; +import org.eclipse.mosaic.lib.objects.road.SimpleRoadPosition; +import org.eclipse.mosaic.lib.objects.v2x.ExternalV2xMessage; +import org.eclipse.mosaic.lib.objects.v2x.V2xMessage; +import org.eclipse.mosaic.lib.objects.vehicle.Consumptions; +import org.eclipse.mosaic.lib.objects.vehicle.Emissions; +import org.eclipse.mosaic.lib.objects.vehicle.VehicleBatteryState; +import org.eclipse.mosaic.lib.objects.vehicle.VehicleConsumptions; +import org.eclipse.mosaic.lib.objects.vehicle.VehicleData; +import org.eclipse.mosaic.lib.objects.vehicle.VehicleEmissions; +import org.eclipse.mosaic.lib.objects.vehicle.VehicleSensors; +import org.eclipse.mosaic.lib.objects.vehicle.VehicleSignals; +import org.eclipse.mosaic.lib.objects.vehicle.VehicleType; +import org.eclipse.mosaic.lib.objects.vehicle.sensor.DistanceSensor; +import org.eclipse.mosaic.lib.objects.vehicle.sensor.RadarSensor; +import org.eclipse.mosaic.lib.util.objects.ObjectInstantiation; +import org.eclipse.mosaic.rti.TIME; +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 gov.dot.fhwa.saxton.CarmaV2xMessage; +import gov.dot.fhwa.saxton.CarmaV2xMessageReceiver; +import gov.dot.fhwa.saxton.TimeSyncMessage; + + +public class CommonMessageAmbassador extends AbstractFederateAmbassador{ + + protected long currentSimulationTime; + protected C commonConfiguration; + + protected R commonRegistrationReceiver; + private Thread registrationRxBackgroundThread; + private CarmaV2xMessageReceiver v2xMessageReceiver; + private Thread v2xRxBackgroundThread; + protected M commonInstanceManager = (M) new CommonInstanceManager(); + protected int timeSyncSeq = 0; + protected Class messageClass; + protected Class configClass; + + @Override + public boolean isTimeRegulating() { + return true; + } + + @Override + public boolean isTimeConstrained() { + return true; + } + + public CommonMessageAmbassador(AmbassadorParameter ambassadorParameter, Class messageClass, Class configClass) { + super(ambassadorParameter); + this.messageClass = messageClass; + this.configClass = configClass; + try { + // Read the CARMA message ambassador configuration file + commonConfiguration = new ObjectInstantiation<>(configClass, log) + .readFile(ambassadorParameter.configuration); + } catch (InstantiationException e) { + log.error("Configuration object could not be instantiated: ", e); + } + + log.info("The update interval of "+ this.getClass().getSimpleName() + "is " + commonConfiguration.updateInterval + " ."); + + // Check the CARMA update interval + if (commonConfiguration.updateInterval <= 0) { + throw new RuntimeException("Invalid update interval for " + this.getClass().getSimpleName() + ", should be >0."); + } + log.info( this.getClass().getSimpleName() + " is generated."); + } + + @Override + public void initialize(long startTime, long endTime) throws InternalFederateException { + super.initialize(startTime, endTime); + + currentSimulationTime = startTime; + try { + rti.requestAdvanceTime(currentSimulationTime, 0, (byte) 1); + } catch (IllegalValueException e) { + log.error("Error during advanceTime request", e); + throw new InternalFederateException(e); + } + + // Initialize listener socket and thread for Common Registration messages + commonRegistrationReceiver = (R) new CommonRegistrationReceiver(messageClass); + commonRegistrationReceiver.init(); + registrationRxBackgroundThread = new Thread(commonRegistrationReceiver); + registrationRxBackgroundThread.start(); + + // Initialize listener socket and thread for Common NS-3 Adapter messages + v2xMessageReceiver = new CarmaV2xMessageReceiver(); + v2xMessageReceiver.init(); + v2xRxBackgroundThread = new Thread(v2xMessageReceiver); + v2xRxBackgroundThread.start(); + } + + @Override + public synchronized void processTimeAdvanceGrant(long time) throws InternalFederateException { + + if (time < currentSimulationTime) { + // process time advance only if time is equal or greater than the next + // simulation time step + return; + } + log.info(this.getClass().getSimpleName()+ " processing timestep to {}.", time); + + try { + List newRegistrations = commonRegistrationReceiver.getReceivedMessages(); + for (T reg : newRegistrations) { + commonInstanceManager.onNewRegistration(reg); + onDsrcRegistrationRequest(reg.getVehicleRole()); + } + // Set current simulation time to most recent time update + currentSimulationTime = time; + if (currentSimulationTime == 0) { + // For the first timestep, clear the message receive queues. + v2xMessageReceiver.getReceivedMessages(); // Automatically empties the queues. + } else { + List> newMessages = v2xMessageReceiver.getReceivedMessages(); + for (Tuple msg : newMessages) { + V2xMessageTransmission msgInt = commonInstanceManager.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); + } + } + // Time Syncmessage in nano seconds + TimeSyncMessage timeSyncMessage = new TimeSyncMessage(currentSimulationTime, timeSyncSeq); + commonInstanceManager.onTimeStepUpdate(timeSyncMessage); + // Increment time + currentSimulationTime += commonConfiguration.updateInterval * TIME.MILLI_SECOND; + timeSyncSeq += 1; + + rti.requestAdvanceTime(currentSimulationTime, 0, (byte) 2); + } catch (IllegalValueException e) { + log.error("Error during advanceTime(" + time + ")", e); + throw new InternalFederateException(e); + } catch (UnknownHostException e) { + log.error("Error during advanceTime(" + time + ")", e); + throw new InternalFederateException(e); + } catch (IOException e) { + log.error("Error during advanceTime(" + time + ")", e); + throw new InternalFederateException(e); + } + + } + + @Override + public void processInteraction(Interaction interaction) throws InternalFederateException { + String type = interaction.getTypeId(); + long interactionTime = interaction.getTime(); + log.trace("Process interaction with type '{}' at time: {}", type, interactionTime); + if (interaction.getTypeId().equals(V2xMessageReception.TYPE_ID)) { + receiveV2xReceptionInteraction((V2xMessageReception) interaction); + } + if (interaction.getTypeId().equals(CarmaV2xMessageReception.TYPE_ID)) { + receiveInteraction((CarmaV2xMessageReception) interaction); + } + if (interaction.getTypeId().equals(VehicleUpdates.TYPE_ID)) { + receiveVehicleUpdateInteraction((VehicleUpdates) interaction); + } + } + + private synchronized void receiveVehicleUpdateInteraction(VehicleUpdates interaction) { + commonInstanceManager.onVehicleUpdates(interaction); + } + + protected V2xMessage lookupV2xMsgIdInBuffer(int id) { + return SimulationKernel.SimulationKernel.getV2xMessageCache().getItem(id); + } + + private synchronized void receiveV2xReceptionInteraction(V2xMessageReception interaction) { + String carlaRoleName = interaction.getReceiverName(); + if (!commonInstanceManager.checkIfRegistered(carlaRoleName)) { + // Abort early as we only are concerned with CARMA Platform vehicles + return; + } + log.info("Processing V2X message reception event for " + interaction.getReceiverName() + " of msg id " + interaction.getMessageId()); + + int messageId = interaction.getMessageId(); + V2xMessage msg = lookupV2xMsgIdInBuffer(messageId); + + if (msg != null && msg instanceof ExternalV2xMessage) { + ExternalV2xMessage msg2 = (ExternalV2xMessage) msg; + commonInstanceManager.onV2XMessageRx(DatatypeConverter.parseHexBinary(msg2.getMessage()), carlaRoleName); + log.info("Sending V2X message reception event for " + interaction.getReceiverName() + " of msg id " + interaction.getMessageId() + " of size " + msg2.getPayLoad().getBytes().length); + } else { + log.warn("Message with id " + interaction.getMessageId() + " received by " + interaction.getReceiverName() + " is no longer in the message buffer to be retrieved! Message transmission failed!!!"); + } + } + + private void onDsrcRegistrationRequest(String vehicleId) throws UnknownHostException { + ExternalVehicleRegistration tempRegistration = new ExternalVehicleRegistration( + currentSimulationTime, + vehicleId, + "carma", + null, + new VehicleType("carma")); + + try { + // Trigger RTI interaction to MOSAIC to exchange the Ad-Hoc configuration + this.rti.triggerInteraction(tempRegistration); + } catch (InternalFederateException | IllegalValueException e) { + // Log error message if there was an issue with the RTI interaction + log.error(e.getMessage()); + } + + VehicleSignals tmpSignals = new VehicleSignals( + false, + false, + false, + false, + false); + + VehicleEmissions tmpEmissions = new VehicleEmissions( + new Emissions( + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ), + new Emissions( + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + )); + + VehicleBatteryState tmpBattery = new VehicleBatteryState("", currentSimulationTime); + IRoadPosition tmpPos = new SimpleRoadPosition("", 0, 0.0, 0.0); + VehicleSensors tmpSensors = new VehicleSensors( + new DistanceSensor(0.0, + 0.0, + 0.0, + 0.0), + new RadarSensor(0.0)); + VehicleConsumptions tmpConsumptions = new VehicleConsumptions( + new Consumptions(0.0, 0.0), + new Consumptions(0.0, 0.0)); + VehicleData tmpVehicle = new VehicleData.Builder(currentSimulationTime, vehicleId) + .position(GeoPoint.ORIGO, CartesianPoint.ORIGO) + .movement(0.0, 0.0, 0.0) + .consumptions(tmpConsumptions) + .emissions(tmpEmissions) + .electric(tmpBattery) + .laneArea("") + .sensors(tmpSensors) + .road(tmpPos) + .signals(tmpSignals) + .orientation(DriveDirection.FORWARD, 0.0, 0.0) + .stopped(false) + .route("") + .create(); + VehicleUpdates tempUpdates = new VehicleUpdates( + currentSimulationTime, + new ArrayList<>(Arrays.asList(tmpVehicle)), + new ArrayList<>(), + new ArrayList<>()); + + try { + // Trigger RTI interaction to MOSAIC to exchange the Ad-Hoc configuration + this.rti.triggerInteraction(tempUpdates); + } catch (InternalFederateException | IllegalValueException e) { + // Log error message if there was an issue with the RTI interaction + log.error(e.getMessage()); + } + + + // Create an InterfaceConfiguration object to represent the configuration of the + // Ad-Hoc interface + // TODO: Replace the transmit power of the ad-hoc interface (in dBm) if necessary + // TODO: Replace the communication range of the ad-hoc interface (in meters) if necessary + Inet4Address vehAddress = IpResolver.getSingleton().registerHost(vehicleId); + log.info("Assigned registered comms device " + vehicleId + " with IP address " + vehAddress.toString()); + InterfaceConfiguration interfaceConfig = new InterfaceConfiguration.Builder(AdHocChannel.CCH) + .ip(vehAddress) + .subnet(IpResolver.getSingleton().getNetMask()) + .power(50) + .radius(300.0) + .create(); + + // Create an AdHocConfiguration object to associate the Ad-Hoc interface + // configuration with the infrastructure instance's ID + AdHocConfiguration adHocConfig = new AdHocConfiguration.Builder(vehicleId) + .addInterface(interfaceConfig) + .create(); + + // Create an AdHocCommunicationConfiguration object to specify the time and + // Ad-Hoc configuration for exchange with another vehicle or component + AdHocCommunicationConfiguration communicationConfig = new AdHocCommunicationConfiguration(currentSimulationTime, + adHocConfig); + log.info("Communications comms device " + vehicleId + " with IP address " + vehAddress.toString() + " success!"); + try { + // Trigger RTI interaction to MOSAIC to exchange the Ad-Hoc configuration + this.rti.triggerInteraction(communicationConfig); + } catch (InternalFederateException | IllegalValueException e) { + // Log error message if there was an issue with the RTI interaction + log.error(e.getMessage()); + } + } + +} diff --git a/co-simulation/lib/mosaic-common-utils/src/main/java/org/eclipse/mosaic/lib/CommonUtil/ambassador/CommonRegistrationMessage.java b/co-simulation/lib/mosaic-common-utils/src/main/java/org/eclipse/mosaic/lib/CommonUtil/ambassador/CommonRegistrationMessage.java new file mode 100644 index 00000000..e58ad2be --- /dev/null +++ b/co-simulation/lib/mosaic-common-utils/src/main/java/org/eclipse/mosaic/lib/CommonUtil/ambassador/CommonRegistrationMessage.java @@ -0,0 +1,81 @@ +/* + * 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.lib.CommonUtil.ambassador; + +public class CommonRegistrationMessage { + + private String vehicleId; + private String vehicleRole; + private String rxMessageIpAddress; + private int rxMessagePort; + private int rxTimeSyncPort; + + public CommonRegistrationMessage(String vehicleId, String vehicleRole, String rxMessageIpAddress,int rxMessagePort, int rxTimeSyncPort) { + this.vehicleId = vehicleId; + this.vehicleRole = vehicleRole; + this.rxMessageIpAddress = rxMessageIpAddress; + this.rxMessagePort = rxMessagePort; + this.rxTimeSyncPort = rxTimeSyncPort; + } + + public String getVehicleId() { + return vehicleId; + } + + public void setVehicleId(String vehicleId) { + this.vehicleId = vehicleId; + } + + public String getVehicleRole() { + return vehicleRole; + } + + public void setVehicleRole(String vehicleRole) { + this.vehicleRole = vehicleRole; + } + + public String getRxMessageIpAddress() { + return rxMessageIpAddress; + } + + public void setRxMessageIpAddress(String rxMessageIpAddress) { + this.rxMessageIpAddress = rxMessageIpAddress; + } + + public int getRxMessagePort() { + return rxMessagePort; + } + + public void setRxMessagePort(int rxMessagePort) { + this.rxMessagePort = rxMessagePort; + } + + public int getRxTimeSyncPort() { + return rxTimeSyncPort; + } + + public void setRxTimeSyncPort(int rxTimeSyncPort) { + this.rxTimeSyncPort = rxTimeSyncPort; + } + + @Override + public String toString() { + return "CommonRegistrationMessage [VehicleId=" + vehicleId + ", VehicleRole=" + vehicleRole + + ", rxMessageIpAddress=" + rxMessageIpAddress + ", rxMessagePort=" + rxMessagePort + + ", rxTimeSyncPort=" + rxTimeSyncPort + "]"; + } + +} diff --git a/co-simulation/lib/mosaic-common-utils/src/main/java/org/eclipse/mosaic/lib/CommonUtil/ambassador/CommonRegistrationReceiver.java b/co-simulation/lib/mosaic-common-utils/src/main/java/org/eclipse/mosaic/lib/CommonUtil/ambassador/CommonRegistrationReceiver.java new file mode 100644 index 00000000..3d642217 --- /dev/null +++ b/co-simulation/lib/mosaic-common-utils/src/main/java/org/eclipse/mosaic/lib/CommonUtil/ambassador/CommonRegistrationReceiver.java @@ -0,0 +1,97 @@ +/* + * 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.lib.CommonUtil.ambassador; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.SocketException; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.Gson; + +public class CommonRegistrationReceiver implements Runnable{ + + public CommonRegistrationReceiver(java.lang.Class type) { + this.type = type; + } + + protected Queue rxQueue = new LinkedList<>(); + private DatagramSocket listenSocket = null; + private static final int listenPort = 1515; + private boolean running = true; + private static final int UDP_MTU = 1535; + protected final Logger log = LoggerFactory.getLogger(this.getClass()); + protected final Class type; + + public void init() { + try { + listenSocket = new DatagramSocket(listenPort); + } catch (SocketException e) { + throw new RuntimeException(e); + } + } + + @Override + public void run() { + byte[] buf = new byte[UDP_MTU]; + while (running) { + DatagramPacket msg = new DatagramPacket(buf, buf.length); + try { + listenSocket.receive(msg); + } catch (IOException e) { + throw new RuntimeException(e); + } + + // parse message + String receivedPacket = new String(msg.getData(), 0, msg.getLength()); + log.debug("Registration JSON received: {}", receivedPacket); + Gson gson = new Gson(); + T parsedMessage = gson.fromJson(receivedPacket, type); + + // Enqueue message for processing on main thread + synchronized (rxQueue) { + log.info("New Common instance '{}' received with Common Registration Receiver.", parsedMessage.getVehicleId()); + rxQueue.add(parsedMessage); + } + } + } + + public void stop() { + if (listenSocket != null) { + listenSocket.close(); + } + + running = false; + } + + public List getReceivedMessages() { + List output = new ArrayList<>(); + + synchronized (rxQueue) { + output.addAll(rxQueue); + rxQueue.clear(); + } + + return output; + } +} diff --git a/co-simulation/lib/mosaic-common-utils/src/main/java/org/eclipse/mosaic/lib/CommonUtil/configuration/CommonConfiguration.java b/co-simulation/lib/mosaic-common-utils/src/main/java/org/eclipse/mosaic/lib/CommonUtil/configuration/CommonConfiguration.java new file mode 100644 index 00000000..4afbffac --- /dev/null +++ b/co-simulation/lib/mosaic-common-utils/src/main/java/org/eclipse/mosaic/lib/CommonUtil/configuration/CommonConfiguration.java @@ -0,0 +1,44 @@ +/* + * 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.lib.CommonUtil.configuration; + +import java.util.List; + +import org.eclipse.mosaic.lib.util.gson.TimeFieldAdapter; + +import com.google.gson.annotations.JsonAdapter; + +public class CommonConfiguration { + + private static final long serialVersionUID = 1479294781446446539L; + + /** + * The time step that the CARMA message ambassador advances time. The default + * value is 1000 (1s). Unit: [ms]. + */ + @JsonAdapter(TimeFieldAdapter.LegacyMilliSeconds.class) + public Long updateInterval = 1000L; + + /** + * Configruation for CARMA vehicles. + */ + public List Vehicles; + + /** + * ID of CARMA vehicle that sends external messages. + */ + public String senderVehicleId; +} diff --git a/co-simulation/lib/mosaic-common-utils/src/main/java/org/eclipse/mosaic/lib/CommonUtil/configuration/CommonVehicleConfiguration.java b/co-simulation/lib/mosaic-common-utils/src/main/java/org/eclipse/mosaic/lib/CommonUtil/configuration/CommonVehicleConfiguration.java new file mode 100644 index 00000000..5d8a6e3d --- /dev/null +++ b/co-simulation/lib/mosaic-common-utils/src/main/java/org/eclipse/mosaic/lib/CommonUtil/configuration/CommonVehicleConfiguration.java @@ -0,0 +1,74 @@ +/* + * 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.lib.CommonUtil.configuration; + +import java.util.List; + +import org.eclipse.mosaic.lib.geo.CartesianPoint; +import org.eclipse.mosaic.lib.geo.GeoPoint; + +public class CommonVehicleConfiguration { + /** + * The route ID on which the vehicle will be spawned. + */ + public String routeID; + + /** + * The lane on which the vehicle will be spawned. + */ + public int lane; + + /** + * Position within the route where the vehicle should be spawned. + */ + public double position; + + /** + * The speed at which the vehicle is supposed to depart. + */ + public double departSpeed; + + /** + * The vehicle type + */ + public String vehicleType; + + /** + * Specify the applications to be used for this vehicle. + */ + public List applications; + + /** + * The geo position at which the vehicle is currently located. + */ + public GeoPoint geoPosition; + + /** + * The projected position at which currently the vehicle is located. + */ + public CartesianPoint projectedPosition; + + /** + * Vehicle heading in degrees + */ + public Double heading; + + /** + * The slope of vehicle in degrees + */ + public double slope; + +} diff --git a/co-simulation/lib/mosaic-common-utils/src/test/java/org/eclipse/mosaic/lib/CommonUtil/ambassador/CommonInstanceManagerTest.java b/co-simulation/lib/mosaic-common-utils/src/test/java/org/eclipse/mosaic/lib/CommonUtil/ambassador/CommonInstanceManagerTest.java new file mode 100644 index 00000000..4e9040b7 --- /dev/null +++ b/co-simulation/lib/mosaic-common-utils/src/test/java/org/eclipse/mosaic/lib/CommonUtil/ambassador/CommonInstanceManagerTest.java @@ -0,0 +1,189 @@ +/* + * 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.lib.CommonUtil.ambassador; + +import java.io.IOException; +import java.net.InetAddress; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.mosaic.interactions.communication.V2xMessageTransmission; +import org.eclipse.mosaic.lib.geo.GeoPoint; +import org.eclipse.mosaic.lib.junit.IpResolverRule; +import org.eclipse.mosaic.lib.objects.addressing.IpResolver; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import org.mockito.internal.util.reflection.FieldSetter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.Gson; + +import gov.dot.fhwa.saxton.CarmaV2xMessage; +import gov.dot.fhwa.saxton.TimeSyncMessage; + +public class CommonInstanceManagerTest { + + private CommonInstanceManager manager; + private CommonInstance instance1; + private CommonInstance instance2; + private CommonInstance instance3; + private final String sampleMessage = + "Version=0.7\n" + + "Type=BSM\n" + + "PSID=0020\n" + + "Priority=6\n" + + "TxMode=ALT\n" + + "TxChannel=172\n" + + "TxInterval=0\n" + + "DeliveryStart=\n" + + "DeliveryStop=\n" + + "Signature=False\n" + + "Encryption=False\n" + + "Payload=00142500400000000f0e35a4e900eb49d20000007fffffff8ffff080fdfa1fa1007fff8000960fa0\n"; + private final Logger log = LoggerFactory.getLogger(this.getClass()); + + /** + * Rule to initialize {@link IpResolver} Singleton. + */ + @Rule + public IpResolverRule ipResolverRule = new IpResolverRule(); + + @Before + public void setUp() throws Exception { + manager = new CommonInstanceManager(); + instance1 = mock(CommonInstance.class); + instance2 = mock(CommonInstance.class); + instance3 = mock(CommonInstance.class); + Map managedInstances = new HashMap<>(); + managedInstances.put("instance1", instance1); + managedInstances.put("instance2", instance2); + managedInstances.put("instance3", instance3); + + // Set private instance field to mock using reflection + FieldSetter.setField(manager, manager.getClass().getDeclaredField("managedInstances"), managedInstances); + } + + @Test + public void testOnNewRegistration() { + // Set up the registration object + String infrastructureId = "infrastructure-123"; + int rxMessagePort = 1234; + int timeSyncPort = 5678; + String ipAddressString = "127.0.0.1"; + + // Mock the behavior of the registration object + CommonRegistrationMessage registration = new CommonRegistrationMessage( + infrastructureId, + infrastructureId, + ipAddressString, + rxMessagePort, + timeSyncPort); + // Ensure checkIfRegistered returns false for infrastructure ID before registering + assertFalse( manager.checkIfRegistered(infrastructureId) ); + + // Call the onNewRegistration method with the mocked registration object + manager.onNewRegistration(registration); + + // Verify that the infrastructure instance was added to the manager + assertTrue( manager.checkIfRegistered(infrastructureId) ); + // Ensure checkIfRegistered returns false for other Ids + assertFalse( manager.checkIfRegistered(infrastructureId + "something") ); + } + + @Test + public void testOnTimeStepUpdate() throws IOException { + TimeSyncMessage message = new TimeSyncMessage(300, 3); + + Gson gson = new Gson(); + byte[] message_bytes = gson.toJson(message).getBytes(); + + manager.onTimeStepUpdate(message); + // Verify that all instances sendTimeSyncMsgs was called. + verify(instance1).sendTimeSyncMsg(message_bytes); + verify(instance2).sendTimeSyncMsg(message_bytes); + verify(instance3).sendTimeSyncMsg(message_bytes); + + } + @Test + public void testOnTimeStepUpdateWithoutRegisteredIntstances() throws NoSuchFieldException, SecurityException, IOException{ + // Verify that with no managed instances nothing is called and no exception is thrown. + Map managedInstances = new HashMap<>(); + + // Set private instance field to mock using reflection + FieldSetter.setField(manager, manager.getClass().getDeclaredField("managedInstances"), managedInstances); + TimeSyncMessage message = new TimeSyncMessage(300, 3); + + manager.onTimeStepUpdate(message); + + verify(instance1, never()).sendTimeSyncMsg(any()); + verify(instance2, never()).sendTimeSyncMsg(any()); + verify(instance3, never()).sendTimeSyncMsg(any()); + + } + @Test + public void testonV2XMessageTx() { + CarmaV2xMessage message = new CarmaV2xMessage(sampleMessage.getBytes()); + // Setup mock addresses for registered carma platform instances + InetAddress address1 = mock(InetAddress.class); + InetAddress address2 = mock(InetAddress.class); + InetAddress address3 = mock(InetAddress.class); + when(instance1.getTargetAddress()).thenReturn(address1); + when(instance2.getTargetAddress()).thenReturn(address2); + when(instance3.getTargetAddress()).thenReturn(address3); + // Register host with IpResolver singleton + IpResolver.getSingleton().registerHost("veh_0"); + // Set CarlaRoleName to veh_0 to macth registered host + when(instance1.getRoleName()).thenReturn("veh_0"); + // Set location to origin + when(instance1.getLocation()).thenReturn(GeoPoint.ORIGO); + + V2xMessageTransmission messageTx = manager.onV2XMessageTx(address1, message, 1000); + assertEquals(1000, messageTx.getTime()); + assertEquals(GeoPoint.ORIGO, messageTx.getSourcePosition()); + assertEquals("veh_0", messageTx.getMessage().getRouting().getSource().getSourceName()); + } + + @Test(expected = IllegalStateException.class) + public void testonV2XMessageTxUnregisteredCarmaPlatform() { + CarmaV2xMessage message = new CarmaV2xMessage(sampleMessage.getBytes()); + InetAddress address1 = mock(InetAddress.class); + InetAddress address2 = mock(InetAddress.class); + InetAddress address3 = mock(InetAddress.class); + InetAddress unregisteredAddress = mock(InetAddress.class); + + when(instance1.getTargetAddress()).thenReturn(address1); + when(instance2.getTargetAddress()).thenReturn(address2); + when(instance3.getTargetAddress()).thenReturn(address3); + IpResolver.getSingleton().registerHost("veh_0"); + when(instance1.getRoleName()).thenReturn("veh_0"); + when(instance1.getLocation()).thenReturn(GeoPoint.ORIGO); + // Attempt to create V2X Message Transmission for unregistered address. + // Throws IllegalStateException + manager.onV2XMessageTx(unregisteredAddress, message, 1000); + + } + +} diff --git a/co-simulation/lib/mosaic-common-utils/src/test/java/org/eclipse/mosaic/lib/CommonUtil/ambassador/CommonInstanceTest.java b/co-simulation/lib/mosaic-common-utils/src/test/java/org/eclipse/mosaic/lib/CommonUtil/ambassador/CommonInstanceTest.java new file mode 100644 index 00000000..23330fb9 --- /dev/null +++ b/co-simulation/lib/mosaic-common-utils/src/test/java/org/eclipse/mosaic/lib/CommonUtil/ambassador/CommonInstanceTest.java @@ -0,0 +1,122 @@ +/* + * 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.lib.CommonUtil.ambassador; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; + +import org.eclipse.mosaic.lib.geo.GeoPoint; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import org.mockito.internal.util.reflection.FieldSetter; + +import com.google.gson.Gson; + +import gov.dot.fhwa.saxton.TimeSyncMessage; + +public class CommonInstanceTest { + + private DatagramSocket socket; + + private CommonInstance instance; + + private InetAddress address; + + @Before + public void setup() throws NoSuchFieldException { + // Initialize Mocks + address = mock(InetAddress.class); + socket = mock(DatagramSocket.class); + // Initialize Infrastructure Instance + + instance = new CommonInstance( + "SomeID", + "SomeRole", + address, + 3456, + 5667); + // Set private instance field to mock using reflection + FieldSetter.setField(instance, instance.getClass().getDeclaredField("rxMsgsSocket"), socket); + + } + + @Test + public void testGetterSetterConstructor() { + // Test getters and constructor for setting and retrieving class members + assertEquals("SomeID", instance.getVehicleId()); + assertEquals("SomeRole", instance.getRoleName()); + assertEquals( GeoPoint.ORIGO, instance.getLocation()); + assertEquals(3456, instance.getV2xPort()); + assertEquals(5667, instance.getTimeSyncPort()); + // Test Setter + instance.setVehicleId("DifferentID"); + assertEquals("DifferentID", instance.getVehicleId()); + instance.setRoleName("DifferentRole"); + assertEquals("DifferentRole", instance.getRoleName()); + instance.setTimeSyncPort(4321); + assertEquals(4321, instance.getTimeSyncPort()); + instance.setV2xPort(5678); + assertEquals(5678, instance.getV2xPort()); + instance.setTargetAddress(address); + assertEquals(address, instance.getTargetAddress()); + } + + @Test + public void testSendV2xMsg() throws IOException { + // Test SendV2xMsg method + String test_msg = "test message"; + instance.sendV2xMsgs(test_msg.getBytes()); + // ArgumentCaptor to capture parameters passed to mock on method calls + ArgumentCaptor packet = ArgumentCaptor.forClass(DatagramPacket.class); + // Verify socket.send(DatagramPacket packet) is called and capture packet + // parameter + verify(socket, times(1)).send(packet.capture()); + + // Verify parameter members + assertArrayEquals(test_msg.getBytes(), packet.getValue().getData()); + assertEquals(instance.getV2xPort(), packet.getValue().getPort()); + assertEquals(address, packet.getValue().getAddress()); + } + + @Test + public void testSendTimeSyncMsg() throws IOException { + // Test SendTimeSyncMsg method + TimeSyncMessage test_msg = new TimeSyncMessage(1,100); + Gson gson = new Gson(); + byte[] bytes = gson.toJson(test_msg).getBytes(); + instance.sendTimeSyncMsg(bytes); + + // ArgumentCaptor to capture parameters passed to mock on method calls + ArgumentCaptor packet = ArgumentCaptor.forClass(DatagramPacket.class); + // Verify socket.send(DatagramPacket packet) is called and capture packet + // parameter + verify(socket, times(1)).send(packet.capture()); + // Convert message to bytes + byte[] message_bytes = gson.toJson(test_msg).getBytes(); + // Verify parameter members + assertArrayEquals(message_bytes, packet.getValue().getData()); + assertEquals(instance.getTimeSyncPort(), packet.getValue().getPort()); + assertEquals(address, packet.getValue().getAddress()); + } +} diff --git a/co-simulation/lib/mosaic-common-utils/src/test/java/org/eclipse/mosaic/lib/CommonUtil/ambassador/CommonMessageAmbassadorTest.java b/co-simulation/lib/mosaic-common-utils/src/test/java/org/eclipse/mosaic/lib/CommonUtil/ambassador/CommonMessageAmbassadorTest.java new file mode 100644 index 00000000..09ccdfc1 --- /dev/null +++ b/co-simulation/lib/mosaic-common-utils/src/test/java/org/eclipse/mosaic/lib/CommonUtil/ambassador/CommonMessageAmbassadorTest.java @@ -0,0 +1,87 @@ +/* + * 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.lib.CommonUtil.ambassador; + +import java.io.File; +import java.io.IOException; + +import org.eclipse.mosaic.lib.CommonUtil.configuration.CommonConfiguration; +import org.eclipse.mosaic.lib.util.junit.TestFileRule; +import org.eclipse.mosaic.rti.TIME; +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.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.RuleChain; +import org.junit.rules.TemporaryFolder; +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; + + +public class CommonMessageAmbassadorTest { + private final TemporaryFolder temporaryFolder = new TemporaryFolder(); + + private final TestFileRule testFileRule = new TestFileRule(temporaryFolder).basedir("carma") + .with("carma_config.json", "/carma_config.json"); + + @Rule + public RuleChain chain = RuleChain.outerRule(temporaryFolder).around(testFileRule); + + private RtiAmbassador rtiMock; + + private CommonMessageAmbassador ambassador; + + @Before + public void setup() throws IOException { + + rtiMock = mock(RtiAmbassador.class); + + FederateDescriptor handleMock = mock(FederateDescriptor.class); + + File workingDir = temporaryFolder.getRoot(); + + CLocalHost testHostConfig = new CLocalHost(); + + testHostConfig.workingDirectory = workingDir.getAbsolutePath(); + + when(handleMock.getHost()).thenReturn(testHostConfig); + + when(handleMock.getId()).thenReturn("carma"); + + ambassador = new CommonMessageAmbassador( + new AmbassadorParameter("carma", testFileRule.get("carma_config.json")), CommonRegistrationMessage.class, CommonConfiguration.class); + + ambassador.setRtiAmbassador(rtiMock); + + ambassador.setFederateDescriptor(handleMock); + } + + @Test + public void initialize() throws Throwable { + + // RUN + ambassador.initialize(0, 100 * TIME.SECOND); + // ASSERT + verify(rtiMock, times(1)).requestAdvanceTime(eq(0L), eq(0L), eq((byte) 1)); + } + +} diff --git a/co-simulation/lib/mosaic-common-utils/src/test/java/org/eclipse/mosaic/lib/CommonUtil/ambassador/CommonRegistrationMessageTest.java b/co-simulation/lib/mosaic-common-utils/src/test/java/org/eclipse/mosaic/lib/CommonUtil/ambassador/CommonRegistrationMessageTest.java new file mode 100644 index 00000000..a9ca5cf5 --- /dev/null +++ b/co-simulation/lib/mosaic-common-utils/src/test/java/org/eclipse/mosaic/lib/CommonUtil/ambassador/CommonRegistrationMessageTest.java @@ -0,0 +1,51 @@ +/* + * 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.lib.CommonUtil.ambassador; + +import static org.junit.Assert.assertEquals; +import org.junit.Test; + +public class CommonRegistrationMessageTest { + @Test + public void testGetterSettersConstructor() { + + CommonRegistrationMessage message = new CommonRegistrationMessage( + "ID", + "role", + "127.0.0.1", + 5678, + 8642); + // Test Getter + assertEquals("ID", message.getVehicleId()); + assertEquals("role", message.getVehicleRole()); + + assertEquals("127.0.0.1", message.getRxMessageIpAddress()); + assertEquals(5678, message.getRxMessagePort()); + assertEquals(8642, message.getRxTimeSyncPort()); + message.setRxMessagePort(7777); + message.setRxTimeSyncPort(6666); + message.setVehicleId("SOMEID"); + message.setVehicleRole("SOMEROLL"); + message.setRxMessageIpAddress("someIP"); + assertEquals("SOMEID", message.getVehicleId()); + assertEquals("SOMEROLL", message.getVehicleRole()); + + assertEquals("someIP", message.getRxMessageIpAddress()); + assertEquals(7777, message.getRxMessagePort()); + assertEquals(6666, message.getRxTimeSyncPort()); + + } +} diff --git a/co-simulation/lib/mosaic-common-utils/src/test/java/org/eclipse/mosaic/lib/CommonUtil/configuration/CommonConfigurationTest.java b/co-simulation/lib/mosaic-common-utils/src/test/java/org/eclipse/mosaic/lib/CommonUtil/configuration/CommonConfigurationTest.java new file mode 100644 index 00000000..8bccc76f --- /dev/null +++ b/co-simulation/lib/mosaic-common-utils/src/test/java/org/eclipse/mosaic/lib/CommonUtil/configuration/CommonConfigurationTest.java @@ -0,0 +1,60 @@ +/* + * 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.lib.CommonUtil.configuration; + +import java.io.InputStreamReader; +import java.lang.reflect.Type; + +import org.eclipse.mosaic.lib.geo.MutableCartesianPoint; +import org.eclipse.mosaic.lib.geo.MutableGeoPoint; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import org.junit.Test; + +import com.google.common.reflect.TypeToken; +import com.google.gson.Gson; + +public class CommonConfigurationTest { + + @Test + public void readConfig_assertProperties() throws InstantiationException{ + + String validConfig = "/carma_config.json"; + CommonConfiguration commonConfiguration = geCommonConfiguration(validConfig); + + assertNotNull(commonConfiguration); // assert that configuration is created + assertEquals(Long.valueOf(100L), commonConfiguration.updateInterval); + assertEquals("0", commonConfiguration.Vehicles.get(0).routeID); + assertEquals(1, commonConfiguration.Vehicles.get(0).lane); + assertEquals(Double.valueOf(0.0D), commonConfiguration.Vehicles.get(0).position, 0.001); + assertEquals(Double.valueOf(0.0D), commonConfiguration.Vehicles.get(0).departSpeed, 0.001); + assertEquals("vehicle.chevrolet.impala", commonConfiguration.Vehicles.get(0).vehicleType); + assertEquals("org.eclipse.mosaic.app.tutorial.VehicleCommunicationApp", + commonConfiguration.Vehicles.get(0).applications.get(0)); + assertEquals(new MutableGeoPoint(52.579272059028646, 13.467165499469328), + commonConfiguration.Vehicles.get(0).geoPosition); + assertEquals(new MutableCartesianPoint(501.62, 116.95, 0.0), + commonConfiguration.Vehicles.get(0).projectedPosition); + assertEquals(Double.valueOf(24.204351784500364D), commonConfiguration.Vehicles.get(0).heading); + assertEquals(Double.valueOf(0.0), commonConfiguration.Vehicles.get(0).slope, 0.001); + assertEquals("carma_0", commonConfiguration.senderVehicleId); + } + + private CommonConfiguration geCommonConfiguration(String filePath) throws InstantiationException{ + Type type = new TypeToken>() {}.getType(); + return new Gson().fromJson(new InputStreamReader(getClass().getResourceAsStream(filePath)), type); + } +} diff --git a/co-simulation/lib/mosaic-common-utils/src/test/resources/carma_config.json b/co-simulation/lib/mosaic-common-utils/src/test/resources/carma_config.json new file mode 100644 index 00000000..b40a8049 --- /dev/null +++ b/co-simulation/lib/mosaic-common-utils/src/test/resources/carma_config.json @@ -0,0 +1,25 @@ +{ + "updateInterval": 100, + "Vehicles":[ +{ + "routeID": "0", + "lane": 1, + "position": 0, + "departSpeed": 0, + "vehicleType": "vehicle.chevrolet.impala", + "applications":["org.eclipse.mosaic.app.tutorial.VehicleCommunicationApp"], + "geoPosition": { + "latitude": 52.579272059028646, + "longitude": 13.467165499469328 + }, + "projectedPosition": + { + "x": 501.62, + "y": 116.95 + }, + + "heading": 24.204351784500364, + "slope": 0.0 +}], +"senderVehicleId":"carma_0" +} diff --git a/co-simulation/lib/mosaic-utils/pom.xml b/co-simulation/lib/mosaic-utils/pom.xml index ebe6ccc5..20affba4 100644 --- a/co-simulation/lib/mosaic-utils/pom.xml +++ b/co-simulation/lib/mosaic-utils/pom.xml @@ -81,4 +81,4 @@ - + \ No newline at end of file diff --git a/co-simulation/pom.xml b/co-simulation/pom.xml index 5faa7301..c54156ad 100644 --- a/co-simulation/pom.xml +++ b/co-simulation/pom.xml @@ -51,6 +51,7 @@ rti/mosaic-starter lib/mosaic-communication + lib/mosaic-common-utils lib/mosaic-database lib/mosaic-docker lib/mosaic-geomath @@ -73,6 +74,7 @@ fed/mosaic-carma fed/mosaic-infrastructure fed/mosaic-carma-cloud + fed/mosaic-carma-messenger test/mosaic-integration-tests @@ -501,6 +503,7 @@ **/ClientServerChannelProtos.java fed/mosaic-carma/** fed/mosaic-carla/** + fed/mosaic-carma-messenger/** true ${project.build.sourceEncoding} @@ -595,28 +598,28 @@ true - - org.jacoco - jacoco-maven-plugin - 0.8.6 - - ${skip.coverage} - - - - - prepare-agent - - - - report - prepare-package - - report - - - - + + org.jacoco + jacoco-maven-plugin + 0.8.6 + + ${skip.coverage} + + + + + prepare-agent + + + + report + prepare-package + + report + + + +