diff --git a/README.md b/README.md index ec121915b..5f7ee1392 100644 --- a/README.md +++ b/README.md @@ -92,20 +92,20 @@ But also schemas of custom messages for V2X: _Note: none of the provided implementation is able to use different versions of a schema, they are using the following versions: -| Schema | Rust | Python | Java | Swift | -|:-----------------:|:-----------------------------------------------------------------------------------:|:---------------------------------------------------------:|:-------------------------------------------:|:-------------------------------------------:| -| **Bootstrap** | | | | | -| **CAM** | [2.2.0](schema/cam/cam_schema_2-2-0.json) [1.1.3](schema/cam/cam_schema_1-1-3.json) | [1.1.3](schema/cam/cam_schema_1-1-3.json) | [1.1.3](schema/cam/cam_schema_1-1-3.json) | [1.1.3](schema/cam/cam_schema_1-1-3.json) | -| **CPM** | [2.1.0](schema/cpm/cpm_schema_2-1-0.json) | [2.1.1](schema/cpm/cpm_schema_2-1-1.json) | [1.2.1](schema/cpm/cpm_schema_1-2-1.json) | | -| **DENM** | [2.2.0](schema/denm/denm_schema_2-2-0.json) | [1.1.3](schema/denm/denm_schema_1-1-3.json) | [1.1.3](schema/denm/denm_schema_1-1-3.json) | [1.1.3](schema/denm/denm_schema_1-1-3.json) | -| **Information** | [2.1.0](schema/information/information_schema_2-1-0.json) | [1.2.0](schema/information/information_schema_1-2-0.json) | | | -| **MAPEM** | | | | | -| **Neighbourhood** | | | | | -| **Region** | | | | | -| **SPATEM** | | | | | -| **SREM** | | | | | -| **SSEM** | | | | | -| **Status** | | [1.2.0](schema/status/status_schema_1-2-0.json) | | | +| Schema | Rust | Python | Java | Swift | +|:-----------------:|:-----------------------------------------------------------------------------------:|:---------------------------------------------------------:|:-----------------------------------------------------------------------------------:|:-------------------------------------------:| +| **Bootstrap** | | | | | +| **CAM** | [2.2.0](schema/cam/cam_schema_2-2-0.json) [1.1.3](schema/cam/cam_schema_1-1-3.json) | [1.1.3](schema/cam/cam_schema_1-1-3.json) | [2.3.0](schema/cam/cam_schema_2-3-0.json) [1.1.3](schema/cam/cam_schema_1-1-3.json) | [1.1.3](schema/cam/cam_schema_1-1-3.json) | +| **CPM** | [2.1.0](schema/cpm/cpm_schema_2-1-0.json) | [2.1.1](schema/cpm/cpm_schema_2-1-1.json) | [1.2.1](schema/cpm/cpm_schema_1-2-1.json) | | +| **DENM** | [2.2.0](schema/denm/denm_schema_2-2-0.json) | [1.1.3](schema/denm/denm_schema_1-1-3.json) | [1.1.3](schema/denm/denm_schema_1-1-3.json) | [1.1.3](schema/denm/denm_schema_1-1-3.json) | +| **Information** | [2.1.0](schema/information/information_schema_2-1-0.json) | [1.2.0](schema/information/information_schema_1-2-0.json) | | | +| **MAPEM** | | | | | +| **Neighbourhood** | | | | | +| **Region** | | | | | +| **SPATEM** | | | | | +| **SREM** | | | | | +| **SSEM** | | | | | +| **Status** | | [1.2.0](schema/status/status_schema_1-2-0.json) | | | Languages --------- diff --git a/THIRD-PARTY.md b/THIRD-PARTY.md index 9065ae014..21974e60c 100644 --- a/THIRD-PARTY.md +++ b/THIRD-PARTY.md @@ -210,6 +210,9 @@ #### Square OkHttp - [Source code](https://github.com/square/okhttp) +#### Jackson Core +- [Source code](https://github.com/FasterXML/jackson-core) + ## Swift ### License Apache 2.0 diff --git a/java/iot3/examples/src/main/java/com/orange/Iot3MobilityBootstrapExample.java b/java/iot3/examples/src/main/java/com/orange/Iot3MobilityBootstrapExample.java index bb2f746c8..184cd1abc 100644 --- a/java/iot3/examples/src/main/java/com/orange/Iot3MobilityBootstrapExample.java +++ b/java/iot3/examples/src/main/java/com/orange/Iot3MobilityBootstrapExample.java @@ -8,13 +8,16 @@ import com.orange.iot3mobility.TrueTime; import com.orange.iot3mobility.Utils; import com.orange.iot3mobility.its.EtsiUtils; +import com.orange.iot3mobility.messages.cam.core.CamCodec; +import com.orange.iot3mobility.messages.cam.core.CamVersion; +import com.orange.iot3mobility.messages.cam.v113.model.CamEnvelope113; +import com.orange.iot3mobility.messages.cam.v230.model.CamEnvelope230; import com.orange.iot3mobility.roadobjects.HazardType; import com.orange.iot3mobility.its.StationType; import com.orange.iot3mobility.its.json.JsonValue; import com.orange.iot3mobility.its.json.Position; import com.orange.iot3mobility.its.json.PositionConfidence; import com.orange.iot3mobility.its.json.PositionConfidenceEllipse; -import com.orange.iot3mobility.its.json.cam.CAM; import com.orange.iot3mobility.its.json.cpm.*; import com.orange.iot3mobility.its.json.denm.DENM; import com.orange.iot3mobility.managers.IoT3RoadHazardCallback; @@ -26,6 +29,7 @@ import com.orange.iot3mobility.roadobjects.RoadUser; import com.orange.iot3mobility.roadobjects.SensorObject; +import java.io.IOException; import java.net.URI; import java.util.List; import java.util.concurrent.Executors; @@ -125,12 +129,6 @@ public void newRoadUser(RoadUser roadUser) { System.out.println("New Road User: " + roadUser.getUuid()); LatLng position = roadUser.getPosition(); System.out.println("Road User position: " + position); - // the CAM on which this object is based can still be accessed - CAM originalCam = roadUser.getCam(); - double latitude = originalCam.getBasicContainer().getPosition().getLatitudeDegree(); - double longitude = originalCam.getBasicContainer().getPosition().getLongitudeDegree(); - LatLng camPosition = new LatLng(latitude, longitude); - System.out.println("CAM position: " + camPosition); } @Override @@ -146,9 +144,15 @@ public void roadUserExpired(RoadUser roadUser) { } @Override - public void camArrived(CAM cam) { + public void camArrived(CamCodec.CamFrame camFrame) { // if you want to directly process the raw CAM messages - System.out.println("CAM received: " + cam.getJsonCAM()); + if(camFrame.version().equals(CamVersion.V1_1_3)) { + CamEnvelope113 camEnvelope113 = (CamEnvelope113) camFrame.envelope(); + System.out.println("Raw CAM v1.1.3: " + camEnvelope113); + } else if(camFrame.version().equals(CamVersion.V2_3_0)) { + CamEnvelope230 camEnvelope230 = (CamEnvelope230) camFrame.envelope(); + System.out.println("Raw CAM v2.3.0: " + camEnvelope230); + } } }); @@ -221,7 +225,11 @@ private static synchronized void startSendingMessages() { private static void sendTestCam() { LatLng position = new LatLng(48.625218, 2.243448); // center point of UTAC TEQMO - ioT3Mobility.sendPosition(StationType.PASSENGER_CAR, position, 0, 0, 0, 0, 0); + try { + ioT3Mobility.sendPosition(StationType.PASSENGER_CAR, position, 0, 0, 0, 0, 0, CamVersion.V1_1_3); + } catch (IOException e) { + throw new RuntimeException(e); + } } private static void sendTestDenm() { diff --git a/java/iot3/examples/src/main/java/com/orange/Iot3MobilityExample.java b/java/iot3/examples/src/main/java/com/orange/Iot3MobilityExample.java index 3ddefbec1..a3b302d58 100644 --- a/java/iot3/examples/src/main/java/com/orange/Iot3MobilityExample.java +++ b/java/iot3/examples/src/main/java/com/orange/Iot3MobilityExample.java @@ -8,13 +8,16 @@ import com.orange.iot3mobility.TrueTime; import com.orange.iot3mobility.Utils; import com.orange.iot3mobility.its.EtsiUtils; +import com.orange.iot3mobility.messages.cam.core.CamCodec; +import com.orange.iot3mobility.messages.cam.core.CamVersion; +import com.orange.iot3mobility.messages.cam.v113.model.CamEnvelope113; +import com.orange.iot3mobility.messages.cam.v230.model.CamEnvelope230; import com.orange.iot3mobility.roadobjects.HazardType; import com.orange.iot3mobility.its.StationType; import com.orange.iot3mobility.its.json.JsonValue; import com.orange.iot3mobility.its.json.Position; import com.orange.iot3mobility.its.json.PositionConfidence; import com.orange.iot3mobility.its.json.PositionConfidenceEllipse; -import com.orange.iot3mobility.its.json.cam.CAM; import com.orange.iot3mobility.its.json.cpm.*; import com.orange.iot3mobility.its.json.denm.DENM; import com.orange.iot3mobility.managers.IoT3RoadHazardCallback; @@ -27,6 +30,7 @@ import com.orange.iot3mobility.roadobjects.SensorObject; import com.orange.lwm2m.model.CustomLwm2mConnectivityStatisticsExample; +import java.io.IOException; import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -138,12 +142,6 @@ public void newRoadUser(RoadUser roadUser) { System.out.println("New Road User: " + roadUser.getUuid()); LatLng position = roadUser.getPosition(); System.out.println("Road User position: " + position); - // the CAM on which this object is based can still be accessed - CAM originalCam = roadUser.getCam(); - double latitude = originalCam.getBasicContainer().getPosition().getLatitudeDegree(); - double longitude = originalCam.getBasicContainer().getPosition().getLongitudeDegree(); - LatLng camPosition = new LatLng(latitude, longitude); - System.out.println("CAM position: " + camPosition); } @Override @@ -159,9 +157,15 @@ public void roadUserExpired(RoadUser roadUser) { } @Override - public void camArrived(CAM cam) { + public void camArrived(CamCodec.CamFrame camFrame) { // if you want to directly process the raw CAM messages - System.out.println("CAM received: " + cam.getJsonCAM()); + if(camFrame.version().equals(CamVersion.V1_1_3)) { + CamEnvelope113 camEnvelope113 = (CamEnvelope113) camFrame.envelope(); + System.out.println("Raw CAM v1.1.3: " + camEnvelope113); + } else if(camFrame.version().equals(CamVersion.V2_3_0)) { + CamEnvelope230 camEnvelope230 = (CamEnvelope230) camFrame.envelope(); + System.out.println("Raw CAM v2.3.0: " + camEnvelope230); + } } }); @@ -235,7 +239,11 @@ private static synchronized void startSendingMessages() { private static void sendTestCam() { LatLng position = new LatLng(48.625218, 2.243448); // center point of UTAC TEQMO - ioT3Mobility.sendPosition(StationType.PASSENGER_CAR, position, 0, 0, 0, 0, 0); + try { + ioT3Mobility.sendPosition(StationType.PASSENGER_CAR, position, 0, 0, 0, 0, 0, CamVersion.V1_1_3); + } catch (IOException e) { + throw new RuntimeException(e); + } } private static void sendTestDenm() { diff --git a/java/iot3/mobility/build.gradle b/java/iot3/mobility/build.gradle index e835573e3..bbdb3cd39 100644 --- a/java/iot3/mobility/build.gradle +++ b/java/iot3/mobility/build.gradle @@ -22,6 +22,7 @@ repositories { dependencies { // Internal dependencies, not exposed to consumers on their own compile classpath. implementation 'org.json:json:20240303' + implementation 'com.fasterxml.jackson.core:jackson-core:2.21.1' implementation 'commons-net:commons-net:3.9.0' implementation project(':core') diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/IoT3Mobility.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/IoT3Mobility.java index a074f5bac..0026af59a 100644 --- a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/IoT3Mobility.java +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/IoT3Mobility.java @@ -15,13 +15,20 @@ import com.orange.iot3core.bootstrap.BootstrapConfig; import com.orange.iot3core.clients.lwm2m.model.*; import com.orange.iot3mobility.its.EtsiUtils; +import com.orange.iot3mobility.messages.EtsiConverter; +import com.orange.iot3mobility.messages.cam.CamHelper; +import com.orange.iot3mobility.messages.cam.core.CamVersion; +import com.orange.iot3mobility.messages.cam.v113.model.*; +import com.orange.iot3mobility.messages.cam.v113.model.HighFrequencyContainer; +import com.orange.iot3mobility.messages.cam.v230.model.CamEnvelope230; +import com.orange.iot3mobility.messages.cam.v230.model.CamStructuredData; +import com.orange.iot3mobility.messages.cam.v230.model.MessageFormat; +import com.orange.iot3mobility.messages.cam.v230.model.basiccontainer.Altitude; +import com.orange.iot3mobility.messages.cam.v230.model.highfrequencycontainer.*; import com.orange.iot3mobility.roadobjects.HazardType; import com.orange.iot3mobility.its.StationType; import com.orange.iot3mobility.its.json.JsonValue; import com.orange.iot3mobility.its.json.Position; -import com.orange.iot3mobility.its.json.cam.BasicContainer; -import com.orange.iot3mobility.its.json.cam.CAM; -import com.orange.iot3mobility.its.json.cam.HighFrequencyContainer; import com.orange.iot3mobility.its.json.cpm.CPM; import com.orange.iot3mobility.its.json.denm.*; import com.orange.iot3mobility.managers.*; @@ -31,10 +38,12 @@ import com.orange.iot3mobility.roadobjects.RoadSensor; import com.orange.iot3mobility.roadobjects.RoadUser; +import java.io.IOException; import java.net.URI; import java.util.List; import java.util.Arrays; import java.util.ArrayList; +import java.util.logging.Logger; /** * Mobility SDK based on the Orange IoT3.0 platform. @@ -47,8 +56,11 @@ */ public class IoT3Mobility { + private static final Logger LOGGER = Logger.getLogger(IoT3Mobility.class.getName()); + private final IoT3Core ioT3Core; private final RoIManager roIManager; + private final CamHelper camHelper = new CamHelper(); private final String uuid; private final String context; @@ -306,14 +318,13 @@ public void setRawMessageCallback(IoT3RawMessageCallback ioT3RawMessageCallback) private void processMessage(String topic, String message) { if(ioT3RawMessageCallback != null) ioT3RawMessageCallback.messageArrived(message); - if(topic.contains("/cam/")) RoadUserManager.processCam(message); + if(topic.contains("/cam/")) RoadUserManager.processCam(message, camHelper); else if(topic.contains("/cpm/")) RoadSensorManager.processCpm(message); else if(topic.contains("/denm/")) RoadHazardManager.processDenm(message); } /** * Share your position and dynamic parameters with other road users. - * Builds a CAM and uses {@link #sendCam(CAM)} * * @param stationType your road user type * @param position your position (latitude, longitude in degrees) @@ -322,60 +333,106 @@ private void processMessage(String topic, String message) { * @param speed your speed in meters per second [0 - 163] * @param acceleration your longitudinal acceleration in m/s² [-16 - 16] * @param yawRate your rotational acceleration in deg/s² [-327 - 327] + * @param camVersion the CAM version you want to emit */ public void sendPosition(StationType stationType, LatLng position, float altitude, - float heading, float speed, float acceleration, float yawRate) { - // check for out of scope values before building the CAM - altitude = Utils.clamp(altitude, -1000, 8000); - heading = Utils.normalizeAngle(heading); - speed = Utils.clamp(speed, 0, 163); - acceleration = Utils.clamp(acceleration, -16, 16); - yawRate = Utils.clamp(yawRate, -327, 327); - - // build the CAM - CAM cam = new CAM.CAMBuilder() - .header( - JsonValue.Origin.SELF.value(), - JsonValue.Version.CURRENT_CAM.value(), - uuid, - TrueTime.getAccurateTime()) - .pduHeader( - 2, - stationId, - (int) (TrueTime.getAccurateETSITime() % 65536)) - .basicContainer( - new BasicContainer( - stationType.getId(), - new Position( - (long) (position.getLatitude() * EtsiUtils.ETSI_COORDINATES_FACTOR), - (long) (position.getLongitude() * EtsiUtils.ETSI_COORDINATES_FACTOR), - (int) (altitude * 100)))) - .highFreqContainer( - new HighFrequencyContainer.HighFrequencyContainerBuilder() - .heading((int) (heading * 10)) - .speed((int) (speed * 100)) - .longitudinalAcceleration((int) (acceleration * 10)) - .yawRate((int) (yawRate * 10)) - .build()) - .build(); - - sendCam(cam); + float heading, float speed, float acceleration, float yawRate, CamVersion camVersion) throws IOException { + if(camVersion == CamVersion.V1_1_3) { + CamEnvelope113 camEnvelope113 = CamEnvelope113.builder() + .origin(Origin.SELF.value) + .sourceUuid(uuid) + .timestamp(TrueTime.getAccurateTime()) + .message(CamMessage113.builder() + .protocolVersion(2) + .stationId(stationId) + .generationDeltaTime(EtsiConverter.generationDeltaTimeEtsi(TrueTime.getAccurateETSITime())) + .basicContainer(BasicContainer.builder() + .stationType(stationType.getId()) + .referencePosition(new ReferencePosition( + EtsiConverter.latitudeEtsi(position.getLatitude()), + EtsiConverter.longitudeEtsi(position.getLongitude()), + EtsiConverter.altitudeEtsi(altitude))) + .build()) + .highFrequencyContainer(HighFrequencyContainer.builder() + .heading(EtsiConverter.headingEtsiFromDegrees(heading)) + .speed(EtsiConverter.speedEtsiFromMetersPerSecond(speed)) + .longitudinalAcceleration(EtsiConverter.accelerationEtsi(acceleration)) + .yawRate(EtsiConverter.yawRateEtsiFromDegreesPerSecond(yawRate)) + .build()) + .build()) + .build(); + sendCam(camEnvelope113); + } else if(camVersion == CamVersion.V2_3_0) { + CamEnvelope230 camEnvelope230 = CamEnvelope230.builder() + .messageFormat(MessageFormat.JSON_RAW.value) + .sourceUuid(uuid) + .timestamp(TrueTime.getAccurateTime()) + .message(CamStructuredData.builder() + .protocolVersion(2) + .stationId(stationId) + .generationDeltaTime(EtsiConverter.generationDeltaTimeEtsi(TrueTime.getAccurateETSITime())) + .basicContainer(com.orange.iot3mobility.messages.cam.v230.model.basiccontainer.BasicContainer.builder() + .stationType(stationType.getId()) + .referencePosition(com.orange.iot3mobility.messages.cam.v230.model.basiccontainer.ReferencePosition.builder() + .latitudeLongitude(EtsiConverter.latitudeEtsi(position.latitude), + EtsiConverter.longitudeEtsi(position.longitude)) + .positionConfidenceEllipse(new com.orange.iot3mobility.messages.cam.v230.model.basiccontainer.PositionConfidenceEllipse(10, 10, + EtsiConverter.headingEtsiFromDegrees(heading))) + .altitude(new Altitude(EtsiConverter.altitudeEtsi(altitude), 15)) + .build()) + .build()) + .highFrequencyContainer(BasicVehicleContainerHighFrequency.builder() + .heading(new Heading(EtsiConverter.headingEtsiFromDegrees(heading), 127)) + .speed(new Speed(EtsiConverter.speedEtsiFromMetersPerSecond(speed), 127)) + .longitudinalAcceleration(new AccelerationComponent( + EtsiConverter.accelerationEtsi(acceleration), 102)) + .yawRate(new YawRate(EtsiConverter.yawRateEtsiFromDegreesPerSecond(yawRate), 8)) + .driveDirection(2) + .vehicleLength(new VehicleLength(1023, 4)) + .vehicleWidth(62) + .curvature(new Curvature(1023, 7)) + .curvatureCalculationMode(2) + .build()) + .build()) + .build(); + sendCam(camEnvelope230); + } } /** - * Send a CAM - Cooperative Awareness Message + * Send a CAM - Cooperative Awareness Message - v1.1.3 * - * @param cam the CAM representing your current state + * @param camV113 the CAM representing your current state */ - public void sendCam(CAM cam) { + public void sendCam(CamEnvelope113 camV113) throws IOException { // build the topic - String quadkey = QuadTileHelper.latLngToQuadKey(cam.getBasicContainer().getPosition().getLatitudeDegree(), - cam.getBasicContainer().getPosition().getLongitudeDegree(), 22); + double latitude = EtsiConverter.latitudeDegrees(camV113.message().basicContainer().referencePosition().latitude()); + double longitude = EtsiConverter.latitudeDegrees(camV113.message().basicContainer().referencePosition().longitude()); + String quadkey = QuadTileHelper.latLngToQuadKey(latitude, longitude, 22); String geoExtension = QuadTileHelper.quadKeyToQuadTopic(quadkey); String topic = context + "/inQueue/v2x/cam/" + uuid + geoExtension; // send the message only if the client is connected - if(isConnected()) ioT3Core.mqttPublish(topic, cam.getJsonCAM().toString(), false, 0, 1); + if(isConnected()) ioT3Core.mqttPublish(topic, camHelper.toJson(camV113), false, 0, 1); + } + + /** + * Send a CAM - Cooperative Awareness Message - v2.3.0 + * + * @param camV230 the CAM representing your current state - JSON version only + */ + public void sendCam(CamEnvelope230 camV230) throws IOException { + if(camV230.message() instanceof CamStructuredData cam) { + // build the topic + double latitude = EtsiConverter.latitudeDegrees(cam.basicContainer().referencePosition().latitude()); + double longitude = EtsiConverter.latitudeDegrees(cam.basicContainer().referencePosition().longitude()); + String quadkey = QuadTileHelper.latLngToQuadKey(latitude, longitude, 22); + String geoExtension = QuadTileHelper.quadKeyToQuadTopic(quadkey); + String topic = context + "/inQueue/v2x/cam/" + uuid + geoExtension; + + // send the message only if the client is connected + if(isConnected()) ioT3Core.mqttPublish(topic, camHelper.toJson(camV230), false, 0, 1); + } } /** diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/cam/BasicContainer.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/cam/BasicContainer.java deleted file mode 100644 index a3cffe804..000000000 --- a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/cam/BasicContainer.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - Copyright 2016-2024 Orange - - This software is distributed under the MIT license, see LICENSE.txt file for more details. - - @author Mathieu LEFEBVRE - */ -package com.orange.iot3mobility.its.json.cam; - -import com.orange.iot3mobility.its.json.JsonKey; -import com.orange.iot3mobility.its.json.JsonUtil; -import com.orange.iot3mobility.its.json.Position; -import com.orange.iot3mobility.its.json.PositionConfidence; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * CAM BasicContainer. - *

- * Provides the type and position of a vehicle. - */ -public class BasicContainer { - - private static final Logger LOGGER = Logger.getLogger(BasicContainer.class.getName()); - - private final JSONObject jsonBasicContainer = new JSONObject(); - - /** - * Type of the emitting ITS-station. - *

- * unknown(0), pedestrian(1), cyclist(2), moped(3), motorcycle(4), passengerCar(5), bus(6), - * lightTruck(7), heavyTruck(8), trailer(9), specialVehicles(10), tram(11), roadSideUnit(15). - */ - private final int stationType; - - /** - * Position measured at the reference point of the originating ITS-S. - * The measurement time shall correspond to generationDeltaTime. - *

- * If the station type of the originating ITS-S is set to one out of the values 3 to 11 - * the reference point shall be the ground position of the centre of the front side of - * the bounding box of the vehicle. - *

- * See {@link Position} - */ - private final Position position; - - /** - * Confidence of the originating ITS-S position. - *

- * {@link PositionConfidence} - */ - private final PositionConfidence positionConfidence; - - /** - * Build a CAM BasicContainer. - *

- * These fields are mandatory. - * - * @param stationType {@link #stationType} - * @param position {@link #position} - */ - public BasicContainer( - final int stationType, - final Position position) - { - this(stationType, position, null); - } - - /** - * Build a CAM BasicContainer. - *

- * These fields are mandatory, except positionConfidence - use {@link #BasicContainer(int, Position)} if not known. - * - * @param stationType {@link #stationType} - * @param position {@link #position} - * @param positionConfidence {@link #positionConfidence} - */ - public BasicContainer( - final int stationType, - final Position position, - final PositionConfidence positionConfidence) - { - if(stationType > 255 || stationType < 0) { - throw new IllegalArgumentException("CAM BasicContainer StationType should be in the range of [0 - 255]." - + " Value: " + stationType); - } - this.stationType = stationType; - if(position == null) { - throw new IllegalArgumentException("CAM BasicContainer Position missing."); - } - this.position = position; - this.positionConfidence = positionConfidence; - - createJson(); - } - - private void createJson() { - try { - jsonBasicContainer.put(JsonKey.BasicContainer.STATION_TYPE.key(), stationType); - jsonBasicContainer.put(JsonKey.BasicContainer.POSITION.key(), position.getJson()); - if(positionConfidence != null) - jsonBasicContainer.put(JsonKey.Position.CONFIDENCE.key(), positionConfidence.getJson()); - } catch (JSONException e) { - LOGGER.log(Level.WARNING, "BasicContainer JSON build error", "Error: " + e); - } - } - - public JSONObject getJsonBasicContainer() { - return jsonBasicContainer; - } - - public int getStationType() { - return stationType; - } - - public Position getPosition() { - return position; - } - - public PositionConfidence getPositionConfidence() { - return positionConfidence; - } - - public static BasicContainer jsonParser(JSONObject jsonBasicContainer) { - if(JsonUtil.isNullOrEmpty(jsonBasicContainer)) return null; - try { - int stationType = jsonBasicContainer.getInt(JsonKey.BasicContainer.STATION_TYPE.key()); - JSONObject jsonPosition = jsonBasicContainer.getJSONObject(JsonKey.BasicContainer.POSITION.key()); - Position position = Position.jsonParser(jsonPosition); - JSONObject jsonPositionConfidence = jsonBasicContainer.optJSONObject(JsonKey.Position.CONFIDENCE.key()); - PositionConfidence positionConfidence = PositionConfidence.jsonParser(jsonPositionConfidence); - - return new BasicContainer(stationType, position, positionConfidence); - } catch (JSONException e) { - LOGGER.log(Level.WARNING, "BasicContainer JSON parsing error", "Error: " + e); - } - return null; - } - -} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/cam/CAM.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/cam/CAM.java deleted file mode 100644 index 4e8a16d3a..000000000 --- a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/cam/CAM.java +++ /dev/null @@ -1,316 +0,0 @@ -/* - Copyright 2016-2024 Orange - - This software is distributed under the MIT license, see LICENSE.txt file for more details. - - @author Mathieu LEFEBVRE - */ -package com.orange.iot3mobility.its.json.cam; - -import com.orange.iot3mobility.its.json.JsonKey; -import com.orange.iot3mobility.its.json.JsonUtil; -import com.orange.iot3mobility.its.json.JsonValue; -import com.orange.iot3mobility.its.json.MessageBase; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * Cooperative Awareness Message. - *

- * Cooperative Awareness Messages (CAMs) are messages exchanged in the ITS network between ITS-Ss to create and - * maintain awareness of each other and to support cooperative performance of vehicles using the road network. - *

- * A CAM contains status and attribute information of the originating ITS-S. - */ -public class CAM extends MessageBase { - - private static final Logger LOGGER = Logger.getLogger(CAM.class.getName()); - - private final JSONObject jsonCAM = new JSONObject(); - - /** - * Version of the ITS message and/or communication protocol. - */ - private final int protocolVersion; - - /** - * ITS-station identifier - */ - private final long stationId; - - /** - * Time of the reference position in the CAM, considered as time of the CAM generation. - *

- * TimestampIts mod 65 536. TimestampIts represents an integer value in milliseconds since - * 2004-01-01T00:00:00:000Z. - *

- * oneMilliSec(1). - */ - private final int generationDeltaTime; - - private final BasicContainer basicContainer; - private final HighFrequencyContainer highFrequencyContainer; - private final LowFrequencyContainer lowFrequencyContainer; - - /** - * Build a Cooperative Awareness Message. - */ - private CAM( - final String type, - final String origin, - final String version, - final String sourceUuid, - final long timestamp, - final int protocolVersion, - final long stationId, - final int generationDeltaTime, - final BasicContainer basicContainer, - final HighFrequencyContainer highFrequencyContainer, - final LowFrequencyContainer lowFrequencyContainer) - { - super(type, origin, version, sourceUuid, timestamp); - if(protocolVersion > 255 || protocolVersion < 0) { - throw new IllegalArgumentException("CAM ProtocolVersion should be in the range of [0 - 255]." - + " Value: " + protocolVersion); - } - this.protocolVersion = protocolVersion; - if(stationId > 4294967295L || stationId < 0) { - throw new IllegalArgumentException("CAM StationID should be in the range of [0 - 4294967295]." - + " Value: " + stationId); - } - this.stationId = stationId; - if(generationDeltaTime > 65535 || generationDeltaTime < 0) { - throw new IllegalArgumentException("CAM GenerationDeltaTime should be in the range of [0 - 65535]." - + " Value: " + generationDeltaTime); - } - this.generationDeltaTime = generationDeltaTime; - if(basicContainer == null) { - throw new IllegalArgumentException("CAM BasicContainer missing."); - } - this.basicContainer = basicContainer; - if(highFrequencyContainer == null) { - throw new IllegalArgumentException("CAM HighFrequencyContainer missing."); - } - this.highFrequencyContainer = highFrequencyContainer; - this.lowFrequencyContainer = lowFrequencyContainer; - - createJson(); - } - - private void createJson() { - try { - JSONObject message = new JSONObject(); - message.put(JsonKey.Cam.PROTOCOL_VERSION.key(), protocolVersion); - message.put(JsonKey.Cam.STATION_ID.key(), stationId); - message.put(JsonKey.Cam.GENERATION_DELTA_TIME.key(), generationDeltaTime); - message.put(JsonKey.Cam.BASIC_CONTAINER.key(), basicContainer.getJsonBasicContainer()); - message.put(JsonKey.Cam.HIGH_FREQ_CONTAINER.key(), highFrequencyContainer.getJsonHighFrequencyContainer()); - if(lowFrequencyContainer != null) - message.put(JsonKey.Cam.LOW_FREQ_CONTAINER.key(), lowFrequencyContainer.getJsonLowFrequencyContainer()); - - jsonCAM.put(JsonKey.Header.TYPE.key(), getType()); - jsonCAM.put(JsonKey.Header.ORIGIN.key(), getOrigin()); - jsonCAM.put(JsonKey.Header.VERSION.key(), getVersion()); - jsonCAM.put(JsonKey.Header.SOURCE_UUID.key(), getSourceUuid()); - jsonCAM.put(JsonKey.Header.TIMESTAMP.key(), getTimestamp()); - jsonCAM.put(JsonKey.Header.MESSAGE.key(), message); - } catch (JSONException e) { - LOGGER.log(Level.WARNING, "CAM JSON build error", "Error: " + e); - } - } - - public JSONObject getJsonCAM() { - return jsonCAM; - } - - public int getProtocolVersion() { - return protocolVersion; - } - - public long getStationId() { - return stationId; - } - - public int getGenerationDeltaTime() { - return generationDeltaTime; - } - - public BasicContainer getBasicContainer() { - return basicContainer; - } - - public HighFrequencyContainer getHighFrequencyContainer() { - return highFrequencyContainer; - } - - public LowFrequencyContainer getLowFrequencyContainer() { - return lowFrequencyContainer; - } - - public static class CAMBuilder { - private final String type; - private String origin; - private String version; - private String sourceUuid; - private long timestamp; - private int protocolVersion; - private long stationId; - private int generationDeltaTime; - private BasicContainer basicContainer; - private HighFrequencyContainer highFrequencyContainer; - private LowFrequencyContainer lowFrequencyContainer; - - /** - * Start building a CAM. - */ - public CAMBuilder() { - this.type = JsonValue.Type.CAM.value(); - } - - /** - * Sets the JSON header of the CAM. - *

- * These fields are mandatory. - * - * @param origin The entity responsible for emitting the message. - * @param version JSON message format version. - * @param sourceUuid The identifier of the entity responsible for emitting the message. - * @param timestamp The timestamp when the message was generated since Unix Epoch (1970/01/01), in milliseconds. - */ - public CAMBuilder header(String origin, - String version, - String sourceUuid, - long timestamp) { - this.origin = origin; - this.version = version; - this.sourceUuid = sourceUuid; - this.timestamp = timestamp; - return this; - } - - /** - * Sets the PDU header of the CAM. - *

- * These fields are mandatory. - * - * @param protocolVersion {@link CAM#protocolVersion} - * @param stationId {@link CAM#stationId} - * @param generationDeltaTime {@link CAM#generationDeltaTime} - */ - public CAMBuilder pduHeader(int protocolVersion, - long stationId, - int generationDeltaTime) { - this.protocolVersion = protocolVersion; - this.stationId = stationId; - this.generationDeltaTime = generationDeltaTime; - return this; - } - - /** - * Contains the type and reference position of the emitting vehicle. - * - * @param basicContainer {@link BasicContainer} - */ - public CAMBuilder basicContainer(BasicContainer basicContainer) { - this.basicContainer = basicContainer; - return this; - } - - /** - * Contains detailed information about the emitting vehicle. - * - * @param highFrequencyContainer {@link HighFrequencyContainer} - */ - public CAMBuilder highFreqContainer(HighFrequencyContainer highFrequencyContainer) { - this.highFrequencyContainer = highFrequencyContainer; - return this; - } - - /** - * Optional, contains additional information about the emitting vehicle. - * - * @param lowFrequencyContainer {@link LowFrequencyContainer} - */ - public CAMBuilder lowFreqContainer(LowFrequencyContainer lowFrequencyContainer) { - this.lowFrequencyContainer = lowFrequencyContainer; - return this; - } - - /** - * Build the CAM. - *

- * Call after setting all the mandatory fields. - * - * @return {@link #CAM} - */ - public CAM build() { - return new CAM(type, - origin, - version, - sourceUuid, - timestamp, - protocolVersion, - stationId, - generationDeltaTime, - basicContainer, - highFrequencyContainer, - lowFrequencyContainer); - } - } - - /** - * Parse a CAM in JSON format. - * - * @param jsonCAM The CAM in JSON format - * @return {@link CAM} - */ - public static CAM jsonParser(JSONObject jsonCAM) { - if(JsonUtil.isNullOrEmpty(jsonCAM)) return null; - try { - String type = jsonCAM.getString(JsonKey.Header.TYPE.key()); - - if(type.equals(JsonValue.Type.CAM.value())){ - JSONObject message = jsonCAM.getJSONObject(JsonKey.Header.MESSAGE.key()); - - String origin = jsonCAM.getString(JsonKey.Header.ORIGIN.key()); - String version = jsonCAM.getString(JsonKey.Header.VERSION.key()); - String sourceUuid = jsonCAM.getString(JsonKey.Header.SOURCE_UUID.key()); - long timestamp = jsonCAM.getLong(JsonKey.Header.TIMESTAMP.key()); - - int protocolVersion = message.getInt(JsonKey.Cam.PROTOCOL_VERSION.key()); - long stationId = message.getLong(JsonKey.Cam.STATION_ID.key()); - int generationDeltaTime = message.getInt(JsonKey.Cam.GENERATION_DELTA_TIME.key()); - - JSONObject jsonBasicContainer = message.getJSONObject(JsonKey.Cam.BASIC_CONTAINER.key()); - BasicContainer basicContainer = BasicContainer.jsonParser(jsonBasicContainer); - - JSONObject jsonHighFreqContainer = message.getJSONObject(JsonKey.Cam.HIGH_FREQ_CONTAINER.key()); - HighFrequencyContainer highFrequencyContainer = HighFrequencyContainer.jsonParser(jsonHighFreqContainer); - - JSONObject jsonLowFreqContainer = message.optJSONObject(JsonKey.Cam.LOW_FREQ_CONTAINER.key()); - LowFrequencyContainer lowFrequencyContainer = LowFrequencyContainer.jsonParser(jsonLowFreqContainer); - - return new CAMBuilder() - .header(origin, - version, - sourceUuid, - timestamp) - .pduHeader(protocolVersion, - stationId, - generationDeltaTime) - .basicContainer(basicContainer) - .highFreqContainer(highFrequencyContainer) - .lowFreqContainer(lowFrequencyContainer) - .build(); - } - } catch (JSONException | IllegalArgumentException e) { - LOGGER.log(Level.WARNING, "CAM JSON parsing error", "Error: " + e); - } - return null; - } - -} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/cam/HighFrequencyContainer.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/cam/HighFrequencyContainer.java deleted file mode 100644 index 5b567f7e3..000000000 --- a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/cam/HighFrequencyContainer.java +++ /dev/null @@ -1,834 +0,0 @@ -/* - Copyright 2016-2024 Orange - - This software is distributed under the MIT license, see LICENSE.txt file for more details. - - @author Mathieu LEFEBVRE - */ -package com.orange.iot3mobility.its.json.cam; - -import static com.orange.iot3mobility.its.json.JsonUtil.UNKNOWN; - -import com.orange.iot3mobility.its.EtsiUtils; -import com.orange.iot3mobility.its.json.JsonKey; - -import com.orange.iot3mobility.its.json.JsonUtil; -import com.orange.iot3mobility.its.json.cpm.PerceivedObject; -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * CAM HighFrequencyContainer. - *

