diff --git a/manager/src/main/java/telematics/teltonika/ITeltonikaPayload.java b/manager/src/main/java/telematics/teltonika/ITeltonikaPayload.java index 04ea190..0c733f5 100644 --- a/manager/src/main/java/telematics/teltonika/ITeltonikaPayload.java +++ b/manager/src/main/java/telematics/teltonika/ITeltonikaPayload.java @@ -5,6 +5,7 @@ import org.openremote.model.asset.Asset; import org.openremote.model.attribute.Attribute; import org.openremote.model.attribute.AttributeMap; +import org.openremote.model.value.AttributeDescriptor; import java.util.Map; import java.util.logging.Logger; @@ -20,5 +21,5 @@ public interface ITeltonikaPayload { */ Map getAttributesFromPayload(TeltonikaConfiguration config, TimerService timerService) throws JsonProcessingException; - AttributeMap getAttributes(Map payloadMap, TeltonikaConfiguration config, Logger logger); + AttributeMap getAttributes(Map payloadMap, TeltonikaConfiguration config, Logger logger, Map> descs); } diff --git a/manager/src/main/java/telematics/teltonika/TeltonikaDataPayload.java b/manager/src/main/java/telematics/teltonika/TeltonikaDataPayload.java index dd996f5..414e395 100644 --- a/manager/src/main/java/telematics/teltonika/TeltonikaDataPayload.java +++ b/manager/src/main/java/telematics/teltonika/TeltonikaDataPayload.java @@ -1,6 +1,7 @@ package telematics.teltonika; import com.fasterxml.jackson.databind.ObjectMapper; +import org.checkerframework.checker.units.qual.A; import org.openremote.container.timer.TimerService; import org.openremote.model.Constants; import org.openremote.model.asset.Asset; @@ -8,13 +9,14 @@ import org.openremote.model.attribute.AttributeMap; import org.openremote.model.attribute.MetaItem; import org.openremote.model.attribute.MetaMap; -import org.openremote.model.custom.CarAsset; +import org.openremote.model.custom.VehicleAsset; import org.openremote.model.geo.GeoJSONPoint; import org.openremote.model.teltonika.State; import org.openremote.model.teltonika.TeltonikaParameter; import org.openremote.model.util.ValueUtil; import org.openremote.model.value.*; +import java.lang.reflect.Field; import java.sql.Timestamp; import java.time.Instant; import java.util.*; @@ -56,6 +58,9 @@ public State getState() { private static final Logger logger = Logger.getLogger(TeltonikaDataPayload.class.getName()); + private Logger getLogger() { + return logger; + } /** * Returns list of attributes depending on the Teltonika JSON Payload. @@ -93,7 +98,7 @@ public Map getAttributesFromPayload(TeltonikaCon List customParams = new ArrayList(List.of( new TeltonikaParameterData("pr", new TeltonikaParameter(-1, "Priority", String.valueOf(1), "Unsigned", String.valueOf(0), String.valueOf(4), String.valueOf(1), "-", "0: Low - 1: High - 2: Panic", "all", "Permanent I/O Elements")), new TeltonikaParameterData("alt", new TeltonikaParameter(-1, "Altitude", "2", "Signed", "-1000", "+3000", "1", "m", "meters above sea level", "all", "Permanent I/O Elements")), - new TeltonikaParameterData("ang", new TeltonikaParameter(-1, "Angle", "2", "Signed", "-360", "+460", "1", "deg", "degrees from north pole", "all", "Permanent I/O Elements")), + new TeltonikaParameterData("ang", new TeltonikaParameter(-1, "Direction", "2", "Signed", "-360", "+460", "1", "deg", "degrees from north pole", "all", "Permanent I/O Elements")), new TeltonikaParameterData("sat", new TeltonikaParameter(-1, "Satellites", "1", "Unsigned", "0", "1000", "1", "-", "number of visible satellites", "all", "Permanent I/O Elements")), new TeltonikaParameterData("sp", new TeltonikaParameter(-1, "Speed", "2", "Signed", "0", "1000", "1", "km/h", "speed calculated from satellites", "all", "Permanent I/O Elements")), new TeltonikaParameterData("evt", new TeltonikaParameter(-1, "Event Triggered", "2", "Signed", "0", "10000", "1", "-", "Parameter ID which generated this payload", "all", "Permanent I/O Elements")), @@ -146,8 +151,7 @@ public Map getAttributesFromPayload(TeltonikaCon } - - public AttributeMap getAttributes(Map payloadMap, TeltonikaConfiguration config, Logger logger) { + public AttributeMap getAttributes(Map payloadMap, TeltonikaConfiguration config, Logger logger, Map> descs) { AttributeMap attributes = new AttributeMap(); String[] specialProperties = {"latlng", "ts"}; for (Map.Entry entry : payloadMap.entrySet()) { @@ -175,7 +179,7 @@ public AttributeMap getAttributes(Map payloadMap long unixTimestampMillis = Long.parseLong(entry.getValue().toString()); Timestamp deviceTimestamp = Timestamp.from(Instant.ofEpochMilli(unixTimestampMillis)); //Maybe this attribute should have the value set as server time and the device time as a timestamp? - attributes.add(new Attribute<>(CarAsset.LAST_CONTACT, deviceTimestamp, deviceTimestamp.getTime())); + attributes.add(new Attribute<>(VehicleAsset.LAST_CONTACT, deviceTimestamp, deviceTimestamp.getTime())); //Update all affected attribute timestamps attributes.forEach(attribute -> attribute.setTimestamp(deviceTimestamp.getTime())); @@ -186,6 +190,29 @@ public AttributeMap getAttributes(Map payloadMap } continue; } + if(Objects.equals(parameterId, "ang")){ + //This is the parameter ID which triggered the payload + Object angle = ValueUtil.getValueCoerced(entry.getValue(), ValueType.DIRECTION.getType()).orElseThrow(); + Attribute attr = new Attribute(VehicleAsset.DIRECTION, angle); + attributes.add(attr); + continue; + } + + /* + * Quick explanation here, with the new update that allows for custom Asset Types, we need to somehow + * be able to understand when a parameter has been defined as an Attribute in the custom Asset Type that we + * are using. By using the list of AttributeDescriptors, we can check if the parameter ID is present in the + * list of AttributeDescriptors. If it is, then we can directly use the AttributeDescriptor we found to + * dynamically create that attribute in the way the AttributeDescriptor says, whether the Attribute has been + * created yet or not. + * */ + if(descs.containsKey(parameterId)){ + AttributeDescriptor descriptor = descs.get(parameterId); + Object obj = ValueUtil.getValueCoerced(entry.getValue(), descriptor.getType().getType()).orElseThrow(); + Attribute attr = new Attribute(descs.get(parameterId), obj); + attributes.add(attr); + continue; + } //Create the MetaItem Map MetaMap metaMap = new MetaMap(); @@ -325,7 +352,7 @@ public AttributeMap getAttributes(Map payloadMap } //Timestamp grabbed from the device. - attributes.get(CarAsset.LAST_CONTACT).ifPresent(lastContact -> { + attributes.get(VehicleAsset.LAST_CONTACT).ifPresent(lastContact -> { lastContact.getValue().ifPresent(value -> { attributes.forEach(attribute -> attribute.setTimestamp(value.getTime())); }); diff --git a/manager/src/main/java/telematics/teltonika/TeltonikaMQTTHandler.java b/manager/src/main/java/telematics/teltonika/TeltonikaMQTTHandler.java index 32678e7..44e5f24 100644 --- a/manager/src/main/java/telematics/teltonika/TeltonikaMQTTHandler.java +++ b/manager/src/main/java/telematics/teltonika/TeltonikaMQTTHandler.java @@ -1,7 +1,6 @@ package telematics.teltonika; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import io.netty.buffer.ByteBuf; import io.netty.handler.codec.mqtt.MqttQoS; import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection; @@ -18,6 +17,7 @@ import org.openremote.model.Container; import org.openremote.model.asset.Asset; import org.openremote.model.asset.AssetFilter; +import org.openremote.model.asset.AssetTypeInfo; import org.openremote.model.attribute.*; import org.openremote.model.custom.*; import org.openremote.model.datapoint.ValueDatapoint; @@ -25,10 +25,13 @@ import org.openremote.model.query.filter.*; import org.openremote.model.syslog.SyslogCategory; import org.openremote.model.teltonika.*; +import org.openremote.model.util.ValueUtil; import org.openremote.model.value.*; import telematics.teltonika.helpers.TeltonikaConfigurationFactory; import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -45,6 +48,9 @@ public class TeltonikaMQTTHandler extends MQTTHandler { + public static final Class> TELTONIKA_DEVICE_ASSET_CLASS = CarAsset.class; + public static AssetTypeInfo TELTONIKA_DEVICE_ASSET_INFO = null; + protected static class TeltonikaDevice { String clientId; String commandTopic; @@ -108,6 +114,7 @@ public void start(Container container) throws Exception { assetProcessingService = container.getService(AssetProcessingService.class); AssetDatapointService = container.getService(AssetDatapointService.class); timerService = container.getService(TimerService.class); + TELTONIKA_DEVICE_ASSET_INFO = ValueUtil.getAssetInfo(TELTONIKA_DEVICE_ASSET_CLASS).orElseThrow(); DeviceParameterPath = container.isDevMode() ? Paths.get("../deployment/manager/fleet/FMC003.json") : Paths.get("/deployment/manager/fleet/FMC003.json"); config = TeltonikaConfigurationFactory.createConfiguration(assetStorageService, timerService, getParameterFileString()); if (!identityService.isKeycloakEnabled()) { @@ -195,20 +202,20 @@ private AssetFilter buildConfigurationAssetFilter(){ /** * Creates a filter for the AttributeEvents that could send a command to a Teltonika Device. * - * @return AssetFilter of CarAssets that have both {@code getConfig().getResponseAttribute().getName()} and + * @return AssetFilter of VehicleAssets that have both {@code getConfig().getResponseAttribute().getName()} and * {@code getConfig().getCommandAttribute().getValue().orElse("sendToDevice")} as attributes. */ private AssetFilter buildCommandAssetFilter(){ List> assetsWithAttribute = assetStorageService - .findAll(new AssetQuery().types(CarAsset.class) + .findAll(new AssetQuery().types(VehicleAsset.class) .attributeNames(getConfig().getCommandAttribute().getValue().orElse("sendToDevice"))); - List listOfCarAssetIds = assetsWithAttribute.stream() + List listOfVehicleAssetIds = assetsWithAttribute.stream() .map(Asset::getId) .toList(); AssetFilter event = new AssetFilter<>(); - event.setAssetIds(listOfCarAssetIds.toArray(new String[0])); + event.setAssetIds(listOfVehicleAssetIds.toArray(new String[0])); event.setAttributeNames(getConfig().getCommandAttribute().getValue().orElse("sendToDevice")); return event; } @@ -222,7 +229,7 @@ private void handleAttributeMessage(AttributeEvent event) { // If this is not an AttributeEvent that updates a getConfig().getCommandAttribute().getValue().orElse("sendToDevice") field, ignore if (!Objects.equals(event.getName(), getConfig().getCommandAttribute().getValue().orElse("sendToDevice"))) return; //Find the asset in question - CarAsset asset = assetStorageService.find(event.getId(), CarAsset.class); + VehicleAsset asset = assetStorageService.find(event.getId(), VehicleAsset.class); // Double check, remove later, sanity checks if(asset.hasAttribute(getConfig().getCommandAttribute().getValue().orElse("sendToDevice"))){ @@ -232,7 +239,7 @@ private void handleAttributeMessage(AttributeEvent event) { Optional> imei; String imeiString; try { - imei = asset.getAttribute(CarAsset.IMEI); + imei = asset.getAttribute(VehicleAsset.IMEI); if(imei.isEmpty()) throw new Exception(); if(imei.get().getValue().isEmpty()) throw new Exception(); imeiString = imei.get().getValue().get(); @@ -243,7 +250,7 @@ private void handleAttributeMessage(AttributeEvent event) { } // Get the device subscription information, and even if it's subscribed - TeltonikaMQTTHandler.TeltonikaDevice deviceInfo = connectionSubscriberInfoMap.get(imeiString); + TeltonikaDevice deviceInfo = connectionSubscriberInfoMap.get(imeiString); //If it's null, the device is not subscribed, leave if(deviceInfo == null) { getLogger().info(String.format("Device %s is not subscribed to topic, not posting message", @@ -264,13 +271,13 @@ private void handleAttributeMessage(AttributeEvent event) { } /** - * Sends a Command to the {@link TeltonikaMQTTHandler.TeltonikaDevice} in the correct format. + * Sends a Command to the {@link TeltonikaDevice} in the correct format. * * @param command string of the command, without preformatting. * List of valid commands can be found in Teltonika's website. - * @param device A {@link TeltonikaMQTTHandler.TeltonikaDevice} that is currently subscribed, to which to send the message to. + * @param device A {@link TeltonikaDevice} that is currently subscribed, to which to send the message to. */ - private void sendCommandToTeltonikaDevice(String command, TeltonikaMQTTHandler.TeltonikaDevice device) { + private void sendCommandToTeltonikaDevice(String command, TeltonikaDevice device) { mqttBrokerService.publishMessage(device.commandTopic, Map.of("CMD", command), MqttQoS.EXACTLY_ONCE); } @@ -335,7 +342,7 @@ public boolean canPublish(RemotingConnection connection, KeycloakSecurityContext public void onSubscribe(RemotingConnection connection, Topic topic) { getLogger().info("CONNECT: Device "+topic.getTokens().get(1)+" connected to topic "+topic+"."); - connectionSubscriberInfoMap.put(topic.getTokens().get(3), new TeltonikaMQTTHandler.TeltonikaDevice(topic)); + connectionSubscriberInfoMap.put(topic.getTokens().get(3), new TeltonikaDevice(topic)); } @Override @@ -359,20 +366,20 @@ public Set getPublishListenerTopics() { TELTONIKA_DEVICE_TOKEN + "/" + TOKEN_SINGLE_LEVEL_WILDCARD + "/" + TELTONIKA_DEVICE_SEND_TOPIC ); } - - private long startTimestamp = 0; - private long endTimestamp = 0; +// +// private long startTimestamp = 0; +// private long endTimestamp = 0; @Override public void onPublish(RemotingConnection connection, Topic topic, ByteBuf body) { - startTimestamp = System.currentTimeMillis(); +// startTimestamp = System.currentTimeMillis(); ITeltonikaPayload payload = null; String deviceImei = topic.getTokens().get(3); String deviceUuid = UniqueIdentifierGenerator.generateId(deviceImei); - Asset asset = assetStorageService.find(deviceUuid); + Asset asset = assetStorageService.find(deviceUuid, VehicleAsset.class); String deviceModelNumber = asset != null - ? asset.getAttribute(CarAsset.MODEL_NUMBER).orElseThrow().getValue().orElse(getConfig().getDefaultModelNumber()) + ? asset.getAttribute(VehicleAsset.MODEL_NUMBER).orElseThrow().getValue().orElse(getConfig().getDefaultModelNumber()) : getConfig().getDefaultModelNumber(); if (deviceModelNumber == null){ getLogger().fine("Device Model Number is null, setting to default"); @@ -391,7 +398,7 @@ public void onPublish(RemotingConnection connection, Topic topic, ByteBuf body) AttributeMap attributes; try{ Map data = payload.getAttributesFromPayload(getConfig(), timerService); - attributes = payload.getAttributes(data, getConfig(), getLogger()); + attributes = payload.getAttributes(data, getConfig(), getLogger(), TELTONIKA_DEVICE_ASSET_INFO.getAttributeDescriptors()); }catch (JsonProcessingException e) { getLogger().severe("Failed to getAttributesFromPayload"); getLogger().severe(e.toString()); @@ -413,8 +420,8 @@ public void onPublish(RemotingConnection connection, Topic topic, ByteBuf body) try{ if(getConfig().getStorePayloads().getValue().orElseThrow() && payload instanceof TeltonikaDataPayload){ Attribute payloadAttribute = new Attribute<>("payload", CustomValueTypes.TELTONIKA_PAYLOAD, new TeltonikaDataPayloadModel(((TeltonikaDataPayload) payload).getState())); - payloadAttribute.addMeta(new MetaItem<>(MetaItemType.STORE_DATA_POINTS, true)); - payloadAttribute.setTimestamp(attributes.get(CarAsset.LAST_CONTACT).orElseThrow().getValue().orElseThrow().getTime()); + payloadAttribute.addMeta(new MetaItem<>(STORE_DATA_POINTS, true)); + payloadAttribute.setTimestamp(attributes.get(VehicleAsset.LAST_CONTACT).orElseThrow().getValue().orElseThrow().getTime()); attributes.add(payloadAttribute); } }catch (Exception ignored){} @@ -461,7 +468,7 @@ public void onPublish(RemotingConnection connection, Topic topic, ByteBuf body) new MetaItem<>(READ_ONLY, true) ); // Maybe set this to session.endTime? - attributes.get(CarAsset.LAST_CONTACT).flatMap(Attribute::getValue) + attributes.get(VehicleAsset.LAST_CONTACT).flatMap(Attribute::getValue) .ifPresent(val -> session.setTimestamp(val.getTime())); attributes.add(session); @@ -500,36 +507,54 @@ public void onPublish(RemotingConnection connection, Topic topic, ByteBuf body) */ private void createNewAsset(String newDeviceId, String newDeviceImei, String realm, AttributeMap attributes) { - // This is where you would specify the Asset type that is inherited from the CarAsset class. - CarAsset testAsset = new CarAsset("Teltonika Asset "+newDeviceImei) + Asset newAsset = null; + try { + Constructor> constructor = TELTONIKA_DEVICE_ASSET_CLASS.getConstructor(String.class); + newAsset = constructor.newInstance("Teltonika Asset " + newDeviceImei); + } catch (NoSuchMethodException e) { + getLogger().severe("Constructor for "+ TELTONIKA_DEVICE_ASSET_CLASS.getSimpleName() +" not found with parameter String"); + return; + } catch (InvocationTargetException e) { + getLogger().severe("Constructor for "+ TELTONIKA_DEVICE_ASSET_CLASS.getSimpleName() +" threw an exception"); + return; + } catch (InstantiationException e) { + getLogger().severe("The Class" + TELTONIKA_DEVICE_ASSET_CLASS.getSimpleName() + " is abstract or an interface, and cannot be instantiated."); + return; + } catch (IllegalAccessException e) { + getLogger().severe("The Constructor for " + TELTONIKA_DEVICE_ASSET_CLASS.getSimpleName() + " is not accessible (Could be private, or in general we do not have the needed access modifiers)."); + return; + } + + newAsset = newAsset .setRealm(realm) .setModelNumber(getConfig().getDefaultModelNumber()) .setId(newDeviceId); - testAsset.getAttribute(CarAsset.LOCATION).ifPresentOrElse( + newAsset.getAttribute(VehicleAsset.LOCATION).ifPresentOrElse( attr -> attr.addMeta(new MetaItem<>(STORE_DATA_POINTS, true)), - () -> getLogger().warning("Couldn't find CarAsset.LOCATION") + () -> getLogger().warning("Couldn't find "+TELTONIKA_DEVICE_ASSET_CLASS.getSimpleName()+".LOCATION") ); - testAsset.getAttributes().add(new Attribute<>(CarAsset.IMEI, newDeviceImei)); + newAsset.getAttributes().add(new Attribute<>(VehicleAsset.IMEI, newDeviceImei)); // Create Command and Response Attributes Attribute command = new Attribute<>(new AttributeDescriptor<>(getConfig().getCommandAttribute().getValue().orElse("sendToDevice"), ValueType.TEXT), ""); - testAsset.getAttributes().add(command); + newAsset.getAttributes().add(command); // Attribute response = new Attribute<>(new AttributeDescriptor<>(getConfig().getResponseAttribute().getValue().orElse("sendToDevice"), ValueType.TEXT), ""); -// testAsset.getAttributes().add(response); +// newAsset.getAttributes().add(response); //Now that the asset is created and IMEI is set, pull the packet timestamp, and then //set each of the asset's attributes to have that timestamp. - attributes.get(CarAsset.LAST_CONTACT).flatMap(Attribute::getValue).ifPresent(dateVal -> { - testAsset.setCreatedOn(dateVal); - testAsset.getAttributes().forEach(attribute -> attribute.setTimestamp(dateVal.getTime())); + Asset> finalNewAsset = newAsset; + attributes.get(VehicleAsset.LAST_CONTACT).flatMap(Attribute::getValue).ifPresent(dateVal -> { + finalNewAsset.setCreatedOn(dateVal); + finalNewAsset.getAttributes().forEach(attribute -> attribute.setTimestamp(dateVal.getTime())); attributes.forEach(attribute -> attribute.setTimestamp(dateVal.getTime())); }); - updateAsset(testAsset, attributes); + updateAsset(finalNewAsset, attributes); } @@ -666,13 +691,13 @@ private String getParameterFileString() { * @param asset The asset to be updated. * @param attributes The attributes to be upserted to the Attribute. */ - private void updateAsset(Asset asset, AttributeMap attributes) { - String imei = asset.getAttribute(CarAsset.IMEI) + private void updateAsset(Asset> asset, AttributeMap attributes) { + String imei = asset.getAttribute(VehicleAsset.IMEI) .orElse(new Attribute<>("IMEI", ValueType.TEXT, "Not Found")) .getValue() .orElse("Couldn't Find IMEI"); - getLogger().info("Updating "+ attributes.stream().count() +" attributes of CarAsset with IMEI " + imei + " at Timestamp " + attributes.get(CarAsset.LAST_CONTACT)); + getLogger().info("Updating "+ attributes.stream().count() +" attributes of "+TELTONIKA_DEVICE_ASSET_CLASS.getSimpleName()+" with IMEI " + imei + " at Timestamp " + attributes.get(VehicleAsset.LAST_CONTACT)); AttributeMap nonExistingAttributes = new AttributeMap(); AttributeMap existingAttributes = new AttributeMap(); @@ -703,7 +728,7 @@ private void updateAsset(Asset asset, AttributeMap attributes) { })); endTimestamp = System.currentTimeMillis(); - getLogger().info("Updated CarAsset in " + (endTimestamp - startTimestamp) + "ms"); +// getLogger().info("Updated "+TELTONIKA_DEVICE_ASSET_CLASS.getSimpleName()+" in " + (endTimestamp - startTimestamp) + "ms"); } } \ No newline at end of file diff --git a/manager/src/main/java/telematics/teltonika/TeltonikaResponsePayload.java b/manager/src/main/java/telematics/teltonika/TeltonikaResponsePayload.java index 33cfa7b..6132475 100644 --- a/manager/src/main/java/telematics/teltonika/TeltonikaResponsePayload.java +++ b/manager/src/main/java/telematics/teltonika/TeltonikaResponsePayload.java @@ -8,6 +8,7 @@ import org.openremote.model.attribute.Attribute; import org.openremote.model.attribute.AttributeMap; import org.openremote.model.teltonika.TeltonikaParameter; +import org.openremote.model.value.AttributeDescriptor; import java.util.Map; import java.util.logging.Logger; @@ -63,7 +64,7 @@ public Map getAttributesFromPayload(TeltonikaCon return Map.of(parameter, rsp); } - public AttributeMap getAttributes(Map payloadMap, TeltonikaConfiguration config, Logger logger) { + public AttributeMap getAttributes(Map payloadMap, TeltonikaConfiguration config, Logger logger, Map> descs) { AttributeMap attributeMap = new AttributeMap(); Attribute attribute = config.getResponseAttribute(); diff --git a/model/src/main/java/org/openremote/model/custom/CarAsset.java b/model/src/main/java/org/openremote/model/custom/CarAsset.java index 8886c58..bfdaf19 100644 --- a/model/src/main/java/org/openremote/model/custom/CarAsset.java +++ b/model/src/main/java/org/openremote/model/custom/CarAsset.java @@ -1,51 +1,85 @@ package org.openremote.model.custom; import jakarta.persistence.Entity; -import org.openremote.model.asset.Asset; +import org.openremote.model.Constants; import org.openremote.model.asset.AssetDescriptor; +import org.openremote.model.attribute.MetaItem; +import org.openremote.model.attribute.MetaMap; +import org.openremote.model.teltonika.TeltonikaModelConfigurationAsset; import org.openremote.model.value.AttributeDescriptor; +import org.openremote.model.value.MetaItemType; import org.openremote.model.value.ValueType; import org.openremote.model.value.impl.ColourRGB; +import scala.collection.immutable.Stream; -import java.util.Date; +import java.util.List; +import java.util.Map; import java.util.Optional; +/** + * CarAsset is an extension of the VehicleAsset class, specifically intended for the fleet management use case of OpenRemote. + * It is used as the class for the car asset type that should work using this integration. + * + * This Asset Type is used as both an example and as a viable use-case for the OpenRemote Fleet Telematics integration + * of OpenRemote with Teltonika Telematics. + * + * It contains the correct, user-fillable metadata, while also containing some specific attributes that are widely used + * in the fleet management use case. + * + * In this situation, the user has two options; either extend the Vehicle asset as this class, or extend this asset type, + * to use the attributes that are included in it. + * */ @Entity -public class CarAsset extends Asset { - public static final AttributeDescriptor IMEI = new AttributeDescriptor<>("IMEI", ValueType.TEXT); - public static final AttributeDescriptor LAST_CONTACT = new AttributeDescriptor<>("lastContact", ValueType.DATE_AND_TIME); - public static final AttributeDescriptor MAKE_AND_MODEL = new AttributeDescriptor<>("makeAndModel", ValueType.TEXT).withOptional(true); - public static final AttributeDescriptor MODEL_NUMBER = new AttributeDescriptor<>("modelNumber", ValueType.TEXT).withOptional(true); - public static final AttributeDescriptor MODEL_YEAR = new AttributeDescriptor<>("modelYear", ValueType.INTEGER).withOptional(true); - public static final AttributeDescriptor COLOR = new AttributeDescriptor<>("color", ValueType.COLOUR_RGB).withOptional(true); - public static final AttributeDescriptor LICENSE_PLATE = new AttributeDescriptor<>("licensePlate", ValueType.TEXT).withOptional(true); - - // Figure out a way to use the colour parameter for the color of the car on the map - public static final AssetDescriptor DESCRIPTOR = new AssetDescriptor<>("car", null, CarAsset.class); - - - protected CarAsset(){ - } - public CarAsset(String name){super(name);} - - public Optional getIMEI() { - return getAttributes().getValue(IMEI); - } - public Optional getLastContact() { - return getAttributes().getValue(LAST_CONTACT); - } - public Optional getMakeAndModel() { - return getAttributes().getValue(MAKE_AND_MODEL); - } - public Optional getModelYear() { - return getAttributes().getValue(MODEL_YEAR); - } - public Optional getColor() { - return getAttributes().getValue(COLOR); - } - public Optional getModelNumber(){return getAttributes().getValue(MODEL_NUMBER);} - - public CarAsset setModelNumber(String value){ - getAttributes().getOrCreate(MODEL_NUMBER).setValue(value); - return this; - } +public class CarAsset extends VehicleAsset{ + public static final AssetDescriptor DESCRIPTOR = new AssetDescriptor<>("car", null, CarAsset.class); + + // Vehicle meta-data + public static final AttributeDescriptor COLOR = new AttributeDescriptor<>("color", ValueType.COLOUR_RGB).withOptional(true); + public static final AttributeDescriptor MODEL_YEAR = new AttributeDescriptor<>("modelYear", ValueType.INTEGER).withOptional(true) + .withUnits(Constants.UNITS_YEAR); + public static final AttributeDescriptor LICENSE_PLATE = new AttributeDescriptor<>("licensePlate", ValueType.TEXT).withOptional(true); + + //Ignition + public static final AttributeDescriptor IGNITION_ON = new AttributeDescriptor<>("239", ValueType.BOOLEAN) + .withMeta(TeltonikaModelConfigurationAsset.getPayloadAttributeMeta("Ignition status")); + + //Movement + public static final AttributeDescriptor MOVEMENT = new AttributeDescriptor<>("240", ValueType.BOOLEAN) + .withMeta(TeltonikaModelConfigurationAsset.getPayloadAttributeMeta("Movement status")); + + //odometer + public static final AttributeDescriptor ODOMETER = new AttributeDescriptor<>("16", ValueType.NUMBER) + .withMeta(TeltonikaModelConfigurationAsset.getPayloadAttributeMeta("Odometer")) + .withUnits(Constants.UNITS_METRE); + + + // All the permanent ones (pr, alt, ang, sat, sp, evt) + + public static final AttributeDescriptor EVENT_ATTR_NAME = new AttributeDescriptor<>("evt", ValueType.NUMBER).withOptional(true) + .withMeta(TeltonikaModelConfigurationAsset.getPayloadAttributeMeta("Event triggered by")); + public static final AttributeDescriptor ALTITUDE = new AttributeDescriptor<>("alt", ValueType.NUMBER).withOptional(true) + .withMeta(TeltonikaModelConfigurationAsset.getPayloadAttributeMeta("Altitude")) + .withUnits(Constants.UNITS_METRE); + public static final AttributeDescriptor SATELLITES = new AttributeDescriptor<>("sat", ValueType.NUMBER).withOptional(true) + .withMeta(TeltonikaModelConfigurationAsset.getPayloadAttributeMeta("Number of satellites in use")); + public static final AttributeDescriptor SPEED = new AttributeDescriptor<>("sp", ValueType.NUMBER) + .withMeta(TeltonikaModelConfigurationAsset.getPayloadAttributeMeta("Speed")) + .withUnits(Constants.UNITS_KILO, Constants.UNITS_METRE, Constants.UNITS_PER, Constants.UNITS_HOUR); + public static final AttributeDescriptor PRIORITY = new AttributeDescriptor<>("pr", ValueType.NUMBER).withOptional(true) + .withMeta(TeltonikaModelConfigurationAsset.getPayloadAttributeMeta("Payload priority (0-2)")); + + + + //Hydration + protected CarAsset() { + } + + public CarAsset(String name) { + super(name); + } + public Optional getModelYear() { + return getAttributes().getValue(MODEL_YEAR); + } + public Optional getColor() { + return getAttributes().getValue(COLOR); + } } diff --git a/model/src/main/java/org/openremote/model/custom/VehicleAsset.java b/model/src/main/java/org/openremote/model/custom/VehicleAsset.java new file mode 100644 index 0000000..78fc309 --- /dev/null +++ b/model/src/main/java/org/openremote/model/custom/VehicleAsset.java @@ -0,0 +1,62 @@ +package org.openremote.model.custom; + +import jakarta.persistence.Entity; +import org.openremote.model.Constants; +import org.openremote.model.asset.Asset; +import org.openremote.model.asset.AssetDescriptor; +import org.openremote.model.attribute.MetaMap; +import org.openremote.model.geo.GeoJSONPoint; +import org.openremote.model.teltonika.TeltonikaModelConfigurationAsset; +import org.openremote.model.value.AttributeDescriptor; +import org.openremote.model.value.ValueType; +import org.openremote.model.value.impl.ColourRGB; + +import java.util.Date; +import java.util.Optional; +/** + * {@code VehicleAsset} is a custom asset type specifically intended for the fleet management use case of OpenRemote. + * It is used as the base class of any subsequent vehicle asset types that should work using this integration. + * The VehicleAsset class contains all required attributes and methods to be used by the Teltonika Telematics integration. + * + * In case the user wants to add more attributes to the vehicle asset, they can do so by extending the VehicleAsset class. + * To view such an example, see the CarAsset class. + */ +@Entity +public class VehicleAsset extends Asset { + + public static final AttributeDescriptor LOCATION = new AttributeDescriptor<>("location", ValueType.GEO_JSON_POINT) + .withMeta(TeltonikaModelConfigurationAsset.getPayloadAttributeMeta("Location")); + + public static final AttributeDescriptor IMEI = new AttributeDescriptor<>("IMEI", ValueType.TEXT); + public static final AttributeDescriptor LAST_CONTACT = new AttributeDescriptor<>("lastContact", ValueType.DATE_AND_TIME) + .withMeta(TeltonikaModelConfigurationAsset.getPayloadAttributeMeta("Last message time")); + public static final AttributeDescriptor MODEL_NUMBER = new AttributeDescriptor<>("modelNumber", ValueType.TEXT); + + public static final AttributeDescriptor DIRECTION = new AttributeDescriptor<>("direction", ValueType.DIRECTION) + .withMeta(TeltonikaModelConfigurationAsset.getPayloadAttributeMeta("Direction")) + .withUnits(Constants.UNITS_DEGREE); + + // Figure out a way to use the colour parameter for the color of the car on the map + + + + public static final AssetDescriptor DESCRIPTOR = new AssetDescriptor<>("car", null, VehicleAsset.class); + + protected VehicleAsset(){ + } + public VehicleAsset(String name){super(name);} + + public Optional getIMEI() { + return getAttributes().getValue(IMEI); + } + public Optional getLastContact() { + return getAttributes().getValue(LAST_CONTACT); + } + + public Optional getModelNumber(){return getAttributes().getValue(MODEL_NUMBER);} + + public VehicleAsset setModelNumber(String value){ + getAttributes().getOrCreate(MODEL_NUMBER).setValue(value); + return this; + } +} diff --git a/model/src/main/java/org/openremote/model/teltonika/State.java b/model/src/main/java/org/openremote/model/teltonika/State.java index d07d56d..0fabc08 100644 --- a/model/src/main/java/org/openremote/model/teltonika/State.java +++ b/model/src/main/java/org/openremote/model/teltonika/State.java @@ -3,27 +3,10 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; -import org.openremote.model.Constants; -import org.openremote.model.asset.Asset; -import org.openremote.model.attribute.Attribute; -import org.openremote.model.attribute.AttributeMap; -import org.openremote.model.attribute.MetaItem; -import org.openremote.model.attribute.MetaMap; -import org.openremote.model.custom.CarAsset; -import org.openremote.model.geo.GeoJSONPoint; -import org.openremote.model.util.ValueUtil; -import org.openremote.model.value.*; -import java.io.Serial; import java.io.Serializable; -import java.sql.Timestamp; -import java.time.Instant; import java.util.*; -import java.util.logging.Logger; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import static org.openremote.model.value.MetaItemType.*; @JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "reported" diff --git a/model/src/main/java/org/openremote/model/teltonika/TeltonikaConfigurationAsset.java b/model/src/main/java/org/openremote/model/teltonika/TeltonikaConfigurationAsset.java index 277f1ab..cf2a3ab 100644 --- a/model/src/main/java/org/openremote/model/teltonika/TeltonikaConfigurationAsset.java +++ b/model/src/main/java/org/openremote/model/teltonika/TeltonikaConfigurationAsset.java @@ -21,7 +21,7 @@ public class TeltonikaConfigurationAsset extends Asset DEFAULT_MODEL_NUMBER = new AttributeDescriptor<>("defaultModelNumber", ValueType.TEXT); public static final AttributeDescriptor COMMAND = new AttributeDescriptor<>("command", ValueType.TEXT); public static final AttributeDescriptor RESPONSE = new AttributeDescriptor<>("response", ValueType.TEXT) - .withMeta(new MetaMap(Map.of(MetaItemType.READ_ONLY.getName(), new MetaItem<>(MetaItemType.READ_ONLY, true)))); + .withMeta(TeltonikaModelConfigurationAsset.getPayloadAttributeMeta("Response from device command")); public static final AssetDescriptor DESCRIPTOR = new AssetDescriptor<>("gear", null, TeltonikaConfigurationAsset.class); diff --git a/model/src/main/java/org/openremote/model/teltonika/TeltonikaModelConfigurationAsset.java b/model/src/main/java/org/openremote/model/teltonika/TeltonikaModelConfigurationAsset.java index 8d3bb7c..27f2585 100644 --- a/model/src/main/java/org/openremote/model/teltonika/TeltonikaModelConfigurationAsset.java +++ b/model/src/main/java/org/openremote/model/teltonika/TeltonikaModelConfigurationAsset.java @@ -17,6 +17,8 @@ import java.util.Optional; import java.util.stream.Collectors; +import static org.openremote.model.value.MetaItemType.*; + @Entity public class TeltonikaModelConfigurationAsset extends Asset { public static final AttributeDescriptor MODEL_NUMBER = new AttributeDescriptor<>("modelNumber", ValueType.TEXT); @@ -69,4 +71,17 @@ public CustomValueTypes.TeltonikaParameterMap getParameterMap() { .orElse(new CustomValueTypes.TeltonikaParameterMap()); // or provide a default value other than null, if appropriate } + public static MetaMap getPayloadAttributeMeta(String label){ + MetaMap map = new MetaMap(); + + map.addAll( + new MetaItem<>(STORE_DATA_POINTS, true), + new MetaItem<>(RULE_STATE, true), + new MetaItem<>(READ_ONLY, true), + new MetaItem<>(LABEL, label) + ); + + return map; + } + }