- */
-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;