- * Provides detailed information about a vehicle's status. - */ -public class HighFrequencyContainer { - - private static final Logger LOGGER = Logger.getLogger(HighFrequencyContainer.class.getName()); - - private final JSONObject jsonHighFrequencyContainer = new JSONObject(); - - /** - * Unit: 0.1 degree. Heading of the vehicle movement of the originating ITS-S - * with regards to the true north. - *

- * wgs84North(0), wgs84East(900), wgs84South(1800), wgs84West(2700), unavailable(3601) - */ - private final int heading; - - /** - * Unit 0.01 m/s. Driving speed of the originating ITS-S. - *

- * standstill(0), oneCentimeterPerSec(1), unavailable(16383) - */ - private final int speed; - - /** - * Vehicle drive direction (forward or backward) of the originating ITS-S. - *

- * forward (0), backward (1), unavailable (2) - */ - private final int driveDirection; - - /** - * Unit 0.1 m. Length of the ITS-S. - *

- * tenCentimeters(1), outOfRange(1022), unavailable(1023) - */ - private final int vehicleLength; - - /** - * Unit 0.1 m. Width of the ITS-S. - *

- * tenCentimeters(1), outOfRange(61), unavailable(62) - */ - private final int vehicleWidth; - - /** - * unit: 0.1 m/s2. Vehicle longitudinal acceleration of the originating ITS-S in the centre of the mass - * of the empty vehicle. - *

- * pointOneMeterPerSecSquaredForward(1), pointOneMeterPerSecSquaredBackward(-1), unavailable(161) - */ - private final int longitudinalAcceleration; - - /** - * Unit: 0.1 m/s2. Vehicle lateral acceleration of the originating ITS-S in the centre of the mass of - * the empty vehicle. - *

- * pointOneMeterPerSecSquaredToRight(-1), pointOneMeterPerSecSquaredToLeft(1), unavailable(161) - */ - private final int lateralAcceleration; - - /** - * Unit: 0.1 m/s2. Vertical Acceleration of the originating ITS-S in the centre of the mass of the - * empty vehicle. - *

- * pointOneMeterPerSecSquaredUp(1), pointOneMeterPerSecSquaredDown(-1), unavailable(161) - */ - private final int verticalAcceleration; - - /** - * Unit: 0.01 degree/s. Vehicle rotation around the centre of mass of - * the empty vehicle. The leading sign denotes the direction of rotation. - * The value is negative if the motion is clockwise when viewing from the - * top. - *

- * straight(0), degSec-000-01ToRight(-1), degSec-000-01ToLeft(1), unavailable(32767) - */ - private final int yawRate; - - /** - * The lanePosition of the referencePosition of a vehicle, counted from the - * outside border of the road, in the direction of the traffic flow. - *

- * offTheRoad(-1), innerHardShoulder(0), innermostDrivingLane(1), secondLaneFromInside(2), outterHardShoulder(14) - */ - private final int lanePosition; - - /** - * Inverse of the vehicle current curve radius and the turning direction of the curve with regards to the driving - * direction of the vehicle. - *

- * straight(0), unavailable(1023) - */ - private final int curvature; - - /** - * It describes whether the yaw rate is used to calculate the curvature. - *

- * yawRateUsed(0), yawRateNotUsed(1), unavailable(2) - */ - private final int curvatureCalculationMode; - - /** - * Current controlling mechanism for longitudinal movement of the vehicle. Represented as a bit string: - *

- * brakePedalEngaged (0), gasPedalEngaged (1), emergencyBrakeEngaged (2), collisionWarningEngaged(3), - * accEngaged(4), cruiseControlEngaged(5), speedLimiterEngaged(6) - */ - private final String accelerationControl; - - /** - * equalOrWithinZeroPointOneDegree (1), equalOrWithinOneDegree (10), outOfRange(126), unavailable(127) - */ - private final int headingConfidence; - - /** - * equalOrWithinOneCentimeterPerSec(1), equalOrWithinOneMeterPerSec(100), outOfRange(126), unavailable(127) - */ - private final int speedConfidence; - - /** - * noTrailerPresent(0), trailerPresentWithKnownLength(1), trailerPresentWithUnknownLength(2), - * trailerPresenceIsUnknown(3), unavailable(4) - */ - private final int vehicleLengthConfidence; - - /** - * pointOneMeterPerSecSquared(1), outOfRange(101), unavailable(102) - */ - private final int longitudinalAccelerationConfidence; - - /** - * Unit: 0.1 m/s2. - *

- * pointOneMeterPerSecSquared(1), outOfRange(101), unavailable(102) - */ - private final int lateralAccelerationConfidence; - - /** - * Unit: 0.1 m/s2. - *

- * pointOneMeterPerSecSquared(1), outOfRange(101), unavailable(102) - */ - private final int verticalAccelerationConfidence; - - /** - * degSec-000-01 (0), degSec-000-05 (1), degSec-000-10 (2), degSec-001-00 (3), degSec-005-00 (4), degSec-010-00 (5), - * degSec-100-00 (6), outOfRange (7), unavailable (8) - */ - private final int yawRateConfidence; - - /** - * onePerMeter-0-00002 (0), onePerMeter-0-0001 (1), onePerMeter-0-0005 (2), onePerMeter-0-002 (3), - * onePerMeter-0-01 (4), onePerMeter-0-1 (5), outOfRange (6), unavailable (7) - */ - private final int curvatureConfidence; - - /** - * Build a CAM HighFrequencyContainer. - *

- * Provides detailed information about vehicle's status. - */ - private HighFrequencyContainer( - final int heading, - final int speed, - final int driveDirection, - final int vehicleLength, - final int vehicleWidth, - final int longitudinalAcceleration, - final int lateralAcceleration, - final int verticalAcceleration, - final int yawRate, - final int lanePosition, - final int curvature, - final int curvatureCalculationMode, - final String accelerationControl, - final int headingConfidence, - final int speedConfidence, - final int vehicleLengthConfidence, - final int longitudinalAccelerationConfidence, - final int lateralAccelerationConfidence, - final int verticalAccelerationConfidence, - final int yawRateConfidence, - final int curvatureConfidence) - { - if(heading != UNKNOWN && (heading > 3601 || heading < 0)) { - throw new IllegalArgumentException("CAM HighFrequencyContainer Heading should be in the range of [0 - 3601]." - + " Value: " + heading); - } - this.heading = heading; - if(speed != UNKNOWN && (speed > 16383 || speed < 0)) { - throw new IllegalArgumentException("CAM HighFrequencyContainer Speed should be in the range of [0 - 16383]." - + " Value: " + speed); - } - this.speed = speed; - if(driveDirection != UNKNOWN && (driveDirection > 2 || driveDirection < 0)) { - throw new IllegalArgumentException("CAM HighFrequencyContainer DriveDirection should be in the range of [0 - 2]." - + " Value: " + driveDirection); - } - this.driveDirection = driveDirection; - if(vehicleLength != UNKNOWN && (vehicleLength > 1023 || vehicleLength < 1)) { - throw new IllegalArgumentException("CAM HighFrequencyContainer VehicleLength should be in the range of [1 - 1023]." - + " Value: " + vehicleLength); - } - this.vehicleLength = vehicleLength; - if(vehicleWidth != UNKNOWN && (vehicleWidth > 62 || vehicleWidth < 1)) { - throw new IllegalArgumentException("CAM HighFrequencyContainer VehicleWidth should be in the range of [1 - 62]." - + " Value: " + vehicleWidth); - } - this.vehicleWidth = vehicleWidth; - if(longitudinalAcceleration != UNKNOWN && (longitudinalAcceleration > 161 || longitudinalAcceleration < -160)) { - throw new IllegalArgumentException("CAM HighFrequencyContainer LongitudinalAcceleration should be in the range of [-160 - 161]." - + " Value: " + longitudinalAcceleration); - } - this.longitudinalAcceleration = longitudinalAcceleration; - if(lateralAcceleration != UNKNOWN && (lateralAcceleration > 161 || lateralAcceleration < -160)) { - throw new IllegalArgumentException("CAM HighFrequencyContainer LateralAcceleration should be in the range of [-160 - 161]." - + " Value: " + lateralAcceleration); - } - this.lateralAcceleration = lateralAcceleration; - if(verticalAcceleration != UNKNOWN && (verticalAcceleration > 161 || verticalAcceleration < -160)) { - throw new IllegalArgumentException("CAM HighFrequencyContainer VerticalAcceleration should be in the range of [-160 - 161]." - + " Value: " + verticalAcceleration); - } - this.verticalAcceleration = verticalAcceleration; - if(yawRate != UNKNOWN && (yawRate > 32767 || yawRate < -32766)) { - throw new IllegalArgumentException("CAM HighFrequencyContainer YawRate should be in the range of [-32766 - 32767]." - + " Value: " + yawRate); - } - this.yawRate = yawRate; - if(lanePosition != UNKNOWN && (lanePosition > 14 || lanePosition < -1)) { - throw new IllegalArgumentException("CAM HighFrequencyContainer LanePosition should be in the range of [-1 - 14]." - + " Value: " + lanePosition); - } - this.lanePosition = lanePosition; - if(curvature != UNKNOWN && (curvature > 1023 || curvature < -1023)) { - throw new IllegalArgumentException("CAM HighFrequencyContainer Curvature should be in the range of [-1023 - 1023]." - + " Value: " + curvature); - } - this.curvature = curvature; - if(curvatureCalculationMode != UNKNOWN && (curvatureCalculationMode > 2 || curvatureCalculationMode < 0)) { - throw new IllegalArgumentException("CAM HighFrequencyContainer CurvatureCalculationMode should be in the range of [0 - 2]." - + " Value: " + curvatureCalculationMode); - } - this.curvatureCalculationMode = curvatureCalculationMode; - this.accelerationControl = accelerationControl; - if(headingConfidence != UNKNOWN && (headingConfidence > 127 || headingConfidence < 1)) { - throw new IllegalArgumentException("CAM HighFrequencyContainer HeadingConfidence should be in the range of [1 - 127]." - + " Value: " + headingConfidence); - } - this.headingConfidence = headingConfidence; - if(speedConfidence != UNKNOWN && (speedConfidence > 127 || speedConfidence < 1)) { - throw new IllegalArgumentException("CAM HighFrequencyContainer SpeedConfidence should be in the range of [1 - 127]." - + " Value: " + speedConfidence); - } - this.speedConfidence = speedConfidence; - if(vehicleLengthConfidence != UNKNOWN && (vehicleLengthConfidence > 4 || vehicleLengthConfidence < 0)) { - throw new IllegalArgumentException("CAM HighFrequencyContainer VehicleLengthConfidence should be in the range of [0 - 4]." - + " Value: " + vehicleLengthConfidence); - } - this.vehicleLengthConfidence = vehicleLengthConfidence; - if(longitudinalAccelerationConfidence != UNKNOWN && (longitudinalAccelerationConfidence > 102 || longitudinalAccelerationConfidence < 0)) { - throw new IllegalArgumentException("CAM HighFrequencyContainer LongitudinalAccelerationConfidence should be in the range of [0 - 102]." - + " Value: " + longitudinalAccelerationConfidence); - } - this.longitudinalAccelerationConfidence = longitudinalAccelerationConfidence; - if(lateralAccelerationConfidence != UNKNOWN && (lateralAccelerationConfidence > 102 || lateralAccelerationConfidence < 0)) { - throw new IllegalArgumentException("CAM HighFrequencyContainer LateralAccelerationConfidence should be in the range of [0 - 102]." - + " Value: " + lateralAccelerationConfidence); - } - this.lateralAccelerationConfidence = lateralAccelerationConfidence; - if(verticalAccelerationConfidence != UNKNOWN && (verticalAccelerationConfidence > 102 || verticalAccelerationConfidence < 0)) { - throw new IllegalArgumentException("CAM HighFrequencyContainer VerticalAccelerationConfidence should be in the range of [0 - 102]." - + " Value: " + verticalAccelerationConfidence); - } - this.verticalAccelerationConfidence = verticalAccelerationConfidence; - if(yawRateConfidence != UNKNOWN && (yawRateConfidence > 8 || yawRateConfidence < 0)) { - throw new IllegalArgumentException("CAM HighFrequencyContainer YawRateConfidence should be in the range of [0 - 8]." - + " Value: " + yawRateConfidence); - } - this.yawRateConfidence = yawRateConfidence; - if(curvatureConfidence != UNKNOWN && (curvatureConfidence > 7 || curvatureConfidence < 0)) { - throw new IllegalArgumentException("CAM HighFrequencyContainer CurvatureConfidence should be in the range of [0 - 7]." - + " Value: " + curvatureConfidence); - } - this.curvatureConfidence = curvatureConfidence; - - createJson(); - } - - private void createJson() { - try { - JSONObject confidence = new JSONObject(); - if(headingConfidence != UNKNOWN) - confidence.put(JsonKey.Confidence.HEADING.key(), headingConfidence); - if(speedConfidence != UNKNOWN) - confidence.put(JsonKey.Confidence.SPEED.key(), speedConfidence); - if(vehicleLengthConfidence != UNKNOWN) - confidence.put(JsonKey.Confidence.VEHICLE_LENGTH.key(), vehicleLengthConfidence); - if(longitudinalAccelerationConfidence != UNKNOWN) - confidence.put(JsonKey.Confidence.LONGITUDINAL_ACCELERATION.key(), longitudinalAccelerationConfidence); - if(lateralAccelerationConfidence != UNKNOWN) - confidence.put(JsonKey.Confidence.LATERAL_ACCELERATION.key(), lateralAccelerationConfidence); - if(verticalAccelerationConfidence != UNKNOWN) - confidence.put(JsonKey.Confidence.VERTICAL_ACCELERATION.key(), verticalAccelerationConfidence); - if(yawRateConfidence != UNKNOWN) - confidence.put(JsonKey.Confidence.YAW_RATE.key(), yawRateConfidence); - if(curvatureConfidence != UNKNOWN) - confidence.put(JsonKey.Confidence.CURVATURE.key(), curvatureConfidence); - - if(heading != UNKNOWN) - jsonHighFrequencyContainer.put(JsonKey.HighFrequencyContainer.HEADING.key(), heading); - if(speed != UNKNOWN) - jsonHighFrequencyContainer.put(JsonKey.HighFrequencyContainer.SPEED.key(), speed); - if(driveDirection != UNKNOWN) - jsonHighFrequencyContainer.put(JsonKey.HighFrequencyContainer.DRIVE_DIRECTION.key(), driveDirection); - if(vehicleLength != UNKNOWN) - jsonHighFrequencyContainer.put(JsonKey.HighFrequencyContainer.VEHICLE_LENGTH.key(), vehicleLength); - if(vehicleWidth != UNKNOWN) - jsonHighFrequencyContainer.put(JsonKey.HighFrequencyContainer.VEHICLE_WIDTH.key(), vehicleWidth); - if(longitudinalAcceleration != UNKNOWN) - jsonHighFrequencyContainer.put(JsonKey.HighFrequencyContainer.LONGITUDINAL_ACCELERATION.key(), longitudinalAcceleration); - if(lateralAcceleration != UNKNOWN) - jsonHighFrequencyContainer.put(JsonKey.HighFrequencyContainer.LATERAL_ACCELERATION.key(), lateralAcceleration); - if(verticalAcceleration != UNKNOWN) - jsonHighFrequencyContainer.put(JsonKey.HighFrequencyContainer.VERTICAL_ACCELERATION.key(), verticalAcceleration); - if(yawRate != UNKNOWN) - jsonHighFrequencyContainer.put(JsonKey.HighFrequencyContainer.YAW_RATE.key(), yawRate); - if(lanePosition != UNKNOWN) - jsonHighFrequencyContainer.put(JsonKey.HighFrequencyContainer.LANE_POSITION.key(), lanePosition); - if(curvature != UNKNOWN) - jsonHighFrequencyContainer.put(JsonKey.HighFrequencyContainer.CURVATURE.key(), curvature); - if(curvatureCalculationMode != UNKNOWN) - jsonHighFrequencyContainer.put(JsonKey.HighFrequencyContainer.CURVATURE_CALCULATION_MODE.key(), curvatureCalculationMode); - if(!accelerationControl.isEmpty()) - jsonHighFrequencyContainer.put(JsonKey.HighFrequencyContainer.ACCELERATION_CONTROL.key(), accelerationControl); - if(!JsonUtil.isNullOrEmpty(confidence)) - jsonHighFrequencyContainer.put(JsonKey.HighFrequencyContainer.CONFIDENCE.key(), confidence); - } catch (JSONException e) { - LOGGER.log(Level.WARNING, "HighFrequencyContainer JSON build error", "Error: " + e); - } - } - - public JSONObject getJsonHighFrequencyContainer() { - return jsonHighFrequencyContainer; - } - - public int getHeading() { - return heading; - } - - public float getHeadingDegree() { - return (float) heading / EtsiUtils.ETSI_HEADING_FACTOR; - } - - public int getSpeed() { - return speed; - } - - public float getSpeedMs() { - return (float) speed / EtsiUtils.ETSI_SPEED_FACTOR; - } - - public int getDriveDirection() { - return driveDirection; - } - - public int getVehicleLength() { - return vehicleLength; - } - - public int getVehicleWidth() { - return vehicleWidth; - } - - public int getLongitudinalAcceleration() { - return longitudinalAcceleration; - } - - public float getLongitudinalAccelerationMsSq() { - if(longitudinalAcceleration != UNKNOWN){ - return (float)longitudinalAcceleration/EtsiUtils.ETSI_ACCELERATION_FACTOR; - }else{ - return UNKNOWN; - } - } - - public int getLateralAcceleration() { - return lateralAcceleration; - } - - public float getLateralAccelerationMsSq() { - if(lateralAcceleration != UNKNOWN){ - return (float)lateralAcceleration/EtsiUtils.ETSI_ACCELERATION_FACTOR; - }else{ - return UNKNOWN; - } - } - - public int getVerticalAcceleration() { - return verticalAcceleration; - } - - public float getVerticalAccelerationMsSq() { - if(verticalAcceleration != UNKNOWN){ - return (float)verticalAcceleration/EtsiUtils.ETSI_ACCELERATION_FACTOR; - }else{ - return UNKNOWN; - } - } - - public int getYawRate() { - return yawRate; - } - - public float getYawRateDs() { - if(yawRate != UNKNOWN){ - return (float)yawRate/EtsiUtils.ETSI_YAW_RATE_FACTOR; - }else{ - return UNKNOWN; - } - } - - public int getLanePosition() { - return lanePosition; - } - - public int getCurvature() { - return curvature; - } - - public int getCurvatureCalculationMode() { - return curvatureCalculationMode; - } - - public String getAccelerationControl() { - return accelerationControl; - } - - public int getHeadingConfidence() { - return headingConfidence; - } - - public int getSpeedConfidence() { - return speedConfidence; - } - - public int getVehicleLengthConfidence() { - return vehicleLengthConfidence; - } - - public int getLongitudinalAccelerationConfidence() { - return longitudinalAccelerationConfidence; - } - - public int getLateralAccelerationConfidence() { - return lateralAccelerationConfidence; - } - - public int getVerticalAccelerationConfidence() { - return verticalAccelerationConfidence; - } - - public int getYawRateConfidence() { - return yawRateConfidence; - } - - public int getCurvatureConfidence() { - return curvatureConfidence; - } - - public static class HighFrequencyContainerBuilder { - private int heading = UNKNOWN; - private int speed = UNKNOWN; - private int driveDirection = UNKNOWN; - private int vehicleLength = UNKNOWN; - private int vehicleWidth = UNKNOWN; - private int longitudinalAcceleration = UNKNOWN; - private int lateralAcceleration = UNKNOWN; - private int verticalAcceleration = UNKNOWN; - private int yawRate = UNKNOWN; - private int lanePosition = UNKNOWN; - private int curvature = UNKNOWN; - private int curvatureCalculationMode = UNKNOWN; - private String accelerationControl = ""; - private int headingConfidence = UNKNOWN; - private int speedConfidence = UNKNOWN; - private int vehicleLengthConfidence = UNKNOWN; - private int longitudinalAccelerationConfidence = UNKNOWN; - private int lateralAccelerationConfidence = UNKNOWN; - private int verticalAccelerationConfidence = UNKNOWN; - private int yawRateConfidence = UNKNOWN; - private int curvatureConfidence = UNKNOWN; - - /** - * Start building a HighFrequencyContainer. - */ - public HighFrequencyContainerBuilder() { - } - - /** - * Sets the speed of the ITS-S. - * - * @param speed {@link HighFrequencyContainer#speed} - */ - public HighFrequencyContainerBuilder speed(int speed) { - this.speed = speed; - return this; - } - - /** - * Sets the speed and speed confidence of the ITS-S. - * - * @param speed {@link HighFrequencyContainer#speed} - * @param speedConfidence {@link HighFrequencyContainer#speedConfidence} - */ - public HighFrequencyContainerBuilder speed(int speed, - int speedConfidence) { - this.speed = speed; - this.speedConfidence = speedConfidence; - return this; - } - - /** - * Sets the headinge of the ITS-S. - * - * @param heading {@link HighFrequencyContainer#heading} - */ - public HighFrequencyContainerBuilder heading(int heading) { - this.heading = heading; - return this; - } - - /** - * Sets the heading and heading confidence of the ITS-S. - * - * @param heading {@link HighFrequencyContainer#heading} - * @param headingConfidence {@link HighFrequencyContainer#headingConfidence} - */ - public HighFrequencyContainerBuilder heading(int heading, - int headingConfidence) { - this.heading = heading; - this.headingConfidence = headingConfidence; - return this; - } - - /** - * Sets the drive direction of the ITS-S. - * - * @param driveDirection {@link HighFrequencyContainer#driveDirection} - */ - public HighFrequencyContainerBuilder driveDirection(int driveDirection) { - this.driveDirection = driveDirection; - return this; - } - - /** - * Sets the size of the ITS-S. - * - * @param vehicleLength {@link HighFrequencyContainer#vehicleLength} - * @param vehicleWidth {@link HighFrequencyContainer#vehicleWidth} - */ - public HighFrequencyContainerBuilder vehicleSize(int vehicleLength, - int vehicleWidth) { - this.vehicleLength = vehicleLength; - this.vehicleWidth = vehicleWidth; - return this; - } - - /** - * Sets the size and size confidence of the ITS-S. - * - * @param vehicleLength {@link HighFrequencyContainer#vehicleLength} - * @param vehicleWidth {@link HighFrequencyContainer#vehicleWidth} - * @param vehicleLengthConfidence {@link HighFrequencyContainer#vehicleLengthConfidence} - */ - public HighFrequencyContainerBuilder vehicleSize(int vehicleLength, - int vehicleWidth, - int vehicleLengthConfidence) { - this.vehicleLength = vehicleLength; - this.vehicleWidth = vehicleWidth; - this.vehicleLengthConfidence = vehicleLengthConfidence; - return this; - } - - /** - * Sets the yaw rate of the ITS-S. - * - * @param yawRate {@link HighFrequencyContainer#yawRate} - */ - public HighFrequencyContainerBuilder yawRate(int yawRate) { - this.yawRate = yawRate; - return this; - } - - /** - * Sets the yaw rate and yaw rate confidence of the ITS-S. - * - * @param yawRate {@link HighFrequencyContainer#yawRate} - * @param yawRateConfidence {@link HighFrequencyContainer#yawRateConfidence} - */ - public HighFrequencyContainerBuilder yawRate(int yawRate, - int yawRateConfidence) { - this.yawRate = yawRate; - this.yawRateConfidence = yawRateConfidence; - return this; - } - - /** - * Sets the longitudinal acceleration and of the ITS-S. - * - * @param longitudinalAcceleration {@link HighFrequencyContainer#longitudinalAcceleration} - */ - public HighFrequencyContainerBuilder longitudinalAcceleration(int longitudinalAcceleration) { - this.longitudinalAcceleration = longitudinalAcceleration; - return this; - } - - /** - * Sets the longitudinal acceleration and of the ITS-S and its confidence. - * - * @param longitudinalAcceleration {@link HighFrequencyContainer#longitudinalAcceleration} - * @param longitudinalAccelerationConfidence {@link HighFrequencyContainer#longitudinalAccelerationConfidence} - */ - public HighFrequencyContainerBuilder longitudinalAcceleration(int longitudinalAcceleration, - int longitudinalAccelerationConfidence) { - this.longitudinalAcceleration = longitudinalAcceleration; - this.longitudinalAccelerationConfidence = longitudinalAccelerationConfidence; - return this; - } - - /** - * Sets the lateral acceleration and of the ITS-S. - * - * @param lateralAcceleration {@link HighFrequencyContainer#lateralAcceleration} - */ - public HighFrequencyContainerBuilder lateralAcceleration(int lateralAcceleration) { - this.lateralAcceleration = lateralAcceleration; - return this; - } - - /** - * Sets the lateral acceleration and of the ITS-S and its confidence. - * - * @param lateralAcceleration {@link HighFrequencyContainer#lateralAcceleration} - * @param lateralAccelerationConfidence {@link HighFrequencyContainer#lateralAccelerationConfidence} - */ - public HighFrequencyContainerBuilder lateralAcceleration(int lateralAcceleration, - int lateralAccelerationConfidence) { - this.lateralAcceleration = lateralAcceleration; - this.lateralAccelerationConfidence = lateralAccelerationConfidence; - return this; - } - - /** - * Sets the vertical acceleration and of the ITS-S. - * - * @param verticalAcceleration {@link HighFrequencyContainer#verticalAcceleration} - */ - public HighFrequencyContainerBuilder verticalAcceleration(int verticalAcceleration) { - this.verticalAcceleration = verticalAcceleration; - return this; - } - - /** - * Sets the vertical acceleration and of the ITS-S and its confidence. - * - * @param verticalAcceleration {@link HighFrequencyContainer#verticalAcceleration} - * @param verticalAccelerationConfidence {@link HighFrequencyContainer#verticalAccelerationConfidence} - */ - public HighFrequencyContainerBuilder verticalAcceleration(int verticalAcceleration, - int verticalAccelerationConfidence) { - this.verticalAcceleration = verticalAcceleration; - this.verticalAccelerationConfidence = verticalAccelerationConfidence; - return this; - } - - /** - * Sets the acceleration control mechanism of the ITS-S. - * - * @param accelerationControl {@link HighFrequencyContainer#accelerationControl} - */ - public HighFrequencyContainerBuilder accelerationControl(String accelerationControl) { - this.accelerationControl = accelerationControl; - return this; - } - - /** - * Sets the curvature of the ITS-S and its calculation mode. - * - * @param curvature {@link HighFrequencyContainer#curvature} - * @param curvatureCalculationMode {@link HighFrequencyContainer#curvatureCalculationMode} - */ - public HighFrequencyContainerBuilder curvature(int curvature, - int curvatureCalculationMode) { - this.curvature = curvature; - this.curvatureCalculationMode = curvatureCalculationMode; - return this; - } - - /** - * Sets the curvature of the ITS-S, and its confidence and calculation mode. - * - * @param curvature {@link HighFrequencyContainer#curvature} - * @param curvatureConfidence {@link HighFrequencyContainer#curvatureConfidence} - * @param curvatureCalculationMode {@link HighFrequencyContainer#curvatureCalculationMode} - */ - public HighFrequencyContainerBuilder curvature(int curvature, - int curvatureConfidence, - int curvatureCalculationMode) { - this.curvature = curvature; - this.curvatureConfidence = curvatureConfidence; - this.curvatureCalculationMode = curvatureCalculationMode; - return this; - } - - /** - * Sets the lane position of the ITS-S. - * - * @param lanePosition {@link HighFrequencyContainer#lanePosition} - */ - public HighFrequencyContainerBuilder lanePosition(int lanePosition) { - this.lanePosition = lanePosition; - return this; - } - - /** - * Build the HighFrequencyContainer. - * - * @return {@link #HighFrequencyContainer} - */ - public HighFrequencyContainer build() { - return new HighFrequencyContainer( - heading, - speed, - driveDirection, - vehicleLength, - vehicleWidth, - longitudinalAcceleration, - lateralAcceleration, - verticalAcceleration, - yawRate, - lanePosition, - curvature, - curvatureCalculationMode, - accelerationControl, - headingConfidence, - speedConfidence, - vehicleLengthConfidence, - longitudinalAccelerationConfidence, - lateralAccelerationConfidence, - verticalAccelerationConfidence, - yawRateConfidence, - curvatureConfidence); - } - - } - - public static HighFrequencyContainer jsonParser(JSONObject jsonHighFrequencyContainer) { - if(JsonUtil.isNullOrEmpty(jsonHighFrequencyContainer)) return null; - int heading = jsonHighFrequencyContainer.optInt(JsonKey.HighFrequencyContainer.HEADING.key(), UNKNOWN); - int speed = jsonHighFrequencyContainer.optInt(JsonKey.HighFrequencyContainer.SPEED.key(), UNKNOWN); - int driveDirection = jsonHighFrequencyContainer.optInt(JsonKey.HighFrequencyContainer.DRIVE_DIRECTION.key(), UNKNOWN); - int vehicleLength = jsonHighFrequencyContainer.optInt(JsonKey.HighFrequencyContainer.VEHICLE_LENGTH.key(), UNKNOWN); - int vehicleWidth = jsonHighFrequencyContainer.optInt(JsonKey.HighFrequencyContainer.VEHICLE_WIDTH.key(), UNKNOWN); - int longitudinalAcceleration = jsonHighFrequencyContainer.optInt(JsonKey.HighFrequencyContainer.LONGITUDINAL_ACCELERATION.key(), UNKNOWN); - int lateralAcceleration = jsonHighFrequencyContainer.optInt(JsonKey.HighFrequencyContainer.LATERAL_ACCELERATION.key(), UNKNOWN); - int verticalAcceleration = jsonHighFrequencyContainer.optInt(JsonKey.HighFrequencyContainer.VERTICAL_ACCELERATION.key(), UNKNOWN); - int yawRate = jsonHighFrequencyContainer.optInt(JsonKey.HighFrequencyContainer.YAW_RATE.key(), UNKNOWN); - int lanePosition = jsonHighFrequencyContainer.optInt(JsonKey.HighFrequencyContainer.LANE_POSITION.key(), UNKNOWN); - int curvature = jsonHighFrequencyContainer.optInt(JsonKey.HighFrequencyContainer.CURVATURE.key(), UNKNOWN); - int curvatureCalculationMode = jsonHighFrequencyContainer.optInt(JsonKey.HighFrequencyContainer.CURVATURE_CALCULATION_MODE.key(), UNKNOWN); - String accelerationControl = jsonHighFrequencyContainer.optString(JsonKey.HighFrequencyContainer.ACCELERATION_CONTROL.key(), ""); - - JSONObject confidence = jsonHighFrequencyContainer.optJSONObject(JsonKey.HighFrequencyContainer.CONFIDENCE.key()); - int headingConfidence = UNKNOWN; - int speedConfidence = UNKNOWN; - int vehicleLengthConfidence = UNKNOWN; - int longitudinalAccelerationConfidence = UNKNOWN; - int lateralAccelerationConfidence = UNKNOWN; - int verticalAccelerationConfidence = UNKNOWN; - int yawRateConfidence = UNKNOWN; - int curvatureConfidence = UNKNOWN; - if(confidence != null) { - headingConfidence = confidence.optInt(JsonKey.Confidence.HEADING.key(), UNKNOWN); - speedConfidence = confidence.optInt(JsonKey.Confidence.SPEED.key(), UNKNOWN); - vehicleLengthConfidence = confidence.optInt(JsonKey.Confidence.VEHICLE_LENGTH.key(), UNKNOWN); - longitudinalAccelerationConfidence = confidence.optInt(JsonKey.Confidence.LONGITUDINAL_ACCELERATION.key(), UNKNOWN); - lateralAccelerationConfidence = confidence.optInt(JsonKey.Confidence.LATERAL_ACCELERATION.key(), UNKNOWN); - verticalAccelerationConfidence = confidence.optInt(JsonKey.Confidence.VERTICAL_ACCELERATION.key(), UNKNOWN); - yawRateConfidence = confidence.optInt(JsonKey.Confidence.YAW_RATE.key(), UNKNOWN); - curvatureConfidence = confidence.optInt(JsonKey.Confidence.CURVATURE.key(), UNKNOWN); - } - - return new HighFrequencyContainerBuilder() - .speed(speed, speedConfidence) - .heading(heading, headingConfidence) - .driveDirection(driveDirection) - .longitudinalAcceleration(longitudinalAcceleration, longitudinalAccelerationConfidence) - .lateralAcceleration(lateralAcceleration, lateralAccelerationConfidence) - .verticalAcceleration(verticalAcceleration, verticalAccelerationConfidence) - .accelerationControl(accelerationControl) - .yawRate(yawRate, yawRateConfidence) - .vehicleSize(vehicleLength, vehicleWidth, vehicleLengthConfidence) - .curvature(curvature, curvatureConfidence, curvatureCalculationMode) - .lanePosition(lanePosition) - .build(); - } - -} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/cam/LowFrequencyContainer.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/cam/LowFrequencyContainer.java deleted file mode 100644 index 7a530e26d..000000000 --- a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/cam/LowFrequencyContainer.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - Copyright 2016-2024 Orange - - This software is distributed under the MIT license, see LICENSE.txt file for more details. - - @author Mathieu LEFEBVRE - */ -package com.orange.iot3mobility.its.json.cam; - -import com.orange.iot3mobility.its.json.JsonKey; -import com.orange.iot3mobility.its.json.JsonUtil; -import com.orange.iot3mobility.its.json.PathHistory; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * CAM LowFrequencyContainer. - *

