diff --git a/README.md b/README.md index 704a3028b..e231eab62 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) | [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) | [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) | [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) | [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) | +| **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/java/iot3/examples/src/main/java/com/orange/DenmV113Factory.java b/java/iot3/examples/src/main/java/com/orange/DenmV113Factory.java new file mode 100644 index 000000000..ac3d20a2f --- /dev/null +++ b/java/iot3/examples/src/main/java/com/orange/DenmV113Factory.java @@ -0,0 +1,63 @@ +package com.orange; + +import com.orange.iot3mobility.TrueTime; +import com.orange.iot3mobility.messages.EtsiConverter; +import com.orange.iot3mobility.messages.StationType; +import com.orange.iot3mobility.messages.denm.DenmHelper; +import com.orange.iot3mobility.messages.denm.v113.model.DenmEnvelope113; +import com.orange.iot3mobility.messages.denm.v113.model.DenmMessage113; +import com.orange.iot3mobility.messages.denm.v113.model.Origin; +import com.orange.iot3mobility.messages.denm.v113.model.managementcontainer.ActionId; +import com.orange.iot3mobility.messages.denm.v113.model.managementcontainer.ManagementContainer; +import com.orange.iot3mobility.messages.denm.v113.model.managementcontainer.ReferencePosition; +import com.orange.iot3mobility.messages.denm.v113.model.situationcontainer.EventType; +import com.orange.iot3mobility.messages.denm.v113.model.situationcontainer.SituationContainer; +import com.orange.iot3mobility.quadkey.LatLng; +import com.orange.iot3mobility.roadobjects.HazardType; + +public class DenmV113Factory { + private static final int PROTOCOL_VERSION = 2; + private static final long STATION_ID = 123456L; + private static final int LIFETIME = 10; + private static final int INFO_QUALITY = 7; + + private DenmV113Factory() { + // Factory class + } + + static DenmEnvelope113 createTestDenmEnvelope( + String sourceUuid, + HazardType hazardType, + LatLng position, + StationType stationType) { + return DenmEnvelope113.builder() + .origin(Origin.self.name()) + .sourceUuid(sourceUuid) + .timestamp(TrueTime.getAccurateTime()) + .message(DenmMessage113.builder() + .protocolVersion(PROTOCOL_VERSION) + .stationId(STATION_ID) + .managementContainer(ManagementContainer.builder() + .actionId(new ActionId( + STATION_ID, + DenmHelper.getNextSequenceNumber())) + .detectionTime(TrueTime.getAccurateETSITime()) + .referenceTime(TrueTime.getAccurateETSITime()) + .eventPosition(new ReferencePosition( + EtsiConverter.latitudeEtsi(position.getLatitude()), + EtsiConverter.longitudeEtsi(position.getLongitude()), + 0 + )) + .validityDuration(LIFETIME) + .stationType(stationType.value) + .build()) + .situationContainer(SituationContainer.builder() + .informationQuality(INFO_QUALITY) + .eventType(new EventType( + hazardType.getCause(), + hazardType.getSubcause())) + .build()) + .build()) + .build(); + } +} diff --git a/java/iot3/examples/src/main/java/com/orange/DenmV220Factory.java b/java/iot3/examples/src/main/java/com/orange/DenmV220Factory.java new file mode 100644 index 000000000..34c8c63aa --- /dev/null +++ b/java/iot3/examples/src/main/java/com/orange/DenmV220Factory.java @@ -0,0 +1,66 @@ +package com.orange; + +import com.orange.iot3mobility.TrueTime; +import com.orange.iot3mobility.messages.EtsiConverter; +import com.orange.iot3mobility.messages.StationType; +import com.orange.iot3mobility.messages.denm.DenmHelper; +import com.orange.iot3mobility.messages.denm.v220.model.DenmEnvelope220; +import com.orange.iot3mobility.messages.denm.v220.model.DenmMessage220; +import com.orange.iot3mobility.messages.denm.v220.model.defs.Altitude; +import com.orange.iot3mobility.messages.denm.v220.model.defs.PositionConfidenceEllipse; +import com.orange.iot3mobility.messages.denm.v220.model.managementcontainer.ActionId; +import com.orange.iot3mobility.messages.denm.v220.model.managementcontainer.ManagementContainer; +import com.orange.iot3mobility.messages.denm.v220.model.managementcontainer.ReferencePosition; +import com.orange.iot3mobility.messages.denm.v220.model.situationcontainer.CauseCode; +import com.orange.iot3mobility.messages.denm.v220.model.situationcontainer.SituationContainer; +import com.orange.iot3mobility.quadkey.LatLng; +import com.orange.iot3mobility.roadobjects.HazardType; + +public class DenmV220Factory { + private static final int PROTOCOL_VERSION = 2; + private static final long STATION_ID = 123456L; + private static final int LIFETIME = 10; + private static final int INFO_QUALITY = 7; + + private DenmV220Factory() { + // Factory class + } + + static DenmEnvelope220 createTestDenmEnvelope( + String sourceUuid, + HazardType hazardType, + LatLng position, + StationType stationType) { + return DenmEnvelope220.builder() + .sourceUuid(sourceUuid) + .timestamp(TrueTime.getAccurateTime()) + .message(DenmMessage220.builder() + .protocolVersion(PROTOCOL_VERSION) + .stationId(STATION_ID) + .managementContainer(ManagementContainer.builder() + .actionId(new ActionId( + STATION_ID, + DenmHelper.getNextSequenceNumber())) + .detectionTime(TrueTime.getAccurateETSITime()) + .referenceTime(TrueTime.getAccurateETSITime()) + .eventPosition(ReferencePosition.builder() + .latitude(EtsiConverter.latitudeEtsi(position.getLatitude())) + .longitude(EtsiConverter.longitudeEtsi(position.getLongitude())) + .positionConfidenceEllipse(new PositionConfidenceEllipse( + 4095, 4095, 3601)) + .altitude(new Altitude( + 0, 15)) + .build()) + .validityDuration(LIFETIME) + .stationType(stationType.value) + .build()) + .situationContainer(SituationContainer.builder() + .informationQuality(INFO_QUALITY) + .eventType(new CauseCode( + hazardType.getCause(), + hazardType.getSubcause())) + .build()) + .build()) + .build(); + } +} 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 3b2c624ad..6fd585296 100644 --- a/java/iot3/examples/src/main/java/com/orange/Iot3MobilityBootstrapExample.java +++ b/java/iot3/examples/src/main/java/com/orange/Iot3MobilityBootstrapExample.java @@ -6,6 +6,7 @@ import com.orange.iot3mobility.IoT3Mobility; import com.orange.iot3mobility.IoT3MobilityCallback; import com.orange.iot3mobility.Utils; +import com.orange.iot3mobility.messages.StationType; 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; @@ -14,9 +15,11 @@ import com.orange.iot3mobility.messages.cpm.core.CpmVersion; import com.orange.iot3mobility.messages.cpm.v121.model.CpmEnvelope121; import com.orange.iot3mobility.messages.cpm.v211.model.CpmEnvelope211; +import com.orange.iot3mobility.messages.denm.core.DenmCodec; +import com.orange.iot3mobility.messages.denm.core.DenmVersion; +import com.orange.iot3mobility.messages.denm.v113.model.DenmEnvelope113; +import com.orange.iot3mobility.messages.denm.v220.model.DenmEnvelope220; import com.orange.iot3mobility.roadobjects.HazardType; -import com.orange.iot3mobility.its.StationType; -import com.orange.iot3mobility.its.json.denm.DENM; import com.orange.iot3mobility.managers.IoT3RoadHazardCallback; import com.orange.iot3mobility.managers.IoT3RoadSensorCallback; import com.orange.iot3mobility.managers.IoT3RoadUserCallback; @@ -26,7 +29,6 @@ import com.orange.iot3mobility.roadobjects.RoadUser; import com.orange.iot3mobility.roadobjects.SensorObject; -import java.io.IOException; import java.net.URI; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -112,8 +114,14 @@ public void roadHazardExpired(RoadHazard roadHazard) { } @Override - public void denmArrived(DENM denm) { - System.out.println("DENM received: " + denm.getJsonDENM()); + public void denmArrived(DenmCodec.DenmFrame denmFrame) { + if(denmFrame.version().equals(DenmVersion.V1_1_3)) { + DenmEnvelope113 denmEnvelope113 = (DenmEnvelope113) denmFrame.envelope(); + System.out.println("Raw DENM v1.1.3: " + denmEnvelope113); + } else if(denmFrame.version().equals(DenmVersion.V2_2_0)) { + DenmEnvelope220 denmEnvelope220 = (DenmEnvelope220) denmFrame.envelope(); + System.out.println("Raw DENM v2.2.0: " + denmEnvelope220); + } } }); @@ -221,7 +229,7 @@ private static void setRegionOfInterest(LatLng roiPosition) { private static synchronized void startSendingMessages() { ScheduledExecutorService messageScheduler = Executors.newScheduledThreadPool(1); messageScheduler.scheduleWithFixedDelay(() -> sendTestCam(CamVersion.V1_1_3), 1, 1, TimeUnit.SECONDS); - messageScheduler.scheduleWithFixedDelay(Iot3MobilityBootstrapExample::sendTestDenm, 1, 10, TimeUnit.SECONDS); + messageScheduler.scheduleWithFixedDelay(() -> sendTestDenm(DenmVersion.V1_1_3), 1, 5, TimeUnit.SECONDS); messageScheduler.scheduleWithFixedDelay(() -> sendTestCpm(CpmVersion.V1_2_1), 1, 1, TimeUnit.SECONDS); } @@ -229,14 +237,34 @@ private static void sendTestCam(CamVersion camVersion) { LatLng position = new LatLng(48.625218, 2.243448); // center point of UTAC TEQMO try { ioT3Mobility.sendPosition(StationType.PASSENGER_CAR, position, 0, 0, 0, 0, 0, camVersion); - } catch (IOException e) { + } catch (Exception e) { System.out.println("CAM ERROR: " + e); } } - private static void sendTestDenm() { + private static void sendTestDenm(DenmVersion denmVersion) { LatLng position = new LatLng(48.626059, 2.247904); // planar area of UTAC TEQMO - ioT3Mobility.sendHazard(HazardType.ACCIDENT_NO_SUBCAUSE, position, 10, 7, StationType.PASSENGER_CAR); + HazardType hazardType = HazardType.ACCIDENT_NO_SUBCAUSE; + StationType stationType = StationType.PASSENGER_CAR; + try { + if(denmVersion == DenmVersion.V1_1_3) { + DenmEnvelope113 denmEnvelope113 = DenmV113Factory.createTestDenmEnvelope( + EXAMPLE_UUID, + hazardType, + position, + stationType); + ioT3Mobility.sendDenm(denmEnvelope113); + } else if(denmVersion == DenmVersion.V2_2_0) { + DenmEnvelope220 denmEnvelope220 = DenmV220Factory.createTestDenmEnvelope( + EXAMPLE_UUID, + hazardType, + position, + stationType); + ioT3Mobility.sendDenm(denmEnvelope220); + } + } catch (Exception e) { + System.out.println("DENM ERROR: " + e); + } } private static void sendTestCpm(CpmVersion cpmVersion) { 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 ae4352f5b..39e3b05b6 100644 --- a/java/iot3/examples/src/main/java/com/orange/Iot3MobilityExample.java +++ b/java/iot3/examples/src/main/java/com/orange/Iot3MobilityExample.java @@ -6,6 +6,7 @@ import com.orange.iot3mobility.IoT3Mobility; import com.orange.iot3mobility.IoT3MobilityCallback; import com.orange.iot3mobility.Utils; +import com.orange.iot3mobility.messages.StationType; 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; @@ -14,9 +15,11 @@ import com.orange.iot3mobility.messages.cpm.core.CpmVersion; import com.orange.iot3mobility.messages.cpm.v121.model.CpmEnvelope121; import com.orange.iot3mobility.messages.cpm.v211.model.CpmEnvelope211; +import com.orange.iot3mobility.messages.denm.core.DenmCodec; +import com.orange.iot3mobility.messages.denm.core.DenmVersion; +import com.orange.iot3mobility.messages.denm.v113.model.DenmEnvelope113; +import com.orange.iot3mobility.messages.denm.v220.model.DenmEnvelope220; import com.orange.iot3mobility.roadobjects.HazardType; -import com.orange.iot3mobility.its.StationType; -import com.orange.iot3mobility.its.json.denm.DENM; import com.orange.iot3mobility.managers.IoT3RoadHazardCallback; import com.orange.iot3mobility.managers.IoT3RoadSensorCallback; import com.orange.iot3mobility.managers.IoT3RoadUserCallback; @@ -27,7 +30,6 @@ import com.orange.iot3mobility.roadobjects.SensorObject; import com.orange.lwm2m.model.CustomLwm2mConnectivityStatisticsExample; -import java.io.IOException; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @@ -125,8 +127,14 @@ public void roadHazardExpired(RoadHazard roadHazard) { } @Override - public void denmArrived(DENM denm) { - System.out.println("DENM received: " + denm.getJsonDENM()); + public void denmArrived(DenmCodec.DenmFrame denmFrame) { + if(denmFrame.version().equals(DenmVersion.V1_1_3)) { + DenmEnvelope113 denmEnvelope113 = (DenmEnvelope113) denmFrame.envelope(); + System.out.println("Raw DENM v1.1.3: " + denmEnvelope113); + } else if(denmFrame.version().equals(DenmVersion.V2_2_0)) { + DenmEnvelope220 denmEnvelope220 = (DenmEnvelope220) denmFrame.envelope(); + System.out.println("Raw DENM v2.2.0: " + denmEnvelope220); + } } }); @@ -234,7 +242,7 @@ private static void setRegionOfInterest(LatLng roiPosition) { private static synchronized void startSendingMessages() { ScheduledExecutorService messageScheduler = Executors.newScheduledThreadPool(1); messageScheduler.scheduleWithFixedDelay(() -> sendTestCam(CamVersion.V1_1_3), 1, 1, TimeUnit.SECONDS); - messageScheduler.scheduleWithFixedDelay(Iot3MobilityExample::sendTestDenm, 1, 10, TimeUnit.SECONDS); + messageScheduler.scheduleWithFixedDelay(() -> sendTestDenm(DenmVersion.V1_1_3), 1, 5, TimeUnit.SECONDS); messageScheduler.scheduleWithFixedDelay(() -> sendTestCpm(CpmVersion.V1_2_1), 1, 1, TimeUnit.SECONDS); messageScheduler.scheduleWithFixedDelay(Iot3MobilityExample::sendTestConnStat, 1, 1, TimeUnit.SECONDS); } @@ -243,14 +251,34 @@ private static void sendTestCam(CamVersion camVersion) { LatLng position = new LatLng(48.625218, 2.243448); // center point of UTAC TEQMO try { ioT3Mobility.sendPosition(StationType.PASSENGER_CAR, position, 0, 0, 0, 0, 0, camVersion); - } catch (IOException e) { + } catch (Exception e) { System.out.println("CAM ERROR: " + e); } } - private static void sendTestDenm() { + private static void sendTestDenm(DenmVersion denmVersion) { LatLng position = new LatLng(48.626059, 2.247904); // planar area of UTAC TEQMO - ioT3Mobility.sendHazard(HazardType.ACCIDENT_NO_SUBCAUSE, position, 10, 7, StationType.PASSENGER_CAR); + HazardType hazardType = HazardType.ACCIDENT_NO_SUBCAUSE; + StationType stationType = StationType.PASSENGER_CAR; + try { + if(denmVersion == DenmVersion.V1_1_3) { + DenmEnvelope113 denmEnvelope113 = DenmV113Factory.createTestDenmEnvelope( + EXAMPLE_UUID, + hazardType, + position, + stationType); + ioT3Mobility.sendDenm(denmEnvelope113); + } else if(denmVersion == DenmVersion.V2_2_0) { + DenmEnvelope220 denmEnvelope220 = DenmV220Factory.createTestDenmEnvelope( + EXAMPLE_UUID, + hazardType, + position, + stationType); + ioT3Mobility.sendDenm(denmEnvelope220); + } + } catch (Exception e) { + System.out.println("DENM ERROR: " + e); + } } private static void sendTestCpm(CpmVersion cpmVersion) { 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 1c13cc45e..778c3aca6 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 @@ -14,8 +14,8 @@ import com.orange.iot3core.IoT3CoreCallback; 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.StationType; import com.orange.iot3mobility.messages.cam.CamHelper; import com.orange.iot3mobility.messages.cam.core.CamVersion; import com.orange.iot3mobility.messages.cam.v113.model.*; @@ -28,11 +28,9 @@ import com.orange.iot3mobility.messages.cpm.CpmHelper; import com.orange.iot3mobility.messages.cpm.v121.model.CpmEnvelope121; import com.orange.iot3mobility.messages.cpm.v211.model.CpmEnvelope211; -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.denm.*; +import com.orange.iot3mobility.messages.denm.DenmHelper; +import com.orange.iot3mobility.messages.denm.v113.model.DenmEnvelope113; +import com.orange.iot3mobility.messages.denm.v220.model.DenmEnvelope220; import com.orange.iot3mobility.managers.*; import com.orange.iot3mobility.quadkey.LatLng; import com.orange.iot3mobility.quadkey.QuadTileHelper; @@ -51,8 +49,8 @@ * Mobility SDK based on the Orange IoT3.0 platform. *
IoT3Mobility takes advantage of the IoT3Core to propose: * */ @@ -64,6 +62,7 @@ public class IoT3Mobility { private final RoIManager roIManager; private final CamHelper camHelper = new CamHelper(); private final CpmHelper cpmHelper = new CpmHelper(); + private final DenmHelper denmHelper = new DenmHelper(); private final String uuid; private final String context; @@ -323,7 +322,7 @@ private void processMessage(String topic, String message) { if(ioT3RawMessageCallback != null) ioT3RawMessageCallback.messageArrived(message); if(topic.contains("/cam/")) RoadUserManager.processCam(message, camHelper); else if(topic.contains("/cpm/")) RoadSensorManager.processCpm(message, cpmHelper); - else if(topic.contains("/denm/")) RoadHazardManager.processDenm(message); + else if(topic.contains("/denm/")) RoadHazardManager.processDenm(message, denmHelper); } /** @@ -350,7 +349,7 @@ public void sendPosition(StationType stationType, LatLng position, float altitud .stationId(stationId) .generationDeltaTime(EtsiConverter.generationDeltaTimeEtsi(TrueTime.getAccurateETSITime())) .basicContainer(BasicContainer.builder() - .stationType(stationType.getId()) + .stationType(stationType.value) .referencePosition(new ReferencePosition( EtsiConverter.latitudeEtsi(position.getLatitude()), EtsiConverter.longitudeEtsi(position.getLongitude()), @@ -375,7 +374,7 @@ public void sendPosition(StationType stationType, LatLng position, float altitud .stationId(stationId) .generationDeltaTime(EtsiConverter.generationDeltaTimeEtsi(TrueTime.getAccurateETSITime())) .basicContainer(com.orange.iot3mobility.messages.cam.v230.model.basiccontainer.BasicContainer.builder() - .stationType(stationType.getId()) + .stationType(stationType.value) .referencePosition(com.orange.iot3mobility.messages.cam.v230.model.basiccontainer.ReferencePosition.builder() .latitudeLongitude(EtsiConverter.latitudeEtsi(position.latitude), EtsiConverter.longitudeEtsi(position.longitude)) @@ -439,71 +438,39 @@ public void sendCam(CamEnvelope230 camV230) throws IOException { } /** - * Inform other road users of a road hazard. - * Builds a DENM and uses {@link #sendDenm(DENM)} + * Send a DENM - Decentralized Environment Notification Message - v1.1.3 * - * @param hazardType the type of the reported hazard - * @param position the hazard's position (latitude, longitude in degrees) - * @param lifetime the lifetimes of this hazard in seconds [0 - 86400] - * @param infoQuality the quality of this hazard information [0 - 7] - * @param stationType your road user type + * @param denmV113 the DENM representing a road event */ - public void sendHazard(HazardType hazardType, LatLng position, int lifetime, int infoQuality, - StationType stationType) { - // check for out of scope values before building the DENM - lifetime = (int) Utils.clamp(lifetime, 0, 86400); - infoQuality = (int) Utils.clamp(infoQuality, 0, 7); - - // build the DENM - DENM denm = new DENM.DENMBuilder() - .header( - JsonValue.Origin.SELF.value(), - JsonValue.Version.CURRENT_DENM.value(), - uuid, - TrueTime.getAccurateTime()) - .pduHeader( - 2, - stationId) - .managementContainer( - new ManagementContainer( - new ActionId( - stationId, - EtsiUtils.getNextSequenceNumber()), - TrueTime.getAccurateETSITime(), - TrueTime.getAccurateETSITime(), - new Position( - (long)(position.getLatitude() * EtsiUtils.ETSI_COORDINATES_FACTOR), - (long)(position.getLongitude() * EtsiUtils.ETSI_COORDINATES_FACTOR), - 0), - lifetime, - stationType.getId())) - .situationContainer( - new SituationContainer( - infoQuality, - new EventType( - hazardType.getCause(), - hazardType.getSubcause()))) - .build(); - - sendDenm(denm); + public void sendDenm(DenmEnvelope113 denmV113) throws IOException { + // build the topic + String quadkey = QuadTileHelper.latLngToQuadKey( + EtsiConverter.latitudeDegrees(denmV113.message().managementContainer().eventPosition().latitude()), + EtsiConverter.longitudeDegrees(denmV113.message().managementContainer().eventPosition().longitude()), + 22); + String geoExtension = QuadTileHelper.quadKeyToQuadTopic(quadkey); + String topic = context + "/inQueue/v2x/denm/" + uuid + geoExtension; + + // send the message even if the client is disconnected, so it will be queued + if(ioT3Core != null) ioT3Core.mqttPublish(topic, denmHelper.toJson(denmV113)); } /** - * Send a DENM - Decentralized Environment Notification Message + * Send a DENM - Decentralized Environment Notification Message - v2.2.0 * - * @param denm the DENM representing a road event + * @param denmV220 the DENM representing a road event */ - public void sendDenm(DENM denm) { + public void sendDenm(DenmEnvelope220 denmV220) throws IOException { // build the topic String quadkey = QuadTileHelper.latLngToQuadKey( - denm.getManagementContainer().getEventPosition().getLatitudeDegree(), - denm.getManagementContainer().getEventPosition().getLongitudeDegree(), + EtsiConverter.latitudeDegrees(denmV220.message().managementContainer().eventPosition().latitude()), + EtsiConverter.longitudeDegrees(denmV220.message().managementContainer().eventPosition().longitude()), 22); String geoExtension = QuadTileHelper.quadKeyToQuadTopic(quadkey); String topic = context + "/inQueue/v2x/denm/" + uuid + geoExtension; // send the message even if the client is disconnected, so it will be queued - if(ioT3Core != null) ioT3Core.mqttPublish(topic, denm.getJsonDENM().toString()); + if(ioT3Core != null) ioT3Core.mqttPublish(topic, denmHelper.toJson(denmV220)); } /** diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/TrueTime.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/TrueTime.java index a20aa9875..32492ddd7 100644 --- a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/TrueTime.java +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/TrueTime.java @@ -7,7 +7,7 @@ */ package com.orange.iot3mobility; -import com.orange.iot3mobility.its.EtsiUtils; +import com.orange.iot3mobility.messages.EtsiConverter; import org.apache.commons.net.ntp.NTPUDPClient; import org.apache.commons.net.ntp.TimeInfo; @@ -54,16 +54,8 @@ public static long getAccurateTime(){ return System.currentTimeMillis() - deltaNtpTime; } - public static long getAccurateTimeSec(){ - return getAccurateTime() / 1000; - } - public static long getAccurateETSITime(){ - return getAccurateTime() - EtsiUtils.DELTA_1970_2004_MILLISEC; - } - - public static long getAccurateETSITimeSec(){ - return (getAccurateTime() / 1000) - EtsiUtils.DELTA_1970_2004_SEC; + return EtsiConverter.unixToEtsiTimestampMs(getAccurateTime()); } public static void setDefaultTimeServerAddress(String address){ diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/EtsiUtils.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/EtsiUtils.java deleted file mode 100644 index 246e63410..000000000 --- a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/EtsiUtils.java +++ /dev/null @@ -1,74 +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; - -import java.util.Calendar; - -public class EtsiUtils { - - public static final int DELTA_1970_2004_SEC = 1072915200; - public static final long DELTA_1970_2004_MILLISEC = 1072915200000L; - public static final int ETSI_COORDINATES_FACTOR = 10000000; - public static final int ETSI_ALTITUDE_FACTOR = 100; - public static final int ETSI_SPEED_FACTOR = 100; - public static final int ETSI_ACCELERATION_FACTOR = 10; - public static final int ETSI_HEADING_FACTOR = 10; - public static final int ETSI_YAW_RATE_FACTOR = 10; - public static final int MAX_SEQUENCE_NUMBER = 65535; - - public static int localSequenceNumber = initLocalSequenceNumber(); - - private EtsiUtils() { - throw new UnsupportedOperationException("EtsiUtils class cannot be instantiated"); - } - - private static int initLocalSequenceNumber() { - Calendar calendar = Calendar.getInstance(); - int minutes = calendar.get(Calendar.MINUTE); - int hours = calendar.get(Calendar.HOUR_OF_DAY); - int seconds = calendar.get(Calendar.SECOND); - - int sequenceNumber = hours*3600 + minutes*60 + seconds; - - if(sequenceNumber > MAX_SEQUENCE_NUMBER) - sequenceNumber = sequenceNumber - MAX_SEQUENCE_NUMBER; - - return sequenceNumber; - } - - public static int getNextSequenceNumber() { - localSequenceNumber++; - if(localSequenceNumber > MAX_SEQUENCE_NUMBER) localSequenceNumber = 0; - return localSequenceNumber; - } - - public static long etsiTimestampMsToUnix(long etsiTimestamp) { - return etsiTimestamp + DELTA_1970_2004_MILLISEC; - } - - public static long etsiTimestampSecToUnix(long etsiTimestamp) { - return (etsiTimestamp + DELTA_1970_2004_SEC) * 1000; - } - - public static long unixTimestampToEtsiMs(long unixTimestamp) { - return unixTimestamp - DELTA_1970_2004_MILLISEC; - } - - public static long unixTimestampToEtsiSec(long unixTimestamp) { - return (unixTimestamp - DELTA_1970_2004_MILLISEC) / 1000; - } - - public static long getLatitudeETSI(double latitude) { - return (long)(latitude * ETSI_COORDINATES_FACTOR); - } - - public static long getLongitudeETSI(double longitude) { - return (long)(longitude * ETSI_COORDINATES_FACTOR); - } - -} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/StationType.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/StationType.java deleted file mode 100644 index 46712a4f9..000000000 --- a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/StationType.java +++ /dev/null @@ -1,60 +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; - -public enum StationType { - - UNKNOWN("unknown", 0), - PEDESTRIAN("pedestrian", 1), - CYCLIST("cyclist", 2), - MOPED("moped", 3), - MOTORCYCLE("motorcycle", 4), - PASSENGER_CAR("passengerCar", 5), - BUS("bus", 6), - LIGHT_TRUCK("lightTruck", 7), - HEAVY_TRUCK("heavyTruck", 8), - TRAILER("trailer", 9), - SPECIAL_VEHICLES("specialVehicles", 10), - TRAM("tram", 11), - ROAD_SIDE_UNIT("roadSideUnit", 15); - - private String name; - private int id; - - StationType(String name, int id) { - this.name = name; - this.id = id; - } - - public String getName() { - return name; - } - - public int getId() { - return id; - } - - public static StationType fromName(String name) { - for (StationType stationType : StationType.values()) { - if (stationType.getName().equals(name)) { - return stationType; - } - } - return UNKNOWN; - } - - public static StationType fromId(int id) { - for (StationType stationType : StationType.values()) { - if (stationType.getId() == id) { - return stationType; - } - } - return UNKNOWN; - } - -} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/JsonKey.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/JsonKey.java deleted file mode 100644 index 6aaa35981..000000000 --- a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/JsonKey.java +++ /dev/null @@ -1,339 +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; - -public class JsonKey { - - public enum Header { - - TYPE("type"), - CONTEXT("context"), - ORIGIN("origin"), - VERSION("version"), - SOURCE_UUID("source_uuid"), - DESTINATION_UUID("destination_uuid"), - TIMESTAMP("timestamp"), - MESSAGE_ID("message_id"), - MESSAGE("message"), - SIGNATURE("signature"); - - private final String key; - - Header(String key) { - this.key = key; - } - - public String key() { - return key; - } - } - - public enum Cam { - - PROTOCOL_VERSION("protocol_version"), - STATION_ID("station_id"), - GENERATION_DELTA_TIME("generation_delta_time"), - BASIC_CONTAINER("basic_container"), - HIGH_FREQ_CONTAINER("high_frequency_container"), - LOW_FREQ_CONTAINER("low_frequency_container"); - - private final String key; - - Cam(String key) { - this.key = key; - } - - public String key() { - return key; - } - } - - public enum BasicContainer { - - STATION_TYPE("station_type"), - POSITION("reference_position"); - - private final String key; - - BasicContainer(String key) { - this.key = key; - } - - public String key() { - return key; - } - } - - public enum HighFrequencyContainer { - - HEADING("heading"), - SPEED("speed"), - DRIVE_DIRECTION("drive_direction"), - VEHICLE_LENGTH("vehicle_length"), - VEHICLE_WIDTH("vehicle_width"), - LONGITUDINAL_ACCELERATION("longitudinal_acceleration"), - LATERAL_ACCELERATION("lateral_acceleration"), - VERTICAL_ACCELERATION("vertical_acceleration"), - YAW_RATE("yaw_rate"), - LANE_POSITION("lane_position"), - CURVATURE("curvature"), - CURVATURE_CALCULATION_MODE("curvature_calculation_mode"), - ACCELERATION_CONTROL("acceleration_control"), - CONFIDENCE("confidence"); - - private final String key; - - HighFrequencyContainer(String key) { - this.key = key; - } - - public String key() { - return key; - } - } - - public enum LowFrequencyContainer { - - VEHICLE_ROLE("vehicle_role"), - EXTERIOR_LIGHTS("exterior_lights"), - PATH_HISTORY("path_history"); - - private final String key; - - LowFrequencyContainer(String key) { - this.key = key; - } - - public String key() { - return key; - } - } - - public enum Position { - - LATITUDE("latitude"), - LONGITUDE("longitude"), - ALTITUDE("altitude"), - CONFIDENCE("confidence"); - - private final String key; - - Position(String key) { - this.key = key; - } - - public String key() { - return key; - } - } - - public enum PathPosition { - - DELTA_LATITUDE("delta_latitude"), - DELTA_LONGITUDE("delta_longitude"), - DELTA_ALTITUDE("delta_altitude"); - - private final String key; - - PathPosition(String key) { - this.key = key; - } - - public String key() { - return key; - } - } - - public enum PathPoint { - - PATH_POSITION("path_position"), - PATH_DELTA_TIME("path_delta_time"); - - private final String key; - - PathPoint(String key) { - this.key = key; - } - - public String key() { - return key; - } - } - - public enum Denm { - PROTOCOL_VERSION("protocol_version"), - STATION_ID("station_id"), - MANAGEMENT_CONTAINER("management_container"), - SITUATION_CONTAINER("situation_container"), - LOCATION_CONTAINER("location_container"), - ALACARTE_CONTAINER("alacarte_container"); - - private final String key; - - Denm(String key) { - this.key = key; - } - - public String key() { - return key; - } - } - - public enum ManagementContainer { - ACTION_ID("action_id"), - DETECTION_TIME("detection_time"), - REFERENCE_TIME("reference_time"), - TERMINATION("termination"), - EVENT_POSITION("event_position"), - RELEVANCE_DISTANCE("relevance_distance"), - RELEVANCE_TRAFFIC_DIRECTION("relevance_traffic_direction"), - VALIDITY_DURATION("validity_duration"), - TRANSMISSION_INTERVAL("transmission_interval"), - STATION_TYPE("station_type"); - - private final String key; - - ManagementContainer(String key) { - this.key = key; - } - - public String key() { - return key; - } - } - - public enum ActionId { - ORIGINATING_STATION_ID("originating_station_id"), - SEQUENCE_NUMBER("sequence_number"); - - private final String key; - - ActionId(String key) { - this.key = key; - } - - public String key() { - return key; - } - } - - public enum SituationContainer { - INFO_QUALITY("information_quality"), - EVENT_TYPE("event_type"), - LINKED_CAUSE("linked_cause"), - EVENT_HISTORY("event_history"); - - private final String key; - - SituationContainer(String key) { - this.key = key; - } - - public String key() { - return key; - } - } - - public enum EventType { - CAUSE("cause"), - SUBCAUSE("subcause"); - - private final String key; - - EventType(String key) { - this.key = key; - } - - public String key() { - return key; - } - } - - public enum LinkedCause { - CAUSE("cause"), - SUBCAUSE("subcause"); - - private final String key; - - LinkedCause(String key) { - this.key = key; - } - - public String key() { - return key; - } - } - - public enum LocationContainer { - EVENT_SPEED("event_speed"), - EVENT_POSITION_HEADING("event_position_heading"), - TRACES("traces"), - ROAD_TYPE("road_type"), - CONFIDENCE("confidence"); - - private final String key; - - LocationContainer(String key) { - this.key = key; - } - - public String key() { - return key; - } - } - - public enum AlacarteContainer { - LANE_POSITION("lane_position"), - IMPACT_REDUCTION("impact_reduction"), - EXTERNAL_TEMPERATURE("external_temperature"), - ROAD_WORKS("road_works"), - POSITION_SOLUTION_TYPE("position_solution_type"); - - private final String key; - - AlacarteContainer(String key) { - this.key = key; - } - - public String key() { - return key; - } - } - - public enum Confidence { - - POSITION_SEMI_MAJOR_CONFIDENCE("semi_major_confidence"), - POSITION_SEMI_MINOR_CONFIDENCE("semi_minor_confidence"), - POSITION_SEMI_MAJOR_ORIENTATION("semi_major_orientation"), - ALTITUDE("altitude"), - HEADING("heading"), - SPEED("speed"), - SIZE("size"), - VEHICLE_LENGTH("vehicle_length"), - ACCELERATION("acceleration"), - LONGITUDINAL_ACCELERATION("longitudinal_acceleration"), - LATERAL_ACCELERATION("lateral_acceleration"), - VERTICAL_ACCELERATION("vertical_acceleration"), - YAW_RATE("yaw_rate"), - CURVATURE("curvature"), - EVENT_SPEED("event_speed"), - EVENT_POSITION_HEADING("event_position_heading"), - POSITION_CONFIDENCE_ELLIPSE("position_confidence_ellipse"); - - private final String key; - - Confidence(String key) { - this.key = key; - } - - public String key() { - return key; - } - } - -} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/JsonUtil.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/JsonUtil.java deleted file mode 100644 index f44b67d44..000000000 --- a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/JsonUtil.java +++ /dev/null @@ -1,29 +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; - -import org.json.JSONArray; -import org.json.JSONObject; - -public class JsonUtil { - - public static final int UNKNOWN = Integer.MIN_VALUE; - - public static boolean isNullOrEmpty(JSONObject jsonObject) { - // use the zero length test instead of the isEmpty() method - // to make the SDK compatible with Android apps - return jsonObject == null || jsonObject.length() == 0; - } - - public static boolean isNullOrEmpty(JSONArray jsonArray) { - // use the zero length test instead of the isEmpty() method - // to make the SDK compatible with Android apps - return jsonArray == null || jsonArray.length() == 0; - } - -} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/JsonValue.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/JsonValue.java deleted file mode 100644 index 8ee7131d7..000000000 --- a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/JsonValue.java +++ /dev/null @@ -1,69 +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; - -public class JsonValue { - - public enum Type { - - CAM("cam"), - DENM("denm"), - CPM("cpm"); - - private final String value; - - Type(String value) { - this.value = value; - } - - public String value() { - return value; - } - } - - public enum Origin { - - SELF("self"), - ROAD_USER("road_user"), - GLOBAL_APPLICATION("global_application"), - MEC_APPLICATION("mec_application"), - ON_BOARD_APPLICATION("on_board_application"), - ROADSIDE_CAMERA("roadside_camera"), - ON_BOARD_CAMERA("onboard_camera"), - TRAFFIC_ORCHESTRATOR("traffic_orchestrator"), - DATA_FUSION("data_fusion"); - - private final String value; - - Origin(String value) { - this.value = value; - } - - public String value() { - return value; - } - } - - public enum Version { - - CURRENT_CAM("1.1.3"), - CURRENT_CPM("1.2.1"), - CURRENT_DENM("1.1.3"); - - private final String value; - - Version(String value) { - this.value = value; - } - - public String value() { - return value; - } - } - -} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/MessageBase.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/MessageBase.java deleted file mode 100644 index 0f6c06efe..000000000 --- a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/MessageBase.java +++ /dev/null @@ -1,86 +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; - -import com.orange.iot3mobility.TrueTime; - -/** - * Common base of all Orange ITS JSON messages. - */ -public abstract class MessageBase { - - /** - * Type of the message. - *

- * cam, denm, cpm, etc. - */ - private final String type; - - /** - * The entity responsible for emitting the message. - *

- * self, global_application, mec_application, on_board_application - */ - private final String origin; - - /** - * JSON message format version. - */ - private final String version; - - /** - * The identifier of the entity responsible for emitting the message. - *

- * Format com_type_number, e.g. ora_car_42 - */ - private final String sourceUuid; - - /** - * The timestamp when the message was generated since Unix Epoch (1970/01/01). - *

- * Unit: millisecond. - */ - private long timestamp; - - protected MessageBase(String type, - String origin, - String version, - String sourceUuid, - long timestamp) { - this.type = type; - this.origin = origin; - this.version = version; - this.sourceUuid = sourceUuid; - this.timestamp = timestamp; - } - - public String getType() { - return type; - } - - public String getOrigin() { - return origin; - } - - public String getVersion() { - return version; - } - - public String getSourceUuid() { - return sourceUuid; - } - - public long getTimestamp() { - return timestamp; - } - - public void updateTimestamp() { - timestamp = TrueTime.getAccurateTime(); - } - -} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/PathHistory.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/PathHistory.java deleted file mode 100644 index e6082c0d9..000000000 --- a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/PathHistory.java +++ /dev/null @@ -1,61 +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; - -import org.json.JSONArray; -import org.json.JSONException; - -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; - -public class PathHistory { - - private static final Logger LOGGER = Logger.getLogger(PathHistory.class.getName()); - - private final JSONArray jsonPathHistory = new JSONArray(); - private final List pathPoints; - - public PathHistory(List pathPoints) - { - if(pathPoints == null) this.pathPoints = new ArrayList<>(); - else this.pathPoints = new ArrayList<>(pathPoints); - - createJson(); - } - - private void createJson() { - for(PathPoint pathPoint: pathPoints) { - jsonPathHistory.put(pathPoint.getJsonPathPoint()); - } - } - - public JSONArray getJsonPathHistory() { - return jsonPathHistory; - } - - public List getPathPoints() { - return pathPoints; - } - - public static PathHistory jsonParser(JSONArray jsonPathHistory) { - if(JsonUtil.isNullOrEmpty(jsonPathHistory)) return new PathHistory(null); - ArrayList pathPoints = new ArrayList<>(); - try { - for(int i = 0; i < jsonPathHistory.length(); i++) { - PathPoint pathPoint = PathPoint.jsonParser(jsonPathHistory.getJSONObject(i)); - pathPoints.add(pathPoint); - } - } catch (JSONException e) { - LOGGER.log(Level.WARNING, "PathHistory JSON parsing error", "Error: " + e); - } - return new PathHistory(pathPoints); - } - -} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/PathPoint.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/PathPoint.java deleted file mode 100644 index fa0c31091..000000000 --- a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/PathPoint.java +++ /dev/null @@ -1,83 +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; - -import static com.orange.iot3mobility.its.json.JsonUtil.UNKNOWN; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.logging.Level; -import java.util.logging.Logger; - -public class PathPoint { - - private static final Logger LOGGER = Logger.getLogger(PathPoint.class.getName()); - - private final JSONObject jsonPathPoint = new JSONObject(); - private final PathPosition pathPosition; - private final int pathDeltaTime; - - public PathPoint( - PathPosition pathPosition) - { - this(pathPosition, UNKNOWN); - } - - public PathPoint( - PathPosition pathPosition, - int pathDeltaTime) - { - this.pathPosition = pathPosition; - if(pathDeltaTime != UNKNOWN && (pathDeltaTime > 65535 || pathDeltaTime < 1)) { - throw new IllegalArgumentException("PathPosition PathDeltaTime should be in the range of [1 - 65535]." - + " Value: " + pathDeltaTime); - } - this.pathDeltaTime = pathDeltaTime; - - createJson(); - } - - private void createJson() { - try { - jsonPathPoint.put(JsonKey.PathPoint.PATH_POSITION.key(), pathPosition); - if(pathDeltaTime != UNKNOWN) - jsonPathPoint.put(JsonKey.PathPoint.PATH_DELTA_TIME.key(), pathDeltaTime); - } catch (JSONException e) { - LOGGER.log(Level.WARNING, "PathPoint JSON build error", "Error: " + e); - } - } - - public JSONObject getJsonPathPoint() { - return jsonPathPoint; - } - - public PathPosition getPathPosition() { - return pathPosition; - } - - public int getPathDeltaTime() { - return pathDeltaTime; - } - - public int getPathDeltaTimeMs() { - return pathDeltaTime*10; - } - - public static PathPoint jsonParser(JSONObject jsonPathPoint) { - if(JsonUtil.isNullOrEmpty(jsonPathPoint)) return null; - JSONObject jsonPathPosition = jsonPathPoint.optJSONObject(JsonKey.PathPoint.PATH_POSITION.key()); - PathPosition pathPosition = PathPosition.jsonParser(jsonPathPosition); - int pathDeltaTime = jsonPathPoint.optInt(JsonKey.PathPoint.PATH_DELTA_TIME.key(), UNKNOWN); - - return new PathPoint( - pathPosition, - pathDeltaTime); - } - -} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/PathPosition.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/PathPosition.java deleted file mode 100644 index 81dbe0d12..000000000 --- a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/PathPosition.java +++ /dev/null @@ -1,102 +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; - -import com.orange.iot3mobility.its.EtsiUtils; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.logging.Level; -import java.util.logging.Logger; - -public class PathPosition { - - private static final Logger LOGGER = Logger.getLogger(PathPosition.class.getName()); - - private final JSONObject jsonPathPosition = new JSONObject(); - private final int deltaLatitude; - private final int deltaLongitude; - private final int deltaAltitude; - - public PathPosition( - final int deltaLatitude, - final int deltaLongitude, - final int deltaAltitude) - { - if(deltaLatitude > 131072 || deltaLatitude < -131071) { - throw new IllegalArgumentException("PathPosition DeltaLatitude should be in the range of [-131071 - 131072]." - + " Value: " + deltaLatitude); - } - this.deltaLatitude = deltaLatitude; - if(deltaLongitude > 131072 || deltaLongitude < -131071) { - throw new IllegalArgumentException("PathPosition DeltaLongitude should be in the range of [-131071 - 131072]." - + " Value: " + deltaLongitude); - } - this.deltaLongitude = deltaLongitude; - if(deltaAltitude > 12800 || deltaAltitude < -12700) { - throw new IllegalArgumentException("PathPosition DeltaAltitude should be in the range of [-12700 - 12800]." - + " Value: " + deltaAltitude); - } - this.deltaAltitude = deltaAltitude; - - createJson(); - } - - private void createJson() { - try { - jsonPathPosition.put(JsonKey.PathPosition.DELTA_LATITUDE.key(), deltaLatitude); - jsonPathPosition.put(JsonKey.PathPosition.DELTA_LONGITUDE.key(), deltaLongitude); - jsonPathPosition.put(JsonKey.PathPosition.DELTA_ALTITUDE.key(), deltaAltitude); - } catch (JSONException e) { - LOGGER.log(Level.WARNING, "PathHistory JSON build error", "Error: " + e); - } - } - - public JSONObject getJsonPathPosition() { - return jsonPathPosition; - } - - public int getDeltaLatitude() { - return deltaLatitude; - } - - public double getDeltaLatitudeDegree() { - return (double) deltaLatitude / EtsiUtils.ETSI_COORDINATES_FACTOR; - } - - public int getDeltaLongitude() { - return deltaLongitude; - } - - public double getDeltaLongitudeDegree() { - return (double) deltaLongitude /EtsiUtils.ETSI_COORDINATES_FACTOR; - } - - public int getDeltaAltitude() { - return deltaAltitude; - } - - public static PathPosition jsonParser(JSONObject jsonPathPosition) { - if(JsonUtil.isNullOrEmpty(jsonPathPosition)) return null; - try { - int deltaLatitude = jsonPathPosition.getInt(JsonKey.PathPosition.DELTA_LATITUDE.key()); - int deltaLongitude = jsonPathPosition.getInt(JsonKey.PathPosition.DELTA_LONGITUDE.key()); - int deltaAltitude = jsonPathPosition.getInt(JsonKey.PathPosition.DELTA_ALTITUDE.key()); - - return new PathPosition( - deltaLatitude, - deltaLongitude, - deltaAltitude); - } catch (JSONException e) { - LOGGER.log(Level.WARNING, "PathHistory JSON parsing error", "Error: " + e); - } - return null; - } - -} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/Position.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/Position.java deleted file mode 100644 index f8c0d0b79..000000000 --- a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/Position.java +++ /dev/null @@ -1,118 +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; - -import static com.orange.iot3mobility.its.json.JsonUtil.UNKNOWN; - -import com.orange.iot3mobility.its.EtsiUtils; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * Position consisting of a latitude, longitude and altitude. - *

- * Latitude Unit: 0.1 microdegree. oneMicrodegreeNorth (10), oneMicrodegreeSouth (-10), unavailable(900000001) - *

- * Longitude Unit: 0.1 microdegree. oneMicrodegreeEast (10), oneMicrodegreeWest (-10), unavailable(1800000001) - *

- * Altitude Unit: 0.01 meter. referenceEllipsoidSurface(0), oneCentimeter(1), unavailable(800001) - */ -public class Position { - - private static final Logger LOGGER = Logger.getLogger(Position.class.getName()); - - private final JSONObject jsonPosition = new JSONObject(); - private final long latitude; - private final long longitude; - private final int altitude; - - public Position( - final long latitude, - final long longitude, - final int altitude) - { - if(latitude > 900000001 || latitude < -900000000) { - throw new IllegalArgumentException("Position Latitude should be in the range of [-900000000 - 900000001]." - + " Value: " + latitude); - } - this.latitude = latitude; - if(longitude > 1800000001 || longitude < -1800000000) { - throw new IllegalArgumentException("Position Longitude should be in the range of [-1800000000 - 1800000001]." - + " Value: " + longitude); - } - this.longitude = longitude; - if(altitude != UNKNOWN && (altitude > 800001 || altitude < -100000)) { - throw new IllegalArgumentException("Position Altitude should be in the range of [-100000 - 800001]." - + " Value: " + altitude); - } - this.altitude = altitude; - - createJson(); - } - - private void createJson() { - try { - jsonPosition.put(JsonKey.Position.LATITUDE.key(), latitude); - jsonPosition.put(JsonKey.Position.LONGITUDE.key(), longitude); - if(altitude != UNKNOWN) - jsonPosition.put(JsonKey.Position.ALTITUDE.key(), altitude); - } catch (JSONException e) { - LOGGER.log(Level.WARNING, "Position JSON build error", "Error: " + e); - } - } - - public JSONObject getJson() { - return jsonPosition; - } - - public long getLatitude() { - return latitude; - } - - public double getLatitudeDegree() { - return (double) latitude / EtsiUtils.ETSI_COORDINATES_FACTOR; - } - - public long getLongitude() { - return longitude; - } - - public double getLongitudeDegree() { - return (double) longitude / EtsiUtils.ETSI_COORDINATES_FACTOR; - } - - public int getAltitude() { - return altitude; - } - - public int getAltitudeMeters() { - return altitude/EtsiUtils.ETSI_ALTITUDE_FACTOR; - } - - public static Position jsonParser(JSONObject jsonPosition) { - if(JsonUtil.isNullOrEmpty(jsonPosition)) return null; - try { - long latitude = jsonPosition.getLong(JsonKey.Position.LATITUDE.key()); - long longitude = jsonPosition.getLong(JsonKey.Position.LONGITUDE.key()); - int altitude = jsonPosition.optInt(JsonKey.Position.ALTITUDE.key(), UNKNOWN); - - return new Position( - latitude, - longitude, - altitude); - } catch (JSONException e) { - LOGGER.log(Level.WARNING, "Position JSON parsing error", "Error: " + e); - } - return null; - } - -} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/PositionConfidence.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/PositionConfidence.java deleted file mode 100644 index 680ca8089..000000000 --- a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/PositionConfidence.java +++ /dev/null @@ -1,88 +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; - -import static com.orange.iot3mobility.its.json.JsonUtil.UNKNOWN; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * Confidence of the position. - *

- * The positionConfidenceEllipse provides the accuracy of the measured position - * with the 95 % confidence level. Otherwise, the positionConfidenceEllipse shall be - * set to unavailable. - *

- * If semiMajorOrientation is set to 0° North, then the semiMajorConfidence - * corresponds to the position accuracy in the North/South direction, while the - * semiMinorConfidence corresponds to the position accuracy in the East/West - * direction. This definition implies that the semiMajorConfidence might be smaller - * than the semiMinorConfidence. - */ -public class PositionConfidence { - - private static final Logger LOGGER = Logger.getLogger(PositionConfidence.class.getName()); - - private final JSONObject jsonPositionConfidence = new JSONObject(); - private final PositionConfidenceEllipse positionConfidenceEllipse; - private final int altitudeConfidence; - - public PositionConfidence( - PositionConfidenceEllipse positionConfidenceEllipse, - int altitudeConfidence) - { - this.positionConfidenceEllipse = positionConfidenceEllipse; - if(altitudeConfidence != UNKNOWN && (altitudeConfidence > 15 || altitudeConfidence < 0)) { - throw new IllegalArgumentException("Position Confidence Altitude should be in the range of [0 - 15]." - + " Value: " + altitudeConfidence); - } - this.altitudeConfidence = altitudeConfidence; - - createJson(); - } - - private void createJson() { - try { - if(positionConfidenceEllipse != null) - jsonPositionConfidence.put(JsonKey.Confidence.POSITION_CONFIDENCE_ELLIPSE.key(), - positionConfidenceEllipse.getJsonPositionConfidenceEllipse()); - if(altitudeConfidence != UNKNOWN) - jsonPositionConfidence.put(JsonKey.Confidence.ALTITUDE.key(), altitudeConfidence); - } catch (JSONException e) { - LOGGER.log(Level.WARNING, "PositionConfidence JSON build error", "Error: " + e); - } - } - - public JSONObject getJson() { - return jsonPositionConfidence; - } - - public PositionConfidenceEllipse getPositionConfidenceEllipse() { - return positionConfidenceEllipse; - } - - public int getAltitudeConfidence() { - return altitudeConfidence; - } - - public static PositionConfidence jsonParser(JSONObject jsonPositionConfidence) { - if(JsonUtil.isNullOrEmpty(jsonPositionConfidence)) return null; - JSONObject jsonPositionConfidenceEllipse = jsonPositionConfidence.optJSONObject(JsonKey.Confidence.POSITION_CONFIDENCE_ELLIPSE.key()); - PositionConfidenceEllipse positionConfidenceEllipse = PositionConfidenceEllipse.jsonParser(jsonPositionConfidenceEllipse); - int altitudeConfidence = jsonPositionConfidence.optInt(JsonKey.Confidence.ALTITUDE.key(), UNKNOWN); - - return new PositionConfidence( - positionConfidenceEllipse, - altitudeConfidence); - } - -} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/PositionConfidenceEllipse.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/PositionConfidenceEllipse.java deleted file mode 100644 index 55a8adfeb..000000000 --- a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/PositionConfidenceEllipse.java +++ /dev/null @@ -1,92 +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; - -import static com.orange.iot3mobility.its.json.JsonUtil.UNKNOWN; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.logging.Level; -import java.util.logging.Logger; - -public class PositionConfidenceEllipse { - - private static final Logger LOGGER = Logger.getLogger(PositionConfidenceEllipse.class.getName()); - - private final JSONObject jsonPositionConfidenceEllipse = new JSONObject(); - private final int semiMajorConfidence; - private final int semiMinorConfidence; - private final int semiMajorOrientation; - - public PositionConfidenceEllipse( - final int semiMajorConfidence, - final int semiMinorConfidence, - final int semiMajorOrientation) - { - if(semiMajorConfidence != UNKNOWN && (semiMajorConfidence > 4095 || semiMajorConfidence < 0)) { - throw new IllegalArgumentException("Position SemiMajorConfidence should be in the range of [0 - 4095]." - + " Value: " + semiMajorConfidence); - } - this.semiMajorConfidence = semiMajorConfidence; - if(semiMinorConfidence != UNKNOWN && (semiMinorConfidence > 4095 || semiMinorConfidence < 0)) { - throw new IllegalArgumentException("Position SemiMinorConfidence should be in the range of [0 - 4095]." - + " Value: " + semiMinorConfidence); - } - this.semiMinorConfidence = semiMinorConfidence; - if(semiMajorOrientation != UNKNOWN && (semiMajorOrientation > 3601 || semiMajorOrientation < 0)) { - throw new IllegalArgumentException("Position SemiMajorOrientation should be in the range of [0 - 3601]." - + " Value: " + semiMajorOrientation); - } - this.semiMajorOrientation = semiMajorOrientation; - - createJson(); - } - - private void createJson() { - try { - if (semiMajorConfidence != UNKNOWN) - jsonPositionConfidenceEllipse.put(JsonKey.Confidence.POSITION_SEMI_MAJOR_CONFIDENCE.key(), semiMajorConfidence); - if (semiMinorConfidence != UNKNOWN) - jsonPositionConfidenceEllipse.put(JsonKey.Confidence.POSITION_SEMI_MINOR_CONFIDENCE.key(), semiMinorConfidence); - if (semiMajorOrientation != UNKNOWN) - jsonPositionConfidenceEllipse.put(JsonKey.Confidence.POSITION_SEMI_MAJOR_ORIENTATION.key(), semiMajorOrientation); - } catch (JSONException e) { - LOGGER.log(Level.WARNING, "PositionConfidenceEllipse JSON build error", "Error: " + e); - } - } - - public JSONObject getJsonPositionConfidenceEllipse() { - return jsonPositionConfidenceEllipse; - } - - public int getSemiMajorConfidence() { - return semiMajorConfidence; - } - - public int getSemiMinorConfidence() { - return semiMinorConfidence; - } - - public int getSemiMajorOrientation() { - return semiMajorOrientation; - } - - public static PositionConfidenceEllipse jsonParser(JSONObject jsonPositionConfidenceEllipse) { - if(JsonUtil.isNullOrEmpty(jsonPositionConfidenceEllipse)) return null; - int semiMajorConfidence = jsonPositionConfidenceEllipse.optInt(JsonKey.Confidence.POSITION_SEMI_MAJOR_CONFIDENCE.key(), UNKNOWN);; - int semiMinorConfidence = jsonPositionConfidenceEllipse.optInt(JsonKey.Confidence.POSITION_SEMI_MINOR_CONFIDENCE.key(), UNKNOWN);; - int semiMajorOrientation = jsonPositionConfidenceEllipse.optInt(JsonKey.Confidence.POSITION_SEMI_MAJOR_ORIENTATION.key(), UNKNOWN); - - return new PositionConfidenceEllipse( - semiMajorConfidence, - semiMinorConfidence, - semiMajorOrientation); - } - -} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/denm/ActionId.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/denm/ActionId.java deleted file mode 100644 index 084452dad..000000000 --- a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/denm/ActionId.java +++ /dev/null @@ -1,88 +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.denm; - -import static com.orange.iot3mobility.its.json.JsonUtil.UNKNOWN; - -import com.orange.iot3mobility.its.json.JsonKey; - -import com.orange.iot3mobility.its.json.JsonUtil; -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.logging.Level; -import java.util.logging.Logger; - -public class ActionId { - - private static final Logger LOGGER = Logger.getLogger(ActionId.class.getName()); - - private final JSONObject jsonActionId = new JSONObject(); - private final long originatingStationId; - private final int sequenceNumber; - - public ActionId( - final long originatingStationId) - { - this(originatingStationId, UNKNOWN); - } - - public ActionId( - final long originatingStationId, - final int sequenceNumber) - { - if(originatingStationId > 4294967295L || originatingStationId < 0) { - throw new IllegalArgumentException("DENM OriginStationID should be in the range of [0 - 4294967295]." - + " Value: " + originatingStationId); - } - this.originatingStationId = originatingStationId; - if(sequenceNumber != UNKNOWN && (sequenceNumber > 65535 || sequenceNumber < 0)) { - throw new IllegalArgumentException("DENM SequenceNumber should be in the range of [0 - 65535]." - + " Value: " + sequenceNumber); - } - this.sequenceNumber = sequenceNumber; - - createJson(); - } - - private void createJson() { - try { - jsonActionId.put(JsonKey.ActionId.ORIGINATING_STATION_ID.key(), originatingStationId); - if(sequenceNumber != UNKNOWN) - jsonActionId.put(JsonKey.ActionId.SEQUENCE_NUMBER.key(), sequenceNumber); - } catch (JSONException e) { - LOGGER.log(Level.WARNING, "ActionId JSON build error", "Error: " + e); - } - } - - public JSONObject getJsonActionId() { - return jsonActionId; - } - - public long getOriginatingStationId() { - return originatingStationId; - } - - public int getSequenceNumber() { - return sequenceNumber; - } - - public static ActionId jsonParser(JSONObject jsonActionId) { - if(JsonUtil.isNullOrEmpty(jsonActionId)) return null; - try { - long originStationId = jsonActionId.getLong(JsonKey.ActionId.ORIGINATING_STATION_ID.key()); - int sequenceNumber = jsonActionId.optInt(JsonKey.ActionId.SEQUENCE_NUMBER.key(), UNKNOWN); - - return new ActionId(originStationId, sequenceNumber); - } catch (JSONException e) { - LOGGER.log(Level.WARNING, "ActionId JSON parsing error", "Error: " + e); - } - return null; - } - -} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/denm/AlacarteContainer.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/denm/AlacarteContainer.java deleted file mode 100644 index 21f2fdb0d..000000000 --- a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/denm/AlacarteContainer.java +++ /dev/null @@ -1,126 +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.denm; - -import static com.orange.iot3mobility.its.json.JsonUtil.UNKNOWN; - -import com.orange.iot3mobility.its.json.JsonKey; - -import com.orange.iot3mobility.its.json.JsonUtil; -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.logging.Level; -import java.util.logging.Logger; - -public class AlacarteContainer { - - private static final Logger LOGGER = Logger.getLogger(AlacarteContainer.class.getName()); - - private final JSONObject jsonAlacarteContainer = new JSONObject(); - private final int lanePosition; - private final int impactReduction; - private final int externalTemperature; - private final int roadWorks; - private final int positionSolutionType; - - public AlacarteContainer( - final int lanePosition) - { - this(lanePosition, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN); - } - - public AlacarteContainer( - final int lanePosition, - final int positionSolutionType) - { - this(lanePosition, UNKNOWN, UNKNOWN, UNKNOWN, positionSolutionType); - } - - public AlacarteContainer( - final int lanePosition, - final int impactReduction, - final int externalTemperature, - final int roadWorks, - final int positionSolutionType) - { - if(lanePosition != UNKNOWN && (lanePosition > 14 || lanePosition < -1)) { - throw new IllegalArgumentException("DENM AlacarteContainer LanePosition should be in the range of [-1 - 14]." - + " Value: " + lanePosition); - } - this.lanePosition = lanePosition; - this.impactReduction = impactReduction; - this.externalTemperature = externalTemperature; - this.roadWorks = roadWorks; - if(positionSolutionType != UNKNOWN && (positionSolutionType > 5 || positionSolutionType < 0)) { - throw new IllegalArgumentException("DENM AlacarteContainer PositionSolutionType should be in the range of [0 - 5]." - + " Value: " + lanePosition); - } - this.positionSolutionType = positionSolutionType; - - createJson(); - } - - private void createJson() { - try { - if(lanePosition != UNKNOWN) - jsonAlacarteContainer.put(JsonKey.AlacarteContainer.LANE_POSITION.key(), lanePosition); - if(impactReduction != UNKNOWN) - jsonAlacarteContainer.put(JsonKey.AlacarteContainer.IMPACT_REDUCTION.key(), impactReduction); - if(externalTemperature != UNKNOWN) - jsonAlacarteContainer.put(JsonKey.AlacarteContainer.EXTERNAL_TEMPERATURE.key(), externalTemperature); - if(roadWorks != UNKNOWN) - jsonAlacarteContainer.put(JsonKey.AlacarteContainer.ROAD_WORKS.key(), roadWorks); - if(positionSolutionType != UNKNOWN) - jsonAlacarteContainer.put(JsonKey.AlacarteContainer.POSITION_SOLUTION_TYPE.key(), positionSolutionType); - } catch (JSONException e) { - LOGGER.log(Level.WARNING, "AlaCarteContainer JSON build error", "Error: " + e); - } - } - - public JSONObject getJsonAlacarteContainer() { - return jsonAlacarteContainer; - } - - public int getLanePosition() { - return lanePosition; - } - - public int getImpactReduction() { - return impactReduction; - } - - public int getExternalTemperature() { - return externalTemperature; - } - - public int getRoadWorks() { - return roadWorks; - } - - public int getPositionSolutionType() { - return positionSolutionType; - } - - public static AlacarteContainer jsonParser(JSONObject jsonAlacarteContainer) { - if(JsonUtil.isNullOrEmpty(jsonAlacarteContainer)) return null; - int lanePosition = jsonAlacarteContainer.optInt(JsonKey.AlacarteContainer.LANE_POSITION.key(), UNKNOWN); - int impactReduction = jsonAlacarteContainer.optInt(JsonKey.AlacarteContainer.IMPACT_REDUCTION.key(), UNKNOWN); - final int externalTemperature = jsonAlacarteContainer.optInt(JsonKey.AlacarteContainer.EXTERNAL_TEMPERATURE.key(), UNKNOWN); - final int roadWorks = jsonAlacarteContainer.optInt(JsonKey.AlacarteContainer.ROAD_WORKS.key(), UNKNOWN); - final int positioningSolution = jsonAlacarteContainer.optInt(JsonKey.AlacarteContainer.POSITION_SOLUTION_TYPE.key(), UNKNOWN); - - return new AlacarteContainer( - lanePosition, - impactReduction, - externalTemperature, - roadWorks, - positioningSolution); - } - -} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/denm/DENM.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/denm/DENM.java deleted file mode 100644 index 40aab0094..000000000 --- a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/denm/DENM.java +++ /dev/null @@ -1,340 +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.denm; - -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; - -/** - * Decentralized Environmental Notification Message (DENM) is a message that is mainly used by ITS applications - * in order to alert road users of a detected event. - *

- * DENM is used to describe a variety of events that can be detected by ITS stations (ITS-S). - */ -public class DENM extends MessageBase { - - private static final Logger LOGGER = Logger.getLogger(DENM.class.getName()); - - private JSONObject jsonDENM = new JSONObject(); - - /** - * Version of the ITS message and/or communication protocol. - */ - private final int protocolVersion; - - /** - * ITS-station identifier - */ - private long stationId; - - /** - * contains information related to the DENM management and the DENM protocol. - */ - private final ManagementContainer managementContainer; - - /** - * Contains information related to the type of the detected event. - */ - private final SituationContainer situationContainer; - - /** - * Contains information of the event location, and the location referencing. - */ - private final LocationContainer locationContainer; - - /** - * Contains information specific to the use case which requires the transmission of - * additional information that is not included in the three previous containers. - */ - private final AlacarteContainer alacarteContainer; - - private DENM(final String type, - final String origin, - final String version, - final String sourceUuid, - final long timestamp, - final int protocolVersion, - final long stationId, - final ManagementContainer managementContainer, - final SituationContainer situationContainer, - final LocationContainer locationContainer, - final AlacarteContainer alacarteContainer) - { - super(type, origin, version, sourceUuid, timestamp); - if(protocolVersion > 255 || protocolVersion < 0) { - throw new IllegalArgumentException("DENM ProtocolVersion should be in the range of [0 - 255]." - + " Value: " + protocolVersion); - } - this.protocolVersion = protocolVersion; - if(stationId > 4294967295L || stationId < 0) { - throw new IllegalArgumentException("DENM StationID should be in the range of [0 - 4294967295]." - + " Value: " + stationId); - } - this.stationId = stationId; - if(managementContainer == null) { - throw new IllegalArgumentException("DENM ManagementContainer missing."); - } - this.managementContainer = managementContainer; - this.situationContainer = situationContainer; - this.locationContainer = locationContainer; - this.alacarteContainer = alacarteContainer; - - createJson(); - } - - private void createJson() { - try { - JSONObject message = new JSONObject(); - message.put(JsonKey.Denm.PROTOCOL_VERSION.key(), protocolVersion); - message.put(JsonKey.Denm.STATION_ID.key(), stationId); - message.put(JsonKey.Denm.MANAGEMENT_CONTAINER.key(), managementContainer.getJsonManagementContainer()); - if(situationContainer != null) - message.put(JsonKey.Denm.SITUATION_CONTAINER.key(), situationContainer.getJsonSituationContainer()); - if(locationContainer != null) - message.put(JsonKey.Denm.LOCATION_CONTAINER.key(), locationContainer.getJsonLocationContainer()); - if(alacarteContainer != null) - message.put(JsonKey.Denm.ALACARTE_CONTAINER.key(), alacarteContainer.getJsonAlacarteContainer()); - - jsonDENM.put(JsonKey.Header.TYPE.key(), getType()); - jsonDENM.put(JsonKey.Header.ORIGIN.key(), getOrigin()); - jsonDENM.put(JsonKey.Header.VERSION.key(), getVersion()); - jsonDENM.put(JsonKey.Header.SOURCE_UUID.key(), getSourceUuid()); - jsonDENM.put(JsonKey.Header.TIMESTAMP.key(), getTimestamp()); - jsonDENM.put(JsonKey.Header.MESSAGE.key(), message); - } catch (JSONException e) { - LOGGER.log(Level.WARNING, "DENM JSON build error", "Error: " + e); - } - } - - private void updateJson() { - jsonDENM = new JSONObject(); - createJson(); - } - - public void terminate(long stationId) { - managementContainer.terminate(stationId == this.getManagementContainer().getActionId().getOriginatingStationId()); - this.stationId = stationId; - updateTimestamp(); - updateJson(); - } - - public JSONObject getJsonDENM() { - return jsonDENM; - } - - public int getProtocolVersion() { - return protocolVersion; - } - - public long getStationId() { - return stationId; - } - - public ManagementContainer getManagementContainer() { - return managementContainer; - } - - public SituationContainer getSituationContainer() { - return situationContainer; - } - - public LocationContainer getLocationContainer() { - return locationContainer; - } - - public AlacarteContainer getAlacarteContainer() { - return alacarteContainer; - } - - public static class DENMBuilder { - private final String type; - private String origin; - private String version; - private String sourceUuid; - private long timestamp; - private int protocolVersion; - private long stationId; - private ManagementContainer managementContainer; - private SituationContainer situationContainer; - private LocationContainer locationContainer; - private AlacarteContainer alacarteContainer; - - /** - * Start building a DENM. - */ - public DENMBuilder() { - this.type = JsonValue.Type.DENM.value(); - } - - /** - * Sets the JSON header of the DENM. - *

- * 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 DENMBuilder 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 DENM. - *

- * These fields are mandatory. - * - * @param protocolVersion {@link DENM#protocolVersion} - * @param stationId {@link DENM#stationId} - */ - public DENMBuilder pduHeader(int protocolVersion, - long stationId) { - this.protocolVersion = protocolVersion; - this.stationId = stationId; - return this; - } - - /** - * Sets the management container of the DENM. - *

- * This field is mandatory. - * - * @param managementContainer {@link DENM#managementContainer} - */ - public DENMBuilder managementContainer(ManagementContainer managementContainer) { - this.managementContainer = managementContainer; - return this; - } - - /** - * Sets the situation container of the DENM. - *

- * This field is optional. - * - * @param situationContainer {@link DENM#situationContainer} - */ - public DENMBuilder situationContainer(SituationContainer situationContainer) { - this.situationContainer = situationContainer; - return this; - } - - /** - * Sets the location container of the DENM. - *

- * This field is optional. - * - * @param locationContainer {@link DENM#locationContainer} - */ - public DENMBuilder locationContainer(LocationContainer locationContainer) { - this.locationContainer = locationContainer; - return this; - } - - /** - * Sets the a la carte container of the DENM. - *

- * This field is optional. - * - * @param alacarteContainer {@link DENM#alacarteContainer} - */ - public DENMBuilder alacarteContainer(AlacarteContainer alacarteContainer) { - this.alacarteContainer = alacarteContainer; - return this; - } - - /** - * Build the DENM. - *

- * Call after setting all the mandatory fields. - * - * @return {@link DENM} - */ - public DENM build() { - return new DENM(type, - origin, - version, - sourceUuid, - timestamp, - protocolVersion, - stationId, - managementContainer, - situationContainer, - locationContainer, - alacarteContainer); - } - } - - /** - * Parse a DENM in JSON format. - * - * @param jsonDENM The CAM in JSON format - * @return {@link DENM} - */ - public static DENM jsonParser(JSONObject jsonDENM) { - if(JsonUtil.isNullOrEmpty(jsonDENM)) return null; - try { - String type = jsonDENM.getString(JsonKey.Header.TYPE.key()); - - if(type.equals(JsonValue.Type.DENM.value())){ - JSONObject message = jsonDENM.getJSONObject(JsonKey.Header.MESSAGE.key()); - - String origin = jsonDENM.getString(JsonKey.Header.ORIGIN.key()); - String version = jsonDENM.getString(JsonKey.Header.VERSION.key()); - String sourceUuid = jsonDENM.getString(JsonKey.Header.SOURCE_UUID.key()); - long timestamp = jsonDENM.getLong(JsonKey.Header.TIMESTAMP.key()); - - int protocolVersion = message.getInt(JsonKey.Denm.PROTOCOL_VERSION.key()); - long stationId = message.getLong(JsonKey.Denm.STATION_ID.key()); - - JSONObject jsonManagementContainer = message.getJSONObject(JsonKey.Denm.MANAGEMENT_CONTAINER.key()); - ManagementContainer managementContainer = ManagementContainer.jsonParser(jsonManagementContainer); - - JSONObject jsonSituationContainer = message.optJSONObject(JsonKey.Denm.SITUATION_CONTAINER.key()); - SituationContainer situationContainer = SituationContainer.jsonParser(jsonSituationContainer); - - JSONObject jsonLocationContainer = message.optJSONObject(JsonKey.Denm.LOCATION_CONTAINER.key()); - LocationContainer locationContainer = LocationContainer.jsonParser(jsonLocationContainer); - - JSONObject jsonAlacarteContainer = message.optJSONObject(JsonKey.Denm.ALACARTE_CONTAINER.key()); - AlacarteContainer alacarteContainer = AlacarteContainer.jsonParser(jsonAlacarteContainer); - - return new DENMBuilder() - .header(origin, - version, - sourceUuid, - timestamp) - .pduHeader(protocolVersion, - stationId) - .managementContainer(managementContainer) - .situationContainer(situationContainer) - .locationContainer(locationContainer) - .alacarteContainer(alacarteContainer) - .build(); - } - } catch (JSONException | IllegalArgumentException e) { - LOGGER.log(Level.WARNING, "DENM JSON parsing error", "Error: " + e); - } - return null; - } -} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/denm/EventType.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/denm/EventType.java deleted file mode 100644 index 08537a47d..000000000 --- a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/denm/EventType.java +++ /dev/null @@ -1,79 +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.denm; - -import com.orange.iot3mobility.its.json.JsonKey; - -import com.orange.iot3mobility.its.json.JsonUtil; -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.logging.Level; -import java.util.logging.Logger; - -public class EventType { - - private static final Logger LOGGER = Logger.getLogger(EventType.class.getName()); - - private final JSONObject jsonEventType = new JSONObject(); - private final int cause; - private final int subcause; - - public EventType( - final int cause, - final int subcause) - { - if(cause > 255 || cause < 0) { - throw new IllegalArgumentException("DENM EventType Cause should be in the range of [0 - 255]." - + " Value: " + cause); - } - this.cause = cause; - if(subcause > 255 || subcause < 0) { - throw new IllegalArgumentException("DENM EventType Subcause should be in the range of [0 - 255]." - + " Value: " + subcause); - } - this.subcause = subcause; - - createJson(); - } - - private void createJson() { - try { - jsonEventType.put(JsonKey.EventType.CAUSE.key(), cause); - jsonEventType.put(JsonKey.EventType.SUBCAUSE.key(), subcause); - } catch (JSONException e) { - LOGGER.log(Level.WARNING, "EventType JSON build error", "Error: " + e); - } - } - - public JSONObject getJsonEventType() { - return jsonEventType; - } - - public int getCause() { - return cause; - } - - public int getSubcause() { - return subcause; - } - - public static EventType jsonParser(JSONObject jsonEventType) { - if(JsonUtil.isNullOrEmpty(jsonEventType)) return null; - try { - int cause = jsonEventType.getInt(JsonKey.EventType.CAUSE.key()); - int subcause = jsonEventType.getInt(JsonKey.EventType.SUBCAUSE.key()); - - return new EventType(cause, subcause); - } catch (JSONException e) { - LOGGER.log(Level.WARNING, "EventType JSON parsing error", "Error: " + e); - } - return null; - } - -} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/denm/LinkedCause.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/denm/LinkedCause.java deleted file mode 100644 index 1be6cb43d..000000000 --- a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/denm/LinkedCause.java +++ /dev/null @@ -1,79 +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.denm; - -import com.orange.iot3mobility.its.json.JsonKey; - -import com.orange.iot3mobility.its.json.JsonUtil; -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.logging.Level; -import java.util.logging.Logger; - -public class LinkedCause { - - private static final Logger LOGGER = Logger.getLogger(LinkedCause.class.getName()); - - private final JSONObject jsonLinkedCause = new JSONObject(); - private final int cause; - private final int subcause; - - public LinkedCause( - final int cause, - final int subcause) - { - if(cause > 255 || cause < 0) { - throw new IllegalArgumentException("DENM LinkedCause Cause should be in the range of [0 - 255]." - + " Value: " + cause); - } - this.cause = cause; - if(subcause > 255 || subcause < 0) { - throw new IllegalArgumentException("DENM LinkedCause SubCause should be in the range of [0 - 255]." - + " Value: " + subcause); - } - this.subcause = subcause; - - createJson(); - } - - private void createJson() { - try { - jsonLinkedCause.put(JsonKey.LinkedCause.CAUSE.key(), cause); - jsonLinkedCause.put(JsonKey.LinkedCause.SUBCAUSE.key(), subcause); - } catch (JSONException e) { - LOGGER.log(Level.WARNING, "LinkedCause JSON build error", "Error: " + e); - } - } - - public JSONObject getJsonLinkedCause() { - return jsonLinkedCause; - } - - public int getCause() { - return cause; - } - - public int getSubcause() { - return subcause; - } - - public static LinkedCause jsonParser(JSONObject jsonLinkedCause) { - if(JsonUtil.isNullOrEmpty(jsonLinkedCause)) return null; - try { - int cause = jsonLinkedCause.getInt(JsonKey.EventType.CAUSE.key()); - int subcause = jsonLinkedCause.getInt(JsonKey.EventType.SUBCAUSE.key()); - - return new LinkedCause(cause, subcause); - } catch (JSONException e) { - LOGGER.log(Level.WARNING, "LinkedCause JSON parsing error", "Error: " + e); - } - return null; - } - -} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/denm/LocationContainer.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/denm/LocationContainer.java deleted file mode 100644 index b4b3c6d51..000000000 --- a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/denm/LocationContainer.java +++ /dev/null @@ -1,166 +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.denm; - -import static com.orange.iot3mobility.its.json.JsonUtil.UNKNOWN; - -import com.orange.iot3mobility.its.json.JsonKey; - -import com.orange.iot3mobility.its.json.JsonUtil; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.logging.Level; -import java.util.logging.Logger; - -public class LocationContainer { - - private static final Logger LOGGER = Logger.getLogger(LocationContainer.class.getName()); - - private final JSONObject jsonLocationContainer = new JSONObject(); - private final int eventSpeed; - private final int eventPositionHeading; - private final Traces traces; - private final int roadType; - private final int eventSpeedConfidence; - private final int eventPositionHeadingConfidence; - - public LocationContainer( - final int roadType) - { - this(UNKNOWN, UNKNOWN, null, roadType, UNKNOWN, UNKNOWN); - } - - public LocationContainer( - final int eventSpeed, - final int eventPositionHeading) - { - this(eventSpeed, eventPositionHeading, null, UNKNOWN, UNKNOWN, UNKNOWN); - } - - public LocationContainer( - final int eventSpeed, - final int eventPositionHeading, - final Traces traces, - final int roadType, - final int eventSpeedConfidence, - final int eventPositionHeadingConfidence) - { - if(eventSpeed != UNKNOWN && (eventSpeed > 16383 || eventSpeed < 0)) { - throw new IllegalArgumentException("DENM LocationContainer EventSpeed should be in the range of [0 - 16383]." - + " Value: " + eventSpeed); - } - this.eventSpeed = eventSpeed; - if(eventPositionHeading != UNKNOWN && (eventPositionHeading > 3601 || eventPositionHeading < 0)) { - throw new IllegalArgumentException("DENM LocationContainer EventPositionHeading should be in the range of [0 - 3601]." - + " Value: " + eventPositionHeading); - } - this.eventPositionHeading = eventPositionHeading; - if(traces == null) this.traces = new Traces(null); - else this.traces = traces; - if(roadType != UNKNOWN && (roadType > 3 || roadType < 0)) { - throw new IllegalArgumentException("DENM LocationContainer RoadType should be in the range of [0 - 3]." - + " Value: " + roadType); - } - this.roadType = roadType; - if(eventSpeedConfidence != UNKNOWN && (eventSpeedConfidence > 127 || eventSpeedConfidence < 1)) { - throw new IllegalArgumentException("DENM LocationContainer EventSpeedConfidence should be in the range of [1 - 127]." - + " Value: " + eventSpeedConfidence); - } - this.eventSpeedConfidence = eventSpeedConfidence; - if(eventPositionHeadingConfidence != UNKNOWN && (eventPositionHeadingConfidence > 127 || eventPositionHeadingConfidence < 1)) { - throw new IllegalArgumentException("DENM LocationContainer EventPositionHeadingConfidence should be in the range of [1 - 127]." - + " Value: " + eventPositionHeadingConfidence); - } - this.eventPositionHeadingConfidence = eventPositionHeadingConfidence; - - createJson(); - } - - private void createJson() { - try { - JSONObject confidence = new JSONObject(); - if(eventSpeedConfidence != UNKNOWN) - confidence.put(JsonKey.Confidence.EVENT_SPEED.key(), eventSpeedConfidence); - if(eventPositionHeadingConfidence != UNKNOWN) - confidence.put(JsonKey.Confidence.EVENT_POSITION_HEADING.key(), eventPositionHeadingConfidence); - - if(eventSpeed != UNKNOWN) - jsonLocationContainer.put(JsonKey.LocationContainer.EVENT_SPEED.key(), eventSpeed); - if(eventPositionHeading != UNKNOWN) - jsonLocationContainer.put(JsonKey.LocationContainer.EVENT_POSITION_HEADING.key(), eventPositionHeading); - jsonLocationContainer.put(JsonKey.LocationContainer.TRACES.key(), traces.getJsonTraces()); - if(roadType != UNKNOWN) - jsonLocationContainer.put(JsonKey.LocationContainer.ROAD_TYPE.key(), roadType); - if(!JsonUtil.isNullOrEmpty(confidence)) - jsonLocationContainer.put(JsonKey.LocationContainer.CONFIDENCE.key(), confidence); - } catch (JSONException e) { - LOGGER.log(Level.WARNING, "LocationContainer JSON build error", "Error: " + e); - } - } - - public JSONObject getJsonLocationContainer() { - return jsonLocationContainer; - } - - public int getEventSpeed() { - return eventSpeed; - } - - public int getEventPositionHeading() { - return eventPositionHeading; - } - - public Traces getTraces() { - return traces; - } - - public int getRoadType() { - return roadType; - } - - public int getEventSpeedConfidence() { - return eventSpeedConfidence; - } - - public int getEventPositionHeadingConfidence() { - return eventPositionHeadingConfidence; - } - - public static LocationContainer jsonParser(JSONObject jsonLocationContainer) { - if(JsonUtil.isNullOrEmpty(jsonLocationContainer)) return null; - try { - int eventSpeed = jsonLocationContainer.optInt(JsonKey.LocationContainer.EVENT_SPEED.key(), UNKNOWN); - int eventPositionHeading = jsonLocationContainer.optInt(JsonKey.LocationContainer.EVENT_POSITION_HEADING.key(), UNKNOWN); - JSONArray jsonTraces = jsonLocationContainer.getJSONArray(JsonKey.LocationContainer.TRACES.key()); - Traces traces = Traces.jsonParser(jsonTraces); - int roadType = jsonLocationContainer.optInt(JsonKey.LocationContainer.ROAD_TYPE.key(), UNKNOWN); - - JSONObject confidence = jsonLocationContainer.optJSONObject(JsonKey.LocationContainer.CONFIDENCE.key()); - int eventSpeedConfidence = UNKNOWN; - int eventPositionHeadingConfidence = UNKNOWN; - if(confidence != null) { - eventSpeedConfidence = confidence.optInt(JsonKey.Confidence.EVENT_SPEED.key(), UNKNOWN); - eventPositionHeadingConfidence = confidence.optInt(JsonKey.Confidence.EVENT_POSITION_HEADING.key(), UNKNOWN); - } - - return new LocationContainer( - eventSpeed, - eventPositionHeading, - traces, - roadType, - eventSpeedConfidence, - eventPositionHeadingConfidence); - } catch (JSONException e) { - LOGGER.log(Level.WARNING, "LocationContainer JSON parsing error", "Error: " + e); - } - return null; - } - -} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/denm/ManagementContainer.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/denm/ManagementContainer.java deleted file mode 100644 index 946a7af06..000000000 --- a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/denm/ManagementContainer.java +++ /dev/null @@ -1,343 +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.denm; - -import static com.orange.iot3mobility.its.json.JsonUtil.UNKNOWN; - -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; - -public class ManagementContainer { - - private static final Logger LOGGER = Logger.getLogger(ManagementContainer.class.getName()); - - private JSONObject jsonManagementContainer = new JSONObject(); - - /** - * Contains the originating ITS-S station ID and the sequence number. - */ - private final ActionId actionId; - - /** - * Unit: millisecond since ETSI epoch (2004/01/01, so 1072915200000). - * Time at which the event is detected by the originating ITS-S. - *

- * For the DENM repetition, this shall remain unchanged. - *

- * utcStartOf2004(0), oneMillisecAfterUTCStartOf2004(1) - */ - private final long detectionTime; - - /** - * Unit: millisecond since ETSI epoch (2004/01/01, so 1072915200000). - * Time at which a new DENM, an update DENM or a cancellation DENM is generated. - *

- * utcStartOf2004(0), oneMillisecAfterUTCStartOf2004(1) - */ - private final long referenceTime; - - /** - * Set to terminate an existing DENM. - *

- * isCancellation(0), isNegation (1) - */ - private int termination; - - /** - * Position of the event. - *

- * See {@link Position} - */ - private final Position eventPosition; - - /** - * Distance at which the event is relevant. - *

- * lessThan50m(0), lessThan100m(1), lessThan200m(2), lessThan500m(3), lessThan1000m(4), lessThan5km(5), - * lessThan10km(6), over10km(7) - */ - private final int relevanceDistance; - - /** - * Traffic direction for which the event is relevant. - *

- * allTrafficDirections(0), upstreamTraffic(1), downstreamTraffic(2), oppositeTraffic(3) - */ - private final int relevanceTrafficDirection; - - /** - * Unit: second. Validity duration of the event since detection. - *

- * timeOfDetection(0), oneSecondAfterDetection(1) - */ - private int validityDuration; - - /** - * Unit: millisecond. Transmission interval of the DENM. - *

- * Set if the event is sent periodically. - *

- * oneMilliSecond(1), tenSeconds(10000) - */ - private final int transmissionInterval; - - /** - * Station type of the originating ITS-S. - *

- * 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; - - /** - * Confidence of the event position. - *

- * {@link PositionConfidence} - */ - private final PositionConfidence positionConfidence; - - public ManagementContainer( - final ActionId actionId, - final long detectionTime, - final long referenceTime, - final Position eventPosition, - final int stationType) - { - this( - actionId, - detectionTime, - referenceTime, - UNKNOWN, - eventPosition, - UNKNOWN, - UNKNOWN, - 600, - UNKNOWN, - stationType, - null); - } - - public ManagementContainer( - final ActionId actionId, - final long detectionTime, - final long referenceTime, - final Position eventPosition, - final int validityDuration, - final int stationType) - { - this( - actionId, - detectionTime, - referenceTime, - UNKNOWN, - eventPosition, - UNKNOWN, - UNKNOWN, - validityDuration, - UNKNOWN, - stationType, - null); - } - - public ManagementContainer( - final ActionId actionId, - final long detectionTime, - final long referenceTime, - final int termination, - final Position eventPosition, - final int relevanceDistance, - final int relevanceTrafficDirection, - final int validityDuration, - final int transmissionInterval, - final int stationType, - final PositionConfidence positionConfidence) - { - if(actionId == null) { - throw new IllegalArgumentException("DENM ManagementContainer ActionID missing."); - } - this.actionId = actionId; - if(detectionTime > 789048000000L || detectionTime < 473428800000L) { - throw new IllegalArgumentException("DENM ManagementContainer DetectionTime should be in the range of [473428800000 - 789048000000]." - + " Value: " + detectionTime); - } - this.detectionTime = detectionTime; - if(referenceTime > 789048000000L || referenceTime < 473428800000L) { - throw new IllegalArgumentException("DENM ManagementContainer ReferenceTime should be in the range of [473428800000 - 789048000000]." - + " Value: " + referenceTime); - } - this.referenceTime = referenceTime; - if(termination != UNKNOWN && (termination > 1 || termination < 0)) { - throw new IllegalArgumentException("DENM ManagementContainer Termination should be in the range of [0 - 1]." - + " Value: " + termination); - } - this.termination = termination; - if(eventPosition == null) { - throw new IllegalArgumentException("DENM ManagementContainer EventPosition missing."); - } - this.eventPosition = eventPosition; - if(relevanceDistance != UNKNOWN && (relevanceDistance > 7 || relevanceDistance < 0)) { - throw new IllegalArgumentException("DENM ManagementContainer RelevanceDistance should be in the range of [0 - 7]." - + " Value: " + relevanceDistance); - } - this.relevanceDistance = relevanceDistance; - if(relevanceTrafficDirection != UNKNOWN && (relevanceTrafficDirection > 3 || relevanceTrafficDirection < 0)) { - throw new IllegalArgumentException("DENM ManagementContainer RelevanceTrafficDirection should be in the range of [0 - 3]." - + " Value: " + relevanceTrafficDirection); - } - this.relevanceTrafficDirection = relevanceTrafficDirection; - if(validityDuration != UNKNOWN && (validityDuration > 86400 || validityDuration < 0)) { - throw new IllegalArgumentException("DENM ManagementContainer ValidityDuration should be in the range of [0 - 86400]." - + " Value: " + validityDuration); - } - this.validityDuration = validityDuration; - if(transmissionInterval != UNKNOWN && (transmissionInterval > 10000 || transmissionInterval < 0)) { - throw new IllegalArgumentException("DENM ManagementContainer TransmissionInterval should be in the range of [0 - 10000]." - + " Value: " + transmissionInterval); - } - this.transmissionInterval = transmissionInterval; - if(stationType > 255 || stationType < 0) { - throw new IllegalArgumentException("DENM ManagementContainer StationType should be in the range of [0 - 255]." - + " Value: " + stationType); - } - this.stationType = stationType; - this.positionConfidence = positionConfidence; - - createJson(); - } - - private void createJson() { - JSONObject jsonActionId = actionId.getJsonActionId(); - JSONObject jsonEventPosition = eventPosition.getJson(); - - try { - jsonManagementContainer.put(JsonKey.ManagementContainer.ACTION_ID.key(), jsonActionId); - jsonManagementContainer.put(JsonKey.ManagementContainer.DETECTION_TIME.key(), detectionTime); - jsonManagementContainer.put(JsonKey.ManagementContainer.REFERENCE_TIME.key(), referenceTime); - if(termination != UNKNOWN) - jsonManagementContainer.put(JsonKey.ManagementContainer.TERMINATION.key(), termination); - jsonManagementContainer.put(JsonKey.ManagementContainer.EVENT_POSITION.key(), jsonEventPosition); - if(relevanceDistance != UNKNOWN) - jsonManagementContainer.put(JsonKey.ManagementContainer.RELEVANCE_DISTANCE.key(), relevanceDistance); - if(relevanceTrafficDirection != UNKNOWN) - jsonManagementContainer.put(JsonKey.ManagementContainer.RELEVANCE_TRAFFIC_DIRECTION.key(), relevanceTrafficDirection); - if(validityDuration != UNKNOWN) - jsonManagementContainer.put(JsonKey.ManagementContainer.VALIDITY_DURATION.key(), validityDuration); - if(transmissionInterval != UNKNOWN) - jsonManagementContainer.put(JsonKey.ManagementContainer.TRANSMISSION_INTERVAL.key(), transmissionInterval); - jsonManagementContainer.put(JsonKey.ManagementContainer.STATION_TYPE.key(), stationType); - if(positionConfidence != null) - jsonManagementContainer.put(JsonKey.Position.CONFIDENCE.key(), positionConfidence.getJson()); - } catch (JSONException e) { - LOGGER.log(Level.WARNING, "ManagementContainer JSON build error", "Error: " + e); - } - } - - private void updateJson() { - jsonManagementContainer = new JSONObject(); - createJson(); - } - - public JSONObject getJsonManagementContainer() { - return jsonManagementContainer; - } - - public ActionId getActionId() { - return actionId; - } - - public long getDetectionTime() { - return detectionTime; - } - - public long getReferenceTime() { - return referenceTime; - } - - public int getTermination() { - return termination; - } - - public Position getEventPosition() { - return eventPosition; - } - - public int getRelevanceDistance() { - return relevanceDistance; - } - - public int getRelevanceTrafficDirection() { - return relevanceTrafficDirection; - } - - public int getValidityDuration() { - return validityDuration; - } - - public void terminate(boolean isCancellation) { - validityDuration = 0; - if(isCancellation) termination = 0; // same ITS-S -> cancellation - else termination = 1; // different ITS-S -> negation - updateJson(); - } - - public int getTransmissionInterval() { - return transmissionInterval; - } - - public int getStationType() { - return stationType; - } - - public PositionConfidence getPositionConfidence() { - return positionConfidence; - } - - public static ManagementContainer jsonParser(JSONObject jsonManagementContainer) { - if(JsonUtil.isNullOrEmpty(jsonManagementContainer)) return null; - try { - JSONObject jsonActionId = jsonManagementContainer.getJSONObject(JsonKey.ManagementContainer.ACTION_ID.key()); - ActionId actionId = ActionId.jsonParser(jsonActionId); - long detectionTime = jsonManagementContainer.getLong(JsonKey.ManagementContainer.DETECTION_TIME.key()); - long referenceTime = jsonManagementContainer.getLong(JsonKey.ManagementContainer.REFERENCE_TIME.key()); - int termination = jsonManagementContainer.optInt(JsonKey.ManagementContainer.TERMINATION.key(), UNKNOWN); - JSONObject jsonEventPosition = jsonManagementContainer.getJSONObject(JsonKey.ManagementContainer.EVENT_POSITION.key()); - Position eventPosition = Position.jsonParser(jsonEventPosition); - int relevanceDistance = jsonManagementContainer.optInt(JsonKey.ManagementContainer.RELEVANCE_DISTANCE.key(), UNKNOWN); - int relevanceTrafficDirection = jsonManagementContainer.optInt(JsonKey.ManagementContainer.RELEVANCE_TRAFFIC_DIRECTION.key(), UNKNOWN); - int validityDuration = jsonManagementContainer.optInt(JsonKey.ManagementContainer.VALIDITY_DURATION.key(), 600); - int transmissionInterval = jsonManagementContainer.optInt(JsonKey.ManagementContainer.TRANSMISSION_INTERVAL.key(), UNKNOWN); - int stationType = jsonManagementContainer.optInt(JsonKey.ManagementContainer.STATION_TYPE.key(), 0); - JSONObject jsonPositionConfidence = jsonManagementContainer.optJSONObject(JsonKey.Position.CONFIDENCE.key()); - PositionConfidence positionConfidence = PositionConfidence.jsonParser(jsonPositionConfidence); - - return new ManagementContainer( - actionId, - detectionTime, - referenceTime, - termination, - eventPosition, - relevanceDistance, - relevanceTrafficDirection, - validityDuration, - transmissionInterval, - stationType, - positionConfidence); - } catch (JSONException e) { - LOGGER.log(Level.WARNING, "ManagementContainer JSON parsing error", "Error: " + e); - } - return null; - } -} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/denm/SituationContainer.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/denm/SituationContainer.java deleted file mode 100644 index 1b207219f..000000000 --- a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/denm/SituationContainer.java +++ /dev/null @@ -1,125 +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.denm; - -import static com.orange.iot3mobility.its.json.JsonUtil.UNKNOWN; - -import com.orange.iot3mobility.its.json.JsonKey; - -import com.orange.iot3mobility.its.json.JsonUtil; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.logging.Level; -import java.util.logging.Logger; - -public class SituationContainer { - - private static final Logger LOGGER = Logger.getLogger(SituationContainer.class.getName()); - - private final JSONObject jsonSituationContainer = new JSONObject(); - private final int infoQuality; - private final EventType eventType; - private final LinkedCause linkedCause; - private final JSONArray eventHistory; - - public SituationContainer( - final EventType eventType) - { - this(UNKNOWN, eventType, null, null); - } - - public SituationContainer( - final int infoQuality, - final EventType eventType) - { - this(infoQuality, eventType, null, null); - } - - public SituationContainer( - final int infoQuality, - final EventType eventType, - final LinkedCause linkedCause, - final JSONArray eventHistory) - { - if(infoQuality != UNKNOWN && (infoQuality > 7 || infoQuality < 0)) { - throw new IllegalArgumentException("DENM SituationContainer InfoQuality should be in the range of [0 - 7]." - + " Value: " + infoQuality); - } - this.infoQuality = infoQuality; - if(eventType == null) { - throw new IllegalArgumentException("DENM SituationContainer EventType missing."); - } - this.eventType = eventType; - this.linkedCause = linkedCause; - this.eventHistory = eventHistory; - - createJson(); - } - - private void createJson() { - try { - if(infoQuality != UNKNOWN) - jsonSituationContainer.put(JsonKey.SituationContainer.INFO_QUALITY.key(), infoQuality); - jsonSituationContainer.put(JsonKey.SituationContainer.EVENT_TYPE.key(), eventType.getJsonEventType()); - if(linkedCause != null) - jsonSituationContainer.put(JsonKey.SituationContainer.LINKED_CAUSE.key(), linkedCause.getJsonLinkedCause()); - if(eventHistory != null) - jsonSituationContainer.put(JsonKey.SituationContainer.EVENT_TYPE.key(), eventHistory); - } catch (JSONException e) { - LOGGER.log(Level.WARNING, "SituationContainer JSON build error", "Error: " + e); - } - } - - public JSONObject getJsonSituationContainer() { - return jsonSituationContainer; - } - - public int getInfoQuality() { - return infoQuality; - } - - public boolean hasInfoQuality() { - return infoQuality != UNKNOWN; - } - - public EventType getEventType() { - return eventType; - } - - public LinkedCause getLinkedCause() { - return linkedCause; - } - - public JSONArray getEventHistory() { - return eventHistory; - } - - public static SituationContainer jsonParser(JSONObject jsonSituationContainer) { - if(JsonUtil.isNullOrEmpty(jsonSituationContainer)) return null; - try { - int infoQuality = jsonSituationContainer.optInt(JsonKey.SituationContainer.INFO_QUALITY.key(), UNKNOWN); - JSONObject jsonEventType = jsonSituationContainer.getJSONObject(JsonKey.SituationContainer.EVENT_TYPE.key()); - EventType eventType = EventType.jsonParser(jsonEventType); - JSONObject jsonLinkedCause = jsonSituationContainer.optJSONObject(JsonKey.SituationContainer.LINKED_CAUSE.key()); - LinkedCause linkedCause = LinkedCause.jsonParser(jsonLinkedCause); - JSONArray eventHistory = jsonSituationContainer.optJSONArray(JsonKey.SituationContainer.EVENT_HISTORY.key()); - - return new SituationContainer( - infoQuality, - eventType, - linkedCause, - eventHistory); - } catch (JSONException e) { - LOGGER.log(Level.WARNING, "SituationContainer JSON parsing error", "Error: " + e); - } - return null; - } - -} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/denm/Traces.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/denm/Traces.java deleted file mode 100644 index 79f9bebe7..000000000 --- a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/its/json/denm/Traces.java +++ /dev/null @@ -1,55 +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.denm; - -import com.orange.iot3mobility.its.json.JsonUtil; -import com.orange.iot3mobility.its.json.PathHistory; - -import org.json.JSONArray; - -import java.util.ArrayList; - -public class Traces { - - private final JSONArray jsonTraces = new JSONArray(); - private final ArrayList pathHistories; - - public Traces( - ArrayList pathHistories) - { - if(pathHistories == null) this.pathHistories = new ArrayList<>(); - else this.pathHistories = pathHistories; - - createJson(); - } - - private void createJson() { - for(PathHistory pathHistory: pathHistories) { - jsonTraces.put(pathHistory.getJsonPathHistory()); - } - } - - public JSONArray getJsonTraces() { - return jsonTraces; - } - - public ArrayList getPathHistories() { - return pathHistories; - } - - public static Traces jsonParser(JSONArray jsonTraces) { - if(JsonUtil.isNullOrEmpty(jsonTraces)) return new Traces(null); - ArrayList pathHistories = new ArrayList<>(); - for(int i = 0; i < jsonTraces.length(); i++) { - PathHistory pathHistory = PathHistory.jsonParser(jsonTraces.optJSONArray(i)); - pathHistories.add(pathHistory); - } - return new Traces(pathHistories); - } - -} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/managers/IoT3RoadHazardCallback.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/managers/IoT3RoadHazardCallback.java index 4760bb9b8..567763039 100644 --- a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/managers/IoT3RoadHazardCallback.java +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/managers/IoT3RoadHazardCallback.java @@ -7,7 +7,7 @@ */ package com.orange.iot3mobility.managers; -import com.orange.iot3mobility.its.json.denm.DENM; +import com.orange.iot3mobility.messages.denm.core.DenmCodec; import com.orange.iot3mobility.roadobjects.RoadHazard; public interface IoT3RoadHazardCallback { @@ -18,6 +18,6 @@ public interface IoT3RoadHazardCallback { void roadHazardExpired(RoadHazard roadHazard); - void denmArrived(DENM denm); + void denmArrived(DenmCodec.DenmFrame denmFrame); } diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/managers/RoadHazardManager.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/managers/RoadHazardManager.java index 89769a54e..8c7a99e91 100644 --- a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/managers/RoadHazardManager.java +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/managers/RoadHazardManager.java @@ -8,13 +8,18 @@ package com.orange.iot3mobility.managers; import com.orange.iot3mobility.TrueTime; -import com.orange.iot3mobility.its.json.JsonUtil; -import com.orange.iot3mobility.its.json.denm.DENM; +import com.orange.iot3mobility.messages.denm.DenmHelper; +import com.orange.iot3mobility.messages.denm.core.DenmCodec; +import com.orange.iot3mobility.messages.denm.core.DenmVersion; +import com.orange.iot3mobility.messages.denm.v113.model.DenmEnvelope113; +import com.orange.iot3mobility.messages.denm.v113.model.DenmMessage113; +import com.orange.iot3mobility.messages.denm.v220.model.DenmEnvelope220; +import com.orange.iot3mobility.messages.denm.v220.model.DenmMessage220; import com.orange.iot3mobility.roadobjects.RoadHazard; 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,46 +44,67 @@ public static void init(IoT3RoadHazardCallback ioT3RoadHazardCallback) { startExpirationCheck(); } - public static void processDenm(String message) { + public static void processDenm(String message, DenmHelper denmHelper) { if(ioT3RoadHazardCallback != null) { try { - DENM denm = DENM.jsonParser(new JSONObject(message)); - if(denm == null) { - LOGGER.log(Level.WARNING, TAG, "DENM parsing returned null"); - } else { - ioT3RoadHazardCallback.denmArrived(denm); - //associate the received DENM to a RoadHazard object - String uuid = denm.getManagementContainer().getActionId().getOriginatingStationId() - + "_" + denm.getManagementContainer().getActionId().getSequenceNumber(); - int lifetime = denm.getManagementContainer().getValidityDuration() * 1000; // to ms - long timestamp = denm.getTimestamp(); - boolean expired = TrueTime.getAccurateTime() - timestamp > lifetime; - boolean terminate = denm.getManagementContainer().getTermination() != JsonUtil.UNKNOWN; - if(ROAD_HAZARD_MAP.containsKey(uuid)) { - synchronized (ROAD_HAZARD_MAP) { - RoadHazard roadHazard = ROAD_HAZARD_MAP.get(uuid); - if(roadHazard != null) { - if(terminate) { - ROAD_HAZARD_MAP.values().remove(roadHazard); - synchronized (ROAD_HAZARDS) { - ROAD_HAZARDS.remove(roadHazard); - } - ioT3RoadHazardCallback.roadHazardExpired(roadHazard); - } else { - roadHazard.setDenm(denm); - ioT3RoadHazardCallback.roadHazardUpdate(roadHazard); - } - } + DenmCodec.DenmFrame denmFrame = denmHelper.parse(message); + ioT3RoadHazardCallback.denmArrived(denmFrame); + + String uuid; + int lifetime; + long timestamp; + boolean expired; + boolean terminate; + + if(denmFrame.version().equals(DenmVersion.V1_1_3)) { + DenmEnvelope113 denmEnvelope113 = (DenmEnvelope113) denmFrame.envelope(); + DenmMessage113 denm113 = denmEnvelope113.message(); + timestamp = denmEnvelope113.timestamp(); + uuid = denm113.managementContainer().actionId().originatingStationId() + "_" + + denm113.managementContainer().actionId().sequenceNumber(); + lifetime = denm113.managementContainer().validityDuration() * 1000; + expired = TrueTime.getAccurateTime() - timestamp > lifetime; + terminate = denm113.managementContainer().termination() != null; + createOrUpdateRoadHazard(uuid, expired, terminate, denmFrame); + } else if(denmFrame.version().equals(DenmVersion.V2_2_0)) { + DenmEnvelope220 denmEnvelope220 = (DenmEnvelope220) denmFrame.envelope(); + DenmMessage220 denm220 = denmEnvelope220.message(); + timestamp = denmEnvelope220.timestamp(); + uuid = denm220.managementContainer().actionId().originatingStationId() + "_" + + denm220.managementContainer().actionId().sequenceNumber(); + lifetime = denm220.managementContainer().validityDuration() * 1000; + expired = TrueTime.getAccurateTime() - timestamp > lifetime; + terminate = denm220.managementContainer().termination() != null; + createOrUpdateRoadHazard(uuid, expired, terminate, denmFrame); + } + } catch (JSONException | IOException e) { + LOGGER.log(Level.WARNING, TAG, "DENM parsing error: " + e); + } + } + } + + private static void createOrUpdateRoadHazard(String uuid, boolean expired, boolean terminate, + DenmCodec.DenmFrame denmFrame) { + if(ROAD_HAZARD_MAP.containsKey(uuid)) { + synchronized (ROAD_HAZARD_MAP) { + RoadHazard roadHazard = ROAD_HAZARD_MAP.get(uuid); + if(roadHazard != null) { + if(terminate) { + ROAD_HAZARD_MAP.values().remove(roadHazard); + synchronized (ROAD_HAZARDS) { + ROAD_HAZARDS.remove(roadHazard); } - } else if(!terminate && !expired) { - RoadHazard roadHazard = new RoadHazard(uuid, denm); - addRoadHazard(uuid, roadHazard); - ioT3RoadHazardCallback.newRoadHazard(roadHazard); + ioT3RoadHazardCallback.roadHazardExpired(roadHazard); + } else { + roadHazard.setDenmFrame(denmFrame); + ioT3RoadHazardCallback.roadHazardUpdate(roadHazard); } } - } catch (JSONException e) { - LOGGER.log(Level.WARNING, TAG, "DENM parsing error: " + e); } + } else if(!terminate && !expired) { + RoadHazard roadHazard = new RoadHazard(uuid, denmFrame); + addRoadHazard(uuid, roadHazard); + ioT3RoadHazardCallback.newRoadHazard(roadHazard); } } 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 a5222057e..8da642e79 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 @@ -7,8 +7,8 @@ */ package com.orange.iot3mobility.managers; -import com.orange.iot3mobility.its.StationType; import com.orange.iot3mobility.messages.EtsiConverter; +import com.orange.iot3mobility.messages.StationType; import com.orange.iot3mobility.messages.cam.CamHelper; import com.orange.iot3mobility.messages.cam.core.CamCodec; import com.orange.iot3mobility.messages.cam.core.CamVersion; @@ -61,7 +61,7 @@ public static void processCam(String message, CamHelper camHelper) { 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()); + stationType = StationType.fromValue(camEnvelope113.message().basicContainer().stationType()); position = new LatLng( EtsiConverter.latitudeDegrees(camEnvelope113.message().basicContainer().referencePosition().latitude()), EtsiConverter.longitudeDegrees(camEnvelope113.message().basicContainer().referencePosition().longitude())); @@ -72,7 +72,7 @@ public static void processCam(String message, CamHelper camHelper) { CamEnvelope230 camEnvelope230 = (CamEnvelope230) camFrame.envelope(); if(camEnvelope230.message() instanceof CamStructuredData cam) { uuid = camEnvelope230.sourceUuid() + "_" + cam.stationId(); - stationType = StationType.fromId(cam.basicContainer().stationType()); + stationType = StationType.fromValue(cam.basicContainer().stationType()); position = new LatLng( EtsiConverter.latitudeDegrees(cam.basicContainer().referencePosition().latitude()), EtsiConverter.longitudeDegrees(cam.basicContainer().referencePosition().longitude())); 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 index 6ce766837..528181518 100644 --- 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 @@ -15,6 +15,8 @@ */ public final class EtsiConverter { + public static final long DELTA_1970_2004_MILLISEC = 1072915200000L; + private EtsiConverter() { // Utility class } @@ -39,6 +41,15 @@ public static double epochMillisToSeconds(long epochMillis) { return epochMillis / 1000.0; } + /** + * UNIX -> ETSI + * @param unixTimestampMs UNIX timestamp in milliseconds + * @return ETSI timestamp in milliseconds + */ + public static long unixToEtsiTimestampMs(long unixTimestampMs) { + return unixTimestampMs - DELTA_1970_2004_MILLISEC; + } + // ------------------------------------------------------------------------- // Position (latitude / longitude / altitude) // ------------------------------------------------------------------------- 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 index fab5a9c98..9cbe3e3e0 100644 --- 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 @@ -33,4 +33,11 @@ public enum StationType { StationType(int value) { this.value = value; } + + public static StationType fromValue(int value) { + for(StationType stationType: StationType.values()) { + if(stationType.value == value) return stationType; + } + return UNKNOWN; + } } diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/DenmHelper.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/DenmHelper.java new file mode 100644 index 000000000..35bab4520 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/DenmHelper.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.denm; + +import com.fasterxml.jackson.core.JsonFactory; +import com.orange.iot3mobility.messages.denm.core.DenmCodec; +import com.orange.iot3mobility.messages.denm.core.DenmException; +import com.orange.iot3mobility.messages.denm.core.DenmVersion; +import com.orange.iot3mobility.messages.denm.v113.model.DenmEnvelope113; +import com.orange.iot3mobility.messages.denm.v220.model.DenmEnvelope220; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Calendar; +import java.util.Objects; + +/** + * High-level helper around DenmCodec. Also provides DENM a sequence number helper method. + *

+ * - Manages a shared JsonFactory and DenmCodec instance. + * - Provides String-based APIs (convenient for MQTT payloads). + * - Thread-safe: stateless, all shared components are immutable. + */ +public final class DenmHelper { + + private final JsonFactory jsonFactory; + private final DenmCodec denmCodec; + + /** + * Default constructor: creates its own JsonFactory. + * Recommended in most cases. + */ + public DenmHelper() { + this(new JsonFactory()); + } + + /** + * Constructor with externally-provided JsonFactory (for advanced usage, + * e.g. custom Jackson configuration). + */ + public DenmHelper(JsonFactory jsonFactory) { + this.jsonFactory = Objects.requireNonNull(jsonFactory, "jsonFactory"); + this.denmCodec = new DenmCodec(this.jsonFactory); + } + + // --------------------------------------------------------------------- + // Reading / parsing from String (e.g. MQTT payload) + // --------------------------------------------------------------------- + + /** + * Parse a DENM JSON payload (string) and let DenmCodec detect the version. + * + * @param jsonPayload JSON string containing a DENM envelope + * @return a DenmFrame with detected version and typed envelope + * @throws IOException if JSON is malformed or I/O error in parser + * @throws DenmException (or subclasses) if the DENM structure/fields are invalid + */ + public DenmCodec.DenmFrame parse(String jsonPayload) throws IOException { + Objects.requireNonNull(jsonPayload, "jsonPayload"); + + return denmCodec.read(jsonPayload); + } + + /** + * Parse a DENM JSON payload and cast it to a v1.1.3 envelope. + * Throws if the version is not 1.1.3. + */ + public DenmEnvelope113 parse113(String jsonPayload) throws IOException { + DenmCodec.DenmFrame frame = parse(jsonPayload); + if (frame.version() != DenmVersion.V1_1_3) { + throw new DenmException("Expected DENM version 1.1.3 but got " + frame.version()); + } + return (DenmEnvelope113) frame.envelope(); + } + + /** + * Parse a DENM JSON payload and cast it to a v2.2.0 envelope. + * Throws if the version is not 2.2.0. + */ + public DenmEnvelope220 parse220(String jsonPayload) throws IOException { + DenmCodec.DenmFrame frame = parse(jsonPayload); + if (frame.version() != DenmVersion.V2_2_0) { + throw new DenmException("Expected DENM version 2.2.0 but got " + frame.version()); + } + return (DenmEnvelope220) frame.envelope(); + } + + // --------------------------------------------------------------------- + // Writing / serializing to String + // --------------------------------------------------------------------- + + /** + * Serialize a v1.1.3 DENM envelope to a JSON string. + */ + public String toJson(DenmEnvelope113 envelope113) throws IOException { + Objects.requireNonNull(envelope113, "envelope113"); + return writeToString(DenmVersion.V1_1_3, envelope113); + } + + /** + * Serialize a v2.2.0 DENM envelope to a JSON string. + */ + public String toJson(DenmEnvelope220 envelope220) throws IOException { + Objects.requireNonNull(envelope220, "envelope220"); + return writeToString(DenmVersion.V2_2_0, envelope220); + } + + /** + * Generic entry point for DENM serialization to a JSON string. + */ + public String toJson(DenmVersion version, Object envelope) throws IOException { + Objects.requireNonNull(version, "version"); + Objects.requireNonNull(envelope, "envelope"); + return writeToString(version, envelope); + } + + // --------------------------------------------------------------------- + // Internal helper + // --------------------------------------------------------------------- + + private String writeToString(DenmVersion version, Object envelope) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(1024); + denmCodec.write(version, envelope, out); + return out.toString(StandardCharsets.UTF_8); + } + + // --------------------------------------------------------------------- + // Sequence number helper + // --------------------------------------------------------------------- + + private static int localSequenceNumber = initLocalSequenceNumber(); + + /** + * Simple way to avoid getting the same sequence number after a restart (can cause issues to identify DENMs). + */ + private static int initLocalSequenceNumber() { + Calendar c = Calendar.getInstance(); + int h = c.get(Calendar.HOUR_OF_DAY); + int m = c.get(Calendar.MINUTE); + int s = c.get(Calendar.SECOND); + + int secondsSinceMidnight = h * 3600 + m * 60 + s; // 0..86399 + return secondsSinceMidnight % 65536; // 0..65535 + } + + /** + * Helper method incrementing your DENM sequence number. + * + * @return the next sequence number for a new DENM + */ + public static int getNextSequenceNumber() { + localSequenceNumber++; + if(localSequenceNumber > 65535) localSequenceNumber = 0; + return localSequenceNumber; + } +} + diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/core/DenmCodec.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/core/DenmCodec.java new file mode 100644 index 000000000..12cbdb041 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/core/DenmCodec.java @@ -0,0 +1,131 @@ +/* + 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.denm.core; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.orange.iot3mobility.messages.denm.v113.codec.DenmReader113; +import com.orange.iot3mobility.messages.denm.v113.codec.DenmWriter113; +import com.orange.iot3mobility.messages.denm.v113.model.DenmEnvelope113; +import com.orange.iot3mobility.messages.denm.v220.codec.DenmReader220; +import com.orange.iot3mobility.messages.denm.v220.codec.DenmWriter220; +import com.orange.iot3mobility.messages.denm.v220.model.DenmEnvelope220; + +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 DENM envelopes across all supported versions. + */ +public final class DenmCodec { + + /** + * Wrapper exposing both the detected DENM version and the decoded envelope. + */ + public record DenmFrame(DenmVersion version, T envelope) {} + + private final JsonFactory jsonFactory; + private final DenmReader113 reader113; + private final DenmWriter113 writer113; + private final DenmReader220 reader220; + private final DenmWriter220 writer220; + + public DenmCodec(JsonFactory jsonFactory) { + this.jsonFactory = Objects.requireNonNull(jsonFactory, "jsonFactory"); + this.reader113 = new DenmReader113(jsonFactory); + this.writer113 = new DenmWriter113(jsonFactory); + this.reader220 = new DenmReader220(jsonFactory); + this.writer220 = new DenmWriter220(jsonFactory); + } + + /** + * Reads the full payload, detects the DENM version from the top-level "version" field, + * then delegates to the appropriate reader. The returned {@link DenmFrame} exposes both the version and the + * strongly-typed envelope instance. + */ + public DenmFrame read(InputStream in) throws IOException { + byte[] payload = in.readAllBytes(); + DenmVersion version = detectVersion(payload); + + return switch (version) { + case V1_1_3 -> new DenmFrame<>(version, + reader113.read(new ByteArrayInputStream(payload))); + case V2_2_0 -> new DenmFrame<>(version, + reader220.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 DenmFrame read(String json) throws IOException { + Objects.requireNonNull(json, "json"); + byte[] payload = json.getBytes(StandardCharsets.UTF_8); + + DenmVersion version = detectVersion(payload); + + return switch (version) { + case V1_1_3 -> new DenmFrame<>(version, + reader113.read(new ByteArrayInputStream(payload))); + case V2_2_0 -> new DenmFrame<>(version, + reader220.read(new ByteArrayInputStream(payload))); + }; + } + + /** + * Writes an envelope using the writer that matches the provided version. + */ + public void write(DenmVersion version, Object envelope, OutputStream out) throws IOException { + switch (version) { + case V1_1_3 -> writer113.write(cast(envelope, DenmEnvelope113.class), out); + case V2_2_0 -> writer220.write(cast(envelope, DenmEnvelope220.class), out); + default -> throw new DenmException("Unsupported version: " + version); + } + } + + /** + * Parses only the top-level "version" field without building any tree (streaming mode). + */ + private DenmVersion 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 DenmVersion.fromJsonValue(parser.getValueAsString()); + } else { + parser.skipChildren(); + } + } + } + throw new DenmException("Missing 'version' field in DENM payload"); + } + + private static void expect(JsonToken actual, JsonToken expected) { + if (actual != expected) { + throw new DenmException("Expected token " + expected + " but got " + actual); + } + } + + @SuppressWarnings("unchecked") + private static T cast(Object value, Class type) { + if (!type.isInstance(value)) { + throw new DenmException("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/denm/core/DenmException.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/core/DenmException.java new file mode 100644 index 000000000..06154760a --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/core/DenmException.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.denm.core; + +/** + * Base runtime exception for all DENM codec operations. + */ +public class DenmException extends RuntimeException { + + public DenmException(String message) { + super(message); + } + + public DenmException(String message, Throwable cause) { + super(message, cause); + } + + public DenmException(Throwable cause) { + super(cause); + } +} + diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/core/DenmVersion.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/core/DenmVersion.java new file mode 100644 index 000000000..a4e8aff07 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/core/DenmVersion.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.denm.core; + +/** + * Supported DENM JSON envelope versions. + */ +public enum DenmVersion { + V1_1_3("1.1.3"), + V2_2_0("2.2.0"); + + private final String jsonValue; + + DenmVersion(String jsonValue) { + this.jsonValue = jsonValue; + } + + public String jsonValue() { + return jsonValue; + } + + public static DenmVersion fromJsonValue(String value) { + for (DenmVersion version : values()) { + if (version.jsonValue.equals(value)) { + return version; + } + } + throw new DenmException("Unsupported DENM version: " + value); + } +} + diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/codec/DenmReader113.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/codec/DenmReader113.java new file mode 100644 index 000000000..5f4b4be4d --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/codec/DenmReader113.java @@ -0,0 +1,524 @@ +/* + 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.denm.v113.codec; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.orange.iot3mobility.messages.denm.v113.model.DenmEnvelope113; +import com.orange.iot3mobility.messages.denm.v113.model.DenmMessage113; +import com.orange.iot3mobility.messages.denm.v113.model.path.PathElement; +import com.orange.iot3mobility.messages.denm.v113.model.path.PathPosition; +import com.orange.iot3mobility.messages.denm.v113.model.alacartecontainer.AlacarteContainer; +import com.orange.iot3mobility.messages.denm.v113.model.locationcontainer.DeltaReferencePosition; +import com.orange.iot3mobility.messages.denm.v113.model.locationcontainer.LocationConfidence; +import com.orange.iot3mobility.messages.denm.v113.model.locationcontainer.LocationContainer; +import com.orange.iot3mobility.messages.denm.v113.model.locationcontainer.PathHistory; +import com.orange.iot3mobility.messages.denm.v113.model.locationcontainer.PathPoint; +import com.orange.iot3mobility.messages.denm.v113.model.managementcontainer.ActionId; +import com.orange.iot3mobility.messages.denm.v113.model.managementcontainer.ManagementContainer; +import com.orange.iot3mobility.messages.denm.v113.model.managementcontainer.PositionConfidence; +import com.orange.iot3mobility.messages.denm.v113.model.managementcontainer.PositionConfidenceEllipse; +import com.orange.iot3mobility.messages.denm.v113.model.managementcontainer.ReferencePosition; +import com.orange.iot3mobility.messages.denm.v113.model.situationcontainer.EventType; +import com.orange.iot3mobility.messages.denm.v113.model.situationcontainer.LinkedCause; +import com.orange.iot3mobility.messages.denm.v113.model.situationcontainer.SituationContainer; +import com.orange.iot3mobility.messages.denm.v113.validation.DenmValidationException; +import com.orange.iot3mobility.messages.denm.v113.validation.DenmValidator113; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +public final class DenmReader113 { + + private final JsonFactory jsonFactory; + + public DenmReader113(JsonFactory jsonFactory) { + this.jsonFactory = jsonFactory; + } + + public DenmEnvelope113 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; + List path = null; + DenmMessage113 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 "path" -> path = readPath(parser); + case "message" -> message = readMessage(parser); + default -> parser.skipChildren(); + } + } + + DenmEnvelope113 envelope = new DenmEnvelope113( + requireField(type, "type"), + requireField(origin, "origin"), + requireField(version, "version"), + requireField(sourceUuid, "source_uuid"), + requireField(timestamp, "timestamp"), + path, + requireField(message, "message")); + + DenmValidator113.validateEnvelope(envelope); + return envelope; + } + } + + private List readPath(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_ARRAY); + List elements = new ArrayList<>(); + while (parser.nextToken() != JsonToken.END_ARRAY) { + elements.add(readPathElement(parser)); + } + return elements; + } + + private PathElement readPathElement(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + PathPosition position = null; + String messageType = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "position" -> position = readPathPosition(parser); + case "message_type" -> messageType = parser.getValueAsString(); + default -> parser.skipChildren(); + } + } + + return new PathElement( + requireField(position, "path.position"), + requireField(messageType, "path.message_type")); + } + + private PathPosition readPathPosition(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Integer latitude = null; + Integer longitude = null; + Integer altitude = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "latitude" -> latitude = parser.getIntValue(); + case "longitude" -> longitude = parser.getIntValue(); + case "altitude" -> altitude = parser.getIntValue(); + default -> parser.skipChildren(); + } + } + + return new PathPosition( + requireField(latitude, "path.position.latitude"), + requireField(longitude, "path.position.longitude"), + requireField(altitude, "path.position.altitude")); + } + + private DenmMessage113 readMessage(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + + Integer protocolVersion = null; + Long stationId = null; + ManagementContainer managementContainer = null; + SituationContainer situationContainer = null; + LocationContainer locationContainer = null; + AlacarteContainer alacarteContainer = 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 "management_container" -> managementContainer = readManagementContainer(parser); + case "situation_container" -> situationContainer = readSituationContainer(parser); + case "location_container" -> locationContainer = readLocationContainer(parser); + case "alacarte_container" -> alacarteContainer = readAlacarteContainer(parser); + default -> parser.skipChildren(); + } + } + + return new DenmMessage113( + requireField(protocolVersion, "protocol_version"), + requireField(stationId, "station_id"), + requireField(managementContainer, "management_container"), + situationContainer, + locationContainer, + alacarteContainer); + } + + private ManagementContainer readManagementContainer(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + + ActionId actionId = null; + Long detectionTime = null; + Long referenceTime = null; + Integer termination = null; + ReferencePosition eventPosition = null; + Integer relevanceDistance = null; + Integer relevanceTrafficDirection = null; + Integer validityDuration = null; + Integer transmissionInterval = null; + Integer stationType = null; + PositionConfidence confidence = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "action_id" -> actionId = readActionId(parser); + case "detection_time" -> detectionTime = parser.getLongValue(); + case "reference_time" -> referenceTime = parser.getLongValue(); + case "termination" -> termination = parser.getIntValue(); + case "event_position" -> eventPosition = readReferencePosition(parser); + case "relevance_distance" -> relevanceDistance = parser.getIntValue(); + case "relevance_traffic_direction" -> relevanceTrafficDirection = parser.getIntValue(); + case "validity_duration" -> validityDuration = parser.getIntValue(); + case "transmission_interval" -> transmissionInterval = parser.getIntValue(); + case "station_type" -> stationType = parser.getIntValue(); + case "confidence" -> confidence = readPositionConfidence(parser); + default -> parser.skipChildren(); + } + } + + return new ManagementContainer( + requireField(actionId, "action_id"), + requireField(detectionTime, "detection_time"), + requireField(referenceTime, "reference_time"), + termination, + requireField(eventPosition, "event_position"), + relevanceDistance, + relevanceTrafficDirection, + validityDuration, + transmissionInterval, + stationType, + confidence); + } + + private ActionId readActionId(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Long originatingStationId = null; + Integer sequenceNumber = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "originating_station_id" -> originatingStationId = parser.getLongValue(); + case "sequence_number" -> sequenceNumber = parser.getIntValue(); + default -> parser.skipChildren(); + } + } + + return new ActionId( + requireField(originatingStationId, "originating_station_id"), + requireField(sequenceNumber, "sequence_number")); + } + + private ReferencePosition readReferencePosition(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Integer latitude = null; + Integer longitude = null; + Integer altitude = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "latitude" -> latitude = parser.getIntValue(); + case "longitude" -> longitude = parser.getIntValue(); + case "altitude" -> altitude = parser.getIntValue(); + default -> parser.skipChildren(); + } + } + + return new ReferencePosition( + requireField(latitude, "latitude"), + requireField(longitude, "longitude"), + requireField(altitude, "altitude")); + } + + private PositionConfidence readPositionConfidence(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 = readPositionConfidenceEllipse(parser); + case "altitude" -> altitude = parser.getIntValue(); + default -> parser.skipChildren(); + } + } + + if (ellipse == null && altitude == null) { + return null; + } + return new PositionConfidence(ellipse, altitude); + } + + private PositionConfidenceEllipse readPositionConfidenceEllipse(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + int semiMajor = 4095; + int semiMinor = 4095; + int semiMajorOrientation = 3601; + + 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" -> semiMajorOrientation = parser.getIntValue(); + default -> parser.skipChildren(); + } + } + + return new PositionConfidenceEllipse(semiMajor, semiMinor, semiMajorOrientation); + } + + private SituationContainer readSituationContainer(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Integer informationQuality = null; + EventType eventType = null; + LinkedCause linkedCause = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "information_quality" -> informationQuality = parser.getIntValue(); + case "event_type" -> eventType = readEventType(parser); + case "linked_cause" -> linkedCause = readLinkedCause(parser); + default -> parser.skipChildren(); + } + } + + return new SituationContainer( + informationQuality, + requireField(eventType, "event_type"), + linkedCause); + } + + private EventType readEventType(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(); + switch (field) { + case "cause" -> cause = parser.getIntValue(); + case "subcause" -> subcause = parser.getIntValue(); + default -> parser.skipChildren(); + } + } + + return new EventType( + requireField(cause, "cause"), + subcause); + } + + private LinkedCause readLinkedCause(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(); + switch (field) { + case "cause" -> cause = parser.getIntValue(); + case "subcause" -> subcause = parser.getIntValue(); + default -> parser.skipChildren(); + } + } + + return new LinkedCause( + requireField(cause, "cause"), + subcause); + } + + private LocationContainer readLocationContainer(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Integer eventSpeed = null; + Integer eventPositionHeading = null; + List traces = null; + Integer roadType = null; + LocationConfidence confidence = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "event_speed" -> eventSpeed = parser.getIntValue(); + case "event_position_heading" -> eventPositionHeading = parser.getIntValue(); + case "traces" -> traces = readTraces(parser); + case "road_type" -> roadType = parser.getIntValue(); + case "confidence" -> confidence = readLocationConfidence(parser); + default -> parser.skipChildren(); + } + } + + return new LocationContainer( + eventSpeed, + eventPositionHeading, + requireField(traces, "traces"), + roadType, + confidence); + } + + private List readTraces(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_ARRAY); + List traces = new ArrayList<>(); + while (parser.nextToken() != JsonToken.END_ARRAY) { + traces.add(readTraceElement(parser)); + } + return traces; + } + + private PathHistory readTraceElement(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + List pathHistory = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + if ("path_history".equals(field)) { + pathHistory = readPathHistory(parser); + } else { + parser.skipChildren(); + } + } + + return new PathHistory(requireField(pathHistory, "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 pathPosition = null; + Integer pathDeltaTime = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "path_position" -> pathPosition = readDeltaReferencePosition(parser); + case "path_delta_time" -> pathDeltaTime = parser.getIntValue(); + default -> parser.skipChildren(); + } + } + + return new PathPoint( + requireField(pathPosition, "path_position"), + pathDeltaTime); + } + + private DeltaReferencePosition readDeltaReferencePosition(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + int deltaLatitude = 131072; + int deltaLongitude = 131072; + int deltaAltitude = 12800; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "delta_latitude" -> deltaLatitude = parser.getIntValue(); + case "delta_longitude" -> deltaLongitude = parser.getIntValue(); + case "delta_altitude" -> deltaAltitude = parser.getIntValue(); + default -> parser.skipChildren(); + } + } + + return new DeltaReferencePosition(deltaLatitude, deltaLongitude, deltaAltitude); + } + + private LocationConfidence readLocationConfidence(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Integer eventSpeed = null; + Integer eventPositionHeading = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "event_speed" -> eventSpeed = parser.getIntValue(); + case "event_position_heading" -> eventPositionHeading = parser.getIntValue(); + default -> parser.skipChildren(); + } + } + + if (eventSpeed == null && eventPositionHeading == null) { + return null; + } + return new LocationConfidence(eventSpeed, eventPositionHeading); + } + + private AlacarteContainer readAlacarteContainer(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Integer lanePosition = null; + Integer positioningSolution = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "lane_position" -> lanePosition = parser.getIntValue(); + case "positioning_solution" -> positioningSolution = parser.getIntValue(); + default -> parser.skipChildren(); + } + } + + return new AlacarteContainer(lanePosition, positioningSolution); + } + + private static void expect(JsonToken actual, JsonToken expected) { + if (actual != expected) { + throw new DenmValidationException("Expected token " + expected + " but got " + actual); + } + } + + private static T requireField(T value, String field) { + if (value == null) { + throw new DenmValidationException("Missing mandatory field: " + field); + } + return value; + } +} + diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/codec/DenmWriter113.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/codec/DenmWriter113.java new file mode 100644 index 000000000..ff14e8e50 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/codec/DenmWriter113.java @@ -0,0 +1,273 @@ +/* + 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.denm.v113.codec; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; +import com.orange.iot3mobility.messages.denm.v113.model.DenmEnvelope113; +import com.orange.iot3mobility.messages.denm.v113.model.DenmMessage113; +import com.orange.iot3mobility.messages.denm.v113.model.path.PathElement; +import com.orange.iot3mobility.messages.denm.v113.model.alacartecontainer.AlacarteContainer; +import com.orange.iot3mobility.messages.denm.v113.model.locationcontainer.DeltaReferencePosition; +import com.orange.iot3mobility.messages.denm.v113.model.locationcontainer.LocationConfidence; +import com.orange.iot3mobility.messages.denm.v113.model.locationcontainer.LocationContainer; +import com.orange.iot3mobility.messages.denm.v113.model.locationcontainer.PathHistory; +import com.orange.iot3mobility.messages.denm.v113.model.locationcontainer.PathPoint; +import com.orange.iot3mobility.messages.denm.v113.model.managementcontainer.ActionId; +import com.orange.iot3mobility.messages.denm.v113.model.managementcontainer.ManagementContainer; +import com.orange.iot3mobility.messages.denm.v113.model.managementcontainer.PositionConfidence; +import com.orange.iot3mobility.messages.denm.v113.model.managementcontainer.PositionConfidenceEllipse; +import com.orange.iot3mobility.messages.denm.v113.model.managementcontainer.ReferencePosition; +import com.orange.iot3mobility.messages.denm.v113.model.situationcontainer.EventType; +import com.orange.iot3mobility.messages.denm.v113.model.situationcontainer.LinkedCause; +import com.orange.iot3mobility.messages.denm.v113.model.situationcontainer.SituationContainer; +import com.orange.iot3mobility.messages.denm.v113.validation.DenmValidator113; + +import java.io.IOException; +import java.io.OutputStream; + +public final class DenmWriter113 { + + private final JsonFactory jsonFactory; + + public DenmWriter113(JsonFactory jsonFactory) { + this.jsonFactory = jsonFactory; + } + + public void write(DenmEnvelope113 envelope, OutputStream out) throws IOException { + DenmValidator113.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()); + if (envelope.path() != null) { + gen.writeFieldName("path"); + writePath(gen, envelope.path()); + } + gen.writeFieldName("message"); + writeMessage(gen, envelope.message()); + gen.writeEndObject(); + } + } + + private void writeMessage(JsonGenerator gen, DenmMessage113 message) throws IOException { + gen.writeStartObject(); + gen.writeNumberField("protocol_version", message.protocolVersion()); + gen.writeNumberField("station_id", message.stationId()); + gen.writeFieldName("management_container"); + writeManagementContainer(gen, message.managementContainer()); + if (message.situationContainer() != null) { + gen.writeFieldName("situation_container"); + writeSituationContainer(gen, message.situationContainer()); + } + if (message.locationContainer() != null) { + gen.writeFieldName("location_container"); + writeLocationContainer(gen, message.locationContainer()); + } + if (message.alacarteContainer() != null) { + gen.writeFieldName("alacarte_container"); + writeAlacarteContainer(gen, message.alacarteContainer()); + } + gen.writeEndObject(); + } + + private void writePath(JsonGenerator gen, java.util.List path) throws IOException { + gen.writeStartArray(); + for (PathElement element : path) { + gen.writeStartObject(); + gen.writeFieldName("position"); + gen.writeStartObject(); + gen.writeNumberField("latitude", element.position().latitude()); + gen.writeNumberField("longitude", element.position().longitude()); + gen.writeNumberField("altitude", element.position().altitude()); + gen.writeEndObject(); + gen.writeStringField("message_type", element.messageType()); + gen.writeEndObject(); + } + gen.writeEndArray(); + } + + private void writeManagementContainer(JsonGenerator gen, ManagementContainer management) throws IOException { + gen.writeStartObject(); + writeActionId(gen, management.actionId()); + gen.writeNumberField("detection_time", management.detectionTime()); + gen.writeNumberField("reference_time", management.referenceTime()); + if (management.termination() != null) { + gen.writeNumberField("termination", management.termination()); + } + gen.writeFieldName("event_position"); + writeReferencePosition(gen, management.eventPosition()); + if (management.relevanceDistance() != null) { + gen.writeNumberField("relevance_distance", management.relevanceDistance()); + } + if (management.relevanceTrafficDirection() != null) { + gen.writeNumberField("relevance_traffic_direction", management.relevanceTrafficDirection()); + } + if (management.validityDuration() != null) { + gen.writeNumberField("validity_duration", management.validityDuration()); + } + if (management.transmissionInterval() != null) { + gen.writeNumberField("transmission_interval", management.transmissionInterval()); + } + if (management.stationType() != null) { + gen.writeNumberField("station_type", management.stationType()); + } + if (management.confidence() != null) { + gen.writeFieldName("confidence"); + writePositionConfidence(gen, management.confidence()); + } + gen.writeEndObject(); + } + + private void writeActionId(JsonGenerator gen, ActionId actionId) throws IOException { + gen.writeFieldName("action_id"); + gen.writeStartObject(); + gen.writeNumberField("originating_station_id", actionId.originatingStationId()); + gen.writeNumberField("sequence_number", actionId.sequenceNumber()); + gen.writeEndObject(); + } + + private void writeReferencePosition(JsonGenerator gen, ReferencePosition position) throws IOException { + gen.writeStartObject(); + gen.writeNumberField("latitude", position.latitude()); + gen.writeNumberField("longitude", position.longitude()); + gen.writeNumberField("altitude", position.altitude()); + gen.writeEndObject(); + } + + private void writePositionConfidence(JsonGenerator gen, PositionConfidence confidence) throws IOException { + gen.writeStartObject(); + if (confidence.positionConfidenceEllipse() != null) { + gen.writeFieldName("position_confidence_ellipse"); + writePositionConfidenceEllipse(gen, confidence.positionConfidenceEllipse()); + } + if (confidence.altitude() != null) { + gen.writeNumberField("altitude", confidence.altitude()); + } + gen.writeEndObject(); + } + + private void writePositionConfidenceEllipse(JsonGenerator gen, PositionConfidenceEllipse ellipse) throws IOException { + gen.writeStartObject(); + gen.writeNumberField("semi_major_confidence", ellipse.semiMajorConfidence()); + gen.writeNumberField("semi_minor_confidence", ellipse.semiMinorConfidence()); + gen.writeNumberField("semi_major_orientation", ellipse.semiMajorOrientation()); + gen.writeEndObject(); + } + + private void writeSituationContainer(JsonGenerator gen, SituationContainer situation) throws IOException { + gen.writeStartObject(); + if (situation.informationQuality() != null) { + gen.writeNumberField("information_quality", situation.informationQuality()); + } + gen.writeFieldName("event_type"); + writeEventType(gen, situation.eventType()); + if (situation.linkedCause() != null) { + gen.writeFieldName("linked_cause"); + writeLinkedCause(gen, situation.linkedCause()); + } + gen.writeEndObject(); + } + + private void writeEventType(JsonGenerator gen, EventType eventType) throws IOException { + gen.writeStartObject(); + gen.writeNumberField("cause", eventType.cause()); + if (eventType.subcause() != null) { + gen.writeNumberField("subcause", eventType.subcause()); + } + gen.writeEndObject(); + } + + private void writeLinkedCause(JsonGenerator gen, LinkedCause linkedCause) throws IOException { + gen.writeStartObject(); + gen.writeNumberField("cause", linkedCause.cause()); + if (linkedCause.subcause() != null) { + gen.writeNumberField("subcause", linkedCause.subcause()); + } + gen.writeEndObject(); + } + + private void writeLocationContainer(JsonGenerator gen, LocationContainer location) throws IOException { + gen.writeStartObject(); + if (location.eventSpeed() != null) { + gen.writeNumberField("event_speed", location.eventSpeed()); + } + if (location.eventPositionHeading() != null) { + gen.writeNumberField("event_position_heading", location.eventPositionHeading()); + } + gen.writeFieldName("traces"); + writeTraces(gen, location.traces()); + if (location.roadType() != null) { + gen.writeNumberField("road_type", location.roadType()); + } + if (location.confidence() != null) { + gen.writeFieldName("confidence"); + writeLocationConfidence(gen, location.confidence()); + } + gen.writeEndObject(); + } + + private void writeTraces(JsonGenerator gen, java.util.List traces) throws IOException { + gen.writeStartArray(); + for (PathHistory history : traces) { + gen.writeStartObject(); + gen.writeFieldName("path_history"); + writePathHistory(gen, history.pathHistory()); + gen.writeEndObject(); + } + gen.writeEndArray(); + } + + private void writePathHistory(JsonGenerator gen, java.util.List points) throws IOException { + gen.writeStartArray(); + for (PathPoint point : points) { + 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(); + } + + private void writeDeltaReferencePosition(JsonGenerator gen, DeltaReferencePosition position) throws IOException { + gen.writeStartObject(); + gen.writeNumberField("delta_latitude", position.deltaLatitude()); + gen.writeNumberField("delta_longitude", position.deltaLongitude()); + gen.writeNumberField("delta_altitude", position.deltaAltitude()); + gen.writeEndObject(); + } + + private void writeLocationConfidence(JsonGenerator gen, LocationConfidence confidence) throws IOException { + gen.writeStartObject(); + if (confidence.eventSpeed() != null) { + gen.writeNumberField("event_speed", confidence.eventSpeed()); + } + if (confidence.eventPositionHeading() != null) { + gen.writeNumberField("event_position_heading", confidence.eventPositionHeading()); + } + gen.writeEndObject(); + } + + private void writeAlacarteContainer(JsonGenerator gen, AlacarteContainer alacarte) throws IOException { + gen.writeStartObject(); + if (alacarte.lanePosition() != null) { + gen.writeNumberField("lane_position", alacarte.lanePosition()); + } + if (alacarte.positioningSolution() != null) { + gen.writeNumberField("positioning_solution", alacarte.positioningSolution()); + } + gen.writeEndObject(); + } +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/DenmEnvelope113.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/DenmEnvelope113.java new file mode 100644 index 000000000..234446f92 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/DenmEnvelope113.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.denm.v113.model; + +import com.orange.iot3mobility.messages.denm.v113.model.path.PathElement; + +import java.util.List; + +/** + * DenmEnvelope113 - base class to build a DENM v1.1.3 with its header + * + * @param type message type (denm) + * @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 path Optional. List of {@link PathElement}) + * @param message {@link DenmMessage113} + */ +public record DenmEnvelope113( + String type, + String origin, + String version, + String sourceUuid, + long timestamp, + List path, + DenmMessage113 message) { + + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for DenmEnvelope113. + *

+ * Mandatory fields: + *

    + *
  • type - hardcoded (denm)
  • + *
  • 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 List path; + private DenmMessage113 message; + + private Builder() { + this.type = "denm"; + 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 path(List path) { + this.path = path; + return this; + } + + public Builder message(DenmMessage113 message) { + this.message = message; + return this; + } + + public DenmEnvelope113 build() { + return new DenmEnvelope113( + requireNonNull(type, "type"), + requireNonNull(origin, "origin"), + requireNonNull(version, "version"), + requireNonNull(sourceUuid, "source_uuid"), + requireNonNull(timestamp, "timestamp"), + path, + 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/denm/v113/model/DenmMessage113.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/DenmMessage113.java new file mode 100644 index 000000000..5fe181f8d --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/DenmMessage113.java @@ -0,0 +1,92 @@ +/* + 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.denm.v113.model; + +import com.orange.iot3mobility.messages.denm.v113.model.alacartecontainer.AlacarteContainer; +import com.orange.iot3mobility.messages.denm.v113.model.locationcontainer.LocationContainer; +import com.orange.iot3mobility.messages.denm.v113.model.managementcontainer.ManagementContainer; +import com.orange.iot3mobility.messages.denm.v113.model.situationcontainer.SituationContainer; + +/** + * DenmMessage113 - message DENM v1.1.3 (PDU). + * + * @param protocolVersion protocol version + * @param stationId station identifier + * @param managementContainer {@link ManagementContainer} + * @param situationContainer Optional. {@link SituationContainer} + * @param locationContainer Optional. {@link LocationContainer} + * @param alacarteContainer Optional. {@link AlacarteContainer} + */ +public record DenmMessage113( + int protocolVersion, + long stationId, + ManagementContainer managementContainer, + SituationContainer situationContainer, + LocationContainer locationContainer, + AlacarteContainer alacarteContainer) { + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private Integer protocolVersion; + private Long stationId; + private ManagementContainer managementContainer; + private SituationContainer situationContainer; + private LocationContainer locationContainer; + private AlacarteContainer alacarteContainer; + + public Builder protocolVersion(int protocolVersion) { + this.protocolVersion = protocolVersion; + return this; + } + + public Builder stationId(long stationId) { + this.stationId = stationId; + return this; + } + + public Builder managementContainer(ManagementContainer managementContainer) { + this.managementContainer = managementContainer; + return this; + } + + public Builder situationContainer(SituationContainer situationContainer) { + this.situationContainer = situationContainer; + return this; + } + + public Builder locationContainer(LocationContainer locationContainer) { + this.locationContainer = locationContainer; + return this; + } + + public Builder alacarteContainer(AlacarteContainer alacarteContainer) { + this.alacarteContainer = alacarteContainer; + return this; + } + + public DenmMessage113 build() { + return new DenmMessage113( + requireNonNull(protocolVersion, "protocol_version"), + requireNonNull(stationId, "station_id"), + requireNonNull(managementContainer, "management_container"), + situationContainer, + locationContainer, + alacarteContainer); + } + + 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/denm/v113/model/Origin.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/Origin.java new file mode 100644 index 000000000..332f7128e --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/Origin.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.denm.v113.model; + +/** + * Origin values for DENM v1.1.3 envelopes. + */ +public enum Origin { + self, + global_application, + mec_application, + on_board_application +} + diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/alacartecontainer/AlacarteContainer.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/alacartecontainer/AlacarteContainer.java new file mode 100644 index 000000000..bbc72bae6 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/alacartecontainer/AlacarteContainer.java @@ -0,0 +1,44 @@ +/* + 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.denm.v113.model.alacartecontainer; + +/** + * AlacarteContainer - DENM v1.1.3 alacarte container. + * + * @param lanePosition Optional. offTheRoad(-1), innerHardShoulder(0), innermostDrivingLane(1), + * secondLaneFromInside(2), outterHardShoulder(14) + * @param positioningSolution Optional. noPositioningSolution(0), sGNSS(1), dGNSS(2), sGNSSplusDR(3), + * dGNSSplusDR(4), dR(5) + */ +public record AlacarteContainer( + Integer lanePosition, + Integer positioningSolution) { + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private Integer lanePosition; + private Integer positioningSolution; + + public Builder lanePosition(Integer lanePosition) { + this.lanePosition = lanePosition; + return this; + } + + public Builder positioningSolution(Integer positioningSolution) { + this.positioningSolution = positioningSolution; + return this; + } + + public AlacarteContainer build() { + return new AlacarteContainer(lanePosition, positioningSolution); + } + } +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/locationcontainer/DeltaReferencePosition.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/locationcontainer/DeltaReferencePosition.java new file mode 100644 index 000000000..0f339a439 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/locationcontainer/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.denm.v113.model.locationcontainer; + +/** + * DeltaReferencePosition - delta reference position. + * + * @param deltaLatitude Unit: 0.1 microdegree. oneMicrodegreeNorth (10), oneMicrodegreeSouth (-10), + * unavailable(131072) + * @param deltaLongitude Unit: 0.1 microdegree. oneMicrodegreeEast (10), oneMicrodegreeWest (-10), + * unavailable(131072) + * @param deltaAltitude Unit: centimeter. 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/denm/v113/model/locationcontainer/LocationConfidence.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/locationcontainer/LocationConfidence.java new file mode 100644 index 000000000..d257b8b3e --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/locationcontainer/LocationConfidence.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.denm.v113.model.locationcontainer; + +/** + * LocationConfidence - confidence values. + * + * @param eventSpeed Optional. Unit: 0.01 m/s. equalOrWithinOneCentimeterPerSec(1), + * equalOrWithinOneMeterPerSec(100), outOfRange(126), unavailable(127) + * @param eventPositionHeading Optional. Unit: 0.1 degree. equalOrWithinZeroPointOneDegree (1), + * equalOrWithinOneDegree (10), outOfRange(126), unavailable(127) + */ +public record LocationConfidence( + Integer eventSpeed, + Integer eventPositionHeading) { +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/locationcontainer/LocationContainer.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/locationcontainer/LocationContainer.java new file mode 100644 index 000000000..f9f399aa1 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/locationcontainer/LocationContainer.java @@ -0,0 +1,81 @@ +/* + 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.denm.v113.model.locationcontainer; + +import java.util.List; + +/** + * LocationContainer - DENM v1.1.3 location container. + * + * @param eventSpeed Optional. Unit: 0.01 m/s. standstill(0), oneCentimeterPerSec(1), unavailable(16383) + * @param eventPositionHeading Optional. Unit: 0.1 degree. wgs84North(0), wgs84East(900), wgs84South(1800), + * wgs84West(2700), unavailable(3601) + * @param traces {@link PathHistory} (1 to 7 path histories) + * @param roadType Optional. Type of road segment. Range: 0-3 + * @param confidence Optional. {@link LocationConfidence} + */ +public record LocationContainer( + Integer eventSpeed, + Integer eventPositionHeading, + List traces, + Integer roadType, + LocationConfidence confidence) { + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private Integer eventSpeed; + private Integer eventPositionHeading; + private List traces; + private Integer roadType; + private LocationConfidence confidence; + + public Builder eventSpeed(Integer eventSpeed) { + this.eventSpeed = eventSpeed; + return this; + } + + public Builder eventPositionHeading(Integer eventPositionHeading) { + this.eventPositionHeading = eventPositionHeading; + return this; + } + + public Builder traces(List traces) { + this.traces = traces; + return this; + } + + public Builder roadType(Integer roadType) { + this.roadType = roadType; + return this; + } + + public Builder confidence(LocationConfidence confidence) { + this.confidence = confidence; + return this; + } + + public LocationContainer build() { + return new LocationContainer( + eventSpeed, + eventPositionHeading, + requireNonNull(traces, "traces"), + roadType, + 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/denm/v113/model/locationcontainer/PathHistory.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/locationcontainer/PathHistory.java new file mode 100644 index 000000000..d2a95cecc --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/locationcontainer/PathHistory.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.denm.v113.model.locationcontainer; + +import java.util.List; + +/** + * PathHistory - list of path points. + * + * @param pathHistory {@link PathPoint} list (max 40) + */ +public record PathHistory( + List pathHistory) { +} + diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/locationcontainer/PathPoint.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/locationcontainer/PathPoint.java new file mode 100644 index 000000000..754704c26 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/locationcontainer/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.denm.v113.model.locationcontainer; + +/** + * PathPoint - path point element. + * + * @param pathPosition {@link DeltaReferencePosition} + * @param pathDeltaTime Optional. Unit: 10 millisecond. Range: 1-65535 + */ +public record PathPoint( + DeltaReferencePosition pathPosition, + Integer pathDeltaTime) { +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/managementcontainer/ActionId.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/managementcontainer/ActionId.java new file mode 100644 index 000000000..f4613450f --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/managementcontainer/ActionId.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.denm.v113.model.managementcontainer; + +/** + * ActionId - identifier for a DENM action. + * + * @param originatingStationId originating station identifier + * @param sequenceNumber sequence number + */ +public record ActionId( + long originatingStationId, + int sequenceNumber) { +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/managementcontainer/ManagementContainer.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/managementcontainer/ManagementContainer.java new file mode 100644 index 000000000..7412a6b0a --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/managementcontainer/ManagementContainer.java @@ -0,0 +1,136 @@ +/* + 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.denm.v113.model.managementcontainer; + +/** + * ManagementContainer - DENM v1.1.3 management container. + * + * @param actionId {@link ActionId} + * @param detectionTime Unit: millisecond since ETSI epoch (2004/01/01, so 1072915200000) + * @param referenceTime Unit: millisecond since ETSI epoch (2004/01/01, so 1072915200000) + * @param termination Optional. isCancellation(0), isNegation(1) + * @param eventPosition {@link ReferencePosition} + * @param relevanceDistance Optional. lessThan50m(0), lessThan100m(1), lessThan200m(2), lessThan500m(3), + * lessThan1000m(4), lessThan5km(5), lessThan10km(6), over10km(7) + * @param relevanceTrafficDirection Optional. allTrafficDirections(0), upstreamTraffic(1), downstreamTraffic(2), + * oppositeTraffic(3) + * @param validityDuration Optional. Unit: second. timeOfDetection(0), oneSecondAfterDetection(1) + * @param transmissionInterval Optional. Unit: millisecond. oneMilliSecond(1), tenSeconds(10000) + * @param stationType Optional. 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)) + * @param confidence Optional. {@link PositionConfidence} + */ +public record ManagementContainer( + ActionId actionId, + long detectionTime, + long referenceTime, + Integer termination, + ReferencePosition eventPosition, + Integer relevanceDistance, + Integer relevanceTrafficDirection, + Integer validityDuration, + Integer transmissionInterval, + Integer stationType, + PositionConfidence confidence) { + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private ActionId actionId; + private Long detectionTime; + private Long referenceTime; + private Integer termination; + private ReferencePosition eventPosition; + private Integer relevanceDistance; + private Integer relevanceTrafficDirection; + private Integer validityDuration; + private Integer transmissionInterval; + private Integer stationType; + private PositionConfidence confidence; + + public Builder actionId(ActionId actionId) { + this.actionId = actionId; + return this; + } + + public Builder detectionTime(long detectionTime) { + this.detectionTime = detectionTime; + return this; + } + + public Builder referenceTime(long referenceTime) { + this.referenceTime = referenceTime; + return this; + } + + public Builder termination(Integer termination) { + this.termination = termination; + return this; + } + + public Builder eventPosition(ReferencePosition eventPosition) { + this.eventPosition = eventPosition; + return this; + } + + public Builder relevanceDistance(Integer relevanceDistance) { + this.relevanceDistance = relevanceDistance; + return this; + } + + public Builder relevanceTrafficDirection(Integer relevanceTrafficDirection) { + this.relevanceTrafficDirection = relevanceTrafficDirection; + return this; + } + + public Builder validityDuration(Integer validityDuration) { + this.validityDuration = validityDuration; + return this; + } + + public Builder transmissionInterval(Integer transmissionInterval) { + this.transmissionInterval = transmissionInterval; + return this; + } + + public Builder stationType(Integer stationType) { + this.stationType = stationType; + return this; + } + + public Builder confidence(PositionConfidence confidence) { + this.confidence = confidence; + return this; + } + + public ManagementContainer build() { + return new ManagementContainer( + requireNonNull(actionId, "action_id"), + requireNonNull(detectionTime, "detection_time"), + requireNonNull(referenceTime, "reference_time"), + termination, + requireNonNull(eventPosition, "event_position"), + relevanceDistance, + relevanceTrafficDirection, + validityDuration, + transmissionInterval, + stationType, + 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/denm/v113/model/managementcontainer/PositionConfidence.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/managementcontainer/PositionConfidence.java new file mode 100644 index 000000000..b9fa4a493 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/managementcontainer/PositionConfidence.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.denm.v113.model.managementcontainer; + +/** + * PositionConfidence - confidence object. + * + * @param positionConfidenceEllipse Optional. {@link PositionConfidenceEllipse} + * @param altitude Optional. Confidence level for altitude. Range: 0-15 (unavailable=15) + */ +public record PositionConfidence( + PositionConfidenceEllipse positionConfidenceEllipse, + Integer altitude) { +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/managementcontainer/PositionConfidenceEllipse.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/managementcontainer/PositionConfidenceEllipse.java new file mode 100644 index 000000000..e552217aa --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/managementcontainer/PositionConfidenceEllipse.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.denm.v113.model.managementcontainer; + +/** + * PositionConfidenceEllipse - confidence ellipse. + * + * @param semiMajorConfidence oneCentimeter(1), outOfRange(4094), unavailable(4095) + * @param semiMinorConfidence oneCentimeter(1), outOfRange(4094), unavailable(4095) + * @param semiMajorOrientation wgs84North(0), wgs84East(900), wgs84South(1800), wgs84West(2700), unavailable(3601) + */ +public record PositionConfidenceEllipse( + int semiMajorConfidence, + int semiMinorConfidence, + int semiMajorOrientation) { +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/managementcontainer/ReferencePosition.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/managementcontainer/ReferencePosition.java new file mode 100644 index 000000000..fa5a0f677 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/managementcontainer/ReferencePosition.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.denm.v113.model.managementcontainer; + +/** + * ReferencePosition - event position. + * + * @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/denm/v113/model/path/PathElement.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/path/PathElement.java new file mode 100644 index 000000000..411f8db69 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/path/PathElement.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.denm.v113.model.path; + +/** + * DenmPathElement113 - path element. + * + * @param position {@link PathPosition} + * @param messageType message type (denm, cam, cpm, po) + */ +public record PathElement( + PathPosition position, + String messageType) { +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/path/PathPosition.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/path/PathPosition.java new file mode 100644 index 000000000..708005c66 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/path/PathPosition.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.denm.v113.model.path; + +/** + * DenmPathPosition113 - path position. + * + * @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 PathPosition( + int latitude, + int longitude, + int altitude) { +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/situationcontainer/EventType.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/situationcontainer/EventType.java new file mode 100644 index 000000000..6ceb99be1 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/situationcontainer/EventType.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.denm.v113.model.situationcontainer; + +/** + * EventType - event type definition. + * + * @param cause cause code. Range: 0-255 + * @param subcause Optional. Subcause code. Range: 0-255 (unavailable=0) + */ +public record EventType( + int cause, + Integer subcause) { +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/situationcontainer/LinkedCause.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/situationcontainer/LinkedCause.java new file mode 100644 index 000000000..73f9ffdcd --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/situationcontainer/LinkedCause.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.denm.v113.model.situationcontainer; + +/** + * LinkedCause - linked cause definition. + * + * @param cause cause code. Range: 0-255 + * @param subcause Optional. Subcause code. Range: 0-255 (unavailable=0) + */ +public record LinkedCause( + int cause, + Integer subcause) { +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/situationcontainer/SituationContainer.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/situationcontainer/SituationContainer.java new file mode 100644 index 000000000..4cc839d48 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/model/situationcontainer/SituationContainer.java @@ -0,0 +1,60 @@ +/* + 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.denm.v113.model.situationcontainer; + +/** + * SituationContainer - DENM v1.1.3 situation container. + * + * @param informationQuality Optional. Quality of information. Range: 0-7 (unavailable=0) + * @param eventType event type + * @param linkedCause Optional. Linked cause + */ +public record SituationContainer( + Integer informationQuality, + EventType eventType, + LinkedCause linkedCause) { + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private Integer informationQuality; + private EventType eventType; + private LinkedCause linkedCause; + + public Builder informationQuality(Integer informationQuality) { + this.informationQuality = informationQuality; + return this; + } + + public Builder eventType(EventType eventType) { + this.eventType = eventType; + return this; + } + + public Builder linkedCause(LinkedCause linkedCause) { + this.linkedCause = linkedCause; + return this; + } + + public SituationContainer build() { + return new SituationContainer( + informationQuality, + requireNonNull(eventType, "event_type"), + linkedCause); + } + + 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/denm/v113/validation/DenmValidationException.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/validation/DenmValidationException.java new file mode 100644 index 000000000..bd8a9da77 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/validation/DenmValidationException.java @@ -0,0 +1,15 @@ +/* + 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.denm.v113.validation; + +public final class DenmValidationException extends RuntimeException { + public DenmValidationException(String message) { + super(message); + } +} + diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/validation/DenmValidator113.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/validation/DenmValidator113.java new file mode 100644 index 000000000..8926be081 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v113/validation/DenmValidator113.java @@ -0,0 +1,242 @@ +/* + 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.denm.v113.validation; + +import com.orange.iot3mobility.messages.denm.v113.model.DenmEnvelope113; +import com.orange.iot3mobility.messages.denm.v113.model.DenmMessage113; +import com.orange.iot3mobility.messages.denm.v113.model.path.PathElement; +import com.orange.iot3mobility.messages.denm.v113.model.path.PathPosition; +import com.orange.iot3mobility.messages.denm.v113.model.alacartecontainer.AlacarteContainer; +import com.orange.iot3mobility.messages.denm.v113.model.locationcontainer.DeltaReferencePosition; +import com.orange.iot3mobility.messages.denm.v113.model.locationcontainer.LocationConfidence; +import com.orange.iot3mobility.messages.denm.v113.model.locationcontainer.LocationContainer; +import com.orange.iot3mobility.messages.denm.v113.model.locationcontainer.PathHistory; +import com.orange.iot3mobility.messages.denm.v113.model.locationcontainer.PathPoint; +import com.orange.iot3mobility.messages.denm.v113.model.managementcontainer.ActionId; +import com.orange.iot3mobility.messages.denm.v113.model.managementcontainer.ManagementContainer; +import com.orange.iot3mobility.messages.denm.v113.model.managementcontainer.PositionConfidence; +import com.orange.iot3mobility.messages.denm.v113.model.managementcontainer.PositionConfidenceEllipse; +import com.orange.iot3mobility.messages.denm.v113.model.managementcontainer.ReferencePosition; +import com.orange.iot3mobility.messages.denm.v113.model.situationcontainer.EventType; +import com.orange.iot3mobility.messages.denm.v113.model.situationcontainer.LinkedCause; +import com.orange.iot3mobility.messages.denm.v113.model.situationcontainer.SituationContainer; + +import java.util.List; +import java.util.Objects; + +public final class DenmValidator113 { + + private DenmValidator113() {} + + public static void validateEnvelope(DenmEnvelope113 env) { + requireNonNull("envelope", env); + requireEquals("type", env.type(), "denm"); + 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); + if (env.path() != null) { + validatePath(env.path()); + } + validateMessage(env.message()); + } + + public static void validateMessage(DenmMessage113 msg) { + requireNonNull("message", msg); + checkRange("protocol_version", msg.protocolVersion(), 0, 255); + checkRange("station_id", msg.stationId(), 0, 4294967295L); + validateManagementContainer(msg.managementContainer()); + if (msg.situationContainer() != null) { + validateSituationContainer(msg.situationContainer()); + } + if (msg.locationContainer() != null) { + validateLocationContainer(msg.locationContainer()); + } + if (msg.alacarteContainer() != null) { + validateAlacarteContainer(msg.alacarteContainer()); + } + } + + private static void validateManagementContainer(ManagementContainer container) { + requireNonNull("management_container", container); + validateActionId(container.actionId()); + checkRange("detection_time", container.detectionTime(), 0L, 4398046511103L); + checkRange("reference_time", container.referenceTime(), 0L, 4398046511103L); + checkRange("termination", container.termination(), 0, 1); + validateReferencePosition(container.eventPosition()); + checkRange("relevance_distance", container.relevanceDistance(), 0, 7); + checkRange("relevance_traffic_direction", container.relevanceTrafficDirection(), 0, 3); + checkRange("validity_duration", container.validityDuration(), 0, 86400); + checkRange("transmission_interval", container.transmissionInterval(), 1, 10000); + checkRange("station_type", container.stationType(), 0, 255); + if (container.confidence() != null) { + validatePositionConfidence(container.confidence()); + } + } + + private static void validateActionId(ActionId actionId) { + requireNonNull("action_id", actionId); + checkRange("originating_station_id", actionId.originatingStationId(), 0, 4294967295L); + checkRange("sequence_number", actionId.sequenceNumber(), 0, 65535); + } + + private static void validateReferencePosition(ReferencePosition position) { + requireNonNull("event_position", position); + checkRange("event_position.latitude", position.latitude(), -900000000, 900000001); + checkRange("event_position.longitude", position.longitude(), -1800000000, 1800000001); + checkRange("event_position.altitude", position.altitude(), -100000, 800001); + } + + private static void validatePositionConfidence(PositionConfidence confidence) { + if (confidence.positionConfidenceEllipse() != null) { + validatePositionConfidenceEllipse(confidence.positionConfidenceEllipse()); + } + checkRange("confidence.altitude", confidence.altitude(), 0, 15); + } + + private static void validatePositionConfidenceEllipse(PositionConfidenceEllipse ellipse) { + checkRange("confidence.position_confidence_ellipse.semi_major_confidence", ellipse.semiMajorConfidence(), 0, 4095); + checkRange("confidence.position_confidence_ellipse.semi_minor_confidence", ellipse.semiMinorConfidence(), 0, 4095); + checkRange("confidence.position_confidence_ellipse.semi_major_orientation", ellipse.semiMajorOrientation(), 0, 3601); + } + + private static void validateSituationContainer(SituationContainer situation) { + requireNonNull("situation_container", situation); + checkRange("information_quality", situation.informationQuality(), 0, 7); + validateEventType(situation.eventType()); + if (situation.linkedCause() != null) { + validateLinkedCause(situation.linkedCause()); + } + } + + private static void validateEventType(EventType eventType) { + requireNonNull("event_type", eventType); + checkRange("event_type.cause", eventType.cause(), 0, 255); + checkRange("event_type.subcause", eventType.subcause(), 0, 255); + } + + private static void validateLinkedCause(LinkedCause linkedCause) { + checkRange("linked_cause.cause", linkedCause.cause(), 0, 255); + checkRange("linked_cause.subcause", linkedCause.subcause(), 0, 255); + } + + private static void validateLocationContainer(LocationContainer location) { + requireNonNull("location_container", location); + checkRange("event_speed", location.eventSpeed(), 0, 16383); + checkRange("event_position_heading", location.eventPositionHeading(), 0, 3601); + validateTraces(location.traces()); + checkRange("road_type", location.roadType(), 0, 3); + if (location.confidence() != null) { + validateLocationConfidence(location.confidence()); + } + } + + private static void validateTraces(List traces) { + requireNonNull("traces", traces); + checkSize("traces", traces.size(), 1, 7); + for (int i = 0; i < traces.size(); i++) { + validatePathHistory("traces[" + i + "]", traces.get(i)); + } + } + + private static void validatePathHistory(String prefix, PathHistory history) { + requireNonNull(prefix + ".path_history", history); + List points = requireNonNull(prefix + ".path_history", history.pathHistory()); + if (points.size() > 40) { + throw new DenmValidationException(prefix + ".path_history size exceeds 40"); + } + for (int i = 0; i < points.size(); i++) { + validatePathPoint(prefix + ".path_history[" + i + "]", points.get(i)); + } + } + + private static void validatePathPoint(String prefix, PathPoint point) { + requireNonNull(prefix, point); + DeltaReferencePosition pos = requireNonNull(prefix + ".path_position", point.pathPosition()); + checkRange(prefix + ".delta_latitude", pos.deltaLatitude(), -131071, 131072); + checkRange(prefix + ".delta_longitude", pos.deltaLongitude(), -131071, 131072); + checkRange(prefix + ".delta_altitude", pos.deltaAltitude(), -12700, 12800); + checkRange(prefix + ".path_delta_time", point.pathDeltaTime(), 1, 65535); + } + + private static void validateLocationConfidence(LocationConfidence confidence) { + checkRange("confidence.event_speed", confidence.eventSpeed(), 1, 127); + checkRange("confidence.event_position_heading", confidence.eventPositionHeading(), 1, 127); + } + + private static void validateAlacarteContainer(AlacarteContainer alacarte) { + checkRange("alacarte.lane_position", alacarte.lanePosition(), -1, 14); + if (alacarte.positioningSolution() != null && alacarte.positioningSolution() < 0) { + throw new DenmValidationException("positioning_solution must be >= 0"); + } + } + + private static void validatePath(List path) { + checkSize("path", path.size(), 1, Integer.MAX_VALUE); + for (int i = 0; i < path.size(); i++) { + PathElement element = requireNonNull("path[" + i + "]", path.get(i)); + validatePathPosition("path[" + i + "].position", element.position()); + requireEnum("path[" + i + "].message_type", element.messageType(), + List.of("denm", "cam", "cpm", "po")); + } + } + + private static void validatePathPosition(String prefix, PathPosition position) { + requireNonNull(prefix, position); + checkRange(prefix + ".latitude", position.latitude(), -900000000, 900000001); + checkRange(prefix + ".longitude", position.longitude(), -1800000000, 1800000001); + checkRange(prefix + ".altitude", position.altitude(), -100000, 800001); + } + + private static T requireNonNull(String field, T value) { + if (value == null) { + throw new DenmValidationException("Missing mandatory field: " + field); + } + return value; + } + + private static void requireNotBlank(String field, String value) { + if (value == null || value.isBlank()) { + throw new DenmValidationException("Missing mandatory field: " + field); + } + } + + private static void requireEquals(String field, String actual, String expected) { + if (!Objects.equals(actual, expected)) { + throw new DenmValidationException(field + " must equal '" + expected + "'"); + } + } + + private static void requireEnum(String field, String actual, List allowed) { + if (!allowed.contains(actual)) { + throw new DenmValidationException(field + " must be one of " + allowed); + } + } + + private static void checkRange(String field, Long value, long min, long max) { + if (value != null && (value < min || value > max)) { + throw new DenmValidationException( + field + " out of range [" + min + ", " + max + "] (actual=" + value + ")"); + } + } + + private static void checkRange(String field, Integer value, long min, long max) { + if (value != null && (value < min || value > max)) { + throw new DenmValidationException( + field + " out of range [" + min + ", " + max + "] (actual=" + value + ")"); + } + } + + private static void checkSize(String field, int size, int min, int max) { + if (size < min || size > max) { + throw new DenmValidationException( + field + " size out of range [" + min + ", " + max + "] (actual=" + size + ")"); + } + } +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/codec/DenmReader220.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/codec/DenmReader220.java new file mode 100644 index 000000000..16d48748d --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/codec/DenmReader220.java @@ -0,0 +1,573 @@ +/* + 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.denm.v220.codec; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.orange.iot3mobility.messages.denm.v220.model.DenmEnvelope220; +import com.orange.iot3mobility.messages.denm.v220.model.DenmMessage220; +import com.orange.iot3mobility.messages.denm.v220.model.path.PathElement; +import com.orange.iot3mobility.messages.denm.v220.model.path.PathPosition; +import com.orange.iot3mobility.messages.denm.v220.model.alacartecontainer.AlacarteContainer; +import com.orange.iot3mobility.messages.denm.v220.model.defs.Altitude; +import com.orange.iot3mobility.messages.denm.v220.model.defs.DeltaReferencePosition; +import com.orange.iot3mobility.messages.denm.v220.model.defs.PositionConfidenceEllipse; +import com.orange.iot3mobility.messages.denm.v220.model.locationcontainer.DetectionZone; +import com.orange.iot3mobility.messages.denm.v220.model.locationcontainer.EventPositionHeading; +import com.orange.iot3mobility.messages.denm.v220.model.locationcontainer.EventSpeed; +import com.orange.iot3mobility.messages.denm.v220.model.locationcontainer.LocationContainer; +import com.orange.iot3mobility.messages.denm.v220.model.locationcontainer.PathPoint; +import com.orange.iot3mobility.messages.denm.v220.model.managementcontainer.ActionId; +import com.orange.iot3mobility.messages.denm.v220.model.managementcontainer.ManagementContainer; +import com.orange.iot3mobility.messages.denm.v220.model.managementcontainer.ReferencePosition; +import com.orange.iot3mobility.messages.denm.v220.model.situationcontainer.CauseCode; +import com.orange.iot3mobility.messages.denm.v220.model.situationcontainer.EventZone; +import com.orange.iot3mobility.messages.denm.v220.model.situationcontainer.SituationContainer; +import com.orange.iot3mobility.messages.denm.v220.validation.DenmValidationException; +import com.orange.iot3mobility.messages.denm.v220.validation.DenmValidator220; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +public final class DenmReader220 { + + private final JsonFactory jsonFactory; + + public DenmReader220(JsonFactory jsonFactory) { + this.jsonFactory = jsonFactory; + } + + public DenmEnvelope220 read(InputStream in) throws IOException { + try (JsonParser parser = jsonFactory.createParser(in)) { + expect(parser.nextToken(), JsonToken.START_OBJECT); + + String messageType = null; + String version = null; + String sourceUuid = null; + Long timestamp = null; + List path = null; + DenmMessage220 message = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "message_type" -> messageType = parser.getValueAsString(); + case "version" -> version = parser.getValueAsString(); + case "source_uuid" -> sourceUuid = parser.getValueAsString(); + case "timestamp" -> timestamp = parser.getLongValue(); + case "path" -> path = readPath(parser); + case "message" -> message = readMessage(parser); + default -> parser.skipChildren(); + } + } + + DenmEnvelope220 envelope = new DenmEnvelope220( + requireField(messageType, "message_type"), + requireField(sourceUuid, "source_uuid"), + requireField(timestamp, "timestamp"), + requireField(version, "version"), + path, + requireField(message, "message")); + + DenmValidator220.validateEnvelope(envelope); + return envelope; + } + } + + private List readPath(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_ARRAY); + List elements = new ArrayList<>(); + while (parser.nextToken() != JsonToken.END_ARRAY) { + elements.add(readPathElement(parser)); + } + return elements; + } + + private PathElement readPathElement(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + PathPosition position = null; + String messageType = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "position" -> position = readPathPosition(parser); + case "message_type" -> messageType = parser.getValueAsString(); + default -> parser.skipChildren(); + } + } + + return new PathElement( + requireField(position, "path.position"), + requireField(messageType, "path.message_type")); + } + + private PathPosition readPathPosition(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Integer latitude = null; + Integer longitude = null; + Integer altitude = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "latitude" -> latitude = parser.getIntValue(); + case "longitude" -> longitude = parser.getIntValue(); + case "altitude" -> altitude = parser.getIntValue(); + default -> parser.skipChildren(); + } + } + + return new PathPosition( + requireField(latitude, "path.position.latitude"), + requireField(longitude, "path.position.longitude"), + requireField(altitude, "path.position.altitude")); + } + + private DenmMessage220 readMessage(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + + Integer protocolVersion = null; + Long stationId = null; + ManagementContainer management = null; + SituationContainer situation = null; + LocationContainer location = null; + AlacarteContainer alacarte = 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 "management_container" -> management = readManagement(parser); + case "situation_container" -> situation = readSituation(parser); + case "location_container" -> location = readLocation(parser); + case "alacarte_container" -> alacarte = readAlacarte(parser); + default -> parser.skipChildren(); + } + } + + return new DenmMessage220( + requireField(protocolVersion, "protocol_version"), + requireField(stationId, "station_id"), + requireField(management, "management_container"), + situation, + location, + alacarte); + } + + private ManagementContainer readManagement(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + + ActionId actionId = null; + Long detectionTime = null; + Long referenceTime = null; + Integer termination = null; + ReferencePosition eventPosition = null; + Integer awarenessDistance = null; + Integer trafficDirection = null; + Integer validityDuration = null; + Integer transmissionInterval = null; + Integer stationType = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "action_id" -> actionId = readActionId(parser); + case "detection_time" -> detectionTime = parser.getLongValue(); + case "reference_time" -> referenceTime = parser.getLongValue(); + case "termination" -> termination = parser.getIntValue(); + case "event_position" -> eventPosition = readReferencePosition(parser); + case "awareness_distance" -> awarenessDistance = parser.getIntValue(); + case "traffic_direction" -> trafficDirection = parser.getIntValue(); + case "validity_duration" -> validityDuration = parser.getIntValue(); + case "transmission_interval" -> transmissionInterval = parser.getIntValue(); + case "station_type" -> stationType = parser.getIntValue(); + default -> parser.skipChildren(); + } + } + + return new ManagementContainer( + requireField(actionId, "action_id"), + requireField(detectionTime, "detection_time"), + requireField(referenceTime, "reference_time"), + termination, + requireField(eventPosition, "event_position"), + awarenessDistance, + trafficDirection, + validityDuration, + transmissionInterval, + requireField(stationType, "station_type")); + } + + private ActionId readActionId(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Long originatingStationId = null; + Integer sequenceNumber = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "originating_station_id" -> originatingStationId = parser.getLongValue(); + case "sequence_number" -> sequenceNumber = parser.getIntValue(); + default -> parser.skipChildren(); + } + } + + return new ActionId( + requireField(originatingStationId, "originating_station_id"), + requireField(sequenceNumber, "sequence_number")); + } + + private ReferencePosition readReferencePosition(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Integer latitude = null; + Integer longitude = null; + PositionConfidenceEllipse ellipse = null; + Altitude altitude = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "latitude" -> latitude = parser.getIntValue(); + case "longitude" -> longitude = parser.getIntValue(); + case "position_confidence_ellipse" -> ellipse = readPositionConfidenceEllipse(parser); + case "altitude" -> altitude = readAltitude(parser); + default -> parser.skipChildren(); + } + } + + return new ReferencePosition( + requireField(latitude, "latitude"), + requireField(longitude, "longitude"), + requireField(ellipse, "position_confidence_ellipse"), + requireField(altitude, "altitude")); + } + + private PositionConfidenceEllipse readPositionConfidenceEllipse(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Integer semiMajor = null; + Integer semiMinor = null; + Integer semiMajorOrientation = 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" -> semiMajorOrientation = parser.getIntValue(); + default -> parser.skipChildren(); + } + } + + return new PositionConfidenceEllipse( + requireField(semiMajor, "semi_major"), + requireField(semiMinor, "semi_minor"), + requireField(semiMajorOrientation, "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")); + } + + private SituationContainer readSituation(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Integer informationQuality = null; + CauseCode eventType = null; + CauseCode linkedCause = null; + List eventZone = null; + List linkedDenms = null; + Integer eventEnd = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "information_quality" -> informationQuality = parser.getIntValue(); + case "event_type" -> eventType = readCauseCode(parser); + case "linked_cause" -> linkedCause = readCauseCode(parser); + case "event_zone" -> eventZone = readEventZone(parser); + case "linked_denms" -> linkedDenms = readLinkedDenms(parser); + case "event_end" -> eventEnd = parser.getIntValue(); + default -> parser.skipChildren(); + } + } + + return new SituationContainer( + requireField(informationQuality, "information_quality"), + requireField(eventType, "event_type"), + linkedCause, + eventZone, + linkedDenms, + eventEnd); + } + + 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(); + switch (field) { + case "cause" -> cause = parser.getIntValue(); + case "subcause" -> subcause = parser.getIntValue(); + default -> parser.skipChildren(); + } + } + + return new CauseCode( + requireField(cause, "cause"), + subcause); + } + + private List readEventZone(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_ARRAY); + List zones = new ArrayList<>(); + while (parser.nextToken() != JsonToken.END_ARRAY) { + zones.add(readEventZoneItem(parser)); + } + return zones; + } + + private EventZone readEventZoneItem(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + DeltaReferencePosition eventPosition = null; + Integer eventDeltaTime = null; + Integer informationQuality = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "event_position" -> eventPosition = readDeltaReferencePosition(parser); + case "event_delta_time" -> eventDeltaTime = parser.getIntValue(); + case "information_quality" -> informationQuality = parser.getIntValue(); + default -> parser.skipChildren(); + } + } + + return new EventZone( + requireField(eventPosition, "event_position"), + eventDeltaTime, + requireField(informationQuality, "information_quality")); + } + + private List readLinkedDenms(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_ARRAY); + List linked = new ArrayList<>(); + while (parser.nextToken() != JsonToken.END_ARRAY) { + linked.add(readActionId(parser)); + } + return linked; + } + + private LocationContainer readLocation(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + EventSpeed eventSpeed = null; + EventPositionHeading eventPositionHeading = null; + List detectionZones = null; + Integer roadType = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "event_speed" -> eventSpeed = readEventSpeed(parser); + case "event_position_heading" -> eventPositionHeading = readEventPositionHeading(parser); + case "detection_zones_to_event_position" -> detectionZones = readDetectionZones(parser); + case "road_type" -> roadType = parser.getIntValue(); + default -> parser.skipChildren(); + } + } + + return new LocationContainer( + eventSpeed, + eventPositionHeading, + detectionZones, + roadType); + } + + private EventSpeed readEventSpeed(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 EventSpeed( + requireField(value, "event_speed.value"), + requireField(confidence, "event_speed.confidence")); + } + + private EventPositionHeading readEventPositionHeading(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 EventPositionHeading( + requireField(value, "event_position_heading.value"), + requireField(confidence, "event_position_heading.confidence")); + } + + private List readDetectionZones(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_ARRAY); + List zones = new ArrayList<>(); + while (parser.nextToken() != JsonToken.END_ARRAY) { + zones.add(readDetectionZone(parser)); + } + return zones; + } + + private DetectionZone readDetectionZone(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + List path = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + if ("path".equals(field)) { + path = readPathPoints(parser); + } else { + parser.skipChildren(); + } + } + + return new DetectionZone(requireField(path, "path")); + } + + private List readPathPoints(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 pathPosition = null; + Integer pathDeltaTime = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "path_position" -> pathPosition = readDeltaReferencePosition(parser); + case "path_delta_time" -> pathDeltaTime = parser.getIntValue(); + default -> parser.skipChildren(); + } + } + + return new PathPoint( + requireField(pathPosition, "path_position"), + pathDeltaTime); + } + + private DeltaReferencePosition readDeltaReferencePosition(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Integer deltaLatitude = null; + Integer deltaLongitude = null; + Integer deltaAltitude = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "delta_latitude" -> deltaLatitude = parser.getIntValue(); + case "delta_longitude" -> deltaLongitude = parser.getIntValue(); + case "delta_altitude" -> deltaAltitude = parser.getIntValue(); + default -> parser.skipChildren(); + } + } + + return new DeltaReferencePosition( + requireField(deltaLatitude, "delta_latitude"), + requireField(deltaLongitude, "delta_longitude"), + requireField(deltaAltitude, "delta_altitude")); + } + + private AlacarteContainer readAlacarte(JsonParser parser) throws IOException { + expect(parser.getCurrentToken(), JsonToken.START_OBJECT); + Integer lanePosition = null; + Integer positioningSolution = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String field = parser.currentName(); + parser.nextToken(); + switch (field) { + case "lane_position" -> lanePosition = parser.getIntValue(); + case "positioning_solution" -> positioningSolution = parser.getIntValue(); + default -> parser.skipChildren(); + } + } + + return new AlacarteContainer(lanePosition, positioningSolution); + } + + private static void expect(JsonToken actual, JsonToken expected) { + if (actual != expected) { + throw new DenmValidationException("Expected token " + expected + " but got " + actual); + } + } + + private static T requireField(T value, String field) { + if (value == null) { + throw new DenmValidationException("Missing mandatory field: " + field); + } + return value; + } +} + diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/codec/DenmWriter220.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/codec/DenmWriter220.java new file mode 100644 index 000000000..d58b270c8 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/codec/DenmWriter220.java @@ -0,0 +1,296 @@ +/* + 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.denm.v220.codec; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; +import com.orange.iot3mobility.messages.denm.v220.model.DenmEnvelope220; +import com.orange.iot3mobility.messages.denm.v220.model.DenmMessage220; +import com.orange.iot3mobility.messages.denm.v220.model.path.PathElement; +import com.orange.iot3mobility.messages.denm.v220.model.alacartecontainer.AlacarteContainer; +import com.orange.iot3mobility.messages.denm.v220.model.defs.Altitude; +import com.orange.iot3mobility.messages.denm.v220.model.defs.DeltaReferencePosition; +import com.orange.iot3mobility.messages.denm.v220.model.defs.PositionConfidenceEllipse; +import com.orange.iot3mobility.messages.denm.v220.model.locationcontainer.DetectionZone; +import com.orange.iot3mobility.messages.denm.v220.model.locationcontainer.EventPositionHeading; +import com.orange.iot3mobility.messages.denm.v220.model.locationcontainer.EventSpeed; +import com.orange.iot3mobility.messages.denm.v220.model.locationcontainer.LocationContainer; +import com.orange.iot3mobility.messages.denm.v220.model.locationcontainer.PathPoint; +import com.orange.iot3mobility.messages.denm.v220.model.managementcontainer.ActionId; +import com.orange.iot3mobility.messages.denm.v220.model.managementcontainer.ManagementContainer; +import com.orange.iot3mobility.messages.denm.v220.model.managementcontainer.ReferencePosition; +import com.orange.iot3mobility.messages.denm.v220.model.situationcontainer.CauseCode; +import com.orange.iot3mobility.messages.denm.v220.model.situationcontainer.EventZone; +import com.orange.iot3mobility.messages.denm.v220.model.situationcontainer.SituationContainer; +import com.orange.iot3mobility.messages.denm.v220.validation.DenmValidator220; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.List; + +public final class DenmWriter220 { + + private final JsonFactory jsonFactory; + + public DenmWriter220(JsonFactory jsonFactory) { + this.jsonFactory = jsonFactory; + } + + public void write(DenmEnvelope220 envelope, OutputStream out) throws IOException { + DenmValidator220.validateEnvelope(envelope); + + try (JsonGenerator gen = jsonFactory.createGenerator(out)) { + gen.writeStartObject(); + gen.writeStringField("message_type", envelope.messageType()); + gen.writeStringField("source_uuid", envelope.sourceUuid()); + gen.writeNumberField("timestamp", envelope.timestamp()); + gen.writeStringField("version", envelope.version()); + if (envelope.path() != null) { + gen.writeFieldName("path"); + writePath(gen, envelope.path()); + } + gen.writeFieldName("message"); + writeMessage(gen, envelope.message()); + gen.writeEndObject(); + } + } + + private void writeMessage(JsonGenerator gen, DenmMessage220 message) throws IOException { + gen.writeStartObject(); + gen.writeNumberField("protocol_version", message.protocolVersion()); + gen.writeNumberField("station_id", message.stationId()); + gen.writeFieldName("management_container"); + writeManagement(gen, message.managementContainer()); + if (message.situationContainer() != null) { + gen.writeFieldName("situation_container"); + writeSituation(gen, message.situationContainer()); + } + if (message.locationContainer() != null) { + gen.writeFieldName("location_container"); + writeLocation(gen, message.locationContainer()); + } + if (message.alacarteContainer() != null) { + gen.writeFieldName("alacarte_container"); + writeAlacarte(gen, message.alacarteContainer()); + } + gen.writeEndObject(); + } + + private void writePath(JsonGenerator gen, List path) throws IOException { + gen.writeStartArray(); + for (PathElement element : path) { + gen.writeStartObject(); + gen.writeFieldName("position"); + gen.writeStartObject(); + gen.writeNumberField("latitude", element.position().latitude()); + gen.writeNumberField("longitude", element.position().longitude()); + gen.writeNumberField("altitude", element.position().altitude()); + gen.writeEndObject(); + gen.writeStringField("message_type", element.messageType()); + gen.writeEndObject(); + } + gen.writeEndArray(); + } + + private void writeManagement(JsonGenerator gen, ManagementContainer management) throws IOException { + gen.writeStartObject(); + writeActionId(gen, management.actionId()); + gen.writeNumberField("detection_time", management.detectionTime()); + gen.writeNumberField("reference_time", management.referenceTime()); + if (management.termination() != null) { + gen.writeNumberField("termination", management.termination()); + } + gen.writeFieldName("event_position"); + writeReferencePosition(gen, management.eventPosition()); + if (management.awarenessDistance() != null) { + gen.writeNumberField("awareness_distance", management.awarenessDistance()); + } + if (management.trafficDirection() != null) { + gen.writeNumberField("traffic_direction", management.trafficDirection()); + } + if (management.validityDuration() != null) { + gen.writeNumberField("validity_duration", management.validityDuration()); + } + if (management.transmissionInterval() != null) { + gen.writeNumberField("transmission_interval", management.transmissionInterval()); + } + gen.writeNumberField("station_type", management.stationType()); + gen.writeEndObject(); + } + + private void writeActionId(JsonGenerator gen, ActionId actionId) throws IOException { + gen.writeFieldName("action_id"); + gen.writeStartObject(); + gen.writeNumberField("originating_station_id", actionId.originatingStationId()); + gen.writeNumberField("sequence_number", actionId.sequenceNumber()); + gen.writeEndObject(); + } + + private void writeReferencePosition(JsonGenerator gen, ReferencePosition position) throws IOException { + gen.writeStartObject(); + gen.writeNumberField("latitude", position.latitude()); + gen.writeNumberField("longitude", position.longitude()); + gen.writeFieldName("position_confidence_ellipse"); + writePositionConfidenceEllipse(gen, position.positionConfidenceEllipse()); + gen.writeFieldName("altitude"); + writeAltitude(gen, position.altitude()); + gen.writeEndObject(); + } + + private void writePositionConfidenceEllipse(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(); + } + + private void writeSituation(JsonGenerator gen, SituationContainer situation) throws IOException { + gen.writeStartObject(); + gen.writeNumberField("information_quality", situation.informationQuality()); + gen.writeFieldName("event_type"); + writeCauseCode(gen, situation.eventType()); + if (situation.linkedCause() != null) { + gen.writeFieldName("linked_cause"); + writeCauseCode(gen, situation.linkedCause()); + } + if (situation.eventZone() != null) { + gen.writeFieldName("event_zone"); + writeEventZone(gen, situation.eventZone()); + } + if (situation.linkedDenms() != null) { + gen.writeFieldName("linked_denms"); + writeLinkedDenms(gen, situation.linkedDenms()); + } + if (situation.eventEnd() != null) { + gen.writeNumberField("event_end", situation.eventEnd()); + } + 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 writeEventZone(JsonGenerator gen, List zones) throws IOException { + gen.writeStartArray(); + for (EventZone zone : zones) { + gen.writeStartObject(); + gen.writeFieldName("event_position"); + writeDeltaReferencePosition(gen, zone.eventPosition()); + if (zone.eventDeltaTime() != null) { + gen.writeNumberField("event_delta_time", zone.eventDeltaTime()); + } + gen.writeNumberField("information_quality", zone.informationQuality()); + gen.writeEndObject(); + } + gen.writeEndArray(); + } + + private void writeLinkedDenms(JsonGenerator gen, List linkedDenms) throws IOException { + gen.writeStartArray(); + for (ActionId actionId : linkedDenms) { + gen.writeStartObject(); + gen.writeNumberField("originating_station_id", actionId.originatingStationId()); + gen.writeNumberField("sequence_number", actionId.sequenceNumber()); + gen.writeEndObject(); + } + gen.writeEndArray(); + } + + private void writeLocation(JsonGenerator gen, LocationContainer location) throws IOException { + gen.writeStartObject(); + if (location.eventSpeed() != null) { + gen.writeFieldName("event_speed"); + writeEventSpeed(gen, location.eventSpeed()); + } + if (location.eventPositionHeading() != null) { + gen.writeFieldName("event_position_heading"); + writeEventPositionHeading(gen, location.eventPositionHeading()); + } + if (location.detectionZonesToEventPosition() != null) { + gen.writeFieldName("detection_zones_to_event_position"); + writeDetectionZones(gen, location.detectionZonesToEventPosition()); + } + if (location.roadType() != null) { + gen.writeNumberField("road_type", location.roadType()); + } + gen.writeEndObject(); + } + + private void writeEventSpeed(JsonGenerator gen, EventSpeed speed) throws IOException { + gen.writeStartObject(); + gen.writeNumberField("value", speed.value()); + gen.writeNumberField("confidence", speed.confidence()); + gen.writeEndObject(); + } + + private void writeEventPositionHeading(JsonGenerator gen, EventPositionHeading heading) throws IOException { + gen.writeStartObject(); + gen.writeNumberField("value", heading.value()); + gen.writeNumberField("confidence", heading.confidence()); + gen.writeEndObject(); + } + + private void writeDetectionZones(JsonGenerator gen, List zones) throws IOException { + gen.writeStartArray(); + for (DetectionZone zone : zones) { + gen.writeStartObject(); + gen.writeFieldName("path"); + writePathPoints(gen, zone.path()); + gen.writeEndObject(); + } + gen.writeEndArray(); + } + + private void writePathPoints(JsonGenerator gen, List points) throws IOException { + gen.writeStartArray(); + for (PathPoint point : points) { + 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(); + } + + private void writeDeltaReferencePosition(JsonGenerator gen, DeltaReferencePosition position) throws IOException { + gen.writeStartObject(); + gen.writeNumberField("delta_latitude", position.deltaLatitude()); + gen.writeNumberField("delta_longitude", position.deltaLongitude()); + gen.writeNumberField("delta_altitude", position.deltaAltitude()); + gen.writeEndObject(); + } + + private void writeAlacarte(JsonGenerator gen, AlacarteContainer alacarte) throws IOException { + gen.writeStartObject(); + if (alacarte.lanePosition() != null) { + gen.writeNumberField("lane_position", alacarte.lanePosition()); + } + if (alacarte.positioningSolution() != null) { + gen.writeNumberField("positioning_solution", alacarte.positioningSolution()); + } + gen.writeEndObject(); + } +} + diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/DenmEnvelope220.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/DenmEnvelope220.java new file mode 100644 index 000000000..5c17c0bec --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/DenmEnvelope220.java @@ -0,0 +1,98 @@ +/* + 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.denm.v220.model; + +import com.orange.iot3mobility.messages.denm.v220.model.path.PathElement; + +import java.util.List; + +/** + * DenmEnvelope220 - base class to build a DENM v2.2.0 with its header + * + * @param messageType message type (denm) + * @param sourceUuid identifier. Example: com_car_4294967295, com_application_42 + * @param timestamp Unit: millisecond. The timestamp when the message was generated since Unix Epoch (1970/01/01) + * @param version json message format version (2.2.0) + * @param path Optional. Root source path (ordered list of {@link PathElement}) + * @param message {@link DenmMessage220} + */ +public record DenmEnvelope220( + String messageType, + String sourceUuid, + long timestamp, + String version, + List path, + DenmMessage220 message) { + + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for DenmEnvelope220. + *

+ * Mandatory fields: + *

    + *
  • messageType - hardcoded (denm)
  • + *
  • sourceUuid
  • + *
  • timestamp
  • + *
  • version - hardcoded (2.2.0)
  • + *
  • message
  • + *
+ */ + public static final class Builder { + private final String messageType; + private String sourceUuid; + private Long timestamp; + private final String version; + private List path; + private DenmMessage220 message; + + private Builder() { + this.messageType = "denm"; + this.version = "2.2.0"; + } + + public Builder sourceUuid(String sourceUuid) { + this.sourceUuid = sourceUuid; + return this; + } + + public Builder timestamp(long timestamp) { + this.timestamp = timestamp; + return this; + } + + public Builder path(List path) { + this.path = path; + return this; + } + + public Builder message(DenmMessage220 message) { + this.message = message; + return this; + } + + public DenmEnvelope220 build() { + return new DenmEnvelope220( + requireNonNull(messageType, "message_type"), + requireNonNull(sourceUuid, "source_uuid"), + requireNonNull(timestamp, "timestamp"), + requireNonNull(version, "version"), + path, + 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/denm/v220/model/DenmMessage220.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/DenmMessage220.java new file mode 100644 index 000000000..cbbde3993 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/DenmMessage220.java @@ -0,0 +1,92 @@ +/* + 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.denm.v220.model; + +import com.orange.iot3mobility.messages.denm.v220.model.alacartecontainer.AlacarteContainer; +import com.orange.iot3mobility.messages.denm.v220.model.locationcontainer.LocationContainer; +import com.orange.iot3mobility.messages.denm.v220.model.managementcontainer.ManagementContainer; +import com.orange.iot3mobility.messages.denm.v220.model.situationcontainer.SituationContainer; + +/** + * DenmMessage220 - message DENM v2.2.0 (PDU). + * + * @param protocolVersion protocol version + * @param stationId station identifier + * @param managementContainer {@link ManagementContainer} + * @param situationContainer Optional. {@link SituationContainer} + * @param locationContainer Optional. {@link LocationContainer} + * @param alacarteContainer Optional. {@link AlacarteContainer} + */ +public record DenmMessage220( + int protocolVersion, + long stationId, + ManagementContainer managementContainer, + SituationContainer situationContainer, + LocationContainer locationContainer, + AlacarteContainer alacarteContainer) { + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private Integer protocolVersion; + private Long stationId; + private ManagementContainer managementContainer; + private SituationContainer situationContainer; + private LocationContainer locationContainer; + private AlacarteContainer alacarteContainer; + + public Builder protocolVersion(int protocolVersion) { + this.protocolVersion = protocolVersion; + return this; + } + + public Builder stationId(long stationId) { + this.stationId = stationId; + return this; + } + + public Builder managementContainer(ManagementContainer managementContainer) { + this.managementContainer = managementContainer; + return this; + } + + public Builder situationContainer(SituationContainer situationContainer) { + this.situationContainer = situationContainer; + return this; + } + + public Builder locationContainer(LocationContainer locationContainer) { + this.locationContainer = locationContainer; + return this; + } + + public Builder alacarteContainer(AlacarteContainer alacarteContainer) { + this.alacarteContainer = alacarteContainer; + return this; + } + + public DenmMessage220 build() { + return new DenmMessage220( + requireNonNull(protocolVersion, "protocol_version"), + requireNonNull(stationId, "station_id"), + requireNonNull(managementContainer, "management_container"), + situationContainer, + locationContainer, + alacarteContainer); + } + + 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/denm/v220/model/alacartecontainer/AlacarteContainer.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/alacartecontainer/AlacarteContainer.java new file mode 100644 index 000000000..6d35ce53b --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/alacartecontainer/AlacarteContainer.java @@ -0,0 +1,44 @@ +/* + 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.denm.v220.model.alacartecontainer; + +/** + * AlacarteContainer - DENM v2.2.0 alacarte container. + * + * @param lanePosition Optional. offTheRoad(-1), innerHardShoulder(0), innermostDrivingLane(1), + * secondLaneFromInside(2), outterHardShoulder(14) + * @param positioningSolution Optional. noPositioningSolution(0), sGNSS(1), dGNSS(2), sGNSSplusDR(3), + * dGNSSplusDR(4), dR(5), manuallyByOperator(6) + */ +public record AlacarteContainer( + Integer lanePosition, + Integer positioningSolution) { + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private Integer lanePosition; + private Integer positioningSolution; + + public Builder lanePosition(Integer lanePosition) { + this.lanePosition = lanePosition; + return this; + } + + public Builder positioningSolution(Integer positioningSolution) { + this.positioningSolution = positioningSolution; + return this; + } + + public AlacarteContainer build() { + return new AlacarteContainer(lanePosition, positioningSolution); + } + } +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/defs/Altitude.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/defs/Altitude.java new file mode 100644 index 000000000..a795e0509 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/defs/Altitude.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.denm.v220.model.defs; + +/** + * Altitude - value with confidence. + * + * @param value altitude value (Unit: 0.01 meter). Default: 800001 + * @param confidence altitude confidence. Default: 15 + */ +public record Altitude( + int value, + int confidence) { +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/defs/DeltaReferencePosition.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/defs/DeltaReferencePosition.java new file mode 100644 index 000000000..d062f0f94 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/defs/DeltaReferencePosition.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.denm.v220.model.defs; + +/** + * DeltaReferencePosition - delta reference position. + * + * @param deltaLatitude Unit: 0.1 microdegree. Default: 131072 + * @param deltaLongitude Unit: 0.1 microdegree. Default: 131072 + * @param deltaAltitude Unit: centimeter. Default: 12800 + */ +public record DeltaReferencePosition( + int deltaLatitude, + int deltaLongitude, + int deltaAltitude) { +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/defs/PositionConfidenceEllipse.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/defs/PositionConfidenceEllipse.java new file mode 100644 index 000000000..33943be64 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/defs/PositionConfidenceEllipse.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.denm.v220.model.defs; + +/** + * PositionConfidenceEllipse - confidence ellipse. + * + * @param semiMajor Half of length of the major axis. Default: 4095 + * @param semiMinor Half of length of the minor axis. Default: 4095 + * @param semiMajorOrientation Orientation of the ellipse major axis. Default: 3601 + */ +public record PositionConfidenceEllipse( + int semiMajor, + int semiMinor, + int semiMajorOrientation) { +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/locationcontainer/DetectionZone.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/locationcontainer/DetectionZone.java new file mode 100644 index 000000000..2f478b9c5 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/locationcontainer/DetectionZone.java @@ -0,0 +1,43 @@ +/* + 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.denm.v220.model.locationcontainer; + +import java.util.List; + +/** + * DetectionZone - detection zone approaching event position. + * + * @param path {@link PathPoint} list (max 40) + */ +public record DetectionZone( + List path) { + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private List path; + + public Builder path(List path) { + this.path = path; + return this; + } + + public DetectionZone build() { + return new DetectionZone(requireNonNull(path, "path")); + } + + 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/denm/v220/model/locationcontainer/EventPositionHeading.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/locationcontainer/EventPositionHeading.java new file mode 100644 index 000000000..70b6fb6a3 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/locationcontainer/EventPositionHeading.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.denm.v220.model.locationcontainer; + +/** + * EventPositionHeading - event position heading with confidence. + * + * @param value Unit: 0.1 degree. wgs84North(0), wgs84East(900), wgs84South(1800), wgs84West(2700), + * doNotUse(3600), unavailable(3601) + * @param confidence Unit: 0.1 degree. equalOrWithinZeroPointOneDegree (1), equalOrWithinOneDegree (10), + * outOfRange(126), unavailable(127) + */ +public record EventPositionHeading( + int value, + int confidence) { +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/locationcontainer/EventSpeed.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/locationcontainer/EventSpeed.java new file mode 100644 index 000000000..d6f4bfcd4 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/locationcontainer/EventSpeed.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.denm.v220.model.locationcontainer; + +/** + * EventSpeed - event speed with confidence. + * + * @param value Unit: 0.01 m/s. standstill(0), oneCentimeterPerSec(1), unavailable(16383) + * @param confidence Unit: 0.01 m/s. equalOrWithinOneCentimeterPerSec(1), equalOrWithinOneMeterPerSec(100), + * outOfRange(126), unavailable(127) + */ +public record EventSpeed( + int value, + int confidence) { +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/locationcontainer/LocationContainer.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/locationcontainer/LocationContainer.java new file mode 100644 index 000000000..2d0ef3f0f --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/locationcontainer/LocationContainer.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.denm.v220.model.locationcontainer; + +import java.util.List; + +/** + * LocationContainer - DENM v2.2.0 location container. + * + * @param eventSpeed Optional. {@link EventSpeed} + * @param eventPositionHeading Optional. {@link EventPositionHeading} + * @param detectionZonesToEventPosition Optional. {@link DetectionZone} list (1 to 7) + * @param roadType Optional. Type of road segment. Range: 0-3 + */ +public record LocationContainer( + EventSpeed eventSpeed, + EventPositionHeading eventPositionHeading, + List detectionZonesToEventPosition, + Integer roadType) { + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private EventSpeed eventSpeed; + private EventPositionHeading eventPositionHeading; + private List detectionZonesToEventPosition; + private Integer roadType; + + public Builder eventSpeed(EventSpeed eventSpeed) { + this.eventSpeed = eventSpeed; + return this; + } + + public Builder eventPositionHeading(EventPositionHeading eventPositionHeading) { + this.eventPositionHeading = eventPositionHeading; + return this; + } + + public Builder detectionZonesToEventPosition(List detectionZonesToEventPosition) { + this.detectionZonesToEventPosition = detectionZonesToEventPosition; + return this; + } + + public Builder roadType(Integer roadType) { + this.roadType = roadType; + return this; + } + + public LocationContainer build() { + return new LocationContainer( + eventSpeed, + eventPositionHeading, + detectionZonesToEventPosition, + roadType); + } + } +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/locationcontainer/PathPoint.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/locationcontainer/PathPoint.java new file mode 100644 index 000000000..bd111462e --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/locationcontainer/PathPoint.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.denm.v220.model.locationcontainer; + +import com.orange.iot3mobility.messages.denm.v220.model.defs.DeltaReferencePosition; + +/** + * PathPoint - path point element. + * + * @param pathPosition {@link DeltaReferencePosition} + * @param pathDeltaTime Optional. Unit: 10 millisecond. Range: 1-65535 + */ +public record PathPoint( + DeltaReferencePosition pathPosition, + Integer pathDeltaTime) { +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/managementcontainer/ActionId.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/managementcontainer/ActionId.java new file mode 100644 index 000000000..018950738 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/managementcontainer/ActionId.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.denm.v220.model.managementcontainer; + +/** + * ActionId - identifier for a DENM action. + * + * @param originatingStationId originating station identifier + * @param sequenceNumber sequence number + */ +public record ActionId( + long originatingStationId, + int sequenceNumber) { +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/managementcontainer/ManagementContainer.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/managementcontainer/ManagementContainer.java new file mode 100644 index 000000000..43e16ee7d --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/managementcontainer/ManagementContainer.java @@ -0,0 +1,127 @@ +/* + 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.denm.v220.model.managementcontainer; + +/** + * ManagementContainer - DENM v2.2.0 management container. + * + * @param actionId {@link ActionId} + * @param detectionTime Unit: millisecond since ETSI epoch (2004/01/01, so 1072915200000) + * @param referenceTime Unit: millisecond since ETSI epoch (2004/01/01, so 1072915200000) + * @param termination Optional. isCancellation(0), isNegation(1) + * @param eventPosition {@link ReferencePosition} + * @param awarenessDistance Optional. lessThan50m(0), lessThan100m(1), lessThan200m(2), lessThan500m(3), + * lessThan1000m(4), lessThan5km(5), lessThan10km(6), over10km(7) + * @param trafficDirection Optional. allTrafficDirections(0), upstreamTraffic(1), downstreamTraffic(2), + * oppositeTraffic(3) + * @param validityDuration Optional. Unit: second. timeOfDetection(0), oneSecondAfterDetection(1) + * @param transmissionInterval Optional. Unit: millisecond. oneMilliSecond(1), tenSeconds(10000) + * @param stationType 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 record ManagementContainer( + ActionId actionId, + long detectionTime, + long referenceTime, + Integer termination, + ReferencePosition eventPosition, + Integer awarenessDistance, + Integer trafficDirection, + Integer validityDuration, + Integer transmissionInterval, + Integer stationType) { + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private ActionId actionId; + private Long detectionTime; + private Long referenceTime; + private Integer termination; + private ReferencePosition eventPosition; + private Integer awarenessDistance; + private Integer trafficDirection; + private Integer validityDuration; + private Integer transmissionInterval; + private Integer stationType; + + public Builder actionId(ActionId actionId) { + this.actionId = actionId; + return this; + } + + public Builder detectionTime(long detectionTime) { + this.detectionTime = detectionTime; + return this; + } + + public Builder referenceTime(long referenceTime) { + this.referenceTime = referenceTime; + return this; + } + + public Builder termination(Integer termination) { + this.termination = termination; + return this; + } + + public Builder eventPosition(ReferencePosition eventPosition) { + this.eventPosition = eventPosition; + return this; + } + + public Builder awarenessDistance(Integer awarenessDistance) { + this.awarenessDistance = awarenessDistance; + return this; + } + + public Builder trafficDirection(Integer trafficDirection) { + this.trafficDirection = trafficDirection; + return this; + } + + public Builder validityDuration(Integer validityDuration) { + this.validityDuration = validityDuration; + return this; + } + + public Builder transmissionInterval(Integer transmissionInterval) { + this.transmissionInterval = transmissionInterval; + return this; + } + + public Builder stationType(Integer stationType) { + this.stationType = stationType; + return this; + } + + public ManagementContainer build() { + return new ManagementContainer( + requireNonNull(actionId, "action_id"), + requireNonNull(detectionTime, "detection_time"), + requireNonNull(referenceTime, "reference_time"), + termination, + requireNonNull(eventPosition, "event_position"), + awarenessDistance, + trafficDirection, + validityDuration, + transmissionInterval, + requireNonNull(stationType, "station_type")); + } + + 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/denm/v220/model/managementcontainer/ReferencePosition.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/managementcontainer/ReferencePosition.java new file mode 100644 index 000000000..2748ad20f --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/managementcontainer/ReferencePosition.java @@ -0,0 +1,72 @@ +/* + 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.denm.v220.model.managementcontainer; + +import com.orange.iot3mobility.messages.denm.v220.model.defs.Altitude; +import com.orange.iot3mobility.messages.denm.v220.model.defs.PositionConfidenceEllipse; + +/** + * ReferencePosition - event position with confidence. + * + * @param latitude Latitude of the geographical point. Range: -900000000 to 900000001 + * @param longitude Longitude of the geographical point. Range: -1800000000 to 1800000001 + * @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(); + } + + public static final class Builder { + private Integer latitude; + private Integer longitude; + private PositionConfidenceEllipse positionConfidenceEllipse; + private Altitude altitude; + + public Builder latitude(int latitude) { + this.latitude = latitude; + return this; + } + + public Builder longitude(int longitude) { + 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/denm/v220/model/path/PathElement.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/path/PathElement.java new file mode 100644 index 000000000..8223c91a1 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/path/PathElement.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.denm.v220.model.path; + +/** + * DenmPathElement220 - path element. + * + * @param position {@link PathPosition} + * @param messageType message type (denm, cam, cpm, po) + */ +public record PathElement( + PathPosition position, + String messageType) { +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/path/PathPosition.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/path/PathPosition.java new file mode 100644 index 000000000..42b020823 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/path/PathPosition.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.denm.v220.model.path; + +/** + * DenmPathPosition220 - path position. + * + * @param latitude Latitude of the geographical point. Range: -900000000 to 900000001 + * @param longitude Longitude of the geographical point. Range: -1800000000 to 1800000001 + * @param altitude Unit: 0.01 meter. referenceEllipsoidSurface(0), oneCentimeter(1), unavailable(800001) + */ +public record PathPosition( + int latitude, + int longitude, + int altitude) { +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/situationcontainer/CauseCode.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/situationcontainer/CauseCode.java new file mode 100644 index 000000000..4de3ddc9c --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/situationcontainer/CauseCode.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.denm.v220.model.situationcontainer; + +/** + * CauseCode - cause code definition. + * + * @param cause main cause code. Range: 0-255 + * @param subcause Optional. Subcause code. Range: 0-255 (unavailable=0) + */ +public record CauseCode( + int cause, + Integer subcause) { +} diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/situationcontainer/EventZone.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/situationcontainer/EventZone.java new file mode 100644 index 000000000..8d6ca7581 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/situationcontainer/EventZone.java @@ -0,0 +1,62 @@ +/* + 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.denm.v220.model.situationcontainer; + +import com.orange.iot3mobility.messages.denm.v220.model.defs.DeltaReferencePosition; + +/** + * EventZone - event zone element. + * + * @param eventPosition {@link DeltaReferencePosition} + * @param eventDeltaTime Optional. Unit: 10 millisecond. Range: 0-65535 + * @param informationQuality information quality (0-7) + */ +public record EventZone( + DeltaReferencePosition eventPosition, + Integer eventDeltaTime, + int informationQuality) { + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private DeltaReferencePosition eventPosition; + private Integer eventDeltaTime; + private Integer informationQuality; + + public Builder eventPosition(DeltaReferencePosition eventPosition) { + this.eventPosition = eventPosition; + return this; + } + + public Builder eventDeltaTime(Integer eventDeltaTime) { + this.eventDeltaTime = eventDeltaTime; + return this; + } + + public Builder informationQuality(int informationQuality) { + this.informationQuality = informationQuality; + return this; + } + + public EventZone build() { + return new EventZone( + requireNonNull(eventPosition, "event_position"), + eventDeltaTime, + requireNonNull(informationQuality, "information_quality")); + } + + 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/denm/v220/model/situationcontainer/SituationContainer.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/situationcontainer/SituationContainer.java new file mode 100644 index 000000000..0957b6107 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/model/situationcontainer/SituationContainer.java @@ -0,0 +1,91 @@ +/* + 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.denm.v220.model.situationcontainer; + +import com.orange.iot3mobility.messages.denm.v220.model.managementcontainer.ActionId; + +import java.util.List; + +/** + * SituationContainer - DENM v2.2.0 situation container. + * + * @param informationQuality information quality (0-7) + * @param eventType {@link CauseCode} + * @param linkedCause Optional. {@link CauseCode} + * @param eventZone Optional. {@link EventZone} list + * @param linkedDenms Optional. {@link com.orange.iot3mobility.messages.denm.v220.model.managementcontainer.ActionId} list (1 to 8) + * @param eventEnd Optional. Unit: meter. outOfRange(8190), unavailable(8191) + */ +public record SituationContainer( + int informationQuality, + CauseCode eventType, + CauseCode linkedCause, + List eventZone, + List linkedDenms, + Integer eventEnd) { + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private Integer informationQuality; + private CauseCode eventType; + private CauseCode linkedCause; + private List eventZone; + private List linkedDenms; + private Integer eventEnd; + + public Builder informationQuality(int informationQuality) { + this.informationQuality = informationQuality; + return this; + } + + public Builder eventType(CauseCode eventType) { + this.eventType = eventType; + return this; + } + + public Builder linkedCause(CauseCode linkedCause) { + this.linkedCause = linkedCause; + return this; + } + + public Builder eventZone(List eventZone) { + this.eventZone = eventZone; + return this; + } + + public Builder linkedDenms(List linkedDenms) { + this.linkedDenms = linkedDenms; + return this; + } + + public Builder eventEnd(Integer eventEnd) { + this.eventEnd = eventEnd; + return this; + } + + public SituationContainer build() { + return new SituationContainer( + requireNonNull(informationQuality, "information_quality"), + requireNonNull(eventType, "event_type"), + linkedCause, + eventZone, + linkedDenms, + eventEnd); + } + + 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/denm/v220/validation/DenmValidationException.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/validation/DenmValidationException.java new file mode 100644 index 000000000..bd962f529 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/validation/DenmValidationException.java @@ -0,0 +1,15 @@ +/* + 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.denm.v220.validation; + +public final class DenmValidationException extends RuntimeException { + public DenmValidationException(String message) { + super(message); + } +} + diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/validation/DenmValidator220.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/validation/DenmValidator220.java new file mode 100644 index 000000000..8b5ed6076 --- /dev/null +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/messages/denm/v220/validation/DenmValidator220.java @@ -0,0 +1,264 @@ +/* + 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.denm.v220.validation; + +import com.orange.iot3mobility.messages.denm.v220.model.DenmEnvelope220; +import com.orange.iot3mobility.messages.denm.v220.model.DenmMessage220; +import com.orange.iot3mobility.messages.denm.v220.model.path.PathElement; +import com.orange.iot3mobility.messages.denm.v220.model.path.PathPosition; +import com.orange.iot3mobility.messages.denm.v220.model.alacartecontainer.AlacarteContainer; +import com.orange.iot3mobility.messages.denm.v220.model.defs.Altitude; +import com.orange.iot3mobility.messages.denm.v220.model.defs.DeltaReferencePosition; +import com.orange.iot3mobility.messages.denm.v220.model.defs.PositionConfidenceEllipse; +import com.orange.iot3mobility.messages.denm.v220.model.locationcontainer.DetectionZone; +import com.orange.iot3mobility.messages.denm.v220.model.locationcontainer.EventPositionHeading; +import com.orange.iot3mobility.messages.denm.v220.model.locationcontainer.EventSpeed; +import com.orange.iot3mobility.messages.denm.v220.model.locationcontainer.LocationContainer; +import com.orange.iot3mobility.messages.denm.v220.model.locationcontainer.PathPoint; +import com.orange.iot3mobility.messages.denm.v220.model.managementcontainer.ActionId; +import com.orange.iot3mobility.messages.denm.v220.model.managementcontainer.ManagementContainer; +import com.orange.iot3mobility.messages.denm.v220.model.managementcontainer.ReferencePosition; +import com.orange.iot3mobility.messages.denm.v220.model.situationcontainer.CauseCode; +import com.orange.iot3mobility.messages.denm.v220.model.situationcontainer.EventZone; +import com.orange.iot3mobility.messages.denm.v220.model.situationcontainer.SituationContainer; + +import java.util.List; +import java.util.Objects; + +public final class DenmValidator220 { + + private DenmValidator220() {} + + public static void validateEnvelope(DenmEnvelope220 env) { + requireNonNull("envelope", env); + requireEquals("message_type", env.messageType(), "denm"); + requireEquals("version", env.version(), "2.2.0"); + requireNotBlank("source_uuid", env.sourceUuid()); + checkRange("timestamp", env.timestamp(), 1514764800000L, 1830297600000L); + if (env.path() != null) { + validatePath(env.path()); + } + validateMessage(env.message()); + } + + public static void validateMessage(DenmMessage220 msg) { + requireNonNull("message", msg); + checkRange("protocol_version", msg.protocolVersion(), 0, 255); + checkRange("station_id", msg.stationId(), 0, 4294967295L); + validateManagement(msg.managementContainer()); + if (msg.situationContainer() != null) { + validateSituation(msg.situationContainer()); + } + if (msg.locationContainer() != null) { + validateLocation(msg.locationContainer()); + } + if (msg.alacarteContainer() != null) { + validateAlacarte(msg.alacarteContainer()); + } + } + + private static void validateManagement(ManagementContainer container) { + requireNonNull("management_container", container); + validateActionId(container.actionId()); + checkRange("detection_time", container.detectionTime(), 0L, 4398046511103L); + checkRange("reference_time", container.referenceTime(), 0L, 4398046511103L); + checkRange("termination", container.termination(), 0, 1); + validateReferencePosition(container.eventPosition()); + checkRange("awareness_distance", container.awarenessDistance(), 0, 7); + checkRange("traffic_direction", container.trafficDirection(), 0, 3); + checkRange("validity_duration", container.validityDuration(), 0, 86400); + checkRange("transmission_interval", container.transmissionInterval(), 1, 10000); + requireNonNull("station_type", container.stationType()); + checkRange("station_type", container.stationType(), 0, 255); + } + + private static void validateActionId(ActionId actionId) { + requireNonNull("action_id", actionId); + checkRange("originating_station_id", actionId.originatingStationId(), 0, 4294967295L); + checkRange("sequence_number", actionId.sequenceNumber(), 0, 65535); + } + + private static void validateReferencePosition(ReferencePosition position) { + requireNonNull("event_position", position); + checkRange("event_position.latitude", position.latitude(), -900000000, 900000001); + checkRange("event_position.longitude", position.longitude(), -1800000000, 1800000001); + validatePositionConfidenceEllipse(position.positionConfidenceEllipse()); + validateAltitude(position.altitude()); + } + + private static void validatePositionConfidenceEllipse(PositionConfidenceEllipse ellipse) { + checkRange("position_confidence_ellipse.semi_major", ellipse.semiMajor(), 0, 4095); + checkRange("position_confidence_ellipse.semi_minor", ellipse.semiMinor(), 0, 4095); + checkRange("position_confidence_ellipse.semi_major_orientation", ellipse.semiMajorOrientation(), 0, 3601); + } + + private static void validateAltitude(Altitude altitude) { + checkRange("altitude.value", altitude.value(), -100000, 800001); + checkRange("altitude.confidence", altitude.confidence(), 0, 15); + } + + private static void validateSituation(SituationContainer situation) { + requireNonNull("situation_container", situation); + requireNonNull("information_quality", situation.informationQuality()); + checkRange("information_quality", situation.informationQuality(), 0, 7); + requireNonNull("event_type", situation.eventType()); + validateCauseCode("event_type", situation.eventType()); + if (situation.linkedCause() != null) { + validateCauseCode("linked_cause", situation.linkedCause()); + } + if (situation.eventZone() != null) { + validateEventZones(situation.eventZone()); + } + if (situation.linkedDenms() != null) { + validateLinkedDenms(situation.linkedDenms()); + } + if (situation.eventEnd() != null) { + checkRange("event_end", situation.eventEnd(), -8190, 8191); + } + } + + private static void validateCauseCode(String field, CauseCode causeCode) { + requireNonNull(field, causeCode); + checkRange(field + ".cause", causeCode.cause(), 0, 255); + checkRange(field + ".subcause", causeCode.subcause(), 0, 255); + } + + private static void validateEventZones(List zones) { + for (int i = 0; i < zones.size(); i++) { + EventZone zone = requireNonNull("event_zone[" + i + "]", zones.get(i)); + validateDeltaReferencePosition("event_zone[" + i + "].event_position", zone.eventPosition()); + checkRange("event_zone[" + i + "].event_delta_time", zone.eventDeltaTime(), 0, 65535); + checkRange("event_zone[" + i + "].information_quality", zone.informationQuality(), 0, 7); + } + } + + private static void validateLinkedDenms(List linkedDenms) { + checkSize("linked_denms", linkedDenms.size(), 1, 8); + for (int i = 0; i < linkedDenms.size(); i++) { + validateActionId(linkedDenms.get(i)); + } + } + + private static void validateLocation(LocationContainer location) { + requireNonNull("location_container", location); + if (location.eventSpeed() != null) { + validateEventSpeed(location.eventSpeed()); + } + if (location.eventPositionHeading() != null) { + validateEventPositionHeading(location.eventPositionHeading()); + } + if (location.detectionZonesToEventPosition() != null) { + validateDetectionZones(location.detectionZonesToEventPosition()); + } + checkRange("road_type", location.roadType(), 0, 3); + } + + private static void validateEventSpeed(EventSpeed speed) { + checkRange("event_speed.value", speed.value(), 0, 16383); + checkRange("event_speed.confidence", speed.confidence(), 1, 127); + } + + private static void validateEventPositionHeading(EventPositionHeading heading) { + checkRange("event_position_heading.value", heading.value(), 0, 3601); + checkRange("event_position_heading.confidence", heading.confidence(), 1, 127); + } + + private static void validateDetectionZones(List zones) { + checkSize("detection_zones_to_event_position", zones.size(), 1, 7); + for (int i = 0; i < zones.size(); i++) { + DetectionZone zone = requireNonNull("detection_zones_to_event_position[" + i + "]", zones.get(i)); + validatePathPoints("detection_zones_to_event_position[" + i + "]", zone.path()); + } + } + + private static void validatePathPoints(String prefix, List points) { + if (points.size() > 40) { + throw new DenmValidationException(prefix + ".path size exceeds 40"); + } + for (int i = 0; i < points.size(); i++) { + PathPoint point = requireNonNull(prefix + ".path[" + i + "]", points.get(i)); + validateDeltaReferencePosition(prefix + ".path[" + i + "].path_position", point.pathPosition()); + checkRange(prefix + ".path[" + i + "].path_delta_time", point.pathDeltaTime(), 1, 65535); + } + } + + private static void validateDeltaReferencePosition(String field, DeltaReferencePosition position) { + checkRange(field + ".delta_latitude", position.deltaLatitude(), -131071, 131072); + checkRange(field + ".delta_longitude", position.deltaLongitude(), -131071, 131072); + checkRange(field + ".delta_altitude", position.deltaAltitude(), -12700, 12800); + } + + private static void validateAlacarte(AlacarteContainer alacarte) { + checkRange("alacarte_container.lane_position", alacarte.lanePosition(), -1, 14); + checkRange("alacarte_container.positioning_solution", alacarte.positioningSolution(), 0, 6); + } + + private static void validatePath(List path) { + checkSize("path", path.size(), 1, Integer.MAX_VALUE); + for (int i = 0; i < path.size(); i++) { + PathElement element = requireNonNull("path[" + i + "]", path.get(i)); + validatePathPosition("path[" + i + "].position", element.position()); + requireEnum("path[" + i + "].message_type", element.messageType(), + List.of("denm", "cam", "cpm", "po")); + } + } + + private static void validatePathPosition(String prefix, PathPosition position) { + requireNonNull(prefix, position); + checkRange(prefix + ".latitude", position.latitude(), -900000000, 900000001); + checkRange(prefix + ".longitude", position.longitude(), -1800000000, 1800000001); + checkRange(prefix + ".altitude", position.altitude(), -100000, 800001); + } + + private static T requireNonNull(String field, T value) { + if (value == null) { + throw new DenmValidationException("Missing mandatory field: " + field); + } + return value; + } + + private static void requireNotBlank(String field, String value) { + if (value == null || value.isBlank()) { + throw new DenmValidationException("Missing mandatory field: " + field); + } + } + + private static void requireEquals(String field, String actual, String expected) { + if (!Objects.equals(actual, expected)) { + throw new DenmValidationException(field + " must equal '" + expected + "'"); + } + } + + private static void requireEnum(String field, String actual, List allowed) { + if (!allowed.contains(actual)) { + throw new DenmValidationException(field + " must be one of " + allowed); + } + } + + private static void checkRange(String field, Long value, long min, long max) { + if (value != null && (value < min || value > max)) { + throw new DenmValidationException( + field + " out of range [" + min + ", " + max + "] (actual=" + value + ")"); + } + } + + private static void checkRange(String field, Integer value, long min, long max) { + if (value != null && (value < min || value > max)) { + throw new DenmValidationException( + field + " out of range [" + min + ", " + max + "] (actual=" + value + ")"); + } + } + + private static void checkSize(String field, int size, int min, int max) { + if (size < min || size > max) { + throw new DenmValidationException( + field + " size out of range [" + min + ", " + max + "] (actual=" + size + ")"); + } + } +} + diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/roadobjects/HazardType.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/roadobjects/HazardType.java index bd4c2628f..a8847c181 100644 --- a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/roadobjects/HazardType.java +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/roadobjects/HazardType.java @@ -217,4 +217,13 @@ public int getSubcause() { return subcause; } + public static HazardType getHazardType(int cause, int subcause) { + for(HazardType hazard: HazardType.values()) { + if(hazard.getCause() == cause && hazard.getSubcause() == subcause) { + return hazard; + } + } + return UNDEFINED; + } + } diff --git a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/roadobjects/RoadHazard.java b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/roadobjects/RoadHazard.java index 231404300..07e3e88b0 100644 --- a/java/iot3/mobility/src/main/java/com/orange/iot3mobility/roadobjects/RoadHazard.java +++ b/java/iot3/mobility/src/main/java/com/orange/iot3mobility/roadobjects/RoadHazard.java @@ -8,19 +8,27 @@ package com.orange.iot3mobility.roadobjects; import com.orange.iot3mobility.TrueTime; -import com.orange.iot3mobility.its.json.denm.DENM; +import com.orange.iot3mobility.messages.EtsiConverter; +import com.orange.iot3mobility.messages.denm.core.DenmCodec; +import com.orange.iot3mobility.messages.denm.core.DenmVersion; +import com.orange.iot3mobility.messages.denm.v113.model.DenmEnvelope113; +import com.orange.iot3mobility.messages.denm.v113.model.DenmMessage113; +import com.orange.iot3mobility.messages.denm.v220.model.DenmEnvelope220; +import com.orange.iot3mobility.messages.denm.v220.model.DenmMessage220; import com.orange.iot3mobility.quadkey.LatLng; public class RoadHazard { private final String uuid; + private LatLng position; + private long timestamp; + private int lifetime; private HazardType hazardType; - private DENM denm; + private DenmCodec.DenmFrame denmFrame; - public RoadHazard(String uuid, DENM denm) { + public RoadHazard(String uuid, DenmCodec.DenmFrame denmFrame) { this.uuid = uuid; - this.denm = denm; - findHazardType(); + setDenmFrame(denmFrame); } public String getUuid() { @@ -28,40 +36,46 @@ public String getUuid() { } public LatLng getPosition() { - return new LatLng(denm.getManagementContainer().getEventPosition().getLatitudeDegree(), - denm.getManagementContainer().getEventPosition().getLongitudeDegree()); + return position; } public long getTimestamp() { - return denm.getTimestamp(); + return timestamp; } public boolean stillLiving() { - return TrueTime.getAccurateTime() - getTimestamp() < denm.getManagementContainer().getValidityDuration() * 1000L; + return TrueTime.getAccurateTime() - timestamp < lifetime; } public HazardType getType() { return hazardType; } - public void setDenm(DENM denm) { - this.denm = denm; - } - - public DENM getDenm() { - return denm; + public void setDenmFrame(DenmCodec.DenmFrame denmFrame) { + this.denmFrame = denmFrame; + if(denmFrame.version().equals(DenmVersion.V1_1_3)) { + DenmEnvelope113 denmEnvelope113 = (DenmEnvelope113) denmFrame.envelope(); + DenmMessage113 denm113 = denmEnvelope113.message(); + position = new LatLng(EtsiConverter.latitudeDegrees(denm113.managementContainer().eventPosition().latitude()), + EtsiConverter.longitudeDegrees(denm113.managementContainer().eventPosition().longitude())); + timestamp = denmEnvelope113.timestamp(); + lifetime = denm113.managementContainer().validityDuration() * 1000; + hazardType = HazardType.getHazardType(denm113.situationContainer().eventType().cause(), + denm113.situationContainer().eventType().subcause()); + } else if(denmFrame.version().equals(DenmVersion.V2_2_0)) { + DenmEnvelope220 denmEnvelope220 = (DenmEnvelope220) denmFrame.envelope(); + DenmMessage220 denm220 = denmEnvelope220.message(); + position = new LatLng(EtsiConverter.latitudeDegrees(denm220.managementContainer().eventPosition().latitude()), + EtsiConverter.longitudeDegrees(denm220.managementContainer().eventPosition().longitude())); + timestamp = denmEnvelope220.timestamp(); + lifetime = denm220.managementContainer().validityDuration() * 1000; + hazardType = HazardType.getHazardType(denm220.situationContainer().eventType().cause(), + denm220.situationContainer().eventType().subcause()); + } } - private void findHazardType() { - int cause = denm.getSituationContainer().getEventType().getCause(); - int subcause = denm.getSituationContainer().getEventType().getSubcause(); - for(HazardType hazard: HazardType.values()) { - if(hazard.getCause() == cause && hazard.getSubcause() == subcause) { - hazardType = hazard; - break; - } - } - if(hazardType == null) hazardType = HazardType.UNDEFINED; + public DenmCodec.DenmFrame getDenmFrame() { + return denmFrame; } } 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 276f0d492..0bc750bf7 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 @@ -7,7 +7,7 @@ */ package com.orange.iot3mobility.roadobjects; -import com.orange.iot3mobility.its.StationType; +import com.orange.iot3mobility.messages.StationType; import com.orange.iot3mobility.messages.cam.core.CamCodec; import com.orange.iot3mobility.quadkey.LatLng;