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 69bd5b7..b8fd5b9 100644 --- a/manager/src/main/java/org/openremote/manager/custom/TeltonikaMQTTHandler.java +++ b/manager/src/main/java/org/openremote/manager/custom/TeltonikaMQTTHandler.java @@ -19,18 +19,15 @@ 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.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.MetaItemType; import org.openremote.model.value.ValueType; @@ -46,7 +43,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.*; @@ -67,14 +63,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 +82,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 +90,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,17 +133,93 @@ 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)); + if(assets.isEmpty()) { + getLogger().severe("No Teltonika configuration assets found! Creating defaults..."); + + initializeConfigurationAssets(); + + getLogger().info("Created default configuration"); + } + + + // 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; } /** @@ -126,7 +228,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)); @@ -142,12 +244,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; @@ -162,7 +261,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(); @@ -247,7 +346,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) @@ -272,13 +371,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)); } @@ -338,6 +437,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{ @@ -406,6 +506,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.getAttribute(CarAsset.LOCATION).ifPresentOrElse(attr -> attr.addMeta(new MetaItem<>(STORE_DATA_POINTS, true)), () -> {getLogger().warning("Couldn't find CarAsset.LOCATION");}); @@ -461,12 +562,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( - param -> String.valueOf(param.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"); @@ -618,6 +716,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()) @@ -680,7 +779,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 @@ -709,6 +808,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.setCheckForImei(false); + rootConfig.setDefaultModelNumber("FMC003"); + + 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 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..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,12 +14,12 @@ 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); - /** - * 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); @@ -42,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 a550a1a..7c546a4 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,14 @@ package org.openremote.model.custom; +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 new file mode 100644 index 0000000..b803809 --- /dev/null +++ b/model/src/main/java/org/openremote/model/custom/TeltonikaConfigurationAsset.java @@ -0,0 +1,47 @@ +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 AttributeDescriptor DEFAULT_MODEL_NUMBER = new AttributeDescriptor<>("defaultModelNumber", ValueType.TEXT); + + 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; + } + + 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 new file mode 100644 index 0000000..21bb401 --- /dev/null +++ b/model/src/main/java/org/openremote/model/custom/TeltonikaModelConfigurationAsset.java @@ -0,0 +1,72 @@ +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.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(){} + + 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) { + //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; + } +} 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; 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"