From 1bdabfb4e110d68fa3c5a61613d176de8684ba85 Mon Sep 17 00:00:00 2001 From: pankalog Date: Thu, 8 Feb 2024 13:38:34 +0100 Subject: [PATCH] Custom Asset Type Implementation *BREAKING* Developers can now define their own asset types by extending on VehicleAsset. In TeltonikaMQTTHandler, it is as easy as changing the reference of `TELTONIKA_DEVICE_ASSET_CLASS` to your own custom type that extends the VehicleAsset. In this way, developers can create their own asset types with their own specific ways of parsing the parameters of the payload, without tweaking the code. Due to a change in the Asset Type, current installations will have to move to this to use the new asset types. --- .../teltonika/ITeltonikaPayload.java | 3 +- .../teltonika/TeltonikaDataPayload.java | 39 +++++- .../teltonika/TeltonikaMQTTHandler.java | 99 +++++++++------ .../teltonika/TeltonikaResponsePayload.java | 3 +- .../org/openremote/model/custom/CarAsset.java | 114 ++++++++++++------ .../openremote/model/custom/VehicleAsset.java | 62 ++++++++++ .../org/openremote/model/teltonika/State.java | 17 --- .../TeltonikaConfigurationAsset.java | 2 +- .../TeltonikaModelConfigurationAsset.java | 15 +++ 9 files changed, 251 insertions(+), 103 deletions(-) create mode 100644 model/src/main/java/org/openremote/model/custom/VehicleAsset.java 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; + } + }