From f909b2efd079dfd7f268c9e883bf1067f7edb720 Mon Sep 17 00:00:00 2001 From: pankalog Date: Sun, 24 Dec 2023 18:27:32 +0200 Subject: [PATCH 1/7] Convert to Attribute --- .../org/openremote/model/teltonika/TeltonikaParameter.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/model/src/main/java/org/openremote/model/teltonika/TeltonikaParameter.java b/model/src/main/java/org/openremote/model/teltonika/TeltonikaParameter.java index 7dc1183..19b4518 100644 --- a/model/src/main/java/org/openremote/model/teltonika/TeltonikaParameter.java +++ b/model/src/main/java/org/openremote/model/teltonika/TeltonikaParameter.java @@ -1,6 +1,7 @@ package org.openremote.model.teltonika; +import java.io.Serializable; import java.util.LinkedHashMap; import java.util.Map; import java.util.Objects; @@ -26,7 +27,7 @@ "hwSupport", "parameterGroup" }) -public class TeltonikaParameter { +public class TeltonikaParameter implements Serializable { @JsonProperty("propertyIdInAvlPacket") public Integer propertyId; From 2ba164c7eba030124b2167f40162e1dd7c49dd2b Mon Sep 17 00:00:00 2001 From: pankalog Date: Sun, 24 Dec 2023 18:29:41 +0200 Subject: [PATCH 2/7] New configuration assets --- .../custom/TeltonikaConfigurationAsset.java | 41 +++++++++++++++++++ .../TeltonikaModelConfigurationAsset.java | 35 ++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 model/src/main/java/org/openremote/model/custom/TeltonikaConfigurationAsset.java create mode 100644 model/src/main/java/org/openremote/model/custom/TeltonikaModelConfigurationAsset.java diff --git a/model/src/main/java/org/openremote/model/custom/TeltonikaConfigurationAsset.java b/model/src/main/java/org/openremote/model/custom/TeltonikaConfigurationAsset.java new file mode 100644 index 0000000..823e7cd --- /dev/null +++ b/model/src/main/java/org/openremote/model/custom/TeltonikaConfigurationAsset.java @@ -0,0 +1,41 @@ +package org.openremote.model.custom; + +import jakarta.persistence.Entity; +import org.openremote.model.asset.Asset; +import org.openremote.model.asset.AssetDescriptor; +import org.openremote.model.geo.GeoJSONPoint; +import org.openremote.model.value.AttributeDescriptor; +import org.openremote.model.value.ValueType; + +@Entity +public class TeltonikaConfigurationAsset extends Asset { + public static final AttributeDescriptor WHITELIST = new AttributeDescriptor<>("deviceIMEIWhitelist", ValueType.TEXT.asArray()).withOptional(true); + public static final AttributeDescriptor ENABLED = new AttributeDescriptor<>("Enabled", ValueType.BOOLEAN); + public static final AttributeDescriptor CHECK_FOR_IMEI = new AttributeDescriptor<>("CheckForValidIMEI", ValueType.BOOLEAN); + + public static final AssetDescriptor DESCRIPTOR = new AssetDescriptor<>("gear", null, TeltonikaConfigurationAsset.class); + + protected TeltonikaConfigurationAsset(){ + + } + + public TeltonikaConfigurationAsset(String name){ + super(name); + super.setLocation(new GeoJSONPoint(0,0,0)); + super.setNotes(""); + } + + public TeltonikaConfigurationAsset setWhitelist(String[] whitelist) { + getAttributes().getOrCreate(WHITELIST).setValue(whitelist); + return this; + } + public TeltonikaConfigurationAsset setEnabled(boolean enabled) { + getAttributes().getOrCreate(ENABLED).setValue(enabled); + return this; + } + + public TeltonikaConfigurationAsset setCheckForImei(boolean enabled) { + getAttributes().getOrCreate(CHECK_FOR_IMEI).setValue(enabled); + return this; + } +} diff --git a/model/src/main/java/org/openremote/model/custom/TeltonikaModelConfigurationAsset.java b/model/src/main/java/org/openremote/model/custom/TeltonikaModelConfigurationAsset.java new file mode 100644 index 0000000..06aac74 --- /dev/null +++ b/model/src/main/java/org/openremote/model/custom/TeltonikaModelConfigurationAsset.java @@ -0,0 +1,35 @@ +package org.openremote.model.custom; + +import jakarta.persistence.Entity; +import net.fortuna.ical4j.model.property.Geo; +import org.openremote.model.asset.Asset; +import org.openremote.model.asset.AssetDescriptor; +import org.openremote.model.geo.GeoJSONPoint; +import org.openremote.model.teltonika.TeltonikaParameter; +import org.openremote.model.value.AttributeDescriptor; +import org.openremote.model.value.ValueType; + +@Entity +public class TeltonikaModelConfigurationAsset extends Asset { + public static final AttributeDescriptor MODEL_NUMBER = new AttributeDescriptor<>("modelNumber", ValueType.TEXT); + public static final AttributeDescriptor PARAMETER_DATA = new AttributeDescriptor<>("TeltonikaParameterData", CustomValueTypes.TELTONIKA_PARAMETER.asArray()); + public static final AssetDescriptor DESCRIPTOR = new AssetDescriptor<>("switch", null, TeltonikaModelConfigurationAsset.class); + + protected TeltonikaModelConfigurationAsset(){} + + public TeltonikaModelConfigurationAsset(String name){ + super(name); + super.setLocation(new GeoJSONPoint(0,0,0)); + super.setNotes(""); + } + + public TeltonikaModelConfigurationAsset setModelNumber(String name) { + getAttributes().getOrCreate(MODEL_NUMBER).setValue(name); + return this; + } + + public TeltonikaModelConfigurationAsset setParameterData(TeltonikaParameter[] data) { + getAttributes().getOrCreate(PARAMETER_DATA).setValue(data); + return this; + } +} From 9f8bd8acd7c9c15f2f031c6ddaa7c97e16b59940 Mon Sep 17 00:00:00 2001 From: pankalog Date: Sun, 24 Dec 2023 18:33:39 +0200 Subject: [PATCH 3/7] Corrections and adding TeltonikaParameter to CustomValueTypes --- .../src/main/java/org/openremote/model/custom/CarAsset.java | 5 ++--- .../java/org/openremote/model/custom/CustomValueTypes.java | 2 ++ 2 files changed, 4 insertions(+), 3 deletions(-) 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 1dd304a..3fae91d 100644 --- a/model/src/main/java/org/openremote/model/custom/CarAsset.java +++ b/model/src/main/java/org/openremote/model/custom/CarAsset.java @@ -15,11 +15,10 @@ public class CarAsset extends Asset { 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_YEAR = new AttributeDescriptor<>("modelYear", ValueType.INTEGER).withOptional(true); - /** - * If Rich sees this, sorry for the American spelling! - */ 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); diff --git a/model/src/main/java/org/openremote/model/custom/CustomValueTypes.java b/model/src/main/java/org/openremote/model/custom/CustomValueTypes.java index a550a1a..be3c626 100644 --- a/model/src/main/java/org/openremote/model/custom/CustomValueTypes.java +++ b/model/src/main/java/org/openremote/model/custom/CustomValueTypes.java @@ -1,7 +1,9 @@ package org.openremote.model.custom; +import org.openremote.model.teltonika.TeltonikaParameter; import org.openremote.model.value.ValueDescriptor; public class CustomValueTypes { public static final ValueDescriptor ASSET_STATE_DURATION = new ValueDescriptor<>("AssetStateDuration", AssetStateDuration.class); + public static final ValueDescriptor TELTONIKA_PARAMETER = new ValueDescriptor<>("TeltonikaParameter", TeltonikaParameter.class); } From 3452c7c2300067a1e050c46766d32cfca3f3517d Mon Sep 17 00:00:00 2001 From: pankalog Date: Sun, 24 Dec 2023 18:35:11 +0200 Subject: [PATCH 4/7] Correct references in test --- .../openremote/test/custom/TeltonikaMQTTProtocolTest.groovy | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/src/test/groovy/org/openremote/test/custom/TeltonikaMQTTProtocolTest.groovy b/test/src/test/groovy/org/openremote/test/custom/TeltonikaMQTTProtocolTest.groovy index b2b10a1..d0f3cd9 100644 --- a/test/src/test/groovy/org/openremote/test/custom/TeltonikaMQTTProtocolTest.groovy +++ b/test/src/test/groovy/org/openremote/test/custom/TeltonikaMQTTProtocolTest.groovy @@ -19,6 +19,7 @@ import org.openremote.model.asset.agent.ConnectionStatus import org.openremote.model.attribute.Attribute import org.openremote.model.attribute.AttributeEvent import org.openremote.model.attribute.AttributeRef +import org.openremote.model.custom.CarAsset import org.openremote.model.teltonika.TeltonikaParameter import org.openremote.model.util.ValueUtil import org.openremote.model.value.MetaItemType @@ -325,7 +326,7 @@ class TeltonikaMQTTProtocolTest extends Specification implements ManagerContaine conditions.eventually { asset = assetStorageService.find(UniqueIdentifierGenerator.generateId(getTELTONIKA_DEVICE_IMEI())) assert asset != null; - assert asset.getAttribute("IMEI").get().getValue().get() == (getTELTONIKA_DEVICE_IMEI()); + assert asset.getAttribute(CarAsset.IMEI).get().getValue().get() == (getTELTONIKA_DEVICE_IMEI()); //Make sure that it parsed the attributes, since there is an issue of parsing the FMC003.json file assert asset.getAttributes().size() > 5; @@ -411,7 +412,7 @@ class TeltonikaMQTTProtocolTest extends Specification implements ManagerContaine conditions.eventually { asset = assetStorageService.find(UniqueIdentifierGenerator.generateId(getTELTONIKA_DEVICE_IMEI())) assert asset != null; - assert asset.getAttribute("IMEI").get().getValue().get() == (getTELTONIKA_DEVICE_IMEI()); + assert asset.getAttribute(CarAsset.IMEI).get().getValue().get() == (getTELTONIKA_DEVICE_IMEI()); } when: "the send message attribute is created and then updated" From 0cd8ed3d64de3b432d9874fb315e930ac893bc11 Mon Sep 17 00:00:00 2001 From: pankalog Date: Sun, 24 Dec 2023 18:37:22 +0200 Subject: [PATCH 5/7] Bring it all together --- .../manager/custom/TeltonikaMQTTHandler.java | 54 +++++++++++++++++-- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/manager/src/main/java/org/openremote/manager/custom/TeltonikaMQTTHandler.java b/manager/src/main/java/org/openremote/manager/custom/TeltonikaMQTTHandler.java index 1ba6662..d6f59bd 100644 --- a/manager/src/main/java/org/openremote/manager/custom/TeltonikaMQTTHandler.java +++ b/manager/src/main/java/org/openremote/manager/custom/TeltonikaMQTTHandler.java @@ -19,13 +19,13 @@ import org.openremote.model.asset.Asset; import org.openremote.model.asset.AssetFilter; import org.openremote.model.attribute.*; -import org.openremote.model.custom.AssetStateDuration; -import org.openremote.model.custom.CarAsset; -import org.openremote.model.custom.CustomValueTypes; +import org.openremote.model.custom.*; import org.openremote.model.datapoint.AssetDatapoint; +import org.openremote.model.geo.GeoJSONPoint; import org.openremote.model.query.AssetQuery; import org.openremote.model.query.filter.AttributePredicate; import org.openremote.model.query.filter.NumberPredicate; +import org.openremote.model.query.filter.ParentPredicate; import org.openremote.model.syslog.SyslogCategory; import org.openremote.model.teltonika.IMEIValidator; import org.openremote.model.teltonika.TeltonikaDataPayload; @@ -33,6 +33,7 @@ import org.openremote.model.teltonika.TeltonikaResponsePayload; import org.openremote.model.value.AttributeDescriptor; import org.openremote.model.value.ValueType; +import scala.collection.immutable.Stream; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -113,6 +114,18 @@ public void start(Container container) throws Exception { identityProvider = (ManagerKeycloakIdentityProvider) identityService.getIdentityProvider(); } + List> assets = assetStorageService.findAll(new AssetQuery().types(TeltonikaModelConfigurationAsset.class)); + + if(assets.isEmpty()) { + getLogger().severe("No Teltonika configuration assets found! Creating defaults..."); + + initializeConfigurationAssets(); + + getLogger().info("Created default configuration"); + } + + + clientEventService.addInternalSubscription( AttributeEvent.class, null, @@ -161,7 +174,7 @@ private void handleAttributeMessage(AttributeEvent event) { Optional> imei; String imeiString; try { - imei = asset.getAttribute("IMEI"); + imei = asset.getAttribute(CarAsset.IMEI); if(imei.isEmpty()) throw new Exception(); if(imei.get().getValue().isEmpty()) throw new Exception(); imeiString = imei.get().getValue().get(); @@ -337,6 +350,7 @@ public void onPublish(RemotingConnection connection, Topic topic, ByteBuf body) //Check state of Teltonika AVL ID 250 for FMC003, "Trip". // Optional> sessionAttr = assetChangedTripState(new AttributeRef(asset.getId(), "250")); // We want the state where the attribute 250 (Trip) is set to true. + // TODO: Figure out a way to create this through the UI. AttributePredicate pred = new AttributePredicate("250", new NumberPredicate((double) 1, AssetQuery.Operator.EQUALS)); try{ @@ -640,7 +654,7 @@ private void updateAsset(Asset asset, AttributeMap attributes, Topic topic, R // // attributes.forEach(attribute -> attribute.setTimestamp(packetTimestamp)); - String imei = asset.getAttribute("IMEI").toString(); + String imei = asset.getAttribute(CarAsset.IMEI).toString(); getLogger().info("Updating CarAsset with IMEI "+imei); //OBD details: Prot:6,VIN:WVGZZZ1TZBW068095,TM:15,CNT:19,ST:DATA REQUESTING,P1:0xBE3EA813,P2:0xA005B011,P3:0xFED00400,P4:0x0,MIL:0,DTC:0,ID3,Hdr:7E8,Phy:0 @@ -669,6 +683,36 @@ private void updateAsset(Asset asset, AttributeMap attributes, Topic topic, R assetProcessingService.sendAttributeEvent(attributeEvent); })); } + + private void initializeConfigurationAssets(){ + // Create initial configuration + TeltonikaConfigurationAsset rootConfig = new TeltonikaConfigurationAsset("Teltonika Device Configuration"); + TeltonikaModelConfigurationAsset fmc003 = new TeltonikaModelConfigurationAsset("FMC003"); + + rootConfig.setEnabled(true); + rootConfig.setWhitelist(new String[0]); + rootConfig.setCheckForImei(false); + + fmc003.setModelNumber("FMC003"); + ObjectMapper mapper = new ObjectMapper(); + try { + TeltonikaParameter[] params = mapper.readValue(getParameterFileString(), TeltonikaParameter[].class); + fmc003.setParameterData(params); + + } catch (JsonProcessingException e) { + throw new RuntimeException("Could not parse Teltonika Parameter JSON file"); + } + + rootConfig.setRealm("master"); + fmc003.setRealm("master"); + + rootConfig = assetStorageService.merge(rootConfig); + + fmc003.setParent(rootConfig); + + fmc003 = assetStorageService.merge(fmc003); + + } } //Attribute exists, needs to be updated From da1aa18c441f0c2b72c19104c350a5612f49d63c Mon Sep 17 00:00:00 2001 From: pankalog Date: Mon, 8 Jan 2024 15:56:01 +0100 Subject: [PATCH 6/7] Way too big of a commit to explain --- .../manager/custom/TeltonikaMQTTHandler.java | 143 ++++++++++++++---- .../org/openremote/model/custom/CarAsset.java | 6 + .../model/custom/CustomValueTypes.java | 5 + .../custom/TeltonikaConfigurationAsset.java | 6 + .../TeltonikaModelConfigurationAsset.java | 39 ++++- .../teltonika/TeltonikaConfiguration.java | 73 +++++++++ 6 files changed, 244 insertions(+), 28 deletions(-) create mode 100644 model/src/main/java/org/openremote/model/teltonika/TeltonikaConfiguration.java diff --git a/manager/src/main/java/org/openremote/manager/custom/TeltonikaMQTTHandler.java b/manager/src/main/java/org/openremote/manager/custom/TeltonikaMQTTHandler.java index d6f59bd..e9817d6 100644 --- a/manager/src/main/java/org/openremote/manager/custom/TeltonikaMQTTHandler.java +++ b/manager/src/main/java/org/openremote/manager/custom/TeltonikaMQTTHandler.java @@ -5,6 +5,7 @@ import io.netty.buffer.ByteBuf; import io.netty.handler.codec.mqtt.MqttQoS; import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection; +import org.apache.commons.lang3.NotImplementedException; import org.keycloak.KeycloakSecurityContext; import org.openremote.container.timer.TimerService; import org.openremote.container.util.UniqueIdentifierGenerator; @@ -17,23 +18,21 @@ import org.openremote.manager.security.ManagerKeycloakIdentityProvider; import org.openremote.model.Container; import org.openremote.model.asset.Asset; +import org.openremote.model.asset.AssetEvent; import org.openremote.model.asset.AssetFilter; +import org.openremote.model.asset.impl.EnergyOptimisationAsset; import org.openremote.model.attribute.*; import org.openremote.model.custom.*; import org.openremote.model.datapoint.AssetDatapoint; -import org.openremote.model.geo.GeoJSONPoint; import org.openremote.model.query.AssetQuery; import org.openremote.model.query.filter.AttributePredicate; import org.openremote.model.query.filter.NumberPredicate; import org.openremote.model.query.filter.ParentPredicate; +import org.openremote.model.query.filter.RealmPredicate; import org.openremote.model.syslog.SyslogCategory; -import org.openremote.model.teltonika.IMEIValidator; -import org.openremote.model.teltonika.TeltonikaDataPayload; -import org.openremote.model.teltonika.TeltonikaParameter; -import org.openremote.model.teltonika.TeltonikaResponsePayload; +import org.openremote.model.teltonika.*; import org.openremote.model.value.AttributeDescriptor; import org.openremote.model.value.ValueType; -import scala.collection.immutable.Stream; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -67,14 +66,14 @@ public TeltonikaDevice(Topic topic) { } } + // Ideally, these should be in the Configuration assets, but because I cannot reboot a handler, I cannot change the topics to which the handler handles/subscribes to. + private static final String TELTONIKA_DEVICE_RECEIVE_TOPIC = "data"; private static final String TELTONIKA_DEVICE_SEND_TOPIC = "commands"; private static final String TELTONIKA_DEVICE_TOKEN = "teltonika"; private static final String TELTONIKA_DEVICE_SEND_COMMAND_ATTRIBUTE_NAME = "sendToDevice"; private static final String TELTONIKA_DEVICE_RECEIVE_COMMAND_ATTRIBUTE_NAME = "response"; - private static final boolean CHECK_FOR_VALID_IMEI = false; - private static final Logger LOG = SyslogCategory.getLogger(API, TeltonikaMQTTHandler.class); protected AssetStorageService assetStorageService; @@ -86,6 +85,7 @@ public TeltonikaDevice(Topic topic) { protected final ConcurrentMap connectionSubscriberInfoMap = new ConcurrentHashMap<>(); + /** * Indicates if this handler will handle the specified topic; independent of whether it is a publish or subscribe. * Should generally check the third token (index 2) onwards unless {@link #handlesTopic} has been overridden. @@ -93,7 +93,36 @@ public TeltonikaDevice(Topic topic) { */ @Override protected boolean topicMatches(Topic topic) { - return TELTONIKA_DEVICE_TOKEN.equalsIgnoreCase(topicTokenIndexToString(topic, 2)); + return TELTONIKA_DEVICE_TOKEN.equalsIgnoreCase(topicTokenIndexToString(topic, 2)) && getConfig().getEnabled(); + } + + private TeltonikaConfiguration getConfig() { + List masterAssets = assetStorageService.findAll( + new AssetQuery() + .types(TeltonikaConfigurationAsset.class) + .realm(new RealmPredicate("master")) + + ) + .stream() + .map(asset -> (TeltonikaConfigurationAsset) asset) + .toList(); + + if (masterAssets.size() != 1) { + getLogger().severe("More than 1 Master Teltonika configurations found! Shutting down."); + } + + List modelAssets = assetStorageService.findAll( + new AssetQuery() + .types(TeltonikaModelConfigurationAsset.class) + .realm(new RealmPredicate("master")) + .parents(new ParentPredicate(masterAssets.get(0).getId())) + ) + .stream() + .map(asset -> (TeltonikaModelConfigurationAsset) asset) + .toList(); + + return new TeltonikaConfiguration(masterAssets.get(0), modelAssets); + } @Override @@ -107,12 +136,15 @@ public void start(Container container) throws Exception { timerService = container.getService(TimerService.class); DeviceParameterPath = container.isDevMode() ? Paths.get("../deployment/manager/fleet/FMC003.json") : Paths.get("/deployment/manager/fleet/FMC003.json"); if (!identityService.isKeycloakEnabled()) { - getLogger().warning("MQTT connections are supported when not using Keycloak identity provider, only for the Teltonika Telematics devices, until auto-provisioning is fully implemented."); isKeycloak = false; } else { isKeycloak = true; identityProvider = (ManagerKeycloakIdentityProvider) identityService.getIdentityProvider(); } + getLogger().warning("Anonymous MQTT connections are allowed, only for the Teltonika Telematics devices, until auto-provisioning is fully implemented or until Teltonika Telematics devices allow user-defined username and password MQTT login."); + + + List> assets = assetStorageService.findAll(new AssetQuery().types(TeltonikaModelConfigurationAsset.class)); @@ -125,11 +157,72 @@ public void start(Container container) throws Exception { } - + // Internal Subscription for the command attribute clientEventService.addInternalSubscription( AttributeEvent.class, null, this::handleAttributeMessage); + + //Internal Subscription for the Asset Configuration + + clientEventService.addInternalSubscription( + AttributeEvent.class, + null, + this::handleAssetConfigurationChange + ); + } + + private void handleAssetConfigurationChange(AttributeEvent attributeEvent) { +// throw new NotImplementedException(); + AssetFilter eventFilter = buildConfigurationAssetFilter(); + + if(eventFilter.apply(attributeEvent) == null) return; + + Asset asset = assetStorageService.find(attributeEvent.getAttributeRef().getId()); +// if (asset.getType() == ) + + if(Objects.equals(attributeEvent.getAttributeName(), TeltonikaModelConfigurationAsset.PARAMETER_MAP.getName())) return; + + if (Objects.equals(attributeEvent.getAttributeName(), TeltonikaModelConfigurationAsset.PARAMETER_DATA.getName())){ + TeltonikaParameter[] newParamList = (TeltonikaParameter[]) attributeEvent.getValue().orElseThrow(); + if(newParamList.length == 0) return; + getLogger().info("Model map configuration event: " + Arrays.toString(newParamList)); + TeltonikaModelConfigurationAsset modelAsset = (TeltonikaModelConfigurationAsset) asset; + modelAsset = modelAsset.setParameterData(newParamList); + AttributeEvent modificationEvent = new AttributeEvent( + asset.getId(), + TeltonikaModelConfigurationAsset.PARAMETER_MAP, + modelAsset.getParameterMap() + ); +// LOG.info("Publishing to client inbound queue: " + attribute.getName()); + assetProcessingService.sendAttributeEvent(modificationEvent); + + } + } + + /** + * Creates a filter for the AttributeEvents for all attributes of both Teltonika Configuration assets and Teltonika + * Model configuration assets. + * + * @return Attribute filter for all {@code TeltonikaConfigurationAsset} and {@code TeltonikaModelConfigurationAsset}. + */ + private AssetFilter buildConfigurationAssetFilter(){ + + TeltonikaConfiguration config = getConfig(); + config.getChildModelIDs(); + + + List modelAssets = config.getModelAssets(); + + TeltonikaConfigurationAsset masterAsset = config.getMasterAsset(); + + + List> allIds = new ArrayList<>(modelAssets); + allIds.add(masterAsset); + + AssetFilter event = new AssetFilter<>(); + event.setAssetIds((allIds.stream().map(Asset::getId).toArray(String[]::new))); + return event; } /** @@ -138,7 +231,7 @@ public void start(Container container) throws Exception { * @return AssetFilter of CarAssets that have both {@value TELTONIKA_DEVICE_RECEIVE_COMMAND_ATTRIBUTE_NAME} and * {@value TELTONIKA_DEVICE_SEND_COMMAND_ATTRIBUTE_NAME} as attributes. */ - private AssetFilter buildAssetFilter(){ + private AssetFilter buildCommandAssetFilter(){ List> assetsWithAttribute = assetStorageService .findAll(new AssetQuery().types(CarAsset.class) .attributeNames(TELTONIKA_DEVICE_SEND_COMMAND_ATTRIBUTE_NAME)); @@ -154,12 +247,9 @@ private AssetFilter buildAssetFilter(){ private void handleAttributeMessage(AttributeEvent event) { - AssetFilter eventFilter = buildAssetFilter(); + AssetFilter eventFilter = buildCommandAssetFilter(); - if(eventFilter.apply(event) == null) { - getLogger().info("eventFilter applied and the event is not for me: " + event); - return; - } + if(eventFilter.apply(event) == null) return; // If this is not an AttributeEvent that updates a TELTONIKA_DEVICE_SEND_COMMAND_ATTRIBUTE_NAME field, ignore if (!Objects.equals(event.getAttributeName(), TELTONIKA_DEVICE_SEND_COMMAND_ATTRIBUTE_NAME)) return; @@ -259,7 +349,7 @@ public boolean canSubscribe(RemotingConnection connection, KeycloakSecurityConte return Objects.equals(topic.getTokens().get(2), TELTONIKA_DEVICE_TOKEN) && - (CHECK_FOR_VALID_IMEI ? IMEIValidator.isValidIMEI(imeiValue) : true) && + (getConfig().getCheckForImei() ? IMEIValidator.isValidIMEI(imeiValue) : true) && ( Objects.equals(topic.getTokens().get(4), TELTONIKA_DEVICE_RECEIVE_TOPIC) || Objects.equals(topic.getTokens().get(4), TELTONIKA_DEVICE_SEND_TOPIC) @@ -284,13 +374,13 @@ public boolean canPublish(RemotingConnection connection, KeycloakSecurityContext } public void onSubscribe(RemotingConnection connection, Topic topic) { -// getLogger().info("CONNECT: Device "+topic.tokens.get(1)+" connected to topic "+topic+"."); + getLogger().info("CONNECT: Device "+topic.getTokens().get(1)+" connected to topic "+topic+"."); connectionSubscriberInfoMap.put(topic.getTokens().get(3), new TeltonikaMQTTHandler.TeltonikaDevice(topic)); } @Override public void onUnsubscribe(RemotingConnection connection, Topic topic) { -// getLogger().info("DISCONNECT: Device "+topic.tokens.get(1)+" disconnected from topic "+topic+"."); + getLogger().info("DISCONNECT: Device "+topic.getTokens().get(1)+" disconnected from topic "+topic+"."); connectionSubscriberInfoMap.remove(topic.getTokens().get(3)); } @@ -417,6 +507,7 @@ private void createNewAsset(String newDeviceId, String newDeviceImei, String rea CarAsset testAsset = new CarAsset("Teltonika Asset "+newDeviceImei) .setRealm(realm) + .setModelNumber(getConfig().getDefaultModelNumber()) .setId(newDeviceId); testAsset.getAttributes().add(new Attribute<>(CarAsset.IMEI, newDeviceImei)); @@ -457,12 +548,9 @@ private AttributeMap getAttributesFromPayload(String payloadContent) throws Json // Add each element to the HashMap, with the key being the unique parameter ID and the parameter // being the value - params = Arrays.stream(mapper.readValue(getParameterFileString(), TeltonikaParameter[].class)).collect(Collectors.toMap( - TeltonikaParameter::getPropertyId, // Key Mapper - param -> param, // Value Mapper - (existing, replacement) -> replacement, // Merge Function - HashMap::new - )); + TeltonikaConfiguration config = getConfig(); + + params = config.getParameterMap().get(config.getDefaultModelNumber()); getLogger().info("Parsed "+params.size()+" Teltonika Parameters"); @@ -599,6 +687,7 @@ private Optional> assetChangedTripState(Attribute previousValue, return Optional.empty(); } + //TODO: Convert this into a static field to be used everywhere Attribute tripAttr = new Attribute<>("LastTripStartedAndEndedAt", CustomValueTypes.ASSET_STATE_DURATION, new AssetStateDuration( new Timestamp(StateChangeAssetDatapoint.getTimestamp()), new Timestamp(previousValue.getTimestamp().get()) @@ -690,8 +779,8 @@ private void initializeConfigurationAssets(){ TeltonikaModelConfigurationAsset fmc003 = new TeltonikaModelConfigurationAsset("FMC003"); rootConfig.setEnabled(true); - rootConfig.setWhitelist(new String[0]); rootConfig.setCheckForImei(false); + rootConfig.setDefaultModelNumber("FMC003"); fmc003.setModelNumber("FMC003"); ObjectMapper mapper = new ObjectMapper(); 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 3fae91d..8886c58 100644 --- a/model/src/main/java/org/openremote/model/custom/CarAsset.java +++ b/model/src/main/java/org/openremote/model/custom/CarAsset.java @@ -14,6 +14,7 @@ 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); @@ -41,5 +42,10 @@ public Optional getModelYear() { 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; + } } diff --git a/model/src/main/java/org/openremote/model/custom/CustomValueTypes.java b/model/src/main/java/org/openremote/model/custom/CustomValueTypes.java index be3c626..7c546a4 100644 --- a/model/src/main/java/org/openremote/model/custom/CustomValueTypes.java +++ b/model/src/main/java/org/openremote/model/custom/CustomValueTypes.java @@ -3,7 +3,12 @@ import org.openremote.model.teltonika.TeltonikaParameter; import org.openremote.model.value.ValueDescriptor; +import java.util.HashMap; + public class CustomValueTypes { public static final ValueDescriptor ASSET_STATE_DURATION = new ValueDescriptor<>("AssetStateDuration", AssetStateDuration.class); public static final ValueDescriptor TELTONIKA_PARAMETER = new ValueDescriptor<>("TeltonikaParameter", TeltonikaParameter.class); + public static class TeltonikaParameterMap extends HashMap {} + + public static final ValueDescriptor TELTONIKA_PARAMETER_MAP = new ValueDescriptor<>("TeltonikaParameterMap", TeltonikaParameterMap.class); } diff --git a/model/src/main/java/org/openremote/model/custom/TeltonikaConfigurationAsset.java b/model/src/main/java/org/openremote/model/custom/TeltonikaConfigurationAsset.java index 823e7cd..b803809 100644 --- a/model/src/main/java/org/openremote/model/custom/TeltonikaConfigurationAsset.java +++ b/model/src/main/java/org/openremote/model/custom/TeltonikaConfigurationAsset.java @@ -12,6 +12,7 @@ public class TeltonikaConfigurationAsset extends Asset WHITELIST = new AttributeDescriptor<>("deviceIMEIWhitelist", ValueType.TEXT.asArray()).withOptional(true); public static final AttributeDescriptor ENABLED = new AttributeDescriptor<>("Enabled", ValueType.BOOLEAN); public static final AttributeDescriptor CHECK_FOR_IMEI = new AttributeDescriptor<>("CheckForValidIMEI", ValueType.BOOLEAN); + public static final AttributeDescriptor DEFAULT_MODEL_NUMBER = new AttributeDescriptor<>("defaultModelNumber", ValueType.TEXT); public static final AssetDescriptor DESCRIPTOR = new AssetDescriptor<>("gear", null, TeltonikaConfigurationAsset.class); @@ -38,4 +39,9 @@ public TeltonikaConfigurationAsset setCheckForImei(boolean enabled) { getAttributes().getOrCreate(CHECK_FOR_IMEI).setValue(enabled); return this; } + + public TeltonikaConfigurationAsset setDefaultModelNumber(String value) { + getAttributes().getOrCreate(DEFAULT_MODEL_NUMBER).setValue(value); + return this; + } } diff --git a/model/src/main/java/org/openremote/model/custom/TeltonikaModelConfigurationAsset.java b/model/src/main/java/org/openremote/model/custom/TeltonikaModelConfigurationAsset.java index 06aac74..21bb401 100644 --- a/model/src/main/java/org/openremote/model/custom/TeltonikaModelConfigurationAsset.java +++ b/model/src/main/java/org/openremote/model/custom/TeltonikaModelConfigurationAsset.java @@ -1,18 +1,29 @@ package org.openremote.model.custom; import jakarta.persistence.Entity; -import net.fortuna.ical4j.model.property.Geo; import org.openremote.model.asset.Asset; import org.openremote.model.asset.AssetDescriptor; +import org.openremote.model.attribute.Attribute; +import org.openremote.model.attribute.MetaItem; +import org.openremote.model.attribute.MetaMap; import org.openremote.model.geo.GeoJSONPoint; import org.openremote.model.teltonika.TeltonikaParameter; import org.openremote.model.value.AttributeDescriptor; +import org.openremote.model.value.MetaItemType; import org.openremote.model.value.ValueType; +import java.util.Arrays; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + @Entity public class TeltonikaModelConfigurationAsset extends Asset { public static final AttributeDescriptor MODEL_NUMBER = new AttributeDescriptor<>("modelNumber", ValueType.TEXT); public static final AttributeDescriptor PARAMETER_DATA = new AttributeDescriptor<>("TeltonikaParameterData", CustomValueTypes.TELTONIKA_PARAMETER.asArray()); + + public static final AttributeDescriptor PARAMETER_MAP = new AttributeDescriptor<>("TeltonikaParameterMap", CustomValueTypes.TELTONIKA_PARAMETER_MAP) + .withMeta(new MetaMap(Map.of(MetaItemType.READ_ONLY.getName(), new MetaItem<>(MetaItemType.READ_ONLY, true)))); public static final AssetDescriptor DESCRIPTOR = new AssetDescriptor<>("switch", null, TeltonikaModelConfigurationAsset.class); protected TeltonikaModelConfigurationAsset(){} @@ -29,7 +40,33 @@ public TeltonikaModelConfigurationAsset setModelNumber(String name) { } public TeltonikaModelConfigurationAsset setParameterData(TeltonikaParameter[] data) { + //Get TeltonikaParameter array, cast to Map with parameter ID as key, save to PARAMETER_MAP, remove from PARAMETER_DATA getAttributes().getOrCreate(PARAMETER_DATA).setValue(data); + CustomValueTypes.TeltonikaParameterMap map = Arrays.stream(data).collect(Collectors.toMap( + TeltonikaParameter::getPropertyId, // Key Mapper + param -> param, // Value Mapper + (existing, replacement) -> replacement, // Merge Function + CustomValueTypes.TeltonikaParameterMap::new + )); + + getAttributes().getOrCreate(PARAMETER_MAP).setValue(map); + + // + + + return this; } + + public Optional getModelNumber(){ + return getAttributes().getValue(MODEL); + } + + public CustomValueTypes.TeltonikaParameterMap getParameterMap() { + Optional> map = getAttributes().get(PARAMETER_MAP); + + return map.flatMap(Attribute::getValue) + .orElse(null); // or provide a default value other than null, if appropriate + } + } diff --git a/model/src/main/java/org/openremote/model/teltonika/TeltonikaConfiguration.java b/model/src/main/java/org/openremote/model/teltonika/TeltonikaConfiguration.java new file mode 100644 index 0000000..b7ab270 --- /dev/null +++ b/model/src/main/java/org/openremote/model/teltonika/TeltonikaConfiguration.java @@ -0,0 +1,73 @@ +package org.openremote.model.teltonika; + +import org.openremote.model.custom.CustomValueTypes; +import org.openremote.model.custom.TeltonikaConfigurationAsset; +import org.openremote.model.custom.TeltonikaModelConfigurationAsset; + +import java.util.HashMap; +import java.util.List; +import java.util.stream.Collectors; + +public class TeltonikaConfiguration { + public TeltonikaConfigurationAsset getMasterAsset() { + return masterAsset; + } + + TeltonikaConfigurationAsset masterAsset; + /** + * Maps Model Number to Map of parameters: {@code [{"FMCOO3": {}...]}} + */ + HashMap parameterMap; + + public List getModelAssets() { + return modelAssets; + } + + private List modelAssets; + + public TeltonikaConfiguration(TeltonikaConfigurationAsset master, List models){ + if (master == null) return; + if (models.isEmpty()) return; + + masterAsset = master; + + parameterMap = models.stream().collect(Collectors.toMap( + val ->val.getAttributes().get(TeltonikaModelConfigurationAsset.MODEL_NUMBER).get().getValue().get(), // Key Mapper + TeltonikaModelConfigurationAsset::getParameterMap, // Value Mapper + (existing, replacement) -> replacement, // Merge Function + HashMap::new + )); + + modelAssets = models; + + } + + @Override + public String toString() { + return "TeltonikaConfiguration{" + + "masterAsset=" + masterAsset + + ", parameterMap=" + parameterMap + + ", modelAssets=" + modelAssets + + '}'; + } + + public List getChildModelIDs(){ + return modelAssets.stream().map(TeltonikaModelConfigurationAsset::getId).collect(Collectors.toList()); + } + + public Boolean getEnabled(){ + return masterAsset.getAttribute(TeltonikaConfigurationAsset.ENABLED).get().getValue().get(); + } + + public boolean getCheckForImei() { + return masterAsset.getAttribute(TeltonikaConfigurationAsset.CHECK_FOR_IMEI).get().getValue().get(); + } + + public String getDefaultModelNumber() { + return masterAsset.getAttribute(TeltonikaConfigurationAsset.DEFAULT_MODEL_NUMBER).get().getValue().get(); + } + + public HashMap getParameterMap() { + return parameterMap; + } +} From 35adc5de3b208b9b6e0ecdc6eeacffce71bcb682 Mon Sep 17 00:00:00 2001 From: pankalog Date: Mon, 8 Jan 2024 15:59:53 +0100 Subject: [PATCH 7/7] Optimize imports --- .../org/openremote/manager/custom/TeltonikaMQTTHandler.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/manager/src/main/java/org/openremote/manager/custom/TeltonikaMQTTHandler.java b/manager/src/main/java/org/openremote/manager/custom/TeltonikaMQTTHandler.java index e9817d6..88d12ce 100644 --- a/manager/src/main/java/org/openremote/manager/custom/TeltonikaMQTTHandler.java +++ b/manager/src/main/java/org/openremote/manager/custom/TeltonikaMQTTHandler.java @@ -5,7 +5,6 @@ import io.netty.buffer.ByteBuf; import io.netty.handler.codec.mqtt.MqttQoS; import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection; -import org.apache.commons.lang3.NotImplementedException; import org.keycloak.KeycloakSecurityContext; import org.openremote.container.timer.TimerService; import org.openremote.container.util.UniqueIdentifierGenerator; @@ -18,9 +17,7 @@ import org.openremote.manager.security.ManagerKeycloakIdentityProvider; import org.openremote.model.Container; import org.openremote.model.asset.Asset; -import org.openremote.model.asset.AssetEvent; import org.openremote.model.asset.AssetFilter; -import org.openremote.model.asset.impl.EnergyOptimisationAsset; import org.openremote.model.attribute.*; import org.openremote.model.custom.*; import org.openremote.model.datapoint.AssetDatapoint; @@ -45,7 +42,6 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.logging.Logger; -import java.util.stream.Collectors; import static org.openremote.model.syslog.SyslogCategory.API; import static org.openremote.model.value.MetaItemType.*;