- * Optional, provides additional information about a vehicle. - */ -public class LowFrequencyContainer { - - private static final Logger LOGGER = Logger.getLogger(LowFrequencyContainer.class.getName()); - - private final JSONObject jsonLowFrequencyContainer = new JSONObject(); - - /** - * The role of the vehicle ITS-S that originates the CAM. - *

- * default(0), publicTransport(1), specialTransport(2), dangerousGoods(3), roadWork(4), rescue(5), emergency(6), - * safetyCar(7), agriculture(8),commercial(9),military(10),roadOperator(11),taxi(12), reserved1(13), reserved2(14), - * reserved3(15) - */ - private final int vehicleRole; - - /** - * Status of the exterior light switches represented as a bit string. - *

- * lowBeamHeadlightsOn (0), highBeamHeadlightsOn (1), leftTurnSignalOn (2), rightTurnSignalOn (3), - * daytimeRunningLightsOn (4), reverseLightOn (5), fogLightOn (6), parkingLightsOn (7) - *

- * Examples: "00000000", "10011010", "00000110" - */ - private final String exteriorLights; - - /** - * The path history of the originating ITS-S, a path with a set of path points. - */ - private final PathHistory pathHistory; - - /** - * Build a CAM LowFrequencyContainer. - * - * @param vehicleRole {@link #vehicleRole} - */ - public LowFrequencyContainer( - final int vehicleRole - ) - { - this(vehicleRole, ""); - } - - /** - * Build a CAM LowFrequencyContainer. - * - * @param vehicleRole {@link #vehicleRole} - * @param exteriorLights {@link #exteriorLights} - */ - public LowFrequencyContainer( - final int vehicleRole, - final String exteriorLights - ) - { - this(vehicleRole, exteriorLights, new PathHistory(null)); - } - - /** - * Build a CAM LowFrequencyContainer. - * - * @param vehicleRole {@link #vehicleRole} - * @param exteriorLights {@link #exteriorLights} - * @param pathHistory {@link #pathHistory} - */ - public LowFrequencyContainer( - final int vehicleRole, - final String exteriorLights, - final PathHistory pathHistory - ) - { - if(vehicleRole > 15 || vehicleRole < 0) { - throw new IllegalArgumentException("CAM LowFrequencyContainer VehicleRole should be in the range of [0 - 15]." - + " Value: " + vehicleRole); - } - this.vehicleRole = vehicleRole; - this.exteriorLights = exteriorLights; - this.pathHistory = pathHistory; - - createJson(); - } - - private void createJson() { - try { - jsonLowFrequencyContainer.put(JsonKey.LowFrequencyContainer.VEHICLE_ROLE.key(), vehicleRole); - if(!exteriorLights.isEmpty()) - jsonLowFrequencyContainer.put(JsonKey.LowFrequencyContainer.EXTERIOR_LIGHTS.key(), exteriorLights); - jsonLowFrequencyContainer.put(JsonKey.LowFrequencyContainer.PATH_HISTORY.key(), pathHistory.getJsonPathHistory()); - } catch (JSONException e) { - LOGGER.log(Level.WARNING, "LowFrequencyContainer JSON build error", "Error: " + e); - } - } - - public JSONObject getJsonLowFrequencyContainer() { - return jsonLowFrequencyContainer; - } - - public int getVehicleRole() { - return vehicleRole; - } - - public String getExteriorLights() { - return exteriorLights; - } - - public PathHistory getPathHistory() { - return pathHistory; - } - - public static LowFrequencyContainer jsonParser(JSONObject jsonLowFreqContainer) { - if(JsonUtil.isNullOrEmpty(jsonLowFreqContainer)) return null; - try { - int vehicleRole = jsonLowFreqContainer.getInt(JsonKey.LowFrequencyContainer.VEHICLE_ROLE.key()); - String exteriorLights = jsonLowFreqContainer.optString(JsonKey.LowFrequencyContainer.EXTERIOR_LIGHTS.key()); - JSONArray jsonPathHistory = jsonLowFreqContainer.optJSONArray(JsonKey.LowFrequencyContainer.PATH_HISTORY.key()); - PathHistory pathHistory = PathHistory.jsonParser(jsonPathHistory); - - return new LowFrequencyContainer( - vehicleRole, - exteriorLights, - pathHistory); - } catch (JSONException e) { - LOGGER.log(Level.WARNING, "HighFrequencyContainer JSON parsing error", "Error: " + e); - } - return null; - } - -} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/managers/IoT3RoadUserCallback.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/managers/IoT3RoadUserCallback.java index 2f1be71e4..412b09b0f 100644 --- a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/managers/IoT3RoadUserCallback.java +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/managers/IoT3RoadUserCallback.java @@ -7,7 +7,7 @@ */ package com.orange.iot3mobility.managers; -import com.orange.iot3mobility.its.json.cam.CAM; +import com.orange.iot3mobility.messages.cam.core.CamCodec; import com.orange.iot3mobility.roadobjects.RoadUser; public interface IoT3RoadUserCallback { @@ -18,6 +18,6 @@ public interface IoT3RoadUserCallback { void roadUserExpired(RoadUser roadUser); - void camArrived(CAM cam); + void camArrived(CamCodec.CamFrame camFrame); } diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/managers/RoadUserManager.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/managers/RoadUserManager.java index 6822fac73..a5222057e 100644 --- a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/managers/RoadUserManager.java +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/managers/RoadUserManager.java @@ -8,13 +8,20 @@ package com.orange.iot3mobility.managers; import com.orange.iot3mobility.its.StationType; -import com.orange.iot3mobility.its.json.cam.CAM; +import com.orange.iot3mobility.messages.EtsiConverter; +import com.orange.iot3mobility.messages.cam.CamHelper; +import com.orange.iot3mobility.messages.cam.core.CamCodec; +import com.orange.iot3mobility.messages.cam.core.CamVersion; +import com.orange.iot3mobility.messages.cam.v113.model.CamEnvelope113; +import com.orange.iot3mobility.messages.cam.v230.model.CamEnvelope230; +import com.orange.iot3mobility.messages.cam.v230.model.CamStructuredData; +import com.orange.iot3mobility.messages.cam.v230.model.highfrequencycontainer.BasicVehicleContainerHighFrequency; import com.orange.iot3mobility.quadkey.LatLng; import com.orange.iot3mobility.roadobjects.RoadUser; import org.json.JSONException; -import org.json.JSONObject; +import java.io.IOException; import java.util.*; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -39,47 +46,74 @@ public static void init(IoT3RoadUserCallback ioT3RoadUserCallback) { startExpirationCheck(); } - public static void processCam(String message) { + public static void processCam(String message, CamHelper camHelper) { if(ioT3RoadUserCallback != null) { try { - CAM cam = CAM.jsonParser(new JSONObject(message)); - if(cam == null) { - LOGGER.log(Level.WARNING, TAG, "CAM parsing returned null"); - } else { - ioT3RoadUserCallback.camArrived(cam); - //associate the received CAM to a RoadUser object - String uuid = cam.getSourceUuid() + "_" + cam.getStationId(); - StationType stationType = StationType.fromId(cam.getBasicContainer().getStationType()); - LatLng position = new LatLng( - cam.getBasicContainer().getPosition().getLatitudeDegree(), - cam.getBasicContainer().getPosition().getLongitudeDegree()); - float speed = cam.getHighFrequencyContainer().getSpeedMs(); - float heading = cam.getHighFrequencyContainer().getHeadingDegree(); - if(ROAD_USER_MAP.containsKey(uuid)) { - synchronized (ROAD_USER_MAP) { - RoadUser roadUser = ROAD_USER_MAP.get(uuid); - if(roadUser != null) { - roadUser.updateTimestamp(); - roadUser.setStationType(stationType); - roadUser.setPosition(position); - roadUser.setSpeed(speed); - roadUser.setHeading(heading); - roadUser.setCam(cam); - ioT3RoadUserCallback.roadUserUpdate(roadUser); - } + CamCodec.CamFrame camFrame = camHelper.parse(message); + ioT3RoadUserCallback.camArrived(camFrame); + + String uuid; + StationType stationType; + LatLng position; + double speed; + double heading; + + if(camFrame.version().equals(CamVersion.V1_1_3)) { + CamEnvelope113 camEnvelope113 = (CamEnvelope113) camFrame.envelope(); + uuid = camEnvelope113.sourceUuid() + "_" + camEnvelope113.message().stationId(); + stationType = StationType.fromId(camEnvelope113.message().basicContainer().stationType()); + position = new LatLng( + EtsiConverter.latitudeDegrees(camEnvelope113.message().basicContainer().referencePosition().latitude()), + EtsiConverter.longitudeDegrees(camEnvelope113.message().basicContainer().referencePosition().longitude())); + speed = EtsiConverter.speedMetersPerSecond(camEnvelope113.message().highFrequencyContainer().speed()); + heading = EtsiConverter.headingDegrees(camEnvelope113.message().highFrequencyContainer().heading()); + createOrUpdateRoadUser(uuid, stationType, position, speed, heading, camFrame); + } else if(camFrame.version().equals(CamVersion.V2_3_0)) { + CamEnvelope230 camEnvelope230 = (CamEnvelope230) camFrame.envelope(); + if(camEnvelope230.message() instanceof CamStructuredData cam) { + uuid = camEnvelope230.sourceUuid() + "_" + cam.stationId(); + stationType = StationType.fromId(cam.basicContainer().stationType()); + position = new LatLng( + EtsiConverter.latitudeDegrees(cam.basicContainer().referencePosition().latitude()), + EtsiConverter.longitudeDegrees(cam.basicContainer().referencePosition().longitude())); + if(cam.highFrequencyContainer() instanceof BasicVehicleContainerHighFrequency vehicleContainerHighFrequency) { + speed = EtsiConverter.speedMetersPerSecond(vehicleContainerHighFrequency.speed().value()); + heading = EtsiConverter.headingDegrees(vehicleContainerHighFrequency.heading().value()); + } else { // road-side unit + speed = 0; + heading = 0; } - } else { - RoadUser roadUser = new RoadUser(uuid, stationType, position, speed, heading, cam); - addRoadUser(uuid, roadUser); - ioT3RoadUserCallback.newRoadUser(roadUser); + createOrUpdateRoadUser(uuid, stationType, position, speed, heading, camFrame); } } - } catch (JSONException e) { + } catch (JSONException | IOException e) { LOGGER.log(Level.WARNING, TAG, "CAM parsing error: " + e); } } } + private static void createOrUpdateRoadUser(String uuid, StationType stationType, LatLng position, double speed, + double heading, CamCodec.CamFrame camFrame) { + if(ROAD_USER_MAP.containsKey(uuid)) { + synchronized (ROAD_USER_MAP) { + RoadUser roadUser = ROAD_USER_MAP.get(uuid); + if(roadUser != null) { + roadUser.updateTimestamp(); + roadUser.setStationType(stationType); + roadUser.setPosition(position); + roadUser.setSpeed(speed); + roadUser.setHeading(heading); + roadUser.setCamFrame(camFrame); + ioT3RoadUserCallback.roadUserUpdate(roadUser); + } + } + } else { + RoadUser roadUser = new RoadUser(uuid, stationType, position, speed, heading, camFrame); + addRoadUser(uuid, roadUser); + ioT3RoadUserCallback.newRoadUser(roadUser); + } + } + private static void addRoadUser(String key, RoadUser roadUser) { synchronized (ROAD_USERS) { ROAD_USERS.add(roadUser); @@ -117,7 +151,7 @@ private static synchronized void startExpirationCheck() { /** * Retrieve a read-only list of the Road Users in the vicinity. * - * @return the read-only list of {@link com.orange.iot3mobility.roadobjects.RoadUser} objects + * @return the read-only list of {@link RoadUser} objects */ public static List getRoadUsers() { return Collections.unmodifiableList(ROAD_USERS); diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/EtsiConverter.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/EtsiConverter.java new file mode 100644 index 000000000..17076f5f2 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/EtsiConverter.java @@ -0,0 +1,378 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages; + +/** + * Utility methods to convert ETSI ITS units to/from SI units. + *

+ * "ETSI" values are the encoded integer values from the message. + * "SI" values are returned/taken in standard units (meters, seconds, radians, etc.). + */ +public final class EtsiConverter { + + private EtsiConverter() { + // Utility class + } + + // ------------------------------------------------------------------------- + // Time + // ------------------------------------------------------------------------- + + /** + * SI -> ETSI + * @param etsiTimestampMs ETSI timestamp in milliseconds + * @return encoded value in milliseconds (clamped to 0..65535) + */ + public static int generationDeltaTimeEtsi(long etsiTimestampMs) { + return (int) (etsiTimestampMs & 0xFFFF); // equivalent to % 65536 but ensures positive result + } + + /** + * Convenience: absolute epoch ms -> seconds. + */ + public static double epochMillisToSeconds(long epochMillis) { + return epochMillis / 1000.0; + } + + // ------------------------------------------------------------------------- + // Position (latitude / longitude / altitude) + // ------------------------------------------------------------------------- + + /** + * Latitude in CAM: integer in 1e-7 degrees. + *

+ * ETSI -> SI (degrees) + */ + public static double latitudeDegrees(int latitudeEtsi) { + return latitudeEtsi / 10_000_000.0; + } + + /** + * SI -> ETSI (1e-7 degrees units) + */ + public static int latitudeEtsi(double latitudeDegrees) { + long etsi = Math.round(latitudeDegrees * 10_000_000.0); + if (etsi < -900000000L) etsi = -900000000L; + if (etsi > 900000001L) etsi = 900000001L; + return (int) etsi; + } + + /** + * Longitude in CAM: integer in 1e-7 degrees. + *

+ * ETSI -> SI (degrees) + */ + public static double longitudeDegrees(int longitudeEtsi) { + return longitudeEtsi / 10_000_000.0; + } + + /** + * SI -> ETSI (1e-7 degrees units) + */ + public static int longitudeEtsi(double longitudeDegrees) { + long etsi = Math.round(longitudeDegrees * 10_000_000.0); + if (etsi < -1800000000L) etsi = -1800000000L; + if (etsi > 1800000001L) etsi = 1800000001L; + return (int) etsi; + } + + /** + * Altitude in CAM (example): + * value in [0..800001] with: + * altitudeMeters = value * 0.01 + *

+ * ETSI -> SI (meters) + */ + public static double altitudeMeters(int altitudeEtsi) { + return altitudeEtsi * 0.01; + } + + /** + * SI -> ETSI. + * Inverse of altitudeMeters(): + * value = altitudeMeters / 0.01 + */ + public static int altitudeEtsi(double altitudeMeters) { + long etsi = Math.round((altitudeMeters) / 0.01); + if (etsi < 0L) etsi = 0L; + if (etsi > 800001L) etsi = 800001L; + return (int) etsi; + } + + // ------------------------------------------------------------------------- + // Speed + // ------------------------------------------------------------------------- + + /** + * Speed in CAM: 0..16383 in steps of 0.01 m/s. + *

+ * ETSI -> SI (m/s) + */ + public static double speedMetersPerSecond(int speedEtsi) { + return speedEtsi * 0.01; + } + + /** + * Convenience: ETSI -> SI (km/h). + */ + public static double speedKilometersPerHour(int speedEtsi) { + return speedMetersPerSecond(speedEtsi) * 3.6; + } + + /** + * SI -> ETSI: from m/s. + */ + public static int speedEtsiFromMetersPerSecond(double speedMps) { + long etsi = Math.round(speedMps / 0.01); + if (etsi < 0L) etsi = 0L; + if (etsi > 16383L) etsi = 16383L; + return (int) etsi; + } + + /** + * SI -> ETSI: from km/h (convenience). + */ + public static int speedEtsiFromKilometersPerHour(double speedKmh) { + double mps = speedKmh / 3.6; + return speedEtsiFromMetersPerSecond(mps); + } + + // ------------------------------------------------------------------------- + // Heading / orientation + // ------------------------------------------------------------------------- + + /** + * Heading in CAM: 0..3601 in steps of 0.1° + * - typical sentinel: 3600 or 3601 => unavailable. + *

+ * ETSI -> SI (degrees). Returns NaN if sentinel. + */ + public static double headingDegrees(int headingEtsi) { + if (headingEtsi >= 3600) { + return Double.NaN; + } + return headingEtsi * 0.1; + } + + /** + * ETSI -> SI (radians). Propagate NaN if unavailable. + */ + public static double headingRadians(int headingEtsi) { + double deg = headingDegrees(headingEtsi); + return Double.isNaN(deg) ? Double.NaN : Math.toRadians(deg); + } + + /** + * SI -> ETSI (degrees). If degrees is NaN, return sentinel (3601). + */ + public static int headingEtsiFromDegrees(double headingDegrees) { + if (Double.isNaN(headingDegrees)) { + return 3601; // sentinel for "unavailable" (to adapt if needed) + } + double normalized = ((headingDegrees % 360.0) + 360.0) % 360.0; + long etsi = Math.round(normalized / 0.1); + if (etsi < 0L) etsi = 0L; + if (etsi > 3600L) etsi = 3600L; + return (int) etsi; + } + + /** + * SI -> ETSI (radians). + */ + public static int headingEtsiFromRadians(double headingRadians) { + if (Double.isNaN(headingRadians)) { + return 3601; + } + double deg = Math.toDegrees(headingRadians); + return headingEtsiFromDegrees(deg); + } + + // ------------------------------------------------------------------------- + // Vehicle dimensions + // ------------------------------------------------------------------------- + + /** + * Vehicle length in CAM: steps of 0.1 m. + * + * ETSI -> SI (meters) + */ + public static double vehicleLengthMeters(int lengthEtsi) { + return lengthEtsi * 0.1; + } + + /** + * SI -> ETSI. + */ + public static int vehicleLengthEtsi(double lengthMeters) { + long etsi = Math.round(lengthMeters / 0.1); + if (etsi < 0L) etsi = 0L; + if (etsi > 1023L) etsi = 1023L; + return (int) etsi; + } + + /** + * Vehicle width in CAM: steps of 0.1 m. + *

+ * ETSI -> SI (meters) + */ + public static double vehicleWidthMeters(int widthEtsi) { + return widthEtsi * 0.1; + } + + /** + * SI -> ETSI. + */ + public static int vehicleWidthEtsi(double widthMeters) { + long etsi = Math.round(widthMeters / 0.1); + if (etsi < 0L) etsi = 0L; + if (etsi > 62L) etsi = 62L; + return (int) etsi; + } + + // ------------------------------------------------------------------------- + // Acceleration, curvature, yaw rate + // ------------------------------------------------------------------------- + + /** + * Longitudinal / lateral / vertical acceleration in CAM: + * steps of 0.1 m/s², signed. + *

+ * ETSI -> SI (m/s²) + */ + public static double accelerationMetersPerSecondSquared(int accelerationEtsi) { + return accelerationEtsi * 0.1; + } + + /** + * SI -> ETSI. + */ + public static int accelerationEtsi(double accelerationMps2) { + long etsi = Math.round(accelerationMps2 / 0.1); + if (etsi < -160L) etsi = -160L; + if (etsi > 161L) etsi = 161L; + return (int) etsi; + } + + /** + * Curvature in CAM (example): unit 1/30000 m⁻¹. + *

+ * ETSI -> SI (1/m) + */ + public static double curvaturePerMeter(int curvatureEtsi) { + return curvatureEtsi / 30000.0; + } + + /** + * SI -> ETSI. + */ + public static int curvatureEtsi(double curvaturePerMeter) { + long etsi = Math.round(curvaturePerMeter * 30000.0); + if (etsi < -1023L) etsi = -1023L; + if (etsi > 1023L) etsi = 1023L; + return (int) etsi; + } + + /** + * Yaw rate in CAM: steps of 0.01 deg/s, signed. + *

+ * ETSI -> SI (rad/s) + */ + public static double yawRateRadiansPerSecond(int yawRateEtsi) { + double yawDegPerSec = yawRateEtsi * 0.01; + return Math.toRadians(yawDegPerSec); + } + + /** + * SI -> ETSI. + */ + public static int yawRateEtsiFromRadiansPerSecond(double yawRateRadPerSec) { + double yawDegPerSec = Math.toDegrees(yawRateRadPerSec); + return yawRateEtsiFromDegreesPerSecond(yawDegPerSec); + } + + public static int yawRateEtsiFromDegreesPerSecond(double yawDegPerSec) { + long etsi = Math.round(yawDegPerSec / 0.01); + if (etsi < -32766L) etsi = -32766L; + if (etsi > 32767L) etsi = 32767L; + return (int) etsi; + } + + // ------------------------------------------------------------------------- + // Delta positions (path history etc.) + // ------------------------------------------------------------------------- + + /** + * Delta position (example): 0.1 m steps, signed. + *

+ * ETSI -> SI (meters) + */ + public static double deltaPositionMeters(int deltaEtsi) { + return deltaEtsi * 0.1; + } + + /** + * SI -> ETSI. + */ + public static int deltaPositionEtsi(double deltaMeters) { + long etsi = Math.round(deltaMeters / 0.1); + if (etsi < -131071L) etsi = -131071L; + if (etsi > 131072L) etsi = 131072L; + return (int) etsi; + } + + /** + * Delta altitude (example): 0.1 m steps, signed. + *

+ * ETSI -> SI (meters) + */ + public static double deltaAltitudeMeters(int deltaAltitudeEtsi) { + return deltaAltitudeEtsi * 0.1; + } + + /** + * SI -> ETSI. + */ + public static int deltaAltitudeEtsi(double deltaAltitudeMeters) { + long etsi = Math.round(deltaAltitudeMeters / 0.1); + if (etsi < -12700L) etsi = -12700L; + if (etsi > 12800L) etsi = 12800L; + return (int) etsi; + } + + // ------------------------------------------------------------------------- + // Generic helpers + // ------------------------------------------------------------------------- + + /** + * Degrees -> radians. + */ + public static double degreesToRadians(double deg) { + return Math.toRadians(deg); + } + + /** + * Radians -> degrees. + */ + public static double radiansToDegrees(double rad) { + return Math.toDegrees(rad); + } + + /** + * m/s -> km/h. + */ + public static double metersPerSecondToKilometersPerHour(double mps) { + return mps * 3.6; + } + + /** + * km/h -> m/s. + */ + public static double kilometersPerHourToMetersPerSecond(double kmh) { + return kmh / 3.6; + } +} \ No newline at end of file diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/StationType.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/StationType.java new file mode 100644 index 000000000..fab5a9c98 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/StationType.java @@ -0,0 +1,36 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages; + +/** + * ITS Station Type + *

+ * unknown(0), pedestrian(1), cyclist(2), moped(3), motorcycle(4), passengerCar(5), bus(6), lightTruck(7), + * heavyTruck(8), trailer(9), specialVehicles(10), tram(11), roadSideUnit(15) + */ +public enum StationType { + UNKNOWN(0), + PEDESTRIAN(1), + CYCLIST(2), + MOPED(3), + MOTORCYCLE(4), + PASSENGER_CAR(5), + BUS(6), + LIGHT_TRUCK(7), + HEAVY_TRUCK(8), + TRAILER(9), + SPECIAL_VEHICLES(10), + TRAM(11), + ROAD_SIDE_UNIT(15); + + public final int value; + + StationType(int value) { + this.value = value; + } +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/CamHelper.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/CamHelper.java new file mode 100644 index 000000000..35bf83c1e --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/CamHelper.java @@ -0,0 +1,132 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam; + +import com.fasterxml.jackson.core.JsonFactory; +import com.orange.iot3mobility.messages.cam.core.CamCodec; +import com.orange.iot3mobility.messages.cam.core.CamException; +import com.orange.iot3mobility.messages.cam.core.CamVersion; +import com.orange.iot3mobility.messages.cam.v113.model.CamEnvelope113; +import com.orange.iot3mobility.messages.cam.v230.model.CamEnvelope230; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Objects; + +/** + * High-level helper around CamCodec. + *

+ * - Manages a shared JsonFactory and CamCodec instance. + * - Provides String-based APIs (convenient for MQTT payloads). + * - Thread-safe: stateless, all shared components are immutable. + */ +public final class CamHelper { + + private final JsonFactory jsonFactory; + private final CamCodec camCodec; + + /** + * Default constructor: creates its own JsonFactory. + * Recommended in most cases. + */ + public CamHelper() { + this(new JsonFactory()); + } + + /** + * Constructor with externally-provided JsonFactory (for advanced usage, + * e.g. custom Jackson configuration). + */ + public CamHelper(JsonFactory jsonFactory) { + this.jsonFactory = Objects.requireNonNull(jsonFactory, "jsonFactory"); + this.camCodec = new CamCodec(this.jsonFactory); + } + + // --------------------------------------------------------------------- + // Reading / parsing from String (e.g. MQTT payload) + // --------------------------------------------------------------------- + + /** + * Parse a CAM JSON payload (string) and let CamCodec detect the version. + * + * @param jsonPayload JSON string containing a CAM envelope + * @return a CamFrame with detected version and typed envelope + * @throws IOException if JSON is malformed or I/O error in parser + * @throws CamException (or subclasses) if the CAM structure/fields are invalid + */ + public CamCodec.CamFrame parse(String jsonPayload) throws IOException { + Objects.requireNonNull(jsonPayload, "jsonPayload"); + + return camCodec.read(jsonPayload); + } + + /** + * Parse a CAM JSON payload and cast it to a v1.1.3 envelope. + * Throws if the version is not 1.1.3. + */ + public CamEnvelope113 parse113(String jsonPayload) throws IOException { + CamCodec.CamFrame frame = parse(jsonPayload); + if (frame.version() != CamVersion.V1_1_3) { + throw new CamException("Expected CAM version 1.1.3 but got " + frame.version()); + } + return (CamEnvelope113) frame.envelope(); + } + + /** + * Parse a CAM JSON payload and cast it to a v2.3.0 envelope. + * Throws if the version is not 2.3.0. + */ + public CamEnvelope230 parse230(String jsonPayload) throws IOException { + CamCodec.CamFrame frame = parse(jsonPayload); + if (frame.version() != CamVersion.V2_3_0) { + throw new CamException("Expected CAM version 2.3.0 but got " + frame.version()); + } + return (CamEnvelope230) frame.envelope(); + } + + // --------------------------------------------------------------------- + // Writing / serializing to String + // --------------------------------------------------------------------- + + /** + * Serialize a v1.1.3 CAM envelope to a JSON string. + */ + public String toJson(CamEnvelope113 envelope113) throws IOException { + Objects.requireNonNull(envelope113, "envelope113"); + return writeToString(CamVersion.V1_1_3, envelope113); + } + + /** + * Serialize a v2.3.0 CAM envelope to a JSON string. + */ + public String toJson(CamEnvelope230 envelope230) throws IOException { + Objects.requireNonNull(envelope230, "envelope230"); + return writeToString(CamVersion.V2_3_0, envelope230); + } + + /** + * Generic entry point for CAM serialization to a JSON string. + */ + public String toJson(CamVersion version, Object envelope) throws IOException { + Objects.requireNonNull(version, "version"); + Objects.requireNonNull(envelope, "envelope"); + return writeToString(version, envelope); + } + + // --------------------------------------------------------------------- + // Internal helper + // --------------------------------------------------------------------- + + private String writeToString(CamVersion version, Object envelope) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(1024); + camCodec.write(version, envelope, out); + return out.toString(StandardCharsets.UTF_8); + } +} + diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/core/CamCodec.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/core/CamCodec.java new file mode 100644 index 000000000..bd707f997 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/core/CamCodec.java @@ -0,0 +1,130 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.core; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.orange.iot3mobility.messages.cam.v113.codec.CamReader113; +import com.orange.iot3mobility.messages.cam.v113.codec.CamWriter113; +import com.orange.iot3mobility.messages.cam.v113.model.CamEnvelope113; +import com.orange.iot3mobility.messages.cam.v230.codec.CamReader230; +import com.orange.iot3mobility.messages.cam.v230.codec.CamWriter230; +import com.orange.iot3mobility.messages.cam.v230.model.CamEnvelope230; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.Objects; + +/** + * Unified entry point to decode/encode CAM envelopes across all supported versions. + */ +public final class CamCodec { + + /** + * Wrapper exposing both the detected CAM version and the decoded envelope. + */ + public record CamFrame(CamVersion version, T envelope) {} + + private final JsonFactory jsonFactory; + private final CamReader113 reader113; + private final CamWriter113 writer113; + private final CamReader230 reader230; + private final CamWriter230 writer230; + + public CamCodec(JsonFactory jsonFactory) { + this.jsonFactory = Objects.requireNonNull(jsonFactory, "jsonFactory"); + this.reader113 = new CamReader113(jsonFactory); + this.writer113 = new CamWriter113(jsonFactory); + this.reader230 = new CamReader230(jsonFactory); + this.writer230 = new CamWriter230(jsonFactory); + } + + /** + * Reads the full payload, detects the CAM version from the top-level "version" field, + * then delegates to the appropriate reader. The returned {@link CamFrame} exposes both the version and the + * strongly-typed envelope instance. + */ + public CamFrame read(InputStream in) throws IOException { + byte[] payload = in.readAllBytes(); + CamVersion version = detectVersion(payload); + + return switch (version) { + case V1_1_3 -> new CamFrame<>(version, + reader113.read(new ByteArrayInputStream(payload))); + case V2_3_0 -> new CamFrame<>(version, + reader230.read(new ByteArrayInputStream(payload))); + }; + } + + /** + * Same as {@link #read(InputStream)} but takes a JSON String directly. + * Convenient for MQTT payloads that are received as String. + */ + public CamFrame read(String json) throws IOException { + Objects.requireNonNull(json, "json"); + byte[] payload = json.getBytes(StandardCharsets.UTF_8); + + CamVersion version = detectVersion(payload); + + return switch (version) { + case V1_1_3 -> new CamFrame<>(version, + reader113.read(new ByteArrayInputStream(payload))); + case V2_3_0 -> new CamFrame<>(version, + reader230.read(new ByteArrayInputStream(payload))); + }; + } + + /** + * Writes an envelope using the writer that matches the provided version. + */ + public void write(CamVersion version, Object envelope, OutputStream out) throws IOException { + switch (version) { + case V1_1_3 -> writer113.write(cast(envelope, CamEnvelope113.class), out); + case V2_3_0 -> writer230.write(cast(envelope, CamEnvelope230.class), out); + default -> throw new CamException("Unsupported version: " + version); + } + } + + /** + * Parses only the top-level "version" field without building any tree (streaming mode). + */ + private CamVersion detectVersion(byte[] payload) throws IOException { + try (JsonParser parser = jsonFactory.createParser(payload)) { + expect(parser.nextToken(), JsonToken.START_OBJECT); + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.getCurrentName(); + parser.nextToken(); + if ("version".equals(field)) { + return CamVersion.fromJsonValue(parser.getValueAsString()); + } else { + parser.skipChildren(); + } + } + } + throw new CamException("Missing 'version' field in CAM payload"); + } + + private static void expect(JsonToken actual, JsonToken expected) { + if (actual != expected) { + throw new CamException("Expected token " + expected + " but got " + actual); + } + } + + @SuppressWarnings("unchecked") + private static T cast(Object value, Class type) { + if (!type.isInstance(value)) { + throw new CamException("Expected envelope of type " + type.getName() + + " but got " + (value == null ? "null" : value.getClass().getName())); + } + return (T) value; + } +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/core/CamException.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/core/CamException.java new file mode 100644 index 000000000..b5fe1a0b6 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/core/CamException.java @@ -0,0 +1,26 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.core; + +/** + * Base runtime exception for all CAM codec operations. + */ +public class CamException extends RuntimeException { + + public CamException(String message) { + super(message); + } + + public CamException(String message, Throwable cause) { + super(message, cause); + } + + public CamException(Throwable cause) { + super(cause); + } +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/core/CamVersion.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/core/CamVersion.java new file mode 100644 index 000000000..8f50e777f --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/core/CamVersion.java @@ -0,0 +1,35 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.core; + +/** + * Supported CAM JSON envelope versions. + */ +public enum CamVersion { + V1_1_3("1.1.3"), + V2_3_0("2.3.0"); + + private final String jsonValue; + + CamVersion(String jsonValue) { + this.jsonValue = jsonValue; + } + + public String jsonValue() { + return jsonValue; + } + + public static CamVersion fromJsonValue(String value) { + for (CamVersion version : values()) { + if (version.jsonValue.equals(value)) { + return version; + } + } + throw new CamException("Unsupported CAM version: " + value); + } +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/codec/CamReader113.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/codec/CamReader113.java new file mode 100644 index 000000000..acebab292 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/codec/CamReader113.java @@ -0,0 +1,348 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v113.codec; + +import com.fasterxml.jackson.core.*; +import com.orange.iot3mobility.messages.cam.v113.model.*; +import com.orange.iot3mobility.messages.cam.v113.validation.CamValidationException; +import com.orange.iot3mobility.messages.cam.v113.validation.CamValidator113; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +public final class CamReader113 { + + private final JsonFactory jsonFactory; + + public CamReader113(JsonFactory jsonFactory) { + this.jsonFactory = jsonFactory; + } + + public CamEnvelope113 read(InputStream in) throws IOException { + try (JsonParser parser = jsonFactory.createParser(in)) { + expect(parser.nextToken(), JsonToken.START_OBJECT); + + String type = null; + String origin = null; + String version = null; + String sourceUuid = null; + Long timestamp = null; + CamMessage113 message = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "type" -> type = parser.getValueAsString(); + case "origin" -> origin = parser.getValueAsString(); + case "version" -> version = parser.getValueAsString(); + case "source_uuid" -> sourceUuid = parser.getValueAsString(); + case "timestamp" -> timestamp = parser.getLongValue(); + case "message" -> message = readMessage(parser); + default -> parser.skipChildren(); + } + } + + CamEnvelope113 envelope = new CamEnvelope113( + requireField(type, "type"), + requireField(origin, "origin"), + requireField(version, "version"), + requireField(sourceUuid, "source_uuid"), + requireField(timestamp, "timestamp"), + requireField(message, "message")); + + CamValidator113.validateEnvelope(envelope); + return envelope; + } + } + + private CamMessage113 readMessage(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + + Integer protocolVersion = null; + Long stationId = null; + Integer generationDelta = null; + BasicContainer basic = null; + HighFrequencyContainer high = null; + LowFrequencyContainer low = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "protocol_version" -> protocolVersion = parser.getIntValue(); + case "station_id" -> stationId = parser.getLongValue(); + case "generation_delta_time" -> generationDelta = parser.getIntValue(); + case "basic_container" -> basic = readBasic(parser); + case "high_frequency_container" -> high = readHighFrequency(parser); + case "low_frequency_container" -> low = readLowFrequency(parser); + default -> parser.skipChildren(); + } + } + + return new CamMessage113( + requireField(protocolVersion, "protocol_version"), + requireField(stationId, "station_id"), + requireField(generationDelta, "generation_delta_time"), + requireField(basic, "basic_container"), + requireField(high, "high_frequency_container"), + low); + } + + private BasicContainer readBasic(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Integer stationType = null; + ReferencePosition reference = null; + PositionConfidence confidence = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "station_type" -> stationType = parser.getIntValue(); + case "reference_position" -> reference = readReferencePosition(parser); + case "confidence" -> confidence = readConfidence(parser); + default -> parser.skipChildren(); + } + } + return new BasicContainer( + requireField(stationType, "basic_container.station_type"), + requireField(reference, "basic_container.reference_position"), + confidence); + } + + private ReferencePosition readReferencePosition(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Integer lat = null, lon = null, alt = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "latitude" -> lat = parser.getIntValue(); + case "longitude" -> lon = parser.getIntValue(); + case "altitude" -> alt = parser.getIntValue(); + default -> parser.skipChildren(); + } + } + return new ReferencePosition( + requireField(lat, "latitude"), + requireField(lon, "longitude"), + requireField(alt, "altitude")); + } + + private PositionConfidence readConfidence(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + PositionConfidenceEllipse ellipse = null; + Integer altitude = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "position_confidence_ellipse" -> ellipse = readEllipse(parser); + case "altitude" -> altitude = parser.getIntValue(); + default -> parser.skipChildren(); + } + } + return new PositionConfidence( + requireField(ellipse, "position_confidence_ellipse"), + requireField(altitude, "altitude_confidence")); + } + + private PositionConfidenceEllipse readEllipse(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Integer semiMajor = null, semiMinor = null, orientation = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "semi_major_confidence" -> semiMajor = parser.getIntValue(); + case "semi_minor_confidence" -> semiMinor = parser.getIntValue(); + case "semi_major_orientation" -> orientation = parser.getIntValue(); + default -> parser.skipChildren(); + } + } + return new PositionConfidenceEllipse( + requireField(semiMajor, "semi_major_confidence"), + requireField(semiMinor, "semi_minor_confidence"), + requireField(orientation, "semi_major_orientation")); + } + + private HighFrequencyContainer readHighFrequency(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + + Integer heading = null; + Integer speed = null; + Integer direction = null; + Integer vehicleLength = null; + Integer vehicleWidth = null; + Integer curvature = null; + Integer curvatureMode = null; + Integer longitudinal = null; + Integer yawRate = null; + String accControl = null; + Integer lane = null; + Integer lateral = null; + Integer vertical = null; + HighFrequencyConfidence confidence = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "heading" -> heading = parser.getIntValue(); + case "speed" -> speed = parser.getIntValue(); + case "drive_direction" -> direction = parser.getIntValue(); + case "vehicle_length" -> vehicleLength = parser.getIntValue(); + case "vehicle_width" -> vehicleWidth = parser.getIntValue(); + case "curvature" -> curvature = parser.getIntValue(); + case "curvature_calculation_mode" -> curvatureMode = parser.getIntValue(); + case "longitudinal_acceleration" -> longitudinal = parser.getIntValue(); + case "yaw_rate" -> yawRate = parser.getIntValue(); + case "acceleration_control" -> accControl = parser.getValueAsString(); + case "lane_position" -> lane = parser.getIntValue(); + case "lateral_acceleration" -> lateral = parser.getIntValue(); + case "vertical_acceleration" -> vertical = parser.getIntValue(); + case "confidence" -> confidence = readHighFrequencyConfidence(parser); + default -> parser.skipChildren(); + } + } + + return new HighFrequencyContainer( + heading, + speed, + direction, + vehicleLength, + vehicleWidth, + curvature, + curvatureMode, + longitudinal, + yawRate, + accControl, + lane, + lateral, + vertical, + confidence); + } + + private HighFrequencyConfidence readHighFrequencyConfidence(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Integer heading = null, speed = null, vehicleLength = null, yawRate = null; + Integer longitudinal = null, curvature = null, lateral = null, vertical = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "heading" -> heading = parser.getIntValue(); + case "speed" -> speed = parser.getIntValue(); + case "vehicle_length" -> vehicleLength = parser.getIntValue(); + case "yaw_rate" -> yawRate = parser.getIntValue(); + case "longitudinal_acceleration" -> longitudinal = parser.getIntValue(); + case "curvature" -> curvature = parser.getIntValue(); + case "lateral_acceleration" -> lateral = parser.getIntValue(); + case "vertical_acceleration" -> vertical = parser.getIntValue(); + default -> parser.skipChildren(); + } + } + return new HighFrequencyConfidence( + heading, + speed, + vehicleLength, + yawRate, + longitudinal, + curvature, + lateral, + vertical); + } + + private LowFrequencyContainer readLowFrequency(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Integer vehicleRole = null; + String lights = null; + List history = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "vehicle_role" -> vehicleRole = parser.getIntValue(); + case "exterior_lights" -> lights = parser.getValueAsString(); + case "path_history" -> history = readPathHistory(parser); + default -> parser.skipChildren(); + } + } + return new LowFrequencyContainer( + requireField(vehicleRole, "vehicle_role"), + requireField(lights, "exterior_lights"), + requireField(history, "path_history")); + } + + private List readPathHistory(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_ARRAY); + List points = new ArrayList<>(); + while (parser.nextToken() != JsonToken.END_ARRAY) { + points.add(readPathPoint(parser)); + } + return points; + } + + private PathPoint readPathPoint(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + DeltaReferencePosition delta = null; + Integer deltaTime = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "path_position" -> delta = readDeltaPosition(parser); + case "path_delta_time" -> deltaTime = parser.getIntValue(); + default -> parser.skipChildren(); + } + } + return new PathPoint(requireField(delta, "path_position"), deltaTime); + } + + private DeltaReferencePosition readDeltaPosition(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Integer lat = null, lon = null, alt = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "delta_latitude" -> lat = parser.getIntValue(); + case "delta_longitude" -> lon = parser.getIntValue(); + case "delta_altitude" -> alt = parser.getIntValue(); + default -> parser.skipChildren(); + } + } + return new DeltaReferencePosition( + requireField(lat, "delta_latitude"), + requireField(lon, "delta_longitude"), + requireField(alt, "delta_altitude")); + } + + private static T requireField(T value, String field) { + if (value == null) { + throw new CamValidationException("Missing mandatory field: " + field); + } + return value; + } + + private static void expect(JsonToken actual, JsonToken expected) throws JsonParseException { + if (actual != expected) { + throw new JsonParseException(null, "Expected " + expected + " but got " + actual); + } + } +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/codec/CamWriter113.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/codec/CamWriter113.java new file mode 100644 index 000000000..963b767bf --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/codec/CamWriter113.java @@ -0,0 +1,163 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v113.codec; + +import com.fasterxml.jackson.core.*; +import com.orange.iot3mobility.messages.cam.v113.model.*; +import com.orange.iot3mobility.messages.cam.v113.validation.CamValidator113; + +import java.io.IOException; +import java.io.OutputStream; + +public final class CamWriter113 { + + private final JsonFactory jsonFactory; + + public CamWriter113(JsonFactory jsonFactory) { + this.jsonFactory = jsonFactory; + } + + public void write(CamEnvelope113 envelope, OutputStream out) throws IOException { + CamValidator113.validateEnvelope(envelope); + + try (JsonGenerator gen = jsonFactory.createGenerator(out)) { + gen.writeStartObject(); + gen.writeStringField("type", envelope.type()); + gen.writeStringField("origin", envelope.origin()); + gen.writeStringField("version", envelope.version()); + gen.writeStringField("source_uuid", envelope.sourceUuid()); + gen.writeNumberField("timestamp", envelope.timestamp()); + gen.writeFieldName("message"); + writeMessage(gen, envelope.message()); + gen.writeEndObject(); + } + } + + private void writeMessage(JsonGenerator gen, CamMessage113 msg) throws IOException { + gen.writeStartObject(); + gen.writeNumberField("protocol_version", msg.protocolVersion()); + gen.writeNumberField("station_id", msg.stationId()); + gen.writeNumberField("generation_delta_time", msg.generationDeltaTime()); + + gen.writeFieldName("basic_container"); + writeBasic(gen, msg.basicContainer()); + + gen.writeFieldName("high_frequency_container"); + writeHighFrequency(gen, msg.highFrequencyContainer()); + + if(msg.lowFrequencyContainer() != null) { + gen.writeFieldName("low_frequency_container"); + writeLowFrequency(gen, msg.lowFrequencyContainer()); + } + + gen.writeEndObject(); + } + + private void writeBasic(JsonGenerator gen, BasicContainer basic) throws IOException { + gen.writeStartObject(); + gen.writeNumberField("station_type", basic.stationType()); + + gen.writeFieldName("reference_position"); + gen.writeStartObject(); + gen.writeNumberField("latitude", basic.referencePosition().latitude()); + gen.writeNumberField("longitude", basic.referencePosition().longitude()); + gen.writeNumberField("altitude", basic.referencePosition().altitude()); + gen.writeEndObject(); + + if(basic.confidence() != null) { + gen.writeFieldName("confidence"); + gen.writeStartObject(); + gen.writeFieldName("position_confidence_ellipse"); + gen.writeStartObject(); + gen.writeNumberField("semi_major_confidence", basic.confidence().ellipse().semiMajor()); + gen.writeNumberField("semi_minor_confidence", basic.confidence().ellipse().semiMinor()); + gen.writeNumberField("semi_major_orientation", basic.confidence().ellipse().semiMajorOrientation()); + gen.writeEndObject(); + gen.writeNumberField("altitude", basic.confidence().altitude()); + gen.writeEndObject(); + } + gen.writeEndObject(); + } + + private void writeHighFrequency(JsonGenerator gen, HighFrequencyContainer hf) throws IOException { + gen.writeStartObject(); + if(hf.heading() != null) + gen.writeNumberField("heading", hf.heading()); + if(hf.speed() != null) + gen.writeNumberField("speed", hf.speed()); + if(hf.driveDirection() != null) + gen.writeNumberField("drive_direction", hf.driveDirection()); + if(hf.vehicleLength() != null) + gen.writeNumberField("vehicle_length", hf.vehicleLength()); + if(hf.vehicleWidth() != null) + gen.writeNumberField("vehicle_width", hf.vehicleWidth()); + if(hf.curvature() != null) + gen.writeNumberField("curvature", hf.curvature()); + if(hf.curvatureCalculationMode() != null) + gen.writeNumberField("curvature_calculation_mode", hf.curvatureCalculationMode()); + if(hf.longitudinalAcceleration() != null) + gen.writeNumberField("longitudinal_acceleration", hf.longitudinalAcceleration()); + if(hf.yawRate() != null) + gen.writeNumberField("yaw_rate", hf.yawRate()); + if(hf.accelerationControl() != null) + gen.writeStringField("acceleration_control", hf.accelerationControl()); + if (hf.lanePosition() != null) + gen.writeNumberField("lane_position", hf.lanePosition()); + if (hf.lateralAcceleration() != null) + gen.writeNumberField("lateral_acceleration", hf.lateralAcceleration()); + if (hf.verticalAcceleration() != null) + gen.writeNumberField("vertical_acceleration", hf.verticalAcceleration()); + if(hf.confidence() != null) { + gen.writeFieldName("confidence"); + gen.writeStartObject(); + HighFrequencyConfidence conf = hf.confidence(); + if(conf.heading() != null) + gen.writeNumberField("heading", conf.heading()); + if(conf.speed() != null) + gen.writeNumberField("speed", conf.speed()); + if(conf.vehicleLength() != null) + gen.writeNumberField("vehicle_length", conf.vehicleLength()); + if(conf.yawRate() != null) + gen.writeNumberField("yaw_rate", conf.yawRate()); + if(conf.longitudinalAcceleration() != null) + gen.writeNumberField("longitudinal_acceleration", conf.longitudinalAcceleration()); + if(conf.curvature() != null) + gen.writeNumberField("curvature", conf.curvature()); + if(conf.lateralAcceleration() != null) + gen.writeNumberField("lateral_acceleration", conf.lateralAcceleration()); + if(conf.verticalAcceleration() != null) + gen.writeNumberField("vertical_acceleration", conf.verticalAcceleration()); + gen.writeEndObject(); + } + gen.writeEndObject(); + } + + private void writeLowFrequency(JsonGenerator gen, LowFrequencyContainer lf) throws IOException { + gen.writeStartObject(); + if(lf.vehicleRole() != null) + gen.writeNumberField("vehicle_role", lf.vehicleRole()); + gen.writeStringField("exterior_lights", lf.exteriorLights()); + gen.writeFieldName("path_history"); + gen.writeStartArray(); + for (PathPoint point : lf.pathHistory()) { + gen.writeStartObject(); + gen.writeFieldName("path_position"); + gen.writeStartObject(); + gen.writeNumberField("delta_latitude", point.deltaPosition().deltaLatitude()); + gen.writeNumberField("delta_longitude", point.deltaPosition().deltaLongitude()); + gen.writeNumberField("delta_altitude", point.deltaPosition().deltaAltitude()); + gen.writeEndObject(); + if (point.deltaTime() != null) { + gen.writeNumberField("path_delta_time", point.deltaTime()); + } + gen.writeEndObject(); + } + gen.writeEndArray(); + gen.writeEndObject(); + } +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/model/BasicContainer.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/model/BasicContainer.java new file mode 100644 index 000000000..78f444575 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/model/BasicContainer.java @@ -0,0 +1,73 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v113.model; + +import com.orange.iot3mobility.messages.StationType; + +/** + * BasicContainer v1.1.3 + * + * @param stationType {@link StationType} Integer value + * @param referencePosition {@link ReferencePosition} + * @param confidence {@link PositionConfidence} + */ +public record BasicContainer( + int stationType, + ReferencePosition referencePosition, + PositionConfidence confidence) { + + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for BasicContainer. + *

+ * Mandatory fields: + *

+ */ + public static final class Builder { + private Integer stationType; + private ReferencePosition referencePosition; + private PositionConfidence confidence; + + private Builder() {} + + public Builder stationType(int stationType) { + this.stationType = stationType; + return this; + } + + public Builder referencePosition(ReferencePosition referencePosition) { + this.referencePosition = referencePosition; + return this; + } + + public Builder positionConfidence(PositionConfidence confidence) { + this.confidence = confidence; + return this; + } + + public BasicContainer build() { + return new BasicContainer( + requireNonNull(stationType, "station_type"), + requireNonNull(referencePosition, "reference_position"), + confidence); + } + + private static T requireNonNull(T value, String field) { + if (value == null) { + throw new IllegalStateException("Missing field: " + field); + } + return value; + } + } +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/model/CamEnvelope113.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/model/CamEnvelope113.java new file mode 100644 index 000000000..90abdc795 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/model/CamEnvelope113.java @@ -0,0 +1,95 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v113.model; + +/** + * CamEnvelope113 - base class to build a CAM v1.1.3 with its header + * + * @param type message type (cam) + * @param origin {@link Origin} + * @param version json message format version (1.1.3) + * @param sourceUuid identifier + * @param timestamp Unit: millisecond. The timestamp when the message was generated since Unix Epoch (1970/01/01) + * @param message {@link CamMessage113} + */ +public record CamEnvelope113( + String type, + String origin, + String version, + String sourceUuid, + long timestamp, + CamMessage113 message) { + + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for CamEnvelope113. + *

+ * Mandatory fields: + *

    + *
  • type - hardcoded (cam)
  • + *
  • origin - see {@link Origin}
  • + *
  • version - hardcoded (1.1.3)
  • + *
  • sourceUuid
  • + *
  • timestamp
  • + *
  • message
  • + *
+ */ + public static final class Builder { + private final String type; + private String origin; + private final String version; + private String sourceUuid; + private Long timestamp; + private CamMessage113 message; + + private Builder() { + this.type = "cam"; + this.version = "1.1.3"; + } + + public Builder origin(String origin) { + this.origin = origin; + return this; + } + + public Builder sourceUuid(String sourceUuid) { + this.sourceUuid = sourceUuid; + return this; + } + + public Builder timestamp(long timestamp) { + this.timestamp = timestamp; + return this; + } + + public Builder message(CamMessage113 message) { + this.message = message; + return this; + } + + public CamEnvelope113 build() { + return new CamEnvelope113( + requireNonNull(type, "type"), + requireNonNull(origin, "origin"), + requireNonNull(version, "version"), + requireNonNull(sourceUuid, "source_uuid"), + requireNonNull(timestamp, "timestamp"), + requireNonNull(message, "message")); + } + + private static T requireNonNull(T value, String field) { + if (value == null) { + throw new IllegalStateException("Missing field: " + field); + } + return value; + } + } +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/model/CamMessage113.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/model/CamMessage113.java new file mode 100644 index 000000000..42719d7ce --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/model/CamMessage113.java @@ -0,0 +1,103 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v113.model; + +/** + * CAM v1.1.3 + * + * @param protocolVersion version of the ITS message and/or communication protocol + * @param stationId identifier for an ITS-S [0 - 4294967295] + * @param generationDeltaTime time of the reference position in the CAM, considered as time of the CAM generation. + * TimestampIts mod 65 536. TimestampIts represents an integer value in milliseconds since + * 2004-01-01T00:00:00:000Z. oneMilliSec(1) + * @param basicContainer {@link BasicContainer} + * @param highFrequencyContainer {@link HighFrequencyContainer} + * @param lowFrequencyContainer {@link LowFrequencyContainer} + */ +public record CamMessage113( + int protocolVersion, + long stationId, + int generationDeltaTime, + BasicContainer basicContainer, + HighFrequencyContainer highFrequencyContainer, + LowFrequencyContainer lowFrequencyContainer) { + + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for CamMessage113. + *

+ * Mandatory fields: + *

    + *
  • protocolVersion
  • + *
  • stationId
  • + *
  • generationDeltaTime
  • + *
  • basicContainer
  • + *
  • highFrequencyContainer
  • + *
+ */ + public static final class Builder { + private Integer protocolVersion; + private Long stationId; + private Integer generationDeltaTime; + private BasicContainer basicContainer; + private HighFrequencyContainer highFrequencyContainer; + private LowFrequencyContainer lowFrequencyContainer; + + private Builder() {} + + public Builder protocolVersion(int protocolVersion) { + this.protocolVersion = protocolVersion; + return this; + } + + public Builder stationId(long stationId) { + this.stationId = stationId; + return this; + } + + public Builder generationDeltaTime(int generationDeltaTime) { + this.generationDeltaTime = generationDeltaTime; + return this; + } + + public Builder basicContainer(BasicContainer basicContainer) { + this.basicContainer = basicContainer; + return this; + } + + public Builder highFrequencyContainer(HighFrequencyContainer highFrequencyContainer) { + this.highFrequencyContainer = highFrequencyContainer; + return this; + } + + public Builder lowFrequencyContainer(LowFrequencyContainer lowFrequencyContainer) { + this.lowFrequencyContainer = lowFrequencyContainer; + return this; + } + + public CamMessage113 build() { + return new CamMessage113( + requireNonNull(protocolVersion, "protocol_version"), + requireNonNull(stationId, "station_id"), + requireNonNull(generationDeltaTime, "generation_delta_time"), + requireNonNull(basicContainer, "basic_container"), + requireNonNull(highFrequencyContainer, "high_frequency_container"), + lowFrequencyContainer); + } + + private static T requireNonNull(T value, String field) { + if (value == null) { + throw new IllegalStateException("Missing field: " + field); + } + return value; + } + } +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/model/DeltaReferencePosition.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/model/DeltaReferencePosition.java new file mode 100644 index 000000000..13fb4c813 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/model/DeltaReferencePosition.java @@ -0,0 +1,23 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v113.model; + +/** + * DeltaReferencePosition v1.1.3 + *

+ * Offset position of a detected event point with regards to the previous detected + * event point {@link ReferencePosition}. + * + * @param deltaLatitude oneMicrodegreeNorth (10), oneMicrodegreeSouth (-10) , unavailable(131072) + * @param deltaLongitude oneMicrodegreeEast (10), oneMicrodegreeWest (-10), unavailable(131072) + * @param deltaAltitude oneCentimeterUp (1), oneCentimeterDown (-1), unavailable(12800) + */ +public record DeltaReferencePosition( + int deltaLatitude, + int deltaLongitude, + int deltaAltitude) {} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/model/HighFrequencyConfidence.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/model/HighFrequencyConfidence.java new file mode 100644 index 000000000..2ad8c3ddc --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/model/HighFrequencyConfidence.java @@ -0,0 +1,108 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v113.model; + +/** + * HighFrequencyConfidence v1.1.3 + * + * @param heading equalOrWithinZeroPointOneDegree (1), equalOrWithinOneDegree (10), outOfRange(126), unavailable(127) + * @param speed equalOrWithinOneCentimeterPerSec(1), equalOrWithinOneMeterPerSec(100), outOfRange(126), unavailable(127) + * @param vehicleLength noTrailerPresent(0), trailerPresentWithKnownLength(1), trailerPresentWithUnknownLength(2), + * trailerPresenceIsUnknown(3), unavailable(4) + * @param yawRate degSec-000-01 (0), degSec-000-05 (1), degSec-000-10 (2), degSec-001-00 (3), degSec-005-00 (4), + * degSec-010-00 (5), degSec-100-00 (6), outOfRange (7), unavailable (8) + * @param longitudinalAcceleration Unit: 0.1 m/s2. pointOneMeterPerSecSquared(1), outOfRange(101), unavailable(102) + * @param curvature onePerMeter-0-00002 (0), onePerMeter-0-0001 (1), onePerMeter-0-0005 (2), onePerMeter-0-002 (3), + * onePerMeter-0-01 (4), onePerMeter-0-1 (5), outOfRange (6), unavailable (7) + * @param lateralAcceleration Unit: 0.1 m/s2. pointOneMeterPerSecSquared(1), outOfRange(101), unavailable(102) + * @param verticalAcceleration Unit: 0.1 m/s2. pointOneMeterPerSecSquared(1), outOfRange(101), unavailable(102) + */ +public record HighFrequencyConfidence( + Integer heading, + Integer speed, + Integer vehicleLength, + Integer yawRate, + Integer longitudinalAcceleration, + Integer curvature, + Integer lateralAcceleration, + Integer verticalAcceleration) { + + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for HighFrequencyConfidence. + *

+ * All fields are optional. + */ + public static final class Builder { + private Integer heading; + private Integer speed; + private Integer vehicleLength; + private Integer yawRate; + private Integer longitudinalAcceleration; + private Integer curvature; + private Integer lateralAcceleration; + private Integer verticalAcceleration; + + private Builder() {} + + public Builder heading(int heading) { + this.heading = heading; + return this; + } + + public Builder speed(int speed) { + this.speed = speed; + return this; + } + + public Builder vehicleLength(int vehicleLength) { + this.vehicleLength = vehicleLength; + return this; + } + + public Builder yawRate(int yawRate) { + this.yawRate = yawRate; + return this; + } + + public Builder longitudinalAcceleration(int longitudinalAcceleration) { + this.longitudinalAcceleration = longitudinalAcceleration; + return this; + } + + public Builder curvature(int curvature) { + this.curvature = curvature; + return this; + } + + public Builder lateralAcceleration(int lateralAcceleration) { + this.lateralAcceleration = lateralAcceleration; + return this; + } + + public Builder verticalAcceleration(int verticalAcceleration) { + this.verticalAcceleration = verticalAcceleration; + return this; + } + + public HighFrequencyConfidence build() { + return new HighFrequencyConfidence( + heading, + speed, + vehicleLength, + yawRate, + longitudinalAcceleration, + curvature, + lateralAcceleration, + verticalAcceleration); + } + } +} \ No newline at end of file diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/model/HighFrequencyContainer.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/model/HighFrequencyContainer.java new file mode 100644 index 000000000..b28319025 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/model/HighFrequencyContainer.java @@ -0,0 +1,162 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v113.model; + +/** + * HighFrequencyContainer v1.1.3 + * + * @param heading Unit: 0.1 degree. wgs84North(0), wgs84East(900), wgs84South(1800), wgs84West(2700), unavailable(3601) + * @param speed Unit 0.01 m/s. standstill(0), oneCentimeterPerSec(1), unavailable(16383) + * @param driveDirection forward (0), backward (1), unavailable (2) + * @param vehicleLength tenCentimeters(1), outOfRange(1022), unavailable(1023) + * @param vehicleWidth tenCentimeters(1), outOfRange(61), unavailable(62) + * @param curvature straight(0), unavailable(1023) + * @param curvatureCalculationMode Whether the yaw rate is used to calculate the curvature: yawRateUsed(0), + * yawRateNotUsed(1), unavailable(2) + * @param longitudinalAcceleration Unit: 0.1 m/s2. pointOneMeterPerSecSquaredForward(1), + * pointOneMeterPerSecSquaredBackward(-1), unavailable(161) + * @param yawRate Unit: 0.01 degree/s: straight(0), degSec-000-01ToRight(-1), degSec-000-01ToLeft(1), unavailable(32767) + * @param accelerationControl Current controlling mechanism for longitudinal movement of the vehicle. Represented as a + * bit string: brakePedalEngaged (0), gasPedalEngaged (1), emergencyBrakeEngaged (2), + * collisionWarningEngaged(3), accEngaged(4), cruiseControlEngaged(5), speedLimiterEngaged(6) + * @param lanePosition offTheRoad(-1), innerHardShoulder(0), innermostDrivingLane(1), secondLaneFromInside(2), + * outerHardShoulder(14) + * @param lateralAcceleration Unit: 0.1 m/s2. pointOneMeterPerSecSquaredToRight(-1), + * pointOneMeterPerSecSquaredToLeft(1), unavailable(161) + * @param verticalAcceleration Unit: 0.1 m/s2. pointOneMeterPerSecSquaredUp(1), pointOneMeterPerSecSquaredDown(-1), + * unavailable(161) + * @param confidence {@link PositionConfidence} + */ +public record HighFrequencyContainer( + Integer heading, + Integer speed, + Integer driveDirection, + Integer vehicleLength, + Integer vehicleWidth, + Integer curvature, + Integer curvatureCalculationMode, + Integer longitudinalAcceleration, + Integer yawRate, + String accelerationControl, + Integer lanePosition, + Integer lateralAcceleration, + Integer verticalAcceleration, + HighFrequencyConfidence confidence) { + + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for BasicContainer. + *

+ * All fields are optional. + */ + public static final class Builder { + private Integer heading; + private Integer speed; + private Integer driveDirection; + private Integer vehicleLength; + private Integer vehicleWidth; + private Integer curvature; + private Integer curvatureCalculationMode; + private Integer longitudinalAcceleration; + private Integer yawRate; + private String accelerationControl; + private Integer lanePosition; + private Integer lateralAcceleration; + private Integer verticalAcceleration; + private HighFrequencyConfidence confidence; + + private Builder() {} + + public Builder heading(int heading) { + this.heading = heading; + return this; + } + + public Builder speed(int speed) { + this.speed = speed; + return this; + } + + public Builder driveDirection(int driveDirection) { + this.driveDirection = driveDirection; + return this; + } + + public Builder vehicleSize(int vehicleLength, int vehicleWidth) { + this.vehicleLength = vehicleLength; + this.vehicleWidth = vehicleWidth; + return this; + } + + public Builder curvature(int curvature) { + this.curvature = curvature; + return this; + } + + public Builder curvatureCalculationMode(int curvatureCalculationMode) { + this.curvatureCalculationMode = curvatureCalculationMode; + return this; + } + + public Builder longitudinalAcceleration(int longitudinalAcceleration) { + this.longitudinalAcceleration = longitudinalAcceleration; + return this; + } + + public Builder yawRate(int yawRate) { + this.yawRate = yawRate; + return this; + } + + public Builder accelerationControl(String accelerationControl) { + this.accelerationControl = accelerationControl; + return this; + } + + public Builder lanePosition(int lanePosition) { + this.lanePosition = lanePosition; + return this; + } + + public Builder lateralAcceleration(int lateralAcceleration) { + this.lateralAcceleration = lateralAcceleration; + return this; + } + + public Builder verticalAcceleration(int verticalAcceleration) { + this.verticalAcceleration = verticalAcceleration; + return this; + } + + public Builder highFrequencyConfidence(HighFrequencyConfidence confidence) { + this.confidence = confidence; + return this; + } + + public HighFrequencyContainer build() { + return new HighFrequencyContainer( + heading, + speed, + driveDirection, + vehicleLength, + vehicleWidth, + curvature, + curvatureCalculationMode, + longitudinalAcceleration, + yawRate, + accelerationControl, + lanePosition, + lateralAcceleration, + verticalAcceleration, + confidence); + } + } +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/model/LowFrequencyContainer.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/model/LowFrequencyContainer.java new file mode 100644 index 000000000..fe12c950d --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/model/LowFrequencyContainer.java @@ -0,0 +1,78 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v113.model; + +import java.util.List; + +/** + * LowFrequencyContainer v1.1.3 + * + * @param vehicleRole default(0), publicTransport(1), specialTransport(2), dangerousGoods(3), roadWork(4), rescue(5), + * emergency(6), safetyCar(7), agriculture(8),commercial(9),military(10),roadOperator(11),taxi(12), + * reserved1(13), reserved2(14), reserved3(15) + * @param exteriorLights Status of the exterior light switches represented as a bit string: lowBeamHeadlightsOn (0), + * highBeamHeadlightsOn (1), leftTurnSignalOn (2), rightTurnSignalOn (3), + * daytimeRunningLightsOn (4), reverseLightOn (5), fogLightOn (6), parkingLightsOn (7) + * @param pathHistory the path history, a path with a set of {@link PathPoint} + */ +public record LowFrequencyContainer( + Integer vehicleRole, + String exteriorLights, + List pathHistory) { + + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for LowFrequencyContainer. + *

+ * Mandatory fields: + *

    + *
  • vehicleRole
  • + *
  • exteriorLights
  • + *
  • pathHistory
  • + *
+ */ + public static final class Builder { + private Integer vehicleRole; + private String exteriorLights; + private List pathHistory; + + private Builder() {} + + public Builder vehicleRole(int vehicleRole) { + this.vehicleRole = vehicleRole; + return this; + } + + public Builder exteriorLights(String exteriorLights) { + this.exteriorLights = exteriorLights; + return this; + } + + public Builder pathHistory(List pathHistory) { + this.pathHistory = pathHistory; + return this; + } + + public LowFrequencyContainer build() { + return new LowFrequencyContainer( + requireNonNull(vehicleRole, "vehicle_role"), + requireNonNull(exteriorLights, "exterior_lights"), + requireNonNull(pathHistory, "path_history")); + } + + private static T requireNonNull(T value, String field) { + if (value == null) { + throw new IllegalStateException("Missing field: " + field); + } + return value; + } + } +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/model/Origin.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/model/Origin.java new file mode 100644 index 000000000..e5b676608 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/model/Origin.java @@ -0,0 +1,26 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v113.model; + +/** + * Origin v1.1.3 + *

+ * The entity responsible for this message. + */ +public enum Origin { + SELF("self"), + GLOBAL_APPLICATION("global_application"), + MEC_APPLICATION("mec_application"), + ON_BOARD_APPLICATION("on_board_application"); + + public final String value; + + Origin(String value) { + this.value = value; + } +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/model/PathPoint.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/model/PathPoint.java new file mode 100644 index 000000000..966a02776 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/model/PathPoint.java @@ -0,0 +1,19 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v113.model; + +/** + * PathPoint v1.1.3 + * + * @param deltaPosition {@link DeltaReferencePosition} + * @param deltaTime time traveled by the detecting ITS-S since the previous detected event point + * (generation_delta_time). tenMilliSecondsInPast(1) + */ +public record PathPoint( + DeltaReferencePosition deltaPosition, + Integer deltaTime) {} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/model/PositionConfidence.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/model/PositionConfidence.java new file mode 100644 index 000000000..95c358ed9 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/model/PositionConfidence.java @@ -0,0 +1,20 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v113.model; + +/** + * PositionConfidence v1.1.3 + * + * @param ellipse {@link PositionConfidenceEllipse} + * @param altitude alt-000-01 (0), alt-000-02 (1), alt-000-05 (2), alt-000-10 (3), alt-000-20 (4), alt-000-50 (5), + * alt-001-00 (6), alt-002-00 (7), alt-005-00 (8), alt-010-00 (9), alt-020-00 (10), alt-050-00 (11), + * alt-100-00 (12), alt-200-00 (13), outOfRange (14), unavailable (15) + */ +public record PositionConfidence( + PositionConfidenceEllipse ellipse, + int altitude) {} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/model/PositionConfidenceEllipse.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/model/PositionConfidenceEllipse.java new file mode 100644 index 000000000..2e56515d5 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/model/PositionConfidenceEllipse.java @@ -0,0 +1,20 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v113.model; + +/** + * PositionConfidenceEllipse v1.1.3 + * + * @param semiMajor oneCentimeter(1), outOfRange(4094), unavailable(4095) + * @param semiMinor oneCentimeter(1), outOfRange(4094), unavailable(4095) + * @param semiMajorOrientation wgs84North(0), wgs84East(900), wgs84South(1800), wgs84West(2700), unavailable(3601) + */ +public record PositionConfidenceEllipse( + int semiMajor, + int semiMinor, + int semiMajorOrientation) {} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/model/ReferencePosition.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/model/ReferencePosition.java new file mode 100644 index 000000000..5c76bfd1b --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/model/ReferencePosition.java @@ -0,0 +1,20 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v113.model; + +/** + * ReferencePosition v1.1.3 + * + * @param latitude Unit: 0.1 microdegree. oneMicrodegreeNorth (10), oneMicrodegreeSouth (-10), unavailable(900000001) + * @param longitude Unit: 0.1 microdegree. oneMicrodegreeEast (10), oneMicrodegreeWest (-10), unavailable(1800000001) + * @param altitude Unit: 0.01 meter. referenceEllipsoidSurface(0), oneCentimeter(1), unavailable(800001) + */ +public record ReferencePosition( + int latitude, + int longitude, + int altitude) {} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/validation/CamValidationException.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/validation/CamValidationException.java new file mode 100644 index 000000000..9a02428a7 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/validation/CamValidationException.java @@ -0,0 +1,14 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v113.validation; + +public final class CamValidationException extends RuntimeException { + public CamValidationException(String message) { + super(message); + } +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/validation/CamValidator113.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/validation/CamValidator113.java new file mode 100644 index 000000000..77ac6f8b1 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v113/validation/CamValidator113.java @@ -0,0 +1,161 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v113.validation; + +import com.orange.iot3mobility.messages.cam.v113.model.*; + +import java.util.List; +import java.util.Objects; + +public final class CamValidator113 { + + private CamValidator113() {} + + public static void validateEnvelope(CamEnvelope113 env) { + requireEquals("type", env.type(), "cam"); + requireEnum("origin", env.origin(), + List.of("self", "global_application", "mec_application", "on_board_application")); + requireEquals("version", env.version(), "1.1.3"); + requireNotBlank("source_uuid", env.sourceUuid()); + checkRange("timestamp", env.timestamp(), 1514764800000L, 1830297600000L); + validateMessage(env.message()); + } + + public static void validateMessage(CamMessage113 msg) { + requireNonNull("message", msg); + checkRange("protocol_version", msg.protocolVersion(), 0, 255); + checkRange("station_id", msg.stationId(), 0, 4294967295L); + checkRange("generation_delta_time", msg.generationDeltaTime(), 0, 65535); + validateBasic(msg.basicContainer()); + validateHighFrequency(msg.highFrequencyContainer()); + validateLowFrequency(msg.lowFrequencyContainer()); + } + + private static void validateBasic(BasicContainer basic) { + requireNonNull("basic_container", basic); + checkRange("station_type", basic.stationType(), 0, 255); + ReferencePosition ref = requireNonNull("basic_container.reference_position", basic.referencePosition()); + checkRange("latitude", ref.latitude(), -900000000, 900000001); + checkRange("longitude", ref.longitude(), -1800000000, 1800000001); + checkRange("altitude", ref.altitude(), -100000, 800001); + + PositionConfidence conf = basic.confidence(); + if(conf != null) { + PositionConfidenceEllipse ellipse = requireNonNull( + "basic_container.confidence.position_confidence_ellipse", + conf.ellipse()); + checkRange("semi_major_confidence", ellipse.semiMajor(), 0, 4095); + checkRange("semi_minor_confidence", ellipse.semiMinor(), 0, 4095); + checkRange("semi_major_orientation", ellipse.semiMajorOrientation(), 0, 3601); + checkRange("altitude_confidence", conf.altitude(), 0, 15); + } + } + + private static void validateHighFrequency(HighFrequencyContainer hf) { + requireNonNull("high_frequency_container", hf); + checkRange("heading", hf.heading(), 0, 3601); + checkRange("speed", hf.speed(), 0, 16383); + checkRange("drive_direction", hf.driveDirection(), 0, 2); + checkRange("vehicle_length", hf.vehicleLength(), 1, 1023); + checkRange("vehicle_width", hf.vehicleWidth(), 1, 62); + checkRange("curvature", hf.curvature(), -1023, 1023); + checkRange("curvature_calculation_mode", hf.curvatureCalculationMode(), 0, 2); + checkRange("longitudinal_acceleration", hf.longitudinalAcceleration(), -160, 161); + checkRange("yaw_rate", hf.yawRate(), -32766, 32767); + + if (hf.accelerationControl() != null && !hf.accelerationControl().isBlank() + && !hf.accelerationControl().matches("[01]{7}")) { + throw new CamValidationException("acceleration_control must be 7-bit binary string"); + } + checkRange("lane_position", hf.lanePosition(), -1, 14); + checkRange("lateral_acceleration", hf.lateralAcceleration(), -160, 161); + checkRange("vertical_acceleration", hf.verticalAcceleration(), -160, 161); + validateHighFrequencyConfidence(hf.confidence()); + } + + private static void validateHighFrequencyConfidence(HighFrequencyConfidence conf) { + if(conf != null) { + checkRange("confidence.heading", conf.heading(), 1, 127); + checkRange("confidence.speed", conf.speed(), 1, 127); + checkRange("confidence.vehicle_length", conf.vehicleLength(), 0, 4); + checkRange("confidence.yaw_rate", conf.yawRate(), 0, 8); + checkRange("confidence.longitudinal_acceleration", conf.longitudinalAcceleration(), 0, 102); + checkRange("confidence.curvature", conf.curvature(), 0, 7); + checkRange("confidence.lateral_acceleration", conf.lateralAcceleration(), 0, 102); + checkRange("confidence.vertical_acceleration", conf.verticalAcceleration(), 0, 102); + } + } + + private static void validateLowFrequency(LowFrequencyContainer lf) { + if(lf != null) { + checkRange("vehicle_role", lf.vehicleRole(), 0, 15); + requireNotBlank("exterior_lights", lf.exteriorLights()); + if (!lf.exteriorLights().matches("[01]{8}")) { + throw new CamValidationException("exterior_lights must be 8-bit binary string"); + } + List history = requireNonNull("path_history", lf.pathHistory()); + if (history.size() > 40) { + throw new CamValidationException("path_history size exceeds 40"); + } + for (int i = 0; i < history.size(); i++) { + validatePathPoint(history.get(i), i); + } + } + } + + private static void validatePathPoint(PathPoint point, int index) { + String prefix = "path_history[" + index + "]"; + requireNonNull(prefix, point); + DeltaReferencePosition delta = requireNonNull(prefix + ".path_position", point.deltaPosition()); + checkRange(prefix + ".delta_latitude", delta.deltaLatitude(), -131071, 131072); + checkRange(prefix + ".delta_longitude", delta.deltaLongitude(), -131071, 131072); + checkRange(prefix + ".delta_altitude", delta.deltaAltitude(), -12700, 12800); + if (point.deltaTime() != null) { + checkRange(prefix + ".path_delta_time", point.deltaTime(), 1, 65535); + } + } + + private static T requireNonNull(String field, T value) { + if (value == null) { + throw new CamValidationException("Missing mandatory field: " + field); + } + return value; + } + + private static void requireNotBlank(String field, String value) { + if (value == null || value.isBlank()) { + throw new CamValidationException("Missing mandatory field: " + field); + } + } + + private static void requireEquals(String field, String actual, String expected) { + if (!Objects.equals(actual, expected)) { + throw new CamValidationException(field + " must equal '" + expected + "'"); + } + } + + private static void requireEnum(String field, String actual, List allowed) { + if (!allowed.contains(actual)) { + throw new CamValidationException(field + " must be one of " + allowed); + } + } + + private static void checkRange(String field, Integer value, long min, long max) { + if (value != null && (value < min || value > max)) { + throw new CamValidationException( + field + " out of range [" + min + ", " + max + "] (actual=" + value + ")"); + } + } + + private static void checkRange(String field, Long value, long min, long max) { + if (value != null && (value < min || value > max)) { + throw new CamValidationException( + field + " out of range [" + min + ", " + max + "] (actual=" + value + ")"); + } + } +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/codec/CamReader230.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/codec/CamReader230.java new file mode 100644 index 000000000..51c7d521e --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/codec/CamReader230.java @@ -0,0 +1,1074 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.codec; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.orange.iot3mobility.messages.cam.v230.model.*; +import com.orange.iot3mobility.messages.cam.v230.model.basiccontainer.*; +import com.orange.iot3mobility.messages.cam.v230.model.highfrequencycontainer.*; +import com.orange.iot3mobility.messages.cam.v230.model.lowfrequencycontainer.*; +import com.orange.iot3mobility.messages.cam.v230.model.specialvehiclecontainer.*; +import com.orange.iot3mobility.messages.cam.v230.validation.CamValidationException; +import com.orange.iot3mobility.messages.cam.v230.validation.CamValidator230; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Streaming JSON reader for CAM 2.3.0 payloads (structured JSON or ASN.1). + */ +public final class CamReader230 { + + private final JsonFactory jsonFactory; + + public CamReader230(JsonFactory jsonFactory) { + this.jsonFactory = Objects.requireNonNull(jsonFactory, "jsonFactory"); + } + + public CamEnvelope230 read(InputStream in) throws IOException { + try (JsonParser parser = jsonFactory.createParser(in)) { + expect(parser.nextToken(), JsonToken.START_OBJECT); + + String messageType = null; + String messageFormat = null; + String sourceUuid = null; + Long timestamp = null; + String version = null; + CamMessage230 payload = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "message_type" -> messageType = parser.getValueAsString(); + case "message_format" -> messageFormat = parser.getValueAsString(); + case "source_uuid" -> sourceUuid = parser.getValueAsString(); + case "timestamp" -> timestamp = parser.getLongValue(); + case "version" -> version = parser.getValueAsString(); + case "message" -> payload = readPayload(parser); + default -> parser.skipChildren(); + } + } + + CamEnvelope230 envelope = new CamEnvelope230( + messageType, + messageFormat, + sourceUuid, + requireField(timestamp, "timestamp"), + requireField(version, "version"), + requireField(payload, "message")); + + CamValidator230.validateEnvelope(envelope); + return envelope; + } + } + + private CamMessage230 readPayload(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + + boolean structuredDetected = false; + boolean asn1Detected = false; + + Integer protocolVersion = null; + Long stationId = null; + Integer generationDeltaTime = null; + BasicContainer basic = null; + HighFrequencyContainer high = null; + LowFrequencyContainer low = null; + SpecialVehicleContainer special = null; + + String asn1Version = null; + String asn1Payload = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "protocol_version" -> { + structuredDetected = true; + protocolVersion = parser.getIntValue(); + } + case "station_id" -> { + structuredDetected = true; + stationId = parser.getLongValue(); + } + case "generation_delta_time" -> { + structuredDetected = true; + generationDeltaTime = parser.getIntValue(); + } + case "basic_container" -> { + structuredDetected = true; + basic = readBasicContainer(parser); + } + case "high_frequency_container" -> { + structuredDetected = true; + high = readHighFrequencyContainer(parser); + } + case "low_frequency_container" -> { + structuredDetected = true; + low = readLowFrequencyContainer(parser); + } + case "special_vehicle_container" -> { + structuredDetected = true; + special = readSpecialVehicleContainer(parser); + } + case "version" -> { + asn1Detected = true; + asn1Version = parser.getValueAsString(); + } + case "payload" -> { + asn1Detected = true; + asn1Payload = parser.getValueAsString(); + } + default -> parser.skipChildren(); + } + + if (structuredDetected && asn1Detected) { + throw new CamValidationException("message cannot mix structured CAM fields with ASN.1 payload"); + } + } + + if (structuredDetected) { + return new CamStructuredData( + requireField(protocolVersion, "protocol_version"), + requireField(stationId, "station_id"), + requireField(generationDeltaTime, "generation_delta_time"), + requireField(basic, "basic_container"), + requireField(high, "high_frequency_container"), + low, + special); + } else if (asn1Detected) { + return new CamAsn1Payload( + requireField(asn1Version, "version"), + requireField(asn1Payload, "payload")); + } else { + throw new CamValidationException("message payload is empty"); + } + } + + /* --------------------------------------------------------------------- */ + /* Basic container */ + /* --------------------------------------------------------------------- */ + + private BasicContainer readBasicContainer(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Integer stationType = null; + ReferencePosition reference = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "station_type" -> stationType = parser.getIntValue(); + case "reference_position" -> reference = readReferencePosition(parser); + default -> parser.skipChildren(); + } + } + return new BasicContainer( + requireField(stationType, "basic_container.station_type"), + requireField(reference, "basic_container.reference_position")); + } + + private ReferencePosition readReferencePosition(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Integer lat = null; + Integer lon = null; + PositionConfidenceEllipse ellipse = null; + Altitude altitude = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "latitude" -> lat = parser.getIntValue(); + case "longitude" -> lon = parser.getIntValue(); + case "position_confidence_ellipse" -> ellipse = readEllipse(parser); + case "altitude" -> altitude = readAltitude(parser); + default -> parser.skipChildren(); + } + } + return new ReferencePosition( + requireField(lat, "reference_position.latitude"), + requireField(lon, "reference_position.longitude"), + requireField(ellipse, "reference_position.position_confidence_ellipse"), + requireField(altitude, "reference_position.altitude")); + } + + private PositionConfidenceEllipse readEllipse(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Integer semiMajor = null; + Integer semiMinor = null; + Integer orientation = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "semi_major" -> semiMajor = parser.getIntValue(); + case "semi_minor" -> semiMinor = parser.getIntValue(); + case "semi_major_orientation" -> orientation = parser.getIntValue(); + default -> parser.skipChildren(); + } + } + return new PositionConfidenceEllipse( + requireField(semiMajor, "position_confidence_ellipse.semi_major"), + requireField(semiMinor, "position_confidence_ellipse.semi_minor"), + requireField(orientation, "position_confidence_ellipse.semi_major_orientation")); + } + + private Altitude readAltitude(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Integer value = null; + Integer confidence = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "value" -> value = parser.getIntValue(); + case "confidence" -> confidence = parser.getIntValue(); + default -> parser.skipChildren(); + } + } + return new Altitude( + requireField(value, "altitude.value"), + requireField(confidence, "altitude.confidence")); + } + + /* --------------------------------------------------------------------- */ + /* High-frequency containers */ + /* --------------------------------------------------------------------- */ + + private HighFrequencyContainer readHighFrequencyContainer(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + BasicVehicleContainerHighFrequency basicVehicle = null; + RsuContainerHighFrequency rsu = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "basic_vehicle_container_high_frequency" -> + basicVehicle = readBasicVehicleHF(parser); + case "rsu_container_high_frequency" -> + rsu = readRsuHF(parser); + default -> parser.skipChildren(); + } + } + + if (basicVehicle != null && rsu != null) { + throw new CamValidationException("high_frequency_container cannot contain both vehicle and RSU sub-containers"); + } else if (basicVehicle != null) { + return basicVehicle; + } else if (rsu != null) { + return rsu; + } + throw new CamValidationException("high_frequency_container is missing required sub-container"); + } + + private BasicVehicleContainerHighFrequency readBasicVehicleHF(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + + Heading heading = null; + Speed speed = null; + Integer driveDirection = null; + VehicleLength vehicleLength = null; + Integer vehicleWidth = null; + AccelerationComponent longitudinal = null; + Curvature curvature = null; + Integer curvatureMode = null; + YawRate yawRate = null; + AccelerationControl accControl = null; + Integer lanePosition = null; + SteeringWheelAngle steering = null; + AccelerationComponent lateral = null; + AccelerationComponent vertical = null; + Integer performanceClass = null; + CenDsrcTollingZone cenZone = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "heading" -> heading = readHeading(parser); + case "speed" -> speed = readSpeed(parser); + case "drive_direction" -> driveDirection = parser.getIntValue(); + case "vehicle_length" -> vehicleLength = readVehicleLength(parser); + case "vehicle_width" -> vehicleWidth = parser.getIntValue(); + case "longitudinal_acceleration" -> longitudinal = readAccelerationComponent(parser); + case "curvature" -> curvature = readCurvature(parser); + case "curvature_calculation_mode" -> curvatureMode = parser.getIntValue(); + case "yaw_rate" -> yawRate = readYawRate(parser); + case "acceleration_control" -> accControl = readAccelerationControl(parser); + case "lane_position" -> lanePosition = parser.getIntValue(); + case "steering_wheel_angle" -> steering = readSteeringWheelAngle(parser); + case "lateral_acceleration" -> lateral = readAccelerationComponent(parser); + case "vertical_acceleration" -> vertical = readAccelerationComponent(parser); + case "performance_class" -> performanceClass = parser.getIntValue(); + case "cen_dsrc_tolling_zone" -> cenZone = readCenDsrcTollingZone(parser); + default -> parser.skipChildren(); + } + } + + return new BasicVehicleContainerHighFrequency( + requireField(heading, "high_frequency_container.heading"), + requireField(speed, "high_frequency_container.speed"), + requireField(driveDirection, "high_frequency_container.drive_direction"), + requireField(vehicleLength, "high_frequency_container.vehicle_length"), + requireField(vehicleWidth, "high_frequency_container.vehicle_width"), + requireField(longitudinal, "high_frequency_container.longitudinal_acceleration"), + requireField(curvature, "high_frequency_container.curvature"), + requireField(curvatureMode, "high_frequency_container.curvature_calculation_mode"), + requireField(yawRate, "high_frequency_container.yaw_rate"), + accControl, + lanePosition, + steering, + lateral, + vertical, + performanceClass, + cenZone); + } + + private RsuContainerHighFrequency readRsuHF(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + List zones = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + if ("protected_communication_zones_rsu".equals(field)) { + zones = readProtectedZones(parser); + } else { + parser.skipChildren(); + } + } + return new RsuContainerHighFrequency( + requireField(zones, "rsu_container_high_frequency.protected_communication_zones_rsu")); + } + + private List readProtectedZones(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_ARRAY); + List zones = new ArrayList<>(); + + while (parser.nextToken() != JsonToken.END_ARRAY) { + zones.add(readProtectedZone(parser)); + } + return zones; + } + + private ProtectedCommunicationZone readProtectedZone(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Integer type = null; + Long expiry = null; + Integer latitude = null; + Integer longitude = null; + Integer radius = null; + Integer zoneId = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "protected_zone_type" -> type = parser.getIntValue(); + case "expiry_time" -> expiry = parser.getLongValue(); + case "protected_zone_latitude" -> latitude = parser.getIntValue(); + case "protected_zone_longitude" -> longitude = parser.getIntValue(); + case "protected_zone_radius" -> radius = parser.getIntValue(); + case "protected_zone_id" -> zoneId = parser.getIntValue(); + default -> parser.skipChildren(); + } + } + + return new ProtectedCommunicationZone( + requireField(type, "protected_zone_type"), + expiry, + requireField(latitude, "protected_zone_latitude"), + requireField(longitude, "protected_zone_longitude"), + radius, + zoneId); + } + + /* --------------------------------------------------------------------- */ + /* Low-frequency container */ + /* --------------------------------------------------------------------- */ + + private LowFrequencyContainer readLowFrequencyContainer(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + BasicVehicleContainerLowFrequency basic = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + if ("basic_vehicle_container_low_frequency".equals(field) + || "basic_vehicle_container_low_frequency ".equals(field)) { // schema typo + basic = readBasicVehicleLF(parser); + } else { + parser.skipChildren(); + } + } + return new LowFrequencyContainer(requireField(basic, "basic_vehicle_container_low_frequency")); + } + + private BasicVehicleContainerLowFrequency readBasicVehicleLF(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Integer vehicleRole = null; + ExteriorLights lights = null; + List history = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "vehicle_role" -> vehicleRole = parser.getIntValue(); + case "exterior_lights" -> lights = readExteriorLights(parser); + case "path_history" -> history = readPathHistory(parser); + default -> parser.skipChildren(); + } + } + return new BasicVehicleContainerLowFrequency( + requireField(vehicleRole, "vehicle_role"), + requireField(lights, "exterior_lights"), + requireField(history, "path_history")); + } + + private ExteriorLights readExteriorLights(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Boolean lowBeam = null; + Boolean highBeam = null; + Boolean leftTurn = null; + Boolean rightTurn = null; + Boolean daytime = null; + Boolean reverse = null; + Boolean fog = null; + Boolean parking = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "low_beam_headlights_on" -> lowBeam = parser.getBooleanValue(); + case "high_beam_headlights_on" -> highBeam = parser.getBooleanValue(); + case "left_turn_signal_on" -> leftTurn = parser.getBooleanValue(); + case "right_turn_signal_on" -> rightTurn = parser.getBooleanValue(); + case "daytime_running_lights_on" -> daytime = parser.getBooleanValue(); + case "reverse_light_on" -> reverse = parser.getBooleanValue(); + case "fog_light_on" -> fog = parser.getBooleanValue(); + case "parking_lights_on" -> parking = parser.getBooleanValue(); + default -> parser.skipChildren(); + } + } + return new ExteriorLights( + requireField(lowBeam, "exterior_lights.low_beam_headlights_on"), + requireField(highBeam, "exterior_lights.high_beam_headlights_on"), + requireField(leftTurn, "exterior_lights.left_turn_signal_on"), + requireField(rightTurn, "exterior_lights.right_turn_signal_on"), + requireField(daytime, "exterior_lights.daytime_running_lights_on"), + requireField(reverse, "exterior_lights.reverse_light_on"), + requireField(fog, "exterior_lights.fog_light_on"), + requireField(parking, "exterior_lights.parking_lights_on")); + } + + private List readPathHistory(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_ARRAY); + List points = new ArrayList<>(); + while (parser.nextToken() != JsonToken.END_ARRAY) { + points.add(readPathPoint(parser)); + } + return points; + } + + private PathPoint readPathPoint(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + DeltaReferencePosition delta = null; + Integer deltaTime = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "path_position" -> delta = readDeltaReferencePosition(parser); + case "path_delta_time" -> deltaTime = parser.getIntValue(); + default -> parser.skipChildren(); + } + } + return new PathPoint(requireField(delta, "path_position"), deltaTime); + } + + private DeltaReferencePosition readDeltaReferencePosition(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Integer lat = null; + Integer lon = null; + Integer alt = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "delta_latitude" -> lat = parser.getIntValue(); + case "delta_longitude" -> lon = parser.getIntValue(); + case "delta_altitude" -> alt = parser.getIntValue(); + default -> parser.skipChildren(); + } + } + return new DeltaReferencePosition( + requireField(lat, "delta_latitude"), + requireField(lon, "delta_longitude"), + requireField(alt, "delta_altitude")); + } + + /* --------------------------------------------------------------------- */ + /* Special vehicle container */ + /* --------------------------------------------------------------------- */ + + private SpecialVehicleContainer readSpecialVehicleContainer(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + SpecialVehiclePayload payload = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + payload = switch (field) { + case "public_transport_container" -> readPublicTransport(parser); + case "special_transport_container" -> readSpecialTransport(parser); + case "dangerous_goods_container" -> readDangerousGoods(parser); + case "road_works_container_basic" -> readRoadWorks(parser); + case "rescue_container" -> readRescue(parser); + case "emergency_container" -> readEmergency(parser); + case "safety_car_container" -> readSafetyCar(parser); + default -> { + parser.skipChildren(); + yield payload; + } + }; + } + return new SpecialVehicleContainer(requireField(payload, "special_vehicle_container payload")); + } + + private PublicTransportContainer readPublicTransport(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Boolean embarkation = null; + PtActivation activation = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + if ("embarkation_status".equals(field)) { + embarkation = parser.getBooleanValue(); + } else if ("pt_activation".equals(field)) { + activation = readPtActivation(parser); + } else { + parser.skipChildren(); + } + } + return new PublicTransportContainer( + requireField(embarkation, "public_transport_container.embarkation_status"), + activation); + } + + private PtActivation readPtActivation(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Integer type = null; + String data = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + if ("pt_activation_type".equals(field)) { + type = parser.getIntValue(); + } else if ("pt_activation_data".equals(field)) { + data = parser.getValueAsString(); + } else { + parser.skipChildren(); + } + } + return new PtActivation( + requireField(type, "pt_activation.pt_activation_type"), + requireField(data, "pt_activation.pt_activation_data")); + } + + private SpecialTransportContainer readSpecialTransport(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + SpecialTransportType type = null; + LightBarSiren lightBar = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + if ("special_transport_type".equals(field)) { + type = readSpecialTransportType(parser); + } else if ("light_bar_siren_in_use".equals(field)) { + lightBar = readLightBar(parser); + } else { + parser.skipChildren(); + } + } + return new SpecialTransportContainer( + requireField(type, "special_transport_container.special_transport_type"), + requireField(lightBar, "special_transport_container.light_bar_siren_in_use")); + } + + private SpecialTransportType readSpecialTransportType(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Boolean heavy = null, width = null, length = null, height = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "heavy_load" -> heavy = parser.getBooleanValue(); + case "excess_width" -> width = parser.getBooleanValue(); + case "excess_length" -> length = parser.getBooleanValue(); + case "excess_height" -> height = parser.getBooleanValue(); + default -> parser.skipChildren(); + } + } + return new SpecialTransportType( + requireField(heavy, "special_transport_type.heavy_load"), + requireField(width, "special_transport_type.excess_width"), + requireField(length, "special_transport_type.excess_length"), + requireField(height, "special_transport_type.excess_height")); + } + + private DangerousGoodsContainer readDangerousGoods(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Integer goods = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + if ("dangerous_goods_basic".equals(field)) { + goods = parser.getIntValue(); + } else { + parser.skipChildren(); + } + } + return new DangerousGoodsContainer( + requireField(goods, "dangerous_goods_basic")); + } + + private RoadWorksContainer readRoadWorks(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Integer subCause = null; + LightBarSiren lightBar = null; + ClosedLanes closedLanes = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "road_works_sub_cause_code" -> subCause = parser.getIntValue(); + case "light_bar_siren_in_use" -> lightBar = readLightBar(parser); + case "closed_lanes" -> closedLanes = readClosedLanes(parser); + default -> parser.skipChildren(); + } + } + return new RoadWorksContainer(subCause, lightBar, closedLanes); + } + + private ClosedLanes readClosedLanes(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Integer inner = null; + Integer outer = null; + DrivingLaneStatus status = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + if ("inner_hard_shoulder_status".equals(field)) { + inner = parser.getIntValue(); + } else if ("outer_hard_shoulder_status".equals(field)) { + outer = parser.getIntValue(); + } else if ("driving_lane_status".equals(field)) { + status = readDrivingLaneStatus(parser); + } else { + parser.skipChildren(); + } + } + return new ClosedLanes(inner, outer, status); + } + + private DrivingLaneStatus readDrivingLaneStatus(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Boolean[] lanes = new Boolean[13]; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + if (field.startsWith("lane_") && field.endsWith("_closed")) { + int index = Integer.parseInt(field.substring(5, field.length() - 7)) - 1; + lanes[index] = parser.getBooleanValue(); + } else { + parser.skipChildren(); + } + } + for (int i = 0; i < lanes.length; i++) { + if (lanes[i] == null) { + throw new CamValidationException("driving_lane_status is missing lane_" + (i + 1) + "_closed"); + } + } + return new DrivingLaneStatus( + lanes[0], lanes[1], lanes[2], lanes[3], lanes[4], lanes[5], lanes[6], + lanes[7], lanes[8], lanes[9], lanes[10], lanes[11], lanes[12]); + } + + private RescueContainer readRescue(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + LightBarSiren lightBar = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + if ("light_bar_siren_in_use".equals(parser.currentName())) { + parser.nextToken(); + lightBar = readLightBar(parser); + } else { + parser.skipChildren(); + } + } + return new RescueContainer(requireField(lightBar, "rescue_container.light_bar_siren_in_use")); + } + + private EmergencyContainer readEmergency(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + LightBarSiren lightBar = null; + IncidentIndication indication = null; + EmergencyPriority priority = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "light_bar_siren_in_use" -> lightBar = readLightBar(parser); + case "incident_indication" -> indication = readIncidentIndication(parser); + case "emergency_priority" -> priority = readEmergencyPriority(parser); + default -> parser.skipChildren(); + } + } + return new EmergencyContainer(lightBar, indication, priority); + } + + private SafetyCarContainer readSafetyCar(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + LightBarSiren lightBar = null; + IncidentIndication indication = null; + Integer trafficRule = null; + Integer speedLimit = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "light_bar_siren_in_use" -> lightBar = readLightBar(parser); + case "incident_indication" -> indication = readIncidentIndication(parser); + case "traffic_rule" -> trafficRule = parser.getIntValue(); + case "speed_limit" -> speedLimit = parser.getIntValue(); + default -> parser.skipChildren(); + } + } + return new SafetyCarContainer(lightBar, indication, trafficRule, speedLimit); + } + + private IncidentIndication readIncidentIndication(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + CauseCode causeCode = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + if ("cc_and_scc".equals(parser.currentName())) { + parser.nextToken(); + causeCode = readCauseCode(parser); + } else { + parser.skipChildren(); + } + } + return new IncidentIndication(requireField(causeCode, "incident_indication.cc_and_scc")); + } + + private CauseCode readCauseCode(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Integer cause = null; + Integer subcause = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + if ("cause".equals(field)) { + cause = parser.getIntValue(); + } else if ("subcause".equals(field)) { + subcause = parser.getIntValue(); + } else { + parser.skipChildren(); + } + } + return new CauseCode(requireField(cause, "cause_code.cause"), subcause); + } + + private EmergencyPriority readEmergencyPriority(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Boolean rightOfWay = null; + Boolean freeCrossing = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + if ("request_for_right_of_way".equals(field)) { + rightOfWay = parser.getBooleanValue(); + } else if ("request_for_free_crossing_at_a_traffic_light".equals(field)) { + freeCrossing = parser.getBooleanValue(); + } else { + parser.skipChildren(); + } + } + return new EmergencyPriority( + requireField(rightOfWay, "emergency_priority.request_for_right_of_way"), + requireField(freeCrossing, "emergency_priority.request_for_free_crossing_at_a_traffic_light")); + } + + private LightBarSiren readLightBar(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Boolean lightBar = null; + Boolean siren = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + if ("light_bar_activated".equals(field)) { + lightBar = parser.getBooleanValue(); + } else if ("siren_activated".equals(field)) { + siren = parser.getBooleanValue(); + } else { + parser.skipChildren(); + } + } + return new LightBarSiren( + requireField(lightBar, "light_bar_siren_in_use.light_bar_activated"), + requireField(siren, "light_bar_siren_in_use.siren_activated")); + } + + /* --------------------------------------------------------------------- */ + /* Shared primitive readers */ + /* --------------------------------------------------------------------- */ + + private Heading readHeading(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Integer value = null; + Integer confidence = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + if ("value".equals(field)) { + value = parser.getIntValue(); + } else if ("confidence".equals(field)) { + confidence = parser.getIntValue(); + } else { + parser.skipChildren(); + } + } + return new Heading( + requireField(value, "heading.value"), + requireField(confidence, "heading.confidence")); + } + + private Speed readSpeed(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Integer value = null; + Integer confidence = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + if ("value".equals(field)) { + value = parser.getIntValue(); + } else if ("confidence".equals(field)) { + confidence = parser.getIntValue(); + } else { + parser.skipChildren(); + } + } + return new Speed( + requireField(value, "speed.value"), + requireField(confidence, "speed.confidence")); + } + + private VehicleLength readVehicleLength(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Integer value = null; + Integer confidence = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + if ("value".equals(field)) { + value = parser.getIntValue(); + } else if ("confidence".equals(field)) { + confidence = parser.getIntValue(); + } else { + parser.skipChildren(); + } + } + return new VehicleLength( + requireField(value, "vehicle_length.value"), + requireField(confidence, "vehicle_length.confidence")); + } + + private AccelerationComponent readAccelerationComponent(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Integer value = null; + Integer confidence = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + if ("value".equals(field)) { + value = parser.getIntValue(); + } else if ("confidence".equals(field)) { + confidence = parser.getIntValue(); + } else { + parser.skipChildren(); + } + } + return new AccelerationComponent( + requireField(value, "acceleration_component.value"), + requireField(confidence, "acceleration_component.confidence")); + } + + private Curvature readCurvature(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Integer value = null; + Integer confidence = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + if ("value".equals(field)) { + value = parser.getIntValue(); + } else if ("confidence".equals(field)) { + confidence = parser.getIntValue(); + } else { + parser.skipChildren(); + } + } + return new Curvature( + requireField(value, "curvature.value"), + requireField(confidence, "curvature.confidence")); + } + + private YawRate readYawRate(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Integer value = null; + Integer confidence = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + if ("value".equals(field)) { + value = parser.getIntValue(); + } else if ("confidence".equals(field)) { + confidence = parser.getIntValue(); + } else { + parser.skipChildren(); + } + } + return new YawRate( + requireField(value, "yaw_rate.value"), + requireField(confidence, "yaw_rate.confidence")); + } + + private SteeringWheelAngle readSteeringWheelAngle(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Integer value = null; + Integer confidence = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + if ("value".equals(field)) { + value = parser.getIntValue(); + } else if ("confidence".equals(field)) { + confidence = parser.getIntValue(); + } else { + parser.skipChildren(); + } + } + return new SteeringWheelAngle( + requireField(value, "steering_wheel_angle.value"), + requireField(confidence, "steering_wheel_angle.confidence")); + } + + private AccelerationControl readAccelerationControl(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Boolean brake = null, gas = null, emergency = null, collision = null, acc = null, cruise = null, limiter = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "brake_pedal_engaged" -> brake = parser.getBooleanValue(); + case "gas_pedal_engaged" -> gas = parser.getBooleanValue(); + case "emergency_brake_engaged" -> emergency = parser.getBooleanValue(); + case "collision_warning_engaged" -> collision = parser.getBooleanValue(); + case "acc_engaged" -> acc = parser.getBooleanValue(); + case "cruise_control_engaged" -> cruise = parser.getBooleanValue(); + case "speed_limiter_engaged" -> limiter = parser.getBooleanValue(); + default -> parser.skipChildren(); + } + } + return new AccelerationControl( + requireField(brake, "acceleration_control.brake_pedal_engaged"), + requireField(gas, "acceleration_control.gas_pedal_engaged"), + requireField(emergency, "acceleration_control.emergency_brake_engaged"), + requireField(collision, "acceleration_control.collision_warning_engaged"), + requireField(acc, "acceleration_control.acc_engaged"), + requireField(cruise, "acceleration_control.cruise_control_engaged"), + requireField(limiter, "acceleration_control.speed_limiter_engaged")); + } + + private CenDsrcTollingZone readCenDsrcTollingZone(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Integer latitude = null; + Integer longitude = null; + Integer zoneId = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + if ("protected_zone_latitude".equals(field)) { + latitude = parser.getIntValue(); + } else if ("protected_zone_longitude".equals(field)) { + longitude = parser.getIntValue(); + } else if ("cen_dsrc_tolling_zone_id".equals(field)) { + zoneId = parser.getIntValue(); + } else { + parser.skipChildren(); + } + } + return new CenDsrcTollingZone( + requireField(latitude, "cen_dsrc_tolling_zone.protected_zone_latitude"), + requireField(longitude, "cen_dsrc_tolling_zone.protected_zone_longitude"), + zoneId); + } + + /* --------------------------------------------------------------------- */ + + private static T requireField(T value, String field) { + if (value == null) { + throw new CamValidationException("Missing mandatory field: " + field); + } + return value; + } + + private static void expect(JsonToken actual, JsonToken expected) throws JsonParseException { + if (actual != expected) { + throw new JsonParseException(null, "Expected token " + expected + " but got " + actual); + } + } +} \ No newline at end of file diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/codec/CamWriter230.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/codec/CamWriter230.java new file mode 100644 index 000000000..00ef890ed --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/codec/CamWriter230.java @@ -0,0 +1,520 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.codec; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; +import com.orange.iot3mobility.messages.cam.v230.model.*; +import com.orange.iot3mobility.messages.cam.v230.model.basiccontainer.*; +import com.orange.iot3mobility.messages.cam.v230.model.highfrequencycontainer.*; +import com.orange.iot3mobility.messages.cam.v230.model.lowfrequencycontainer.*; +import com.orange.iot3mobility.messages.cam.v230.model.specialvehiclecontainer.*; +import com.orange.iot3mobility.messages.cam.v230.validation.CamValidator230; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Objects; + +/** + * Streaming JSON writer for CAM 2.3.0 payloads (structured JSON or ASN.1). + */ +public final class CamWriter230 { + + private final JsonFactory jsonFactory; + + public CamWriter230(JsonFactory jsonFactory) { + this.jsonFactory = Objects.requireNonNull(jsonFactory, "jsonFactory"); + } + + public void write(CamEnvelope230 envelope, OutputStream out) throws IOException { + CamValidator230.validateEnvelope(envelope); + + try (JsonGenerator gen = jsonFactory.createGenerator(out)) { + gen.writeStartObject(); + gen.writeStringField("message_type", envelope.messageType()); + if (envelope.messageFormat() != null) { + gen.writeStringField("message_format", envelope.messageFormat()); + } + gen.writeStringField("source_uuid", envelope.sourceUuid()); + gen.writeNumberField("timestamp", envelope.timestamp()); + gen.writeStringField("version", envelope.version()); + gen.writeFieldName("message"); + writePayload(gen, envelope.message()); + gen.writeEndObject(); + } + } + + private void writePayload(JsonGenerator gen, CamMessage230 payload) throws IOException { + if (payload instanceof CamStructuredData structured) { + gen.writeStartObject(); + gen.writeNumberField("protocol_version", structured.protocolVersion()); + gen.writeNumberField("station_id", structured.stationId()); + gen.writeNumberField("generation_delta_time", structured.generationDeltaTime()); + + gen.writeFieldName("basic_container"); + writeBasicContainer(gen, structured.basicContainer()); + + gen.writeFieldName("high_frequency_container"); + writeHighFrequencyContainer(gen, structured.highFrequencyContainer()); + + if (structured.lowFrequencyContainer() != null) { + gen.writeFieldName("low_frequency_container"); + writeLowFrequencyContainer(gen, structured.lowFrequencyContainer()); + } + if (structured.specialVehicleContainer() != null) { + gen.writeFieldName("special_vehicle_container"); + writeSpecialVehicleContainer(gen, structured.specialVehicleContainer()); + } + gen.writeEndObject(); + } else if (payload instanceof CamAsn1Payload asn1) { + gen.writeStartObject(); + gen.writeStringField("version", asn1.version()); + gen.writeStringField("payload", asn1.payload()); + gen.writeEndObject(); + } else { + throw new IllegalArgumentException("Unsupported payload type: " + payload.getClass().getName()); + } + } + + /* --------------------------------------------------------------------- */ + /* Basic container */ + /* --------------------------------------------------------------------- */ + + private void writeBasicContainer(JsonGenerator gen, BasicContainer basic) throws IOException { + gen.writeStartObject(); + gen.writeNumberField("station_type", basic.stationType()); + gen.writeFieldName("reference_position"); + writeReferencePosition(gen, basic.referencePosition()); + gen.writeEndObject(); + } + + private void writeReferencePosition(JsonGenerator gen, ReferencePosition reference) throws IOException { + gen.writeStartObject(); + gen.writeNumberField("latitude", reference.latitude()); + gen.writeNumberField("longitude", reference.longitude()); + gen.writeFieldName("position_confidence_ellipse"); + writeEllipse(gen, reference.positionConfidenceEllipse()); + gen.writeFieldName("altitude"); + writeAltitude(gen, reference.altitude()); + gen.writeEndObject(); + } + + private void writeEllipse(JsonGenerator gen, PositionConfidenceEllipse ellipse) throws IOException { + gen.writeStartObject(); + gen.writeNumberField("semi_major", ellipse.semiMajor()); + gen.writeNumberField("semi_minor", ellipse.semiMinor()); + gen.writeNumberField("semi_major_orientation", ellipse.semiMajorOrientation()); + gen.writeEndObject(); + } + + private void writeAltitude(JsonGenerator gen, Altitude altitude) throws IOException { + gen.writeStartObject(); + gen.writeNumberField("value", altitude.value()); + gen.writeNumberField("confidence", altitude.confidence()); + gen.writeEndObject(); + } + + /* --------------------------------------------------------------------- */ + /* High-frequency containers */ + /* --------------------------------------------------------------------- */ + + private void writeHighFrequencyContainer(JsonGenerator gen, HighFrequencyContainer container) throws IOException { + gen.writeStartObject(); + if (container instanceof BasicVehicleContainerHighFrequency basicVehicle) { + gen.writeFieldName("basic_vehicle_container_high_frequency"); + writeBasicVehicleHF(gen, basicVehicle); + } else if (container instanceof RsuContainerHighFrequency rsu) { + gen.writeFieldName("rsu_container_high_frequency"); + writeRsuHF(gen, rsu); + } else { + throw new IllegalArgumentException("Unknown high frequency container: " + container.getClass().getName()); + } + gen.writeEndObject(); + } + + private void writeBasicVehicleHF(JsonGenerator gen, BasicVehicleContainerHighFrequency container) throws IOException { + gen.writeStartObject(); + gen.writeFieldName("heading"); + writeHeading(gen, container.heading()); + gen.writeFieldName("speed"); + writeSpeed(gen, container.speed()); + gen.writeNumberField("drive_direction", container.driveDirection()); + gen.writeFieldName("vehicle_length"); + writeVehicleLength(gen, container.vehicleLength()); + gen.writeNumberField("vehicle_width", container.vehicleWidth()); + gen.writeFieldName("longitudinal_acceleration"); + writeAccelerationComponent(gen, container.longitudinalAcceleration()); + gen.writeFieldName("curvature"); + writeCurvature(gen, container.curvature()); + gen.writeNumberField("curvature_calculation_mode", container.curvatureCalculationMode()); + gen.writeFieldName("yaw_rate"); + writeYawRate(gen, container.yawRate()); + + if (container.accelerationControl() != null) { + gen.writeFieldName("acceleration_control"); + writeAccelerationControl(gen, container.accelerationControl()); + } + if (container.lanePosition() != null) { + gen.writeNumberField("lane_position", container.lanePosition()); + } + if (container.steeringWheelAngle() != null) { + gen.writeFieldName("steering_wheel_angle"); + writeSteeringWheelAngle(gen, container.steeringWheelAngle()); + } + if (container.lateralAcceleration() != null) { + gen.writeFieldName("lateral_acceleration"); + writeAccelerationComponent(gen, container.lateralAcceleration()); + } + if (container.verticalAcceleration() != null) { + gen.writeFieldName("vertical_acceleration"); + writeAccelerationComponent(gen, container.verticalAcceleration()); + } + if (container.performanceClass() != null) { + gen.writeNumberField("performance_class", container.performanceClass()); + } + if (container.cenDsrcTollingZone() != null) { + gen.writeFieldName("cen_dsrc_tolling_zone"); + writeCenDsrcTollingZone(gen, container.cenDsrcTollingZone()); + } + gen.writeEndObject(); + } + + private void writeRsuHF(JsonGenerator gen, RsuContainerHighFrequency container) throws IOException { + gen.writeStartObject(); + gen.writeArrayFieldStart("protected_communication_zones_rsu"); + for (ProtectedCommunicationZone zone : container.protectedCommunicationZonesRsu()) { + gen.writeStartObject(); + gen.writeNumberField("protected_zone_type", zone.protectedZoneType()); + if (zone.expiryTime() != null) { + gen.writeNumberField("expiry_time", zone.expiryTime()); + } + gen.writeNumberField("protected_zone_latitude", zone.protectedZoneLatitude()); + gen.writeNumberField("protected_zone_longitude", zone.protectedZoneLongitude()); + if (zone.protectedZoneRadius() != null) { + gen.writeNumberField("protected_zone_radius", zone.protectedZoneRadius()); + } + if (zone.protectedZoneId() != null) { + gen.writeNumberField("protected_zone_id", zone.protectedZoneId()); + } + gen.writeEndObject(); + } + gen.writeEndArray(); + gen.writeEndObject(); + } + + /* --------------------------------------------------------------------- */ + /* Low-frequency container */ + /* --------------------------------------------------------------------- */ + + private void writeLowFrequencyContainer(JsonGenerator gen, LowFrequencyContainer container) throws IOException { + gen.writeStartObject(); + gen.writeFieldName("basic_vehicle_container_low_frequency"); + writeBasicVehicleLF(gen, container.basicVehicleContainerLowFrequency()); + gen.writeEndObject(); + } + + private void writeBasicVehicleLF(JsonGenerator gen, BasicVehicleContainerLowFrequency low) throws IOException { + gen.writeStartObject(); + gen.writeNumberField("vehicle_role", low.vehicleRole()); + gen.writeFieldName("exterior_lights"); + writeExteriorLights(gen, low.exteriorLights()); + gen.writeArrayFieldStart("path_history"); + for (PathPoint point : low.pathHistory()) { + gen.writeStartObject(); + gen.writeFieldName("path_position"); + writeDeltaReferencePosition(gen, point.pathPosition()); + if (point.pathDeltaTime() != null) { + gen.writeNumberField("path_delta_time", point.pathDeltaTime()); + } + gen.writeEndObject(); + } + gen.writeEndArray(); + gen.writeEndObject(); + } + + private void writeExteriorLights(JsonGenerator gen, ExteriorLights lights) throws IOException { + gen.writeStartObject(); + gen.writeBooleanField("low_beam_headlights_on", lights.lowBeamHeadlightsOn()); + gen.writeBooleanField("high_beam_headlights_on", lights.highBeamHeadlightsOn()); + gen.writeBooleanField("left_turn_signal_on", lights.leftTurnSignalOn()); + gen.writeBooleanField("right_turn_signal_on", lights.rightTurnSignalOn()); + gen.writeBooleanField("daytime_running_lights_on", lights.daytimeRunningLightsOn()); + gen.writeBooleanField("reverse_light_on", lights.reverseLightOn()); + gen.writeBooleanField("fog_light_on", lights.fogLightOn()); + gen.writeBooleanField("parking_lights_on", lights.parkingLightsOn()); + gen.writeEndObject(); + } + + private void writeDeltaReferencePosition(JsonGenerator gen, DeltaReferencePosition delta) throws IOException { + gen.writeStartObject(); + gen.writeNumberField("delta_latitude", delta.deltaLatitude()); + gen.writeNumberField("delta_longitude", delta.deltaLongitude()); + gen.writeNumberField("delta_altitude", delta.deltaAltitude()); + gen.writeEndObject(); + } + + /* --------------------------------------------------------------------- */ + /* Special vehicle container */ + /* --------------------------------------------------------------------- */ + + private void writeSpecialVehicleContainer(JsonGenerator gen, SpecialVehicleContainer container) throws IOException { + gen.writeStartObject(); + SpecialVehiclePayload payload = container.payload(); + if (payload instanceof PublicTransportContainer pt) { + gen.writeFieldName("public_transport_container"); + writePublicTransport(gen, pt); + } else if (payload instanceof SpecialTransportContainer st) { + gen.writeFieldName("special_transport_container"); + writeSpecialTransport(gen, st); + } else if (payload instanceof DangerousGoodsContainer dg) { + gen.writeFieldName("dangerous_goods_container"); + writeDangerousGoods(gen, dg); + } else if (payload instanceof RoadWorksContainer rw) { + gen.writeFieldName("road_works_container_basic"); + writeRoadWorks(gen, rw); + } else if (payload instanceof RescueContainer rescue) { + gen.writeFieldName("rescue_container"); + writeRescue(gen, rescue); + } else if (payload instanceof EmergencyContainer emergency) { + gen.writeFieldName("emergency_container"); + writeEmergency(gen, emergency); + } else if (payload instanceof SafetyCarContainer safetyCar) { + gen.writeFieldName("safety_car_container"); + writeSafetyCar(gen, safetyCar); + } else { + throw new IllegalArgumentException("Unknown special vehicle payload: " + payload.getClass().getName()); + } + gen.writeEndObject(); + } + + private void writePublicTransport(JsonGenerator gen, PublicTransportContainer pt) throws IOException { + gen.writeStartObject(); + gen.writeBooleanField("embarkation_status", pt.embarkationStatus()); + if (pt.ptActivation() != null) { + gen.writeFieldName("pt_activation"); + gen.writeStartObject(); + gen.writeNumberField("pt_activation_type", pt.ptActivation().ptActivationType()); + gen.writeStringField("pt_activation_data", pt.ptActivation().ptActivationData()); + gen.writeEndObject(); + } + gen.writeEndObject(); + } + + private void writeSpecialTransport(JsonGenerator gen, SpecialTransportContainer container) throws IOException { + gen.writeStartObject(); + gen.writeFieldName("special_transport_type"); + gen.writeStartObject(); + SpecialTransportType type = container.specialTransportType(); + gen.writeBooleanField("heavy_load", type.heavyLoad()); + gen.writeBooleanField("excess_width", type.excessWidth()); + gen.writeBooleanField("excess_length", type.excessLength()); + gen.writeBooleanField("excess_height", type.excessHeight()); + gen.writeEndObject(); + + gen.writeFieldName("light_bar_siren_in_use"); + writeLightBar(gen, container.lightBarSirenInUse()); + gen.writeEndObject(); + } + + private void writeDangerousGoods(JsonGenerator gen, DangerousGoodsContainer container) throws IOException { + gen.writeStartObject(); + gen.writeNumberField("dangerous_goods_basic", container.dangerousGoodsBasic()); + gen.writeEndObject(); + } + + private void writeRoadWorks(JsonGenerator gen, RoadWorksContainer container) throws IOException { + gen.writeStartObject(); + if (container.roadWorksSubCauseCode() != null) { + gen.writeNumberField("road_works_sub_cause_code", container.roadWorksSubCauseCode()); + } + if (container.lightBarSirenInUse() != null) { + gen.writeFieldName("light_bar_siren_in_use"); + writeLightBar(gen, container.lightBarSirenInUse()); + } + if (container.closedLanes() != null) { + gen.writeFieldName("closed_lanes"); + writeClosedLanes(gen, container.closedLanes()); + } + gen.writeEndObject(); + } + + private void writeClosedLanes(JsonGenerator gen, ClosedLanes closedLanes) throws IOException { + gen.writeStartObject(); + if (closedLanes.innerHardShoulderStatus() != null) { + gen.writeNumberField("inner_hard_shoulder_status", closedLanes.innerHardShoulderStatus()); + } + if (closedLanes.outerHardShoulderStatus() != null) { + gen.writeNumberField("outer_hard_shoulder_status", closedLanes.outerHardShoulderStatus()); + } + if (closedLanes.drivingLaneStatus() != null) { + gen.writeFieldName("driving_lane_status"); + gen.writeStartObject(); + DrivingLaneStatus status = closedLanes.drivingLaneStatus(); + gen.writeBooleanField("lane_1_closed", status.lane1Closed()); + gen.writeBooleanField("lane_2_closed", status.lane2Closed()); + gen.writeBooleanField("lane_3_closed", status.lane3Closed()); + gen.writeBooleanField("lane_4_closed", status.lane4Closed()); + gen.writeBooleanField("lane_5_closed", status.lane5Closed()); + gen.writeBooleanField("lane_6_closed", status.lane6Closed()); + gen.writeBooleanField("lane_7_closed", status.lane7Closed()); + gen.writeBooleanField("lane_8_closed", status.lane8Closed()); + gen.writeBooleanField("lane_9_closed", status.lane9Closed()); + gen.writeBooleanField("lane_10_closed", status.lane10Closed()); + gen.writeBooleanField("lane_11_closed", status.lane11Closed()); + gen.writeBooleanField("lane_12_closed", status.lane12Closed()); + gen.writeBooleanField("lane_13_closed", status.lane13Closed()); + gen.writeEndObject(); + } + gen.writeEndObject(); + } + + private void writeRescue(JsonGenerator gen, RescueContainer container) throws IOException { + gen.writeStartObject(); + gen.writeFieldName("light_bar_siren_in_use"); + writeLightBar(gen, container.lightBarSirenInUse()); + gen.writeEndObject(); + } + + private void writeEmergency(JsonGenerator gen, EmergencyContainer container) throws IOException { + gen.writeStartObject(); + gen.writeFieldName("light_bar_siren_in_use"); + writeLightBar(gen, container.lightBarSirenInUse()); + if (container.incidentIndication() != null) { + gen.writeFieldName("incident_indication"); + writeIncidentIndication(gen, container.incidentIndication()); + } + if (container.emergencyPriority() != null) { + gen.writeFieldName("emergency_priority"); + writeEmergencyPriority(gen, container.emergencyPriority()); + } + gen.writeEndObject(); + } + + private void writeSafetyCar(JsonGenerator gen, SafetyCarContainer container) throws IOException { + gen.writeStartObject(); + gen.writeFieldName("light_bar_siren_in_use"); + writeLightBar(gen, container.lightBarSirenInUse()); + if (container.incidentIndication() != null) { + gen.writeFieldName("incident_indication"); + writeIncidentIndication(gen, container.incidentIndication()); + } + if (container.trafficRule() != null) { + gen.writeNumberField("traffic_rule", container.trafficRule()); + } + if (container.speedLimit() != null) { + gen.writeNumberField("speed_limit", container.speedLimit()); + } + gen.writeEndObject(); + } + + private void writeIncidentIndication(JsonGenerator gen, IncidentIndication indication) throws IOException { + gen.writeStartObject(); + gen.writeFieldName("cc_and_scc"); + writeCauseCode(gen, indication.ccAndScc()); + gen.writeEndObject(); + } + + private void writeCauseCode(JsonGenerator gen, CauseCode causeCode) throws IOException { + gen.writeStartObject(); + gen.writeNumberField("cause", causeCode.cause()); + if (causeCode.subcause() != null) { + gen.writeNumberField("subcause", causeCode.subcause()); + } + gen.writeEndObject(); + } + + private void writeEmergencyPriority(JsonGenerator gen, EmergencyPriority priority) throws IOException { + gen.writeStartObject(); + gen.writeBooleanField("request_for_right_of_way", priority.requestForRightOfWay()); + gen.writeBooleanField("request_for_free_crossing_at_a_traffic_light", + priority.requestForFreeCrossingAtTrafficLight()); + gen.writeEndObject(); + } + + private void writeLightBar(JsonGenerator gen, LightBarSiren lightBar) throws IOException { + gen.writeStartObject(); + gen.writeBooleanField("light_bar_activated", lightBar.lightBarActivated()); + gen.writeBooleanField("siren_activated", lightBar.sirenActivated()); + gen.writeEndObject(); + } + + /* --------------------------------------------------------------------- */ + /* Primitive helpers */ + /* --------------------------------------------------------------------- */ + + private void writeHeading(JsonGenerator gen, Heading heading) throws IOException { + gen.writeStartObject(); + gen.writeNumberField("value", heading.value()); + gen.writeNumberField("confidence", heading.confidence()); + gen.writeEndObject(); + } + + private void writeSpeed(JsonGenerator gen, Speed speed) throws IOException { + gen.writeStartObject(); + gen.writeNumberField("value", speed.value()); + gen.writeNumberField("confidence", speed.confidence()); + gen.writeEndObject(); + } + + private void writeVehicleLength(JsonGenerator gen, VehicleLength length) throws IOException { + gen.writeStartObject(); + gen.writeNumberField("value", length.value()); + gen.writeNumberField("confidence", length.confidence()); + gen.writeEndObject(); + } + + private void writeAccelerationComponent(JsonGenerator gen, AccelerationComponent component) throws IOException { + gen.writeStartObject(); + gen.writeNumberField("value", component.value()); + gen.writeNumberField("confidence", component.confidence()); + gen.writeEndObject(); + } + + private void writeCurvature(JsonGenerator gen, Curvature curvature) throws IOException { + gen.writeStartObject(); + gen.writeNumberField("value", curvature.value()); + gen.writeNumberField("confidence", curvature.confidence()); + gen.writeEndObject(); + } + + private void writeYawRate(JsonGenerator gen, YawRate yawRate) throws IOException { + gen.writeStartObject(); + gen.writeNumberField("value", yawRate.value()); + gen.writeNumberField("confidence", yawRate.confidence()); + gen.writeEndObject(); + } + + private void writeSteeringWheelAngle(JsonGenerator gen, SteeringWheelAngle angle) throws IOException { + gen.writeStartObject(); + gen.writeNumberField("value", angle.value()); + gen.writeNumberField("confidence", angle.confidence()); + gen.writeEndObject(); + } + + private void writeAccelerationControl(JsonGenerator gen, AccelerationControl control) throws IOException { + gen.writeStartObject(); + gen.writeBooleanField("brake_pedal_engaged", control.brakePedalEngaged()); + gen.writeBooleanField("gas_pedal_engaged", control.gasPedalEngaged()); + gen.writeBooleanField("emergency_brake_engaged", control.emergencyBrakeEngaged()); + gen.writeBooleanField("collision_warning_engaged", control.collisionWarningEngaged()); + gen.writeBooleanField("acc_engaged", control.accEngaged()); + gen.writeBooleanField("cruise_control_engaged", control.cruiseControlEngaged()); + gen.writeBooleanField("speed_limiter_engaged", control.speedLimiterEngaged()); + gen.writeEndObject(); + } + + private void writeCenDsrcTollingZone(JsonGenerator gen, CenDsrcTollingZone zone) throws IOException { + gen.writeStartObject(); + gen.writeNumberField("protected_zone_latitude", zone.protectedZoneLatitude()); + gen.writeNumberField("protected_zone_longitude", zone.protectedZoneLongitude()); + if (zone.cenDsrcTollingZoneId() != null) { + gen.writeNumberField("cen_dsrc_tolling_zone_id", zone.cenDsrcTollingZoneId()); + } + gen.writeEndObject(); + } +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/CamAsn1Payload.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/CamAsn1Payload.java new file mode 100644 index 000000000..737c87dac --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/CamAsn1Payload.java @@ -0,0 +1,55 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.model; + +/** + * CamAsn1Payload v2.3.0 + *

+ * Base64-encoded ASN.1 payload alternative. + * + * @param version ASN.1 PDU version + * @param payload Base64-encoded ASN.1 binary payload + */ +public record CamAsn1Payload( + String version, + String payload) implements CamMessage230 { + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private String version; + private String payload; + + private Builder() {} + + public Builder version(String version) { + this.version = version; + return this; + } + + public Builder payload(String payload) { + this.payload = payload; + return this; + } + + public CamAsn1Payload build() { + return new CamAsn1Payload( + requireNonNull(version, "version"), + requireNonNull(payload, "payload")); + } + + private static T requireNonNull(T value, String field) { + if (value == null) { + throw new IllegalStateException("Missing field: " + field); + } + return value; + } + } +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/CamEnvelope230.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/CamEnvelope230.java new file mode 100644 index 000000000..2b88d9c0d --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/CamEnvelope230.java @@ -0,0 +1,101 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.model; + +/** + * CamEnvelope230 - base class to build a JSON CAM v2.3.0 with its header + *

+ * This 2.3.0 version corresponds to the following ETSI references: + *

    + *
  • CAM TS 103 900 - version 2.2.1
  • + *
  • CDD TS 102 894-2 - version 2.3.1
  • + *
+ * + * @param messageType Type of the message carried in message property (cam) + * @param messageFormat {@link MessageFormat} + * @param sourceUuid Unique id for the message sender + * @param timestamp Timestamp when the message was generated since Unix Epoch (millisecond) + * @param version JSON message format version (2.3.0) + * @param message {@link CamMessage230} + */ +public record CamEnvelope230( + String messageType, + String messageFormat, + String sourceUuid, + long timestamp, + String version, + CamMessage230 message) { + + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for CamEnvelope230. + *

+ * Mandatory fields: + *

    + *
  • messageType - hardcoded (cam)
  • + *
  • messageFormat - see {@link MessageFormat}
  • + *
  • sourceUuid
  • + *
  • timestamp
  • + *
  • version - hardcoded (2.3.0)
  • + *
  • message
  • + *
+ */ + public static final class Builder { + private final String messageType; + private String messageFormat; + private String sourceUuid; + private Long timestamp; + private final String version; + private CamMessage230 message; + + private Builder() { + this.messageType = "cam"; + this.version = "2.3.0"; + } + + public Builder messageFormat(String messageFormat) { + this.messageFormat = messageFormat; + return this; + } + + public Builder sourceUuid(String sourceUuid) { + this.sourceUuid = sourceUuid; + return this; + } + + public Builder timestamp(long timestamp) { + this.timestamp = timestamp; + return this; + } + + public Builder message(CamMessage230 message) { + this.message = message; + return this; + } + + public CamEnvelope230 build() { + return new CamEnvelope230( + requireNonNull(messageType, "message_type"), + requireNonNull(messageFormat, "message_format"), + requireNonNull(sourceUuid, "source_uuid"), + requireNonNull(timestamp, "timestamp"), + requireNonNull(version, "version"), + requireNonNull(message, "message")); + } + + private static T requireNonNull(T value, String field) { + if (value == null) { + throw new IllegalStateException("Missing field: " + field); + } + return value; + } + } +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/CamMessage230.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/CamMessage230.java new file mode 100644 index 000000000..2df6b6dea --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/CamMessage230.java @@ -0,0 +1,16 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.model; + +/** + * CamMessage230 + *

+ * Interface for the two message representations described by the schema + * ({@link CamStructuredData} vs. {@link CamAsn1Payload}). + */ +public sealed interface CamMessage230 permits CamStructuredData, CamAsn1Payload {} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/CamStructuredData.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/CamStructuredData.java new file mode 100644 index 000000000..dd5ee8d0c --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/CamStructuredData.java @@ -0,0 +1,119 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.model; + +import com.orange.iot3mobility.messages.cam.v230.model.basiccontainer.BasicContainer; +import com.orange.iot3mobility.messages.cam.v230.model.highfrequencycontainer.HighFrequencyContainer; +import com.orange.iot3mobility.messages.cam.v230.model.lowfrequencycontainer.LowFrequencyContainer; +import com.orange.iot3mobility.messages.cam.v230.model.specialvehiclecontainer.SpecialVehicleContainer; + +/** + * CAM v2.3.0 + *

+ * Structured JSON CAM payload. + * + * @param protocolVersion Version of the ITS message + * @param stationId Identifier for an ITS-S + * @param generationDeltaTime Time of the reference position in the CAM, considered as time of the CAM generation. + * TimestampIts mod 65 536. TimestampIts represents an integer value in milliseconds since + * 2004-01-01T00:00:00:000Z. oneMilliSec(1) + * @param basicContainer {@link BasicContainer} + * @param highFrequencyContainer {@link HighFrequencyContainer} + * @param lowFrequencyContainer Optional {@link LowFrequencyContainer} + * @param specialVehicleContainer Optional {@link SpecialVehicleContainer} + */ +public record CamStructuredData( + int protocolVersion, + long stationId, + int generationDeltaTime, + BasicContainer basicContainer, + HighFrequencyContainer highFrequencyContainer, + LowFrequencyContainer lowFrequencyContainer, + SpecialVehicleContainer specialVehicleContainer) implements CamMessage230 { + + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for CamStructuredData. + *

+ * Mandatory fields: + *

    + *
  • protocolVersion
  • + *
  • stationId
  • + *
  • generationDeltaTime
  • + *
  • basicContainer
  • + *
  • highFrequencyContainer
  • + *
+ */ + public static final class Builder { + private Integer protocolVersion; + private Long stationId; + private Integer generationDeltaTime; + private BasicContainer basicContainer; + private HighFrequencyContainer highFrequencyContainer; + private LowFrequencyContainer lowFrequencyContainer; + private SpecialVehicleContainer specialVehicleContainer; + + private Builder() {} + + public Builder protocolVersion(int protocolVersion) { + this.protocolVersion = protocolVersion; + return this; + } + + public Builder stationId(long stationId) { + this.stationId = stationId; + return this; + } + + public Builder generationDeltaTime(int generationDeltaTime) { + this.generationDeltaTime = generationDeltaTime; + return this; + } + + public Builder basicContainer(BasicContainer basicContainer) { + this.basicContainer = basicContainer; + return this; + } + + public Builder highFrequencyContainer(HighFrequencyContainer highFrequencyContainer) { + this.highFrequencyContainer = highFrequencyContainer; + return this; + } + + public Builder lowFrequencyContainer(LowFrequencyContainer lowFrequencyContainer) { + this.lowFrequencyContainer = lowFrequencyContainer; + return this; + } + + public Builder specialVehicleContainer(SpecialVehicleContainer specialVehicleContainer) { + this.specialVehicleContainer = specialVehicleContainer; + return this; + } + + public CamStructuredData build() { + return new CamStructuredData( + requireNonNull(protocolVersion, "protocol_version"), + requireNonNull(stationId, "station_id"), + requireNonNull(generationDeltaTime, "generation_delta_time"), + requireNonNull(basicContainer, "basic_container"), + requireNonNull(highFrequencyContainer, "high_frequency_container"), + lowFrequencyContainer, + specialVehicleContainer); + } + + private static T requireNonNull(T value, String field) { + if (value == null) { + throw new IllegalStateException("Missing field: " + field); + } + return value; + } + } +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/MessageFormat.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/MessageFormat.java new file mode 100644 index 000000000..c7b6b8b65 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/MessageFormat.java @@ -0,0 +1,24 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.model; + +/** + * MessageFormat v2.3.0 + *

+ * json/raw or asn1/uper + */ +public enum MessageFormat { + JSON_RAW("json/raw"), + ASN1_UPER("asn1/uper"); + + public final String value; + + MessageFormat(String value) { + this.value = value; + } +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/basiccontainer/Altitude.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/basiccontainer/Altitude.java new file mode 100644 index 000000000..6a5167659 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/basiccontainer/Altitude.java @@ -0,0 +1,22 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.model.basiccontainer; + +/** + * Altitude v2.3.0 + *

+ * Altitude and an altitude accuracy of the geographical point. + * + * @param value Altitude of a geographical point. Unit: 0,01 metre. negativeOutOfRange (-100000), + * positiveOutOfRange (800000), unavailable (800001) + * @param confidence Confidence level of the altitudeValue. alt-000-01 (0), alt-000-02 (1), alt-000-05 (2), + * alt-000-10 (3), alt-000-20 (4), alt-000-50 (5), alt-001-00 (6), alt-002-00 (7), alt-005-00 (8), + * alt-010-00 (9), alt-020-00 (10), alt-050-00 (11), alt-100-00 (12), alt-200-00 (13), + * outOfRange (14), unavailable (15) + */ +public record Altitude(int value, int confidence) {} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/basiccontainer/BasicContainer.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/basiccontainer/BasicContainer.java new file mode 100644 index 000000000..2d05568a6 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/basiccontainer/BasicContainer.java @@ -0,0 +1,64 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.model.basiccontainer; + +import com.orange.iot3mobility.messages.StationType; + +/** + * BasicContainer v2.3.0 + * + * @param stationType {@link StationType} Integer value + * @param referencePosition {@link ReferencePosition} + */ +public record BasicContainer( + int stationType, + ReferencePosition referencePosition) { + + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for BasicContainer. + *

+ * Mandatory fields: + *

    + *
  • stationType
  • + *
  • referencePosition
  • + *
+ */ + public static final class Builder { + private Integer stationType; + private ReferencePosition referencePosition; + + private Builder() {} + + public Builder stationType(int stationType) { + this.stationType = stationType; + return this; + } + + public Builder referencePosition(ReferencePosition referencePosition) { + this.referencePosition = referencePosition; + return this; + } + + public BasicContainer build() { + return new BasicContainer( + requireNonNull(stationType, "station_type"), + requireNonNull(referencePosition, "reference_position")); + } + + private static T requireNonNull(T value, String field) { + if (value == null) { + throw new IllegalStateException("Missing field: " + field); + } + return value; + } + } +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/basiccontainer/PositionConfidenceEllipse.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/basiccontainer/PositionConfidenceEllipse.java new file mode 100644 index 000000000..0f3d2a36f --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/basiccontainer/PositionConfidenceEllipse.java @@ -0,0 +1,23 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.model.basiccontainer; + +/** + * PositionConfidenceEllipse v2.3.0 + * + * @param semiMajor Half of length of the major axis, i.e. distance between the centre point and major axis point of + * the position accuracy ellipse. Unit: 0,1 metre + * @param semiMinor Half of length of the minor axis, i.e. distance between the centre point and minor axis point of + * the position accuracy ellipse. Unit: 0,1 metre + * @param semiMajorOrientation Angle value in degrees described in the WGS84 reference system with respect to the + * WGS84 north. Unit: 0,1 degree. valueNotUsed (3600), unavailable (3601) + */ +public record PositionConfidenceEllipse( + int semiMajor, + int semiMinor, + int semiMajorOrientation) {} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/basiccontainer/ReferencePosition.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/basiccontainer/ReferencePosition.java new file mode 100644 index 000000000..6ddfb463d --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/basiccontainer/ReferencePosition.java @@ -0,0 +1,80 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.model.basiccontainer; + +/** + * ReferencePosition v2.3.0 + *

+ * Position within a geographic coordinate system together with a confidence ellipse. + * + * @param latitude Latitude of the geographical point. Unit: 0,1 microdegree + * @param longitude Longitude of the geographical point. Unit: 0,1 microdegree + * @param positionConfidenceEllipse {@link PositionConfidenceEllipse} + * @param altitude {@link Altitude} + */ +public record ReferencePosition( + int latitude, + int longitude, + PositionConfidenceEllipse positionConfidenceEllipse, + Altitude altitude) { + + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for ReferencePosition. + *

+ * Mandatory fields: + *

    + *
  • latitude
  • + *
  • longitude
  • + *
  • positionConfidenceEllipse
  • + *
  • altitude
  • + *
+ */ + public static final class Builder { + private Integer latitude; + private Integer longitude; + private PositionConfidenceEllipse positionConfidenceEllipse; + private Altitude altitude; + + private Builder() {} + + public Builder latitudeLongitude(int latitude, int longitude) { + this.latitude = latitude; + this.longitude = longitude; + return this; + } + + public Builder positionConfidenceEllipse(PositionConfidenceEllipse positionConfidenceEllipse) { + this.positionConfidenceEllipse = positionConfidenceEllipse; + return this; + } + + public Builder altitude(Altitude altitude) { + this.altitude = altitude; + return this; + } + + public ReferencePosition build() { + return new ReferencePosition( + requireNonNull(latitude, "latitude"), + requireNonNull(longitude, "longitude"), + requireNonNull(positionConfidenceEllipse, "position_confidence_ellipse"), + requireNonNull(altitude, "altitude")); + } + + private static T requireNonNull(T value, String field) { + if (value == null) { + throw new IllegalStateException("Missing field: " + field); + } + return value; + } + } +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/highfrequencycontainer/AccelerationComponent.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/highfrequencycontainer/AccelerationComponent.java new file mode 100644 index 000000000..1c094b68a --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/highfrequencycontainer/AccelerationComponent.java @@ -0,0 +1,18 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.model.highfrequencycontainer; + +/** + * AccelerationComponent v2.3.0 + * + * @param value Acceleration value. Unit: 0.1 m/s2. pointOneMeterPerSecSquaredForward(1), + * pointOneMeterPerSecSquaredBackward(-1), unavailable(161) + * @param confidence Confidence of acceleration value. pointOneMeterPerSecSquared(1), outOfRange(101), + * unavailable(102) + */ +public record AccelerationComponent(int value, int confidence) {} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/highfrequencycontainer/AccelerationControl.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/highfrequencycontainer/AccelerationControl.java new file mode 100644 index 000000000..a727d9a39 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/highfrequencycontainer/AccelerationControl.java @@ -0,0 +1,28 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.model.highfrequencycontainer; + +/** + * AccelerationControl v2.3.0 + * + * @param brakePedalEngaged Driver is stepping on the brake pedal + * @param gasPedalEngaged Driver is stepping on the gas pedal + * @param emergencyBrakeEngaged Emergency brake system is engaged + * @param collisionWarningEngaged Collision warning system is engaged + * @param accEngaged Adaptative cruise control is engaged + * @param cruiseControlEngaged Cruise control is engaged + * @param speedLimiterEngaged Speed limiter is engaged + */ +public record AccelerationControl( + boolean brakePedalEngaged, + boolean gasPedalEngaged, + boolean emergencyBrakeEngaged, + boolean collisionWarningEngaged, + boolean accEngaged, + boolean cruiseControlEngaged, + boolean speedLimiterEngaged) {} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/highfrequencycontainer/BasicVehicleContainerHighFrequency.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/highfrequencycontainer/BasicVehicleContainerHighFrequency.java new file mode 100644 index 000000000..d0a657ed8 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/highfrequencycontainer/BasicVehicleContainerHighFrequency.java @@ -0,0 +1,200 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.model.highfrequencycontainer; + +/** + * BasicVehicleContainerHighFrequency v2.3.0 + * + * @param heading {@link Heading} + * @param speed {@link Speed} + * @param driveDirection Vehicle drive direction (forward or backward) of the originating ITS-S. forward (0), + * backward (1), unavailable (2) + * @param vehicleLength {@link VehicleLength} + * @param vehicleWidth Vehicle Width of the vehicle ITS-S that originates the CAM excluding side mirrors and possible + * similar extensions. outOfRange (61), unavailable (62) + * @param longitudinalAcceleration Longitudinal {@link AccelerationComponent} + * @param curvature {@link Curvature} + * @param curvatureCalculationMode Whether vehicle yaw-rate is used in the calculation of the curvature of the vehicle + * ITS-S that originates the CAM. yawRateUsed (0), yawRateNotUsed (1), unavailable (2) + * @param yawRate {@link YawRate} + * @param accelerationControl Optional {@link AccelerationControl} + * @param lanePosition Optional component which represents the lanePosition of the referencePosition of a vehicle. + * offTheRoad (-1), innerHardShoulder (0), outerHardShoulder (14) + * @param steeringWheelAngle Optional {@link SteeringWheelAngle} + * @param lateralAcceleration Optional lateral {@link AccelerationComponent} + * @param verticalAcceleration Optional vertical {@link AccelerationComponent} + * @param performanceClass Optional component which characterizes the maximum age of the CAM data elements with regard + * to the generation delta time. unavailable (0), performanceClassA (1), performanceClassB (2) + * @param cenDsrcTollingZone Optional {@link CenDsrcTollingZone} + */ +public record BasicVehicleContainerHighFrequency( + Heading heading, + Speed speed, + int driveDirection, + VehicleLength vehicleLength, + int vehicleWidth, + AccelerationComponent longitudinalAcceleration, + Curvature curvature, + int curvatureCalculationMode, + YawRate yawRate, + AccelerationControl accelerationControl, + Integer lanePosition, + SteeringWheelAngle steeringWheelAngle, + AccelerationComponent lateralAcceleration, + AccelerationComponent verticalAcceleration, + Integer performanceClass, + CenDsrcTollingZone cenDsrcTollingZone) implements HighFrequencyContainer { + + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for BasicVehicleContainerHighFrequency. + *

+ * Mandatory fields: + *

    + *
  • heading
  • + *
  • speed
  • + *
  • driveDirection
  • + *
  • vehicleLength
  • + *
  • vehicleWidth
  • + *
  • longitudinalAcceleration
  • + *
  • curvature
  • + *
  • curvatureCalculationMode
  • + *
  • yawRate
  • + *
+ */ + public static final class Builder { + private Heading heading; + private Speed speed; + private Integer driveDirection; + private VehicleLength vehicleLength; + private Integer vehicleWidth; + private AccelerationComponent longitudinalAcceleration; + private Curvature curvature; + private Integer curvatureCalculationMode; + private YawRate yawRate; + private AccelerationControl accelerationControl; + private Integer lanePosition; + private SteeringWheelAngle steeringWheelAngle; + private AccelerationComponent lateralAcceleration; + private AccelerationComponent verticalAcceleration; + private Integer performanceClass; + private CenDsrcTollingZone cenDsrcTollingZone; + + private Builder() {} + + public Builder heading(Heading heading) { + this.heading = heading; + return this; + } + + public Builder speed(Speed speed) { + this.speed = speed; + return this; + } + + public Builder driveDirection(Integer driveDirection) { + this.driveDirection = driveDirection; + return this; + } + + public Builder vehicleLength(VehicleLength vehicleLength) { + this.vehicleLength = vehicleLength; + return this; + } + + public Builder vehicleWidth(Integer vehicleWidth) { + this.vehicleWidth = vehicleWidth; + return this; + } + + public Builder longitudinalAcceleration(AccelerationComponent longitudinalAcceleration) { + this.longitudinalAcceleration = longitudinalAcceleration; + return this; + } + + public Builder curvature(Curvature curvature) { + this.curvature = curvature; + return this; + } + + public Builder curvatureCalculationMode(Integer curvatureCalculationMode) { + this.curvatureCalculationMode = curvatureCalculationMode; + return this; + } + + public Builder yawRate(YawRate yawRate) { + this.yawRate = yawRate; + return this; + } + + public Builder accelerationControl(AccelerationControl accelerationControl) { + this.accelerationControl = accelerationControl; + return this; + } + + public Builder lanePosition(Integer lanePosition) { + this.lanePosition = lanePosition; + return this; + } + + public Builder steeringWheelAngle(SteeringWheelAngle steeringWheelAngle) { + this.steeringWheelAngle = steeringWheelAngle; + return this; + } + + public Builder lateralAcceleration(AccelerationComponent lateralAcceleration) { + this.lateralAcceleration = lateralAcceleration; + return this; + } + + public Builder verticalAcceleration(AccelerationComponent verticalAcceleration) { + this.verticalAcceleration = verticalAcceleration; + return this; + } + + public Builder performanceClass(Integer performanceClass) { + this.performanceClass = performanceClass; + return this; + } + + public Builder cenDsrcTollingZone(CenDsrcTollingZone cenDsrcTollingZone) { + this.cenDsrcTollingZone = cenDsrcTollingZone; + return this; + } + + public BasicVehicleContainerHighFrequency build() { + return new BasicVehicleContainerHighFrequency( + requireNonNull(heading, "heading"), + requireNonNull(speed, "speed"), + requireNonNull(driveDirection, "drive_direction"), + requireNonNull(vehicleLength, "vehicle_length"), + requireNonNull(vehicleWidth, "vehicle_width"), + requireNonNull(longitudinalAcceleration, "longitudinal_acceleration"), + requireNonNull(curvature, "curvature"), + requireNonNull(curvatureCalculationMode, "curvature_calculation_mode"), + requireNonNull(yawRate, "yaw_rate"), + accelerationControl, + lanePosition, + steeringWheelAngle, + lateralAcceleration, + verticalAcceleration, + performanceClass, + cenDsrcTollingZone); + } + + private static T requireNonNull(T value, String field) { + if (value == null) { + throw new IllegalStateException("Missing field: " + field); + } + return value; + } + } +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/highfrequencycontainer/CenDsrcTollingZone.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/highfrequencycontainer/CenDsrcTollingZone.java new file mode 100644 index 000000000..afc46b8f7 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/highfrequencycontainer/CenDsrcTollingZone.java @@ -0,0 +1,20 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.model.highfrequencycontainer; + +/** + * CenDsrcTollingZone v2.3.0 + * + * @param protectedZoneLatitude Latitude of the protected zone. Unit: 0,1 microdegree + * @param protectedZoneLongitude Longitude of the protected zone. Unit: 0,1 microdegree + * @param cenDsrcTollingZoneId Optional identifier of a protected communication zone + */ +public record CenDsrcTollingZone( + int protectedZoneLatitude, + int protectedZoneLongitude, + Integer cenDsrcTollingZoneId) {} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/highfrequencycontainer/Curvature.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/highfrequencycontainer/Curvature.java new file mode 100644 index 000000000..82cc08548 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/highfrequencycontainer/Curvature.java @@ -0,0 +1,19 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.model.highfrequencycontainer; + +/** + * Curvature v2.3.0 + * + * @param value Curvature of the vehicle trajectory. Unit: 1 / turn radius in meters * 10000. + * outOfRangeNegative (-1023), straight (0), outOfRangePositive (1022), unavailable (1023) + * @param confidence Confidence of the curvature value with a predefined confidence level. onePerMeter-0-00002 (0), + * onePerMeter-0-0001 (1), onePerMeter-0-0005 (2), onePerMeter-0-002 (3), onePerMeter-0-01 (4), + * onePerMeter-0-1 (5), outOfRange (6), unavailable (7) + */ +public record Curvature(int value, int confidence) {} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/highfrequencycontainer/Heading.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/highfrequencycontainer/Heading.java new file mode 100644 index 000000000..606b35be5 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/highfrequencycontainer/Heading.java @@ -0,0 +1,18 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.model.highfrequencycontainer; + +/** + * Heading v2.3.0 + * + * @param value Heading value in a WGS84 co-ordinates system. Unit: 0.1 degree. wgs84North(0), wgs84East(900), + * wgs84South(1800), wgs84West(2700), unavailable(3601) + * @param confidence Confidence of the heading value with a predefined confidence level. + * equalOrWithinZeroPointOneDegree (1), equalOrWithinOneDegree (10), outOfRange(126), unavailable(127) + */ +public record Heading(int value, int confidence) {} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/highfrequencycontainer/HighFrequencyContainer.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/highfrequencycontainer/HighFrequencyContainer.java new file mode 100644 index 000000000..8be53a32c --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/highfrequencycontainer/HighFrequencyContainer.java @@ -0,0 +1,16 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.model.highfrequencycontainer; + +/** + * HighFrequencyContainer v2.3.0 + *

+ * One of {@link BasicVehicleContainerHighFrequency} or {@link RsuContainerHighFrequency} + */ +public sealed interface HighFrequencyContainer + permits BasicVehicleContainerHighFrequency, RsuContainerHighFrequency {} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/highfrequencycontainer/ProtectedCommunicationZone.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/highfrequencycontainer/ProtectedCommunicationZone.java new file mode 100644 index 000000000..067410aa9 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/highfrequencycontainer/ProtectedCommunicationZone.java @@ -0,0 +1,100 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.model.highfrequencycontainer; + +/** + * ProtectedCommunicationZone v2.3.0 + * + * @param protectedZoneType Type of the protected zone. permanentCenDsrcTolling (0), temporaryCenDsrcTolling (1) + * @param expiryTime Optional time at which the validity of the protected communication zone will expire. ITS timestamp + * @param protectedZoneLatitude Latitude of the centre point of the protected communication zone. Unit: 0,1 microdegree + * @param protectedZoneLongitude Longitude of the centre point of the protected communication zone. Unit: 0,1 + * microdegree + * @param protectedZoneRadius Optional radius of the protected communication zone in metres + * @param protectedZoneId Optional ID of the protected communication zone + */ +public record ProtectedCommunicationZone( + int protectedZoneType, + Long expiryTime, + int protectedZoneLatitude, + int protectedZoneLongitude, + Integer protectedZoneRadius, + Integer protectedZoneId) { + + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for ProtectedCommunicationZone. + *

+ * Mandatory fields: + *

    + *
  • protectedZoneType
  • + *
  • protectedZoneLatitude
  • + *
  • protectedZoneLongitude
  • + *
+ */ + public static final class Builder { + private Integer protectedZoneType; + private Long expiryTime; + private Integer protectedZoneLatitude; + private Integer protectedZoneLongitude; + private Integer protectedZoneRadius; + private Integer protectedZoneId; + + private Builder() {} + + public Builder protectedZoneType(int protectedZoneType) { + this.protectedZoneType = protectedZoneType; + return this; + } + + public Builder expiryTime(long expiryTime) { + this.expiryTime = expiryTime; + return this; + } + + public Builder protectedZoneLatitude(int protectedZoneLatitude) { + this.protectedZoneLatitude = protectedZoneLatitude; + return this; + } + + public Builder protectedZoneLongitude(int protectedZoneLongitude) { + this.protectedZoneLongitude = protectedZoneLongitude; + return this; + } + + public Builder protectedZoneRadius(int protectedZoneRadius) { + this.protectedZoneRadius = protectedZoneRadius; + return this; + } + + public Builder protectedZoneId(int protectedZoneId) { + this.protectedZoneId = protectedZoneId; + return this; + } + + public ProtectedCommunicationZone build() { + return new ProtectedCommunicationZone( + requireNonNull(protectedZoneType, "protected_zone_type"), + expiryTime, + requireNonNull(protectedZoneLatitude, "protected_zone_latitude"), + requireNonNull(protectedZoneLongitude, "protected_zone_longitude"), + protectedZoneRadius, + protectedZoneId); + } + + private static T requireNonNull(T value, String field) { + if (value == null) { + throw new IllegalStateException("Missing field: " + field); + } + return value; + } + } +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/highfrequencycontainer/RsuContainerHighFrequency.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/highfrequencycontainer/RsuContainerHighFrequency.java new file mode 100644 index 000000000..a1914c960 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/highfrequencycontainer/RsuContainerHighFrequency.java @@ -0,0 +1,23 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.model.highfrequencycontainer; + +import java.util.List; +import java.util.Objects; + +/** + * RsuContainerHighFrequency v2.3.0 + * + * @param protectedCommunicationZonesRsu List of RSU {@link ProtectedCommunicationZone} + */ +public record RsuContainerHighFrequency( + List protectedCommunicationZonesRsu) implements HighFrequencyContainer { + public RsuContainerHighFrequency { + protectedCommunicationZonesRsu = List.copyOf(Objects.requireNonNull(protectedCommunicationZonesRsu)); + } +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/highfrequencycontainer/Speed.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/highfrequencycontainer/Speed.java new file mode 100644 index 000000000..f411bff67 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/highfrequencycontainer/Speed.java @@ -0,0 +1,17 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.model.highfrequencycontainer; + +/** + * Speed v2.3.0 + * + * @param value Speed value. Unit 0.01 m/s. standstill(0), oneCentimeterPerSec(1), unavailable(16383) + * @param confidence Confidence of the speed value. equalOrWithinOneCentimeterPerSec(1), + * equalOrWithinOneMeterPerSec(100), outOfRange(126), unavailable(127) + */ +public record Speed(int value, int confidence) {} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/highfrequencycontainer/SteeringWheelAngle.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/highfrequencycontainer/SteeringWheelAngle.java new file mode 100644 index 000000000..425392aea --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/highfrequencycontainer/SteeringWheelAngle.java @@ -0,0 +1,18 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.model.highfrequencycontainer; + +/** + * SteeringWheelAngle v2.3.0 + * + * @param value Steering wheel angle value. Negative when the wheel is turned clockwise (i.e. to the right), positive + * when turned anti-clockwise (i.e. to the left). Unit: 1,5 degree. negativeOutOfRange (-511), + * positiveOutOfRange (511), unavailable (512) + * @param confidence Confidence of steering wheel angle value. Unit: 1,5 degree. outOfRange (126), unavailable (127) + */ +public record SteeringWheelAngle(int value, int confidence) {} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/highfrequencycontainer/VehicleLength.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/highfrequencycontainer/VehicleLength.java new file mode 100644 index 000000000..8edca6ca2 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/highfrequencycontainer/VehicleLength.java @@ -0,0 +1,17 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.model.highfrequencycontainer; + +/** + * VehicleLength v2.3.0 + * + * @param value Length of vehicle. tenCentimeters(1), outOfRange(1022), unavailable(1023) + * @param confidence Indication of the length value confidence. noTrailerPresent (0), trailerPresentWithKnownLength (1), + * trailerPresentWithUnknownLength (2), trailerPresenceIsUnknown (3), unavailable (4) + */ +public record VehicleLength(int value, int confidence) {} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/highfrequencycontainer/YawRate.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/highfrequencycontainer/YawRate.java new file mode 100644 index 000000000..184be398d --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/highfrequencycontainer/YawRate.java @@ -0,0 +1,20 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.model.highfrequencycontainer; + +/** + * YawRate v2.3.0 + * + * @param value Yaw rate represents the vehicle rotation around z-axis of the coordinate system. Negative for clockwise + * rotation (i.e. to the right), positive for anti-clockwise rotation (i.e. to the left). Unit: 0,01 degree + * per second. negativeOutOfRange (-32766), positiveOutOfRange (32766), unavailable (32767) + * @param confidence Confidence for the yaw rate value. degSec-000-01 (0), degSec-000-05 (1), degSec-000-10 (2), + * degSec-001-00 (3), degSec-005-00 (4), degSec-010-00 (5), degSec-100-00 (6), outOfRange (7), + * unavailable (8) + */ +public record YawRate(int value, int confidence) {} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/lowfrequencycontainer/BasicVehicleContainerLowFrequency.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/lowfrequencycontainer/BasicVehicleContainerLowFrequency.java new file mode 100644 index 000000000..34df19c41 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/lowfrequencycontainer/BasicVehicleContainerLowFrequency.java @@ -0,0 +1,30 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.model.lowfrequencycontainer; + +import java.util.List; +import java.util.Objects; + +/** + * BasicVehicleContainerLowFrequency v2.3.0 + * + * @param vehicleRole Role of the vehicle ITS-S that originates the CAM. default (0), publicTransport (1), + * specialTransport (2), dangerousGoods (3), roadWork (4), rescue (5), emergency (6), safetyCar (7), + * agriculture (8), commercial (9), military (10), roadOperator (11), taxi (12), uvar (13), + * rfu1 (14), rfu2 (15) + * @param exteriorLights {@link ExteriorLights} + * @param pathHistory List of {@link PathPoint} + */ +public record BasicVehicleContainerLowFrequency( + int vehicleRole, + ExteriorLights exteriorLights, + List pathHistory) { + public BasicVehicleContainerLowFrequency { + pathHistory = List.copyOf(Objects.requireNonNull(pathHistory)); + } +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/lowfrequencycontainer/DeltaReferencePosition.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/lowfrequencycontainer/DeltaReferencePosition.java new file mode 100644 index 000000000..e1e61a4a9 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/lowfrequencycontainer/DeltaReferencePosition.java @@ -0,0 +1,27 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.model.lowfrequencycontainer; + +/** + * DeltaReferencePosition v2.3.0 + *

+ * Geographical point position as a 3-dimensional offset position to a geographical reference point. + * + * @param deltaLatitude Delta latitude offset with regard to the latitude value of the reference position. + * Unit 0,1 microdegree. negativeOutOfRange (-131071), positiveOutOfRange (131071), + * unavailable (131072) + * @param deltaLongitude Delta longitude offset with regard to the longitude value of the reference position. + * Unit 0,1 microdegree. negativeOutOfRange (-131071), positiveOutOfRange (131071), + * unavailable (131072) + * @param deltaAltitude Delta altitude offset with regard to the altitude value of the reference position. + * Unit: 0,01 metre. negativeOutOfRange (-12700), positiveOutOfRange (12799), unavailable (12800) + */ +public record DeltaReferencePosition( + int deltaLatitude, + int deltaLongitude, + int deltaAltitude) {} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/lowfrequencycontainer/ExteriorLights.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/lowfrequencycontainer/ExteriorLights.java new file mode 100644 index 000000000..4953cecb5 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/lowfrequencycontainer/ExteriorLights.java @@ -0,0 +1,32 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.model.lowfrequencycontainer; + +/** + * ExteriorLights v2.3.0 + *

+ * Status of the most important exterior lights switches of the vehicle ITS-S that originates the CAM. + * + * @param lowBeamHeadlightsOn When the low beam headlight switch is on + * @param highBeamHeadlightsOn When the high beam headlight switch is on + * @param leftTurnSignalOn When the left turn signal switch is on + * @param rightTurnSignalOn When the right turn signal switch is on + * @param daytimeRunningLightsOn When the daytime running light switch is on + * @param reverseLightOn When the reverse light switch is on + * @param fogLightOn When the tail fog light switch is on + * @param parkingLightsOn When the parking light switch is on + */ +public record ExteriorLights( + boolean lowBeamHeadlightsOn, + boolean highBeamHeadlightsOn, + boolean leftTurnSignalOn, + boolean rightTurnSignalOn, + boolean daytimeRunningLightsOn, + boolean reverseLightOn, + boolean fogLightOn, + boolean parkingLightsOn) {} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/lowfrequencycontainer/LowFrequencyContainer.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/lowfrequencycontainer/LowFrequencyContainer.java new file mode 100644 index 000000000..37efbe2b4 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/lowfrequencycontainer/LowFrequencyContainer.java @@ -0,0 +1,16 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.model.lowfrequencycontainer; + +/** + * LowFrequencyContainer v2.3.0 + * + * @param basicVehicleContainerLowFrequency {@link BasicVehicleContainerLowFrequency} + */ +public record LowFrequencyContainer( + BasicVehicleContainerLowFrequency basicVehicleContainerLowFrequency) {} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/lowfrequencycontainer/PathPoint.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/lowfrequencycontainer/PathPoint.java new file mode 100644 index 000000000..108420834 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/lowfrequencycontainer/PathPoint.java @@ -0,0 +1,18 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.model.lowfrequencycontainer; + +/** + * + * @param pathPosition {@link DeltaReferencePosition} + * @param pathDeltaTime Optional travel time separated from a waypoint to the predefined reference position. + * Unit: 0,01 second + */ +public record PathPoint( + DeltaReferencePosition pathPosition, + Integer pathDeltaTime) {} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/CauseCode.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/CauseCode.java new file mode 100644 index 000000000..07629c6f9 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/CauseCode.java @@ -0,0 +1,101 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.model.specialvehiclecontainer; + +/** + * CauseCode v2.3.0 + *

+ * Representation of the cause code value of a traffic event. + * + * @param cause Main cause of a detected event. trafficCondition (1), accident (2), roadworks (3), impassability (5), + * adverseWeatherCondition-Adhesion (6), aquaplaning (7), hazardousLocation-SurfaceCondition (9), + * hazardousLocation-ObstacleOnTheRoad (10), hazardousLocation-AnimalOnTheRoad (11), + * humanPresenceOnTheRoad (12), wrongWayDriving (14), rescueAndRecoveryWorkInProgress (15), + * adverseWeatherCondition-ExtremeWeatherCondition (17), adverseWeatherCondition-Visibility (18), + * adverseWeatherCondition-Precipitation (19), violence (20), slowVehicle (26), dangerousEndOfQueue (27), + * publicTransportVehicleApproaching (28), vehicleBreakdown (91), postCrash (92), humanProblem (93), + * stationaryVehicle (94), emergencyVehicleApproaching (95), hazardousLocation-DangerousCurve (96), + * collisionRisk (97), signalViolation (98), dangerousSituation (99), railwayLevelCrossing (100) + * @param subcause Subordinate cause of a detected event. + *

    + *
  • trafficCondition (1): unavailable (0), increasedVolumeOfTraffic (1), + * trafficJamSlowlyIncreasing (2), trafficJamIncreasing (3), trafficJamStronglyIncreasing (4), + * trafficJam (5), trafficJamSlightlyDecreasing (6), trafficJamDecreasing (7), + * trafficJamStronglyDecreasing (8), trafficJamStable (9)
  • + *
  • accident (2): unavailable (0), multiVehicleAccident (1), heavyAccident (2), + * accidentInvolvingLorry (3), accidentInvolvingBus (4), accidentInvolvingHazardousMaterials (5), + * accidentOnOppositeLane (6), unsecuredAccident (7), assistanceRequested (8)
  • + *
  • roadworks (3): unavailable (0), majorRoadworks (1), roadMarkingWork (2), + * slowMovingRoadMaintenance (3), shortTermStationaryRoadworks (4), streetCleaning (5), + * winterService (6), setupPhase (7), remodellingPhase (8), dismantlingPhase (9)
  • + *
  • impassability (5): unavailable (0), flooding (1), dangerOfAvalanches (2), + * blastingOfAvalanches (3), landslips (4), chemicalSpillage (5), winterClosure (6), sinkhole (7), + * earthquakeDamage (8), fallenTrees (9), rockfalls (10), sewerOverflow (11), stormDamage (12), + * subsidence (13), burstPipe (14), burstWaterMain (15), fallenPowerCables (16), snowDrifts (17)
  • + *
  • adverseWeatherCondition-Adhesion (6): unavailable (0), heavyFrostOnRoad (1), fuelOnRoad (2), + * mudOnRoad (3), snowOnRoad (4), iceOnRoad (5), blackIceOnRoad (6), oilOnRoad (7), looseChippings (8), + * instantBlackIce (9), roadsSalted (10)
  • + *
  • aquaplaning (7): none
  • + *
  • hazardousLocation-SurfaceCondition (9): unavailable (0), rockfalls (1), earthquakeDamage (2), + * sewerCollapse (3), subsidence (4), snowDrifts (5), stormDamage (6), burstPipe (7), + * volcanoEruption (8), fallingIce (9), fire (10), flooding (11)
  • + *
  • hazardousLocation-ObstacleOnTheRoad (10): unavailable (0), shedLoad (1), partsOfVehicles (2), + * partsOfTyres (3), bigObjects (4), fallenTrees (5), hubCaps (6), waitingVehicles (7)
  • + *
  • hazardousLocation-AnimalOnTheRoad (11): unavailable (0), wildAnimals (1), herdOfAnimals (2), + * smallAnimals (3), largeAnimals (4), wildAnimalsSmall (5), wildAnimalsLarge (6), domesticAnimals (7), + * domesticAnimalsSmall (8), domesticAnimalsLarge (9)
  • + *
  • humanPresenceOnTheRoad (12): unavailable (0), childrenOnRoadway (1), cyclistOnRoadway (2), + * motorcyclistOnRoadway (3), pedestrian (4), ordinary-pedestrian (5), road-worker (6), + * first-responder (7), lightVruVehicle (8), bicyclist (9), wheelchair-user (10), horse-and-rider (11), + * rollerskater (12), e-scooter (13), personal-transporter (14), pedelec (15), speed-pedelec (16), + * ptw (17), moped (18), motorcycle (19), motorcycle-and-sidecar-right (20), + * motorcycle-and-sidecar-left (21)
  • + *
  • wrongWayDriving (14): unavailable (0), wrongLane (1), wrongDirection (2)
  • + *
  • rescueAndRecoveryWorkInProgress (15): unavailable (0), emergencyVehicles (1), + * rescueHelicopterLanding (2), policeActivityOngoing (3), medicalEmergencyOngoing (4), + * childAbductionInProgress (5), prioritizedVehicle (6), rescueAndRecoveryVehicle (7)
  • + *
  • adverseWeatherCondition-ExtremeWeatherCondition (17): unavailable (0), strongWinds (1), + * damagingHail (2), hurricane (3), thunderstorm (4), tornado (5), blizzard (6)
  • + *
  • adverseWeatherCondition-Visibility (18): unavailable (0), fog (1), smoke (2), heavySnowfall (3), + * heavyRain (4), heavyHail (5), lowSunGlare (6), sandstorms (7), swarmsOfInsects (8)
  • + *
  • adverseWeatherCondition-Precipitation (19): unavailable (0), heavyRain (1), heavySnowfall (2), + * softHail (3)
  • + *
  • violence (20): none
  • + *
  • slowVehicle (26): none
  • + *
  • dangerousEndOfQueue (27): unavailable (0), suddenEndOfQueue (1), queueOverHill (2), + * queueAroundBend (3), queueInTunnel (4)
  • + *
  • publicTransportVehicleApproaching (28): none
  • + *
  • vehicleBreakdown (91): unavailable (0), lackOfFuel (1), lackOfBatteryPower (2), + * engineProblem (3), transmissionProblem (4), engineCoolingProblem (5), brakingSystemProblem (6), + * steeringProblem (7), tyrePuncture (8), tyrePressureProblem (9), vehicleOnFire (10)
  • + *
  • postCrash (92): unavailable (0), accidentWithoutECallTriggered (1), + * accidentWithECallManuallyTriggered (2), accidentWithECallAutomaticallyTriggered (3), + * accidentWithECallTriggeredWithoutAccessToCellularNetwork (4)
  • + *
  • humanProblem (93): unavailable (0), glycemiaProblem (1), heartProblem (2)
  • + *
  • stationaryVehicle (94): unavailable (0), humanProblem (1), vehicleBreakdown (2), postCrash (3), + * publicTransportStop (4), carryingDangerousGoods (5), vehicleOnFire (6)
  • + *
  • emergencyVehicleApproaching (95): unavailable (0), emergencyVehicleApproaching (1), + * prioritizedVehicleApproaching (2)
  • + *
  • hazardousLocation-DangerousCurve (96): unavailable (0), dangerousLeftTurnCurve (1), + * dangerousRightTurnCurve (2), multipleCurvesStartingWithUnknownTurningDirection (3), + * multipleCurvesStartingWithLeftTurn (4), multipleCurvesStartingWithRightTurn (5)
  • + *
  • collisionRisk (97): unavailable (0), longitudinalCollisionRisk (1), crossingCollisionRisk (2), + * lateralCollisionRisk (3), vulnerableRoadUser (4), collisionRiskWithPedestrian (5), + * collisionRiskWithCyclist (6), collisionRiskWithMotorVehicle (7)
  • + *
  • signalViolation (98): unavailable (0), stopSignViolation (1), trafficLightViolation (2), + * turningRegulationViolation (3)
  • + *
  • dangerousSituation (99): unavailable (0), emergencyElectronicBrakeEngaged (1), + * preCrashSystemEngaged (2), espEngaged (3), absEngaged (4), aebEngaged (5), brakeWarningEngaged (6), + * collisionRiskWarningEngaged (7)
  • + *
  • railwayLevelCrossing (100): unavailable (0), doNotCrossAbnormalSituation (1), closed (2), + * unguarded (3), nominal (4), trainApproaching (5)
  • + *
+ */ +public record CauseCode( + int cause, + Integer subcause) {} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/ClosedLanes.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/ClosedLanes.java new file mode 100644 index 000000000..b70336ad4 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/ClosedLanes.java @@ -0,0 +1,24 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.model.specialvehiclecontainer; + +/** + * ClosedLanes v2.3.0 + *

+ * Provides information about the opening/closure status of the lanes ahead. + * + * @param innerHardShoulderStatus Optional and shall be included if an inner hard shoulder is present and the + * information is known. availableForStopping(0), closed(1), availableForDriving(2) + * @param outerHardShoulderStatus Optional and shall be included if an outer hard shoulder is present and the + * information is known. availableForStopping(0), closed(1), availableForDriving(2) + * @param drivingLaneStatus Optional {@link DrivingLaneStatus} and shall be included if the information is known + */ +public record ClosedLanes( + Integer innerHardShoulderStatus, + Integer outerHardShoulderStatus, + DrivingLaneStatus drivingLaneStatus) {} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/DangerousGoodsContainer.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/DangerousGoodsContainer.java new file mode 100644 index 000000000..f0fe0e0f2 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/DangerousGoodsContainer.java @@ -0,0 +1,20 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.model.specialvehiclecontainer; + +/** + * DangerousGoodsContainer v2.3.0 + *

+ * If the vehicleRole component is set to dangerousGoods(3) this container shall be present. + * + * @param dangerousGoodsBasic Type of the dangerous goods being carried by a heavy vehicle. explosives1 (0), + * explosives2 (1), explosives3 (2), explosives4…(15), infectiousSubstances (16), + * radioactiveMaterial (17), corrosiveSubstances (18), miscellaneousDangerousSubstances (19) + */ +public record DangerousGoodsContainer( + int dangerousGoodsBasic) implements SpecialVehiclePayload {} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/DrivingLaneStatus.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/DrivingLaneStatus.java new file mode 100644 index 000000000..197cb3a19 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/DrivingLaneStatus.java @@ -0,0 +1,40 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.model.specialvehiclecontainer; + +/** + * DrivingLaneStatus v2.3.0 + * + * @param lane1Closed Indicates whether lane 1 is closed or not + * @param lane2Closed Indicates whether lane 2 is closed or not + * @param lane3Closed Indicates whether lane 3 is closed or not + * @param lane4Closed Indicates whether lane 4 is closed or not + * @param lane5Closed Indicates whether lane 5 is closed or not + * @param lane6Closed Indicates whether lane 6 is closed or not + * @param lane7Closed Indicates whether lane 7 is closed or not + * @param lane8Closed Indicates whether lane 8 is closed or not + * @param lane9Closed Indicates whether lane 9 is closed or not + * @param lane10Closed Indicates whether lane 10 is closed or not + * @param lane11Closed Indicates whether lane 11 is closed or not + * @param lane12Closed Indicates whether lane 12 is closed or not + * @param lane13Closed Indicates whether lane 13 is closed or not + */ +public record DrivingLaneStatus( + boolean lane1Closed, + boolean lane2Closed, + boolean lane3Closed, + boolean lane4Closed, + boolean lane5Closed, + boolean lane6Closed, + boolean lane7Closed, + boolean lane8Closed, + boolean lane9Closed, + boolean lane10Closed, + boolean lane11Closed, + boolean lane12Closed, + boolean lane13Closed) {} \ No newline at end of file diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/EmergencyContainer.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/EmergencyContainer.java new file mode 100644 index 000000000..22de65627 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/EmergencyContainer.java @@ -0,0 +1,69 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.model.specialvehiclecontainer; + +/** + * EmergencyContainer v2.3.0 + *

+ * If the vehicleRole component is set to emergency(6) this container shall be present. + * + * @param lightBarSirenInUse {@link LightBarSiren} + * @param incidentIndication Optional {@link IncidentIndication} + * @param emergencyPriority Optional {@link EmergencyPriority} + */ +public record EmergencyContainer( + LightBarSiren lightBarSirenInUse, + IncidentIndication incidentIndication, + EmergencyPriority emergencyPriority) implements SpecialVehiclePayload { + + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for EmergencyContainer + *

+ * Mandatory field: lightBarSirenInUse + */ + public static final class Builder { + private LightBarSiren lightBarSirenInUse; + private IncidentIndication incidentIndication; + private EmergencyPriority emergencyPriority; + + private Builder() {} + + public Builder lightBarSirenInUse(LightBarSiren lightBarSirenInUse) { + this.lightBarSirenInUse = lightBarSirenInUse; + return this; + } + + public Builder incidentIndication(IncidentIndication incidentIndication) { + this.incidentIndication = incidentIndication; + return this; + } + + public Builder emergencyPriority(EmergencyPriority emergencyPriority) { + this.emergencyPriority = emergencyPriority; + return this; + } + + public EmergencyContainer build() { + return new EmergencyContainer( + requireNonNull(lightBarSirenInUse, "light_bar_siren_in_use"), + incidentIndication, + emergencyPriority); + } + + private static T requireNonNull(T value, String field) { + if (value == null) { + throw new IllegalStateException("Missing field: " + field); + } + return value; + } + } +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/EmergencyPriority.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/EmergencyPriority.java new file mode 100644 index 000000000..2758c0748 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/EmergencyPriority.java @@ -0,0 +1,21 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.model.specialvehiclecontainer; + +/** + * EmergencyPriority v2.3.0 + *

+ * Represents right of way indicator of the vehicle ITS-S that originates the CAM PDU. + * + * @param requestForRightOfWay When the vehicle is requesting/assuming the right of way + * @param requestForFreeCrossingAtTrafficLight When the vehicle is requesting/assuming the right to pass at a (red) + * traffic light + */ +public record EmergencyPriority( + boolean requestForRightOfWay, + boolean requestForFreeCrossingAtTrafficLight) {} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/IncidentIndication.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/IncidentIndication.java new file mode 100644 index 000000000..8c73ef97f --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/IncidentIndication.java @@ -0,0 +1,20 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.model.specialvehiclecontainer; + +/** + * IncidentIndication v2.3.0 + *

+ * Incident related to the roadworks to provide additional information of the roadworks zone. + * + * @param ccAndScc {@link CauseCode} (Choice has been made not to use CauseCodeV2 object as defined in DENM TS CDD, + * because it would require a object definition for each cause just to hold the + * subcause code; Using the deprecated CauseCode instead) + */ +public record IncidentIndication( + CauseCode ccAndScc) {} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/LightBarSiren.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/LightBarSiren.java new file mode 100644 index 000000000..317e4b9db --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/LightBarSiren.java @@ -0,0 +1,20 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.model.specialvehiclecontainer; + +/** + * LightBarSiren v2.3.0 + *

+ * Status of light bar and any sort of audible alarm system besides the horn. + * + * @param lightBarActivated When the light bar is activated + * @param sirenActivated When the siren is activated + */ +public record LightBarSiren( + boolean lightBarActivated, + boolean sirenActivated) {} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/PtActivation.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/PtActivation.java new file mode 100644 index 000000000..00535499c --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/PtActivation.java @@ -0,0 +1,19 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.model.specialvehiclecontainer; + +/** + * PtActivation v2.3.0 + * + * @param ptActivationType Type of activation. undefinedCodingType (0), r09-16CodingType (1), vdv-50149CodingType (2) + * @param ptActivationData Data of activation or information like the public transport line number or the schedule + * delay of a public transport vehicle + */ +public record PtActivation( + int ptActivationType, + String ptActivationData) {} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/PublicTransportContainer.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/PublicTransportContainer.java new file mode 100644 index 000000000..53af85638 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/PublicTransportContainer.java @@ -0,0 +1,20 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.model.specialvehiclecontainer; + +/** + * PublicTransportContainer v2.3.0 + *

+ * If the vehicleRole component is set to publicTransport(1) this container shall be present. + * + * @param embarkationStatus Passenger embarkation is currently ongoing + * @param ptActivation Optional {@link PtActivation} used for controlling traffic lights, barriers, bollards, etc. + */ +public record PublicTransportContainer( + boolean embarkationStatus, + PtActivation ptActivation) implements SpecialVehiclePayload {} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/RescueContainer.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/RescueContainer.java new file mode 100644 index 000000000..602813a17 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/RescueContainer.java @@ -0,0 +1,18 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.model.specialvehiclecontainer; + +/** + * RescueContainer v2.3.0 + *

+ * If the vehicleRole component is set to rescue(5) this container shall be present. + * + * @param lightBarSirenInUse {@link LightBarSiren} + */ +public record RescueContainer( + LightBarSiren lightBarSirenInUse) implements SpecialVehiclePayload {} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/RoadWorksContainer.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/RoadWorksContainer.java new file mode 100644 index 000000000..a2f869ecd --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/RoadWorksContainer.java @@ -0,0 +1,25 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.model.specialvehiclecontainer; + +/** + * RoadWorksContainer v2.3.0 + *

+ * If the vehicleRole component is set to roadWork(4) this container shall be present. + * + * @param roadWorksSubCauseCode Optional component, in case the originating ITS-S is mounted to a vehicle ITS-S + * participating to roadwork. unavailable (0), majorRoadworks (1), roadMarkingWork (2), + * slowMovingRoadMaintenance (3), shortTermStationaryRoadworks (4), streetCleaning (5), + * winterService (6), setupPhase (7), remodellingPhase (8), dismantlingPhase (9) + * @param lightBarSirenInUse {@link LightBarSiren} + * @param closedLanes Optional {@link ClosedLanes} + */ +public record RoadWorksContainer( + Integer roadWorksSubCauseCode, + LightBarSiren lightBarSirenInUse, + ClosedLanes closedLanes) implements SpecialVehiclePayload {} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/SafetyCarContainer.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/SafetyCarContainer.java new file mode 100644 index 000000000..bb458808d --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/SafetyCarContainer.java @@ -0,0 +1,27 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.model.specialvehiclecontainer; + +/** + * SafetyCarContainer v2.3.0 + *

+ * If the vehicleRole component is set to safetyCar(7) this container shall be present. + * + * @param lightBarSirenInUse {@link LightBarSiren} + * @param incidentIndication Optional {@link IncidentIndication} + * @param trafficRule Optional rule to indicate whether vehicles are allowed to overtake a safety car that is + * originating this CAM. noPassing(0), noPassingForTrucks(1), passToRight(2), passToLeft(3), + * passToLeftOrRight (4) + * @param speedLimit Optional speed to indicate whether a speed limit is applied to vehicles following the safety car. + * Unit: km/h + */ +public record SafetyCarContainer( + LightBarSiren lightBarSirenInUse, + IncidentIndication incidentIndication, + Integer trafficRule, + Integer speedLimit) implements SpecialVehiclePayload {} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/SpecialTransportContainer.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/SpecialTransportContainer.java new file mode 100644 index 000000000..a2b336c68 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/SpecialTransportContainer.java @@ -0,0 +1,20 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.model.specialvehiclecontainer; + +/** + * SpecialTransportContainer v2.3.0 + *

+ * If the vehicleRole component is set to specialTransport(2) this container shall be present. + * + * @param specialTransportType {@link SpecialTransportType} + * @param lightBarSirenInUse {@link LightBarSiren} + */ +public record SpecialTransportContainer( + SpecialTransportType specialTransportType, + LightBarSiren lightBarSirenInUse) implements SpecialVehiclePayload {} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/SpecialTransportType.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/SpecialTransportType.java new file mode 100644 index 000000000..e61943c3e --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/SpecialTransportType.java @@ -0,0 +1,24 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.model.specialvehiclecontainer; + +/** + * SpecialTransportType v2.3.0 + *

+ * Vehicle is carrying goods in the special transport conditions. + * + * @param heavyLoad Vehicle is carrying goods with heavy load + * @param excessWidth Vehicle is carrying goods in excess of width + * @param excessLength Vehicle is carrying goods in excess of length + * @param excessHeight Vehicle is carrying goods in excess of height + */ +public record SpecialTransportType( + boolean heavyLoad, + boolean excessWidth, + boolean excessLength, + boolean excessHeight) {} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/SpecialVehicleContainer.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/SpecialVehicleContainer.java new file mode 100644 index 000000000..8c4b14736 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/SpecialVehicleContainer.java @@ -0,0 +1,16 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.model.specialvehiclecontainer; + +/** + * SpecialVehicleContainer v2.3.0 + * + * @param payload {@link SpecialVehiclePayload} + */ +public record SpecialVehicleContainer( + SpecialVehiclePayload payload) {} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/SpecialVehiclePayload.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/SpecialVehiclePayload.java new file mode 100644 index 000000000..7323d1e52 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/model/specialvehiclecontainer/SpecialVehiclePayload.java @@ -0,0 +1,31 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.model.specialvehiclecontainer; + +/** + * SpecialVehiclePayload v2.3.0 + *

+ * One of : + *

    + *
  • {@link EmergencyContainer}
  • + *
  • {@link DangerousGoodsContainer}
  • + *
  • {@link PublicTransportContainer}
  • + *
  • {@link RescueContainer}
  • + *
  • {@link RoadWorksContainer}
  • + *
  • {@link SafetyCarContainer}
  • + *
  • {@link SpecialTransportContainer}
  • + *
+ */ +public sealed interface SpecialVehiclePayload + permits EmergencyContainer, + DangerousGoodsContainer, + PublicTransportContainer, + RescueContainer, + RoadWorksContainer, + SafetyCarContainer, + SpecialTransportContainer {} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/validation/CamValidationException.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/validation/CamValidationException.java new file mode 100644 index 000000000..554a73fa9 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/validation/CamValidationException.java @@ -0,0 +1,25 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.validation; + +import com.orange.iot3mobility.messages.cam.core.CamException; + +/** + * Dedicated exception thrown whenever a CAM 2.3.0 payload is not compliant + * with the JSON schema or the project-specific rules. + */ +public final class CamValidationException extends CamException { + + public CamValidationException(String message) { + super(message); + } + + public CamValidationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/validation/CamValidator230.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/validation/CamValidator230.java new file mode 100644 index 000000000..3c3b60bf2 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/cam/v230/validation/CamValidator230.java @@ -0,0 +1,405 @@ +/* + Copyright 2016-2026 Orange + + This software is distributed under the MIT license, see LICENSE.txt file for more details. + + @author Mathieu LEFEBVRE + */ +package com.orange.iot3mobility.messages.cam.v230.validation; + +import com.orange.iot3mobility.messages.cam.v230.model.*; +import com.orange.iot3mobility.messages.cam.v230.model.basiccontainer.*; +import com.orange.iot3mobility.messages.cam.v230.model.highfrequencycontainer.*; +import com.orange.iot3mobility.messages.cam.v230.model.lowfrequencycontainer.*; +import com.orange.iot3mobility.messages.cam.v230.model.specialvehiclecontainer.*; + +import java.util.Base64; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +/** + * Static validation utility for CAM 2.3.0 envelopes (structured JSON or ASN.1 payloads). + */ +public final class CamValidator230 { + + private static final long MIN_TIMESTAMP = 1514764800000L; // 2018-01-01 + private static final Set MESSAGE_FORMATS = Set.of("json/raw", "asn1/uper"); + + private CamValidator230() { + } + + public static void validateEnvelope(CamEnvelope230 envelope) { + requireNonNull("envelope", envelope); + requireEquals("message_type", envelope.messageType(), "cam"); + if (envelope.messageFormat() != null && !MESSAGE_FORMATS.contains(envelope.messageFormat())) { + throw new CamValidationException("message_format must be one of " + MESSAGE_FORMATS); + } + requireNotBlank("source_uuid", envelope.sourceUuid()); + checkMin("timestamp", envelope.timestamp(), MIN_TIMESTAMP); + requireEquals("version", envelope.version(), "2.3.0"); + + CamMessage230 payload = requireNonNull("message", envelope.message()); + if (payload instanceof CamStructuredData structured) { + if (envelope.messageFormat() != null && !"json/raw".equals(envelope.messageFormat())) { + throw new CamValidationException("message_format must be 'json/raw' for structured payloads"); + } + validateStructured(structured); + } else if (payload instanceof CamAsn1Payload asn1) { + if (envelope.messageFormat() != null && !"asn1/uper".equals(envelope.messageFormat())) { + throw new CamValidationException("message_format must be 'asn1/uper' for ASN.1 payloads"); + } + validateAsn1(asn1); + } else { + throw new CamValidationException("Unsupported CAM payload type: " + payload.getClass().getName()); + } + } + + /* ------------------------------------------------------------------------ + Structured payload + --------------------------------------------------------------------- */ + + private static void validateStructured(CamStructuredData message) { + requireNonNull("structured_message", message); + checkRange("protocol_version", message.protocolVersion(), 0, 255); + checkRange("station_id", message.stationId(), 0, 4294967295L); + checkRange("generation_delta_time", message.generationDeltaTime(), 0, 65535); + + validateBasicContainer(message.basicContainer()); + validateHighFrequencyContainer(message.highFrequencyContainer()); + + if (message.lowFrequencyContainer() != null) { + validateLowFrequencyContainer(message.lowFrequencyContainer()); + } + if (message.specialVehicleContainer() != null) { + validateSpecialVehicleContainer(message.specialVehicleContainer()); + } + } + + private static void validateBasicContainer(BasicContainer basic) { + requireNonNull("basic_container", basic); + checkRange("basic_container.station_type", basic.stationType(), 0, 255); + validateReferencePosition(basic.referencePosition()); + } + + private static void validateReferencePosition(ReferencePosition reference) { + requireNonNull("reference_position", reference); + checkRange("reference_position.latitude", reference.latitude(), -900000000, 900000001); + checkRange("reference_position.longitude", reference.longitude(), -1800000000, 1800000001); + validatePositionConfidenceEllipse("reference_position.position_confidence_ellipse", + reference.positionConfidenceEllipse()); + validateAltitude("reference_position.altitude", reference.altitude()); + } + + private static void validatePositionConfidenceEllipse(String prefix, PositionConfidenceEllipse ellipse) { + requireNonNull(prefix, ellipse); + checkRange(prefix + ".semi_major", ellipse.semiMajor(), 0, 4095); + checkRange(prefix + ".semi_minor", ellipse.semiMinor(), 0, 4095); + checkRange(prefix + ".semi_major_orientation", ellipse.semiMajorOrientation(), 0, 3601); + } + + private static void validateAltitude(String prefix, Altitude altitude) { + requireNonNull(prefix, altitude); + checkRange(prefix + ".value", altitude.value(), -100000, 800001); + checkRange(prefix + ".confidence", altitude.confidence(), 0, 15); + } + + private static void validateHighFrequencyContainer(HighFrequencyContainer container) { + requireNonNull("high_frequency_container", container); + if (container instanceof BasicVehicleContainerHighFrequency basicVehicle) { + validateBasicVehicleHF(basicVehicle); + } else if (container instanceof RsuContainerHighFrequency rsu) { + validateRsuHF(rsu); + } else { + throw new CamValidationException("Unknown high frequency container type: " + container.getClass().getName()); + } + } + + private static void validateBasicVehicleHF(BasicVehicleContainerHighFrequency container) { + validateHeading("high_frequency_container.heading", container.heading()); + validateSpeed("high_frequency_container.speed", container.speed()); + checkRange("high_frequency_container.drive_direction", container.driveDirection(), 0, 2); + validateVehicleLength("high_frequency_container.vehicle_length", container.vehicleLength()); + checkRange("high_frequency_container.vehicle_width", container.vehicleWidth(), 1, 62); + validateAccelerationComponent("high_frequency_container.longitudinal_acceleration", container.longitudinalAcceleration()); + validateCurvature("high_frequency_container.curvature", container.curvature()); + checkRange("high_frequency_container.curvature_calculation_mode", container.curvatureCalculationMode(), 0, 2); + validateYawRate("high_frequency_container.yaw_rate", container.yawRate()); + + if (container.lanePosition() != null) { + checkRange("high_frequency_container.lane_position", container.lanePosition(), -1, 14); + } + if (container.steeringWheelAngle() != null) { + validateSteeringWheelAngle("high_frequency_container.steering_wheel_angle", container.steeringWheelAngle()); + } + if (container.lateralAcceleration() != null) { + validateAccelerationComponent("high_frequency_container.lateral_acceleration", container.lateralAcceleration()); + } + if (container.verticalAcceleration() != null) { + validateAccelerationComponent("high_frequency_container.vertical_acceleration", container.verticalAcceleration()); + } + if (container.performanceClass() != null) { + checkRange("high_frequency_container.performance_class", container.performanceClass(), 0, 7); + } + if (container.cenDsrcTollingZone() != null) { + validateCenDsrcTollingZone(container.cenDsrcTollingZone()); + } + } + + private static void validateRsuHF(RsuContainerHighFrequency container) { + List zones = + requireNonNull("rsu_container_high_frequency.protected_communication_zones_rsu", + container.protectedCommunicationZonesRsu()); + int size = zones.size(); + if (size < 1 || size > 16) { + throw new CamValidationException("protected_communication_zones_rsu size out of range [1,16]: " + size); + } + for (int i = 0; i < zones.size(); i++) { + validateProtectedCommunicationZone("protected_communication_zones_rsu[" + i + "]", zones.get(i)); + } + } + + private static void validateProtectedCommunicationZone(String prefix, ProtectedCommunicationZone zone) { + requireNonNull(prefix, zone); + checkRange(prefix + ".protected_zone_type", zone.protectedZoneType(), 0, 1); + if (zone.expiryTime() != null) { + checkRange(prefix + ".expiry_time", zone.expiryTime(), 0, 4398046511103L); + } + checkRange(prefix + ".protected_zone_latitude", zone.protectedZoneLatitude(), -900000000, 900000001); + checkRange(prefix + ".protected_zone_longitude", zone.protectedZoneLongitude(), -1800000000, 1800000001); + if (zone.protectedZoneRadius() != null) { + checkRange(prefix + ".protected_zone_radius", zone.protectedZoneRadius(), 1, 255); + } + if (zone.protectedZoneId() != null) { + checkRange(prefix + ".protected_zone_id", zone.protectedZoneId(), 0, 134217727); + } + } + + private static void validateCenDsrcTollingZone(CenDsrcTollingZone zone) { + requireNonNull("cen_dsrc_tolling_zone", zone); + checkRange("cen_dsrc_tolling_zone.protected_zone_latitude", zone.protectedZoneLatitude(), -900000000, 900000001); + checkRange("cen_dsrc_tolling_zone.protected_zone_longitude", zone.protectedZoneLongitude(), -1800000000, 1800000001); + if (zone.cenDsrcTollingZoneId() != null) { + checkRange("cen_dsrc_tolling_zone.cen_dsrc_tolling_zone_id", zone.cenDsrcTollingZoneId(), 0, 134217727); + } + } + + private static void validateLowFrequencyContainer(LowFrequencyContainer container) { + BasicVehicleContainerLowFrequency low = + requireNonNull("low_frequency_container.basic_vehicle_container_low_frequency", + container.basicVehicleContainerLowFrequency()); + checkRange("low_frequency_container.vehicle_role", low.vehicleRole(), 0, 15); + requireNonNull("low_frequency_container.exterior_lights", low.exteriorLights()); + + List history = requireNonNull("low_frequency_container.path_history", low.pathHistory()); + if (history.size() > 40) { + throw new CamValidationException("path_history size exceeds 40"); + } + for (int i = 0; i < history.size(); i++) { + validatePathPoint("path_history[" + i + "]", history.get(i)); + } + } + + private static void validatePathPoint(String prefix, PathPoint pathPoint) { + requireNonNull(prefix, pathPoint); + validateDeltaReferencePosition(prefix + ".path_position", pathPoint.pathPosition()); + if (pathPoint.pathDeltaTime() != null) { + checkRange(prefix + ".path_delta_time", pathPoint.pathDeltaTime(), 1, 65535); + } + } + + private static void validateDeltaReferencePosition(String prefix, DeltaReferencePosition delta) { + requireNonNull(prefix, delta); + checkRange(prefix + ".delta_latitude", delta.deltaLatitude(), -131071, 131072); + checkRange(prefix + ".delta_longitude", delta.deltaLongitude(), -131071, 131072); + checkRange(prefix + ".delta_altitude", delta.deltaAltitude(), -12700, 12800); + } + + private static void validateSpecialVehicleContainer(SpecialVehicleContainer container) { + SpecialVehiclePayload payload = requireNonNull("special_vehicle_container.payload", container.payload()); + if (payload instanceof PublicTransportContainer pt) { + validatePublicTransport(pt); + } else if (payload instanceof SpecialTransportContainer st) { + validateLightBar("special_transport_container.light_bar_siren_in_use", st.lightBarSirenInUse()); + } else if (payload instanceof DangerousGoodsContainer dg) { + checkRange("dangerous_goods_container.dangerous_goods_basic", dg.dangerousGoodsBasic(), 0, 19); + } else if (payload instanceof RoadWorksContainer rw) { + validateRoadWorks(rw); + } else if (payload instanceof RescueContainer rescue) { + validateLightBar("rescue_container.light_bar_siren_in_use", rescue.lightBarSirenInUse()); + } else if (payload instanceof EmergencyContainer emergency) { + validateEmergency(emergency); + } else if (payload instanceof SafetyCarContainer safetyCar) { + validateSafetyCar(safetyCar); + } else { + throw new CamValidationException("Unknown special vehicle payload: " + payload.getClass().getName()); + } + } + + private static void validatePublicTransport(PublicTransportContainer container) { + requireNonNull("public_transport_container", container); + if (container.ptActivation() != null) { + PtActivation activation = container.ptActivation(); + checkRange("public_transport_container.pt_activation.pt_activation_type", + activation.ptActivationType(), 0, 255); + checkStringLength("public_transport_container.pt_activation.pt_activation_data", + activation.ptActivationData(), 1, 20); + } + } + + private static void validateRoadWorks(RoadWorksContainer container) { + validateLightBar("road_works_container.light_bar_siren_in_use", container.lightBarSirenInUse()); + if (container.roadWorksSubCauseCode() != null) { + checkRange("road_works_container.road_works_sub_cause_code", + container.roadWorksSubCauseCode(), 0, 255); + } + if (container.closedLanes() != null) { + ClosedLanes lanes = container.closedLanes(); + if (lanes.innerHardShoulderStatus() != null) { + checkRange("road_works_container.closed_lanes.inner_hard_shoulder_status", + lanes.innerHardShoulderStatus(), 0, 2); + } + if (lanes.outerHardShoulderStatus() != null) { + checkRange("road_works_container.closed_lanes.outer_hard_shoulder_status", + lanes.outerHardShoulderStatus(), 0, 2); + } + } + } + + private static void validateEmergency(EmergencyContainer container) { + validateLightBar("emergency_container.light_bar_siren_in_use", container.lightBarSirenInUse()); + if (container.incidentIndication() != null) { + validateIncidentIndication("emergency_container.incident_indication", container.incidentIndication()); + } + } + + private static void validateSafetyCar(SafetyCarContainer container) { + validateLightBar("safety_car_container.light_bar_siren_in_use", container.lightBarSirenInUse()); + if (container.incidentIndication() != null) { + validateIncidentIndication("safety_car_container.incident_indication", container.incidentIndication()); + } + if (container.trafficRule() != null) { + checkRange("safety_car_container.traffic_rule", container.trafficRule(), 0, 4); + } + if (container.speedLimit() != null) { + checkRange("safety_car_container.speed_limit", container.speedLimit(), 1, 255); + } + } + + private static void validateIncidentIndication(String prefix, IncidentIndication indication) { + requireNonNull(prefix, indication); + validateCauseCode(prefix + ".cc_and_scc", indication.ccAndScc()); + } + + private static void validateCauseCode(String prefix, CauseCode causeCode) { + requireNonNull(prefix, causeCode); + checkRange(prefix + ".cause", causeCode.cause(), 0, 255); + if (causeCode.subcause() != null) { + checkRange(prefix + ".subcause", causeCode.subcause(), 0, 255); + } + } + + private static void validateLightBar(String prefix, LightBarSiren lightBar) { + requireNonNull(prefix, lightBar); + } + + /* ------------------------------------------------------------------------ + ASN.1 payload branch + --------------------------------------------------------------------- */ + + private static void validateAsn1(CamAsn1Payload payload) { + requireNotBlank("message.version", payload.version()); + String encoded = requireNotBlank("message.payload", payload.payload()); + try { + Base64.getDecoder().decode(encoded); + } catch (IllegalArgumentException ex) { + throw new CamValidationException("message.payload must be valid Base64", ex); + } + } + + /* ------------------------------------------------------------------------ + Shared helpers + --------------------------------------------------------------------- */ + + private static void validateHeading(String prefix, Heading heading) { + requireNonNull(prefix, heading); + checkRange(prefix + ".value", heading.value(), 0, 3601); + checkRange(prefix + ".confidence", heading.confidence(), 1, 127); + } + + private static void validateSpeed(String prefix, Speed speed) { + requireNonNull(prefix, speed); + checkRange(prefix + ".value", speed.value(), 0, 16383); + checkRange(prefix + ".confidence", speed.confidence(), 1, 127); + } + + private static void validateVehicleLength(String prefix, VehicleLength length) { + requireNonNull(prefix, length); + checkRange(prefix + ".value", length.value(), 1, 1023); + checkRange(prefix + ".confidence", length.confidence(), 0, 4); + } + + private static void validateAccelerationComponent(String prefix, AccelerationComponent component) { + requireNonNull(prefix, component); + checkRange(prefix + ".value", component.value(), -160, 161); + checkRange(prefix + ".confidence", component.confidence(), 0, 102); + } + + private static void validateCurvature(String prefix, Curvature curvature) { + requireNonNull(prefix, curvature); + checkRange(prefix + ".value", curvature.value(), -1023, 1023); + checkRange(prefix + ".confidence", curvature.confidence(), 0, 7); + } + + private static void validateYawRate(String prefix, YawRate yawRate) { + requireNonNull(prefix, yawRate); + checkRange(prefix + ".value", yawRate.value(), -32766, 32767); + checkRange(prefix + ".confidence", yawRate.confidence(), 0, 8); + } + + private static void validateSteeringWheelAngle(String prefix, SteeringWheelAngle angle) { + requireNonNull(prefix, angle); + checkRange(prefix + ".value", angle.value(), -511, 512); + checkRange(prefix + ".confidence", angle.confidence(), 1, 127); + } + + private static T requireNonNull(String field, T value) { + if (value == null) { + throw new CamValidationException("Missing mandatory field: " + field); + } + return value; + } + + private static String requireNotBlank(String field, String value) { + if (value == null || value.isBlank()) { + throw new CamValidationException("Missing mandatory field: " + field); + } + return value; + } + + private static void requireEquals(String field, String actual, String expected) { + if (!Objects.equals(actual, expected)) { + throw new CamValidationException(field + " must equal '" + expected + "'"); + } + } + + private static void checkRange(String field, long value, long min, long max) { + if (value < min || value > max) { + throw new CamValidationException(field + " out of range [" + min + ", " + max + "] (actual=" + value + ")"); + } + } + + private static void checkMin(String field, long value, long min) { + if (value < min) { + throw new CamValidationException(field + " inferior to min [" + min + "] (actual=" + value + ")"); + } + } + + private static void checkStringLength(String field, String value, int min, int max) { + requireNotBlank(field, value); + int len = value.length(); + if (len < min || len > max) { + throw new CamValidationException(field + " length out of range [" + min + ", " + max + "] (actual=" + len + ")"); + } + } +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/roadobjects/RoadUser.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/roadobjects/RoadUser.java index 42fbd5170..276f0d492 100644 --- a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/roadobjects/RoadUser.java +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/roadobjects/RoadUser.java @@ -8,7 +8,7 @@ package com.orange.iot3mobility.roadobjects; import com.orange.iot3mobility.its.StationType; -import com.orange.iot3mobility.its.json.cam.CAM; +import com.orange.iot3mobility.messages.cam.core.CamCodec; import com.orange.iot3mobility.quadkey.LatLng; public class RoadUser { @@ -18,18 +18,18 @@ public class RoadUser { private final String uuid; private StationType stationType; private LatLng position; - private float speed; // m/s - private float heading; // degree + private double speed; // m/s + private double heading; // degree private long timestamp; - private CAM cam; + private CamCodec.CamFrame camFrame; - public RoadUser(String uuid, StationType stationType, LatLng position, float speed, float heading, CAM cam) { + public RoadUser(String uuid, StationType stationType, LatLng position, double speed, double heading, CamCodec.CamFrame camFrame) { this.uuid = uuid; this.setStationType(stationType); this.position = position; this.speed = speed; this.heading = heading; - this.cam = cam; + this.camFrame = camFrame; updateTimestamp(); } @@ -53,32 +53,32 @@ public void setPosition(LatLng position) { this.position = position; } - public float getSpeed() { + public double getSpeed() { return speed; } - public float getSpeedKmh() { + public double getSpeedKmh() { return speed * 3.6f; } - public void setSpeed(float speed) { + public void setSpeed(double speed) { this.speed = speed; } - public float getHeading() { + public double getHeading() { return heading; } - public void setHeading(float heading) { + public void setHeading(double heading) { this.heading = heading; } - public void setCam(CAM cam) { - this.cam = cam; + public void setCamFrame(CamCodec.CamFrame camFrame) { + this.camFrame = camFrame; } - public CAM getCam() { - return cam; + public CamCodec.CamFrame getCamFrame() { + return camFrame; } public long getTimestamp() {