Commit e86c07599c54d434d44196133f2e2322e0008752
Merge branch 'develop/3.2' of https://github.com/thingsboard/thingsboard into fe…
…ature/device-provision-3.2-onlyProfileVersion
Showing
10 changed files
with
98 additions
and
58 deletions
@@ -282,6 +282,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement | @@ -282,6 +282,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement | ||
282 | } | 282 | } |
283 | } | 283 | } |
284 | 284 | ||
285 | + | ||
286 | + | ||
285 | private <T> TransportServiceCallback<Void> getPubAckCallback(final ChannelHandlerContext ctx, final int msgId, final T msg) { | 287 | private <T> TransportServiceCallback<Void> getPubAckCallback(final ChannelHandlerContext ctx, final int msgId, final T msg) { |
286 | return new TransportServiceCallback<Void>() { | 288 | return new TransportServiceCallback<Void>() { |
287 | @Override | 289 | @Override |
@@ -34,7 +34,7 @@ public class JacksonUtil { | @@ -34,7 +34,7 @@ public class JacksonUtil { | ||
34 | return fromValue != null ? OBJECT_MAPPER.convertValue(fromValue, toValueType) : null; | 34 | return fromValue != null ? OBJECT_MAPPER.convertValue(fromValue, toValueType) : null; |
35 | } catch (IllegalArgumentException e) { | 35 | } catch (IllegalArgumentException e) { |
36 | throw new IllegalArgumentException("The given object value: " | 36 | throw new IllegalArgumentException("The given object value: " |
37 | - + fromValue + " cannot be converted to " + toValueType); | 37 | + + fromValue + " cannot be converted to " + toValueType, e); |
38 | } | 38 | } |
39 | } | 39 | } |
40 | 40 | ||
@@ -43,7 +43,7 @@ public class JacksonUtil { | @@ -43,7 +43,7 @@ public class JacksonUtil { | ||
43 | return string != null ? OBJECT_MAPPER.readValue(string, clazz) : null; | 43 | return string != null ? OBJECT_MAPPER.readValue(string, clazz) : null; |
44 | } catch (IOException e) { | 44 | } catch (IOException e) { |
45 | throw new IllegalArgumentException("The given string value: " | 45 | throw new IllegalArgumentException("The given string value: " |
46 | - + string + " cannot be transformed to Json object"); | 46 | + + string + " cannot be transformed to Json object", e); |
47 | } | 47 | } |
48 | } | 48 | } |
49 | 49 | ||
@@ -52,7 +52,7 @@ public class JacksonUtil { | @@ -52,7 +52,7 @@ public class JacksonUtil { | ||
52 | return value != null ? OBJECT_MAPPER.writeValueAsString(value) : null; | 52 | return value != null ? OBJECT_MAPPER.writeValueAsString(value) : null; |
53 | } catch (JsonProcessingException e) { | 53 | } catch (JsonProcessingException e) { |
54 | throw new IllegalArgumentException("The given Json object value: " | 54 | throw new IllegalArgumentException("The given Json object value: " |
55 | - + value + " cannot be transformed to a String"); | 55 | + + value + " cannot be transformed to a String", e); |
56 | } | 56 | } |
57 | } | 57 | } |
58 | 58 | ||
@@ -71,7 +71,7 @@ public class JacksonUtil { | @@ -71,7 +71,7 @@ public class JacksonUtil { | ||
71 | return fromString(toString(value), (Class<T>) value.getClass()); | 71 | return fromString(toString(value), (Class<T>) value.getClass()); |
72 | } | 72 | } |
73 | 73 | ||
74 | - public static JsonNode valueToTree(Alarm alarm) { | 74 | + public static <T> JsonNode valueToTree(T alarm) { |
75 | return OBJECT_MAPPER.valueToTree(alarm); | 75 | return OBJECT_MAPPER.valueToTree(alarm); |
76 | } | 76 | } |
77 | } | 77 | } |
@@ -47,16 +47,14 @@ import java.util.concurrent.ExecutionException; | @@ -47,16 +47,14 @@ import java.util.concurrent.ExecutionException; | ||
47 | class DeviceProfileAlarmState { | 47 | class DeviceProfileAlarmState { |
48 | 48 | ||
49 | private final EntityId originator; | 49 | private final EntityId originator; |
50 | - private final DeviceProfileAlarm alarmDefinition; | 50 | + private DeviceProfileAlarm alarmDefinition; |
51 | private volatile Map<AlarmSeverity, AlarmRule> createRulesSortedBySeverityDesc; | 51 | private volatile Map<AlarmSeverity, AlarmRule> createRulesSortedBySeverityDesc; |
52 | private volatile Alarm currentAlarm; | 52 | private volatile Alarm currentAlarm; |
53 | private volatile boolean initialFetchDone; | 53 | private volatile boolean initialFetchDone; |
54 | 54 | ||
55 | public DeviceProfileAlarmState(EntityId originator, DeviceProfileAlarm alarmDefinition) { | 55 | public DeviceProfileAlarmState(EntityId originator, DeviceProfileAlarm alarmDefinition) { |
56 | this.originator = originator; | 56 | this.originator = originator; |
57 | - this.alarmDefinition = alarmDefinition; | ||
58 | - this.createRulesSortedBySeverityDesc = new TreeMap<>(Comparator.comparingInt(AlarmSeverity::ordinal)); | ||
59 | - this.createRulesSortedBySeverityDesc.putAll(alarmDefinition.getCreateRules()); | 57 | + this.updateState(alarmDefinition); |
60 | } | 58 | } |
61 | 59 | ||
62 | public void process(TbContext ctx, TbMsg msg, DeviceDataSnapshot data) throws ExecutionException, InterruptedException { | 60 | public void process(TbContext ctx, TbMsg msg, DeviceDataSnapshot data) throws ExecutionException, InterruptedException { |
@@ -111,6 +109,12 @@ class DeviceProfileAlarmState { | @@ -111,6 +109,12 @@ class DeviceProfileAlarmState { | ||
111 | ctx.tellNext(newMsg, relationType); | 109 | ctx.tellNext(newMsg, relationType); |
112 | } | 110 | } |
113 | 111 | ||
112 | + public void updateState(DeviceProfileAlarm alarm) { | ||
113 | + this.alarmDefinition = alarm; | ||
114 | + this.createRulesSortedBySeverityDesc = new TreeMap<>(Comparator.comparingInt(AlarmSeverity::ordinal)); | ||
115 | + this.createRulesSortedBySeverityDesc.putAll(alarmDefinition.getCreateRules()); | ||
116 | + } | ||
117 | + | ||
114 | private TbAlarmResult calculateAlarmResult(TbContext ctx, AlarmSeverity severity) { | 118 | private TbAlarmResult calculateAlarmResult(TbContext ctx, AlarmSeverity severity) { |
115 | if (currentAlarm != null) { | 119 | if (currentAlarm != null) { |
116 | currentAlarm.setEndTs(System.currentTimeMillis()); | 120 | currentAlarm.setEndTs(System.currentTimeMillis()); |
@@ -20,6 +20,7 @@ import lombok.Getter; | @@ -20,6 +20,7 @@ import lombok.Getter; | ||
20 | import org.thingsboard.server.common.data.DeviceProfile; | 20 | import org.thingsboard.server.common.data.DeviceProfile; |
21 | import org.thingsboard.server.common.data.device.profile.AlarmRule; | 21 | import org.thingsboard.server.common.data.device.profile.AlarmRule; |
22 | import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; | 22 | import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; |
23 | +import org.thingsboard.server.common.data.id.DeviceProfileId; | ||
23 | import org.thingsboard.server.common.data.query.EntityKey; | 24 | import org.thingsboard.server.common.data.query.EntityKey; |
24 | import org.thingsboard.server.common.data.query.KeyFilter; | 25 | import org.thingsboard.server.common.data.query.KeyFilter; |
25 | 26 | ||
@@ -55,4 +56,8 @@ class DeviceProfileState { | @@ -55,4 +56,8 @@ class DeviceProfileState { | ||
55 | } | 56 | } |
56 | } | 57 | } |
57 | } | 58 | } |
59 | + | ||
60 | + public DeviceProfileId getProfileId() { | ||
61 | + return deviceProfile.getId(); | ||
62 | + } | ||
58 | } | 63 | } |
@@ -20,8 +20,10 @@ import org.thingsboard.rule.engine.api.TbContext; | @@ -20,8 +20,10 @@ import org.thingsboard.rule.engine.api.TbContext; | ||
20 | import org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode; | 20 | import org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode; |
21 | import org.thingsboard.server.common.data.DataConstants; | 21 | import org.thingsboard.server.common.data.DataConstants; |
22 | import org.thingsboard.server.common.data.Device; | 22 | import org.thingsboard.server.common.data.Device; |
23 | +import org.thingsboard.server.common.data.DeviceProfile; | ||
23 | import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; | 24 | import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; |
24 | import org.thingsboard.server.common.data.id.DeviceId; | 25 | import org.thingsboard.server.common.data.id.DeviceId; |
26 | +import org.thingsboard.server.common.data.id.DeviceProfileId; | ||
25 | import org.thingsboard.server.common.data.id.EntityId; | 27 | import org.thingsboard.server.common.data.id.EntityId; |
26 | import org.thingsboard.server.common.data.kv.AttributeKvEntry; | 28 | import org.thingsboard.server.common.data.kv.AttributeKvEntry; |
27 | import org.thingsboard.server.common.data.kv.KvEntry; | 29 | import org.thingsboard.server.common.data.kv.KvEntry; |
@@ -40,20 +42,44 @@ import java.util.Set; | @@ -40,20 +42,44 @@ import java.util.Set; | ||
40 | import java.util.concurrent.ConcurrentHashMap; | 42 | import java.util.concurrent.ConcurrentHashMap; |
41 | import java.util.concurrent.ConcurrentMap; | 43 | import java.util.concurrent.ConcurrentMap; |
42 | import java.util.concurrent.ExecutionException; | 44 | import java.util.concurrent.ExecutionException; |
45 | +import java.util.stream.Collectors; | ||
43 | 46 | ||
44 | class DeviceState { | 47 | class DeviceState { |
45 | 48 | ||
49 | + private final DeviceId deviceId; | ||
46 | private DeviceProfileState deviceProfile; | 50 | private DeviceProfileState deviceProfile; |
47 | private DeviceDataSnapshot latestValues; | 51 | private DeviceDataSnapshot latestValues; |
48 | private final ConcurrentMap<String, DeviceProfileAlarmState> alarmStates = new ConcurrentHashMap<>(); | 52 | private final ConcurrentMap<String, DeviceProfileAlarmState> alarmStates = new ConcurrentHashMap<>(); |
49 | 53 | ||
50 | - public DeviceState(DeviceProfileState deviceProfile) { | 54 | + public DeviceState(DeviceId deviceId, DeviceProfileState deviceProfile) { |
55 | + this.deviceId = deviceId; | ||
51 | this.deviceProfile = deviceProfile; | 56 | this.deviceProfile = deviceProfile; |
52 | } | 57 | } |
53 | 58 | ||
59 | + public void updateProfile(TbContext ctx, DeviceProfile deviceProfile) throws ExecutionException, InterruptedException { | ||
60 | + Set<EntityKey> oldKeys = this.deviceProfile.getEntityKeys(); | ||
61 | + this.deviceProfile.updateDeviceProfile(deviceProfile); | ||
62 | + if (latestValues != null) { | ||
63 | + Set<EntityKey> keysToFetch = new HashSet<>(this.deviceProfile.getEntityKeys()); | ||
64 | + keysToFetch.removeAll(oldKeys); | ||
65 | + if (!keysToFetch.isEmpty()) { | ||
66 | + addEntityKeysToSnapshot(ctx, deviceId, keysToFetch, latestValues); | ||
67 | + } | ||
68 | + } | ||
69 | + Set<String> newAlarmStateIds = this.deviceProfile.getAlarmSettings().stream().map(DeviceProfileAlarm::getId).collect(Collectors.toSet()); | ||
70 | + alarmStates.keySet().removeIf(id -> !newAlarmStateIds.contains(id)); | ||
71 | + for (DeviceProfileAlarm alarm : this.deviceProfile.getAlarmSettings()) { | ||
72 | + if (alarmStates.containsKey(alarm.getId())) { | ||
73 | + alarmStates.get(alarm.getId()).updateState(alarm); | ||
74 | + } else { | ||
75 | + alarmStates.putIfAbsent(alarm.getId(), new DeviceProfileAlarmState(deviceId, alarm)); | ||
76 | + } | ||
77 | + } | ||
78 | + } | ||
79 | + | ||
54 | public void process(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException { | 80 | public void process(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException { |
55 | if (latestValues == null) { | 81 | if (latestValues == null) { |
56 | - latestValues = fetchLatestValues(ctx, msg.getOriginator()); | 82 | + latestValues = fetchLatestValues(ctx, deviceId); |
57 | } | 83 | } |
58 | if (msg.getType().equals(SessionMsgType.POST_TELEMETRY_REQUEST.name())) { | 84 | if (msg.getType().equals(SessionMsgType.POST_TELEMETRY_REQUEST.name())) { |
59 | processTelemetry(ctx, msg); | 85 | processTelemetry(ctx, msg); |
@@ -69,7 +95,7 @@ class DeviceState { | @@ -69,7 +95,7 @@ class DeviceState { | ||
69 | List<KvEntry> data = entry.getValue(); | 95 | List<KvEntry> data = entry.getValue(); |
70 | latestValues = merge(latestValues, ts, data); | 96 | latestValues = merge(latestValues, ts, data); |
71 | for (DeviceProfileAlarm alarm : deviceProfile.getAlarmSettings()) { | 97 | for (DeviceProfileAlarm alarm : deviceProfile.getAlarmSettings()) { |
72 | - DeviceProfileAlarmState alarmState = alarmStates.computeIfAbsent(alarm.getId(), a -> new DeviceProfileAlarmState(msg.getOriginator(), alarm)); | 98 | + DeviceProfileAlarmState alarmState = alarmStates.computeIfAbsent(alarm.getId(), a -> new DeviceProfileAlarmState(deviceId, alarm)); |
73 | alarmState.process(ctx, msg, latestValues); | 99 | alarmState.process(ctx, msg, latestValues); |
74 | } | 100 | } |
75 | } | 101 | } |
@@ -85,8 +111,13 @@ class DeviceState { | @@ -85,8 +111,13 @@ class DeviceState { | ||
85 | } | 111 | } |
86 | 112 | ||
87 | private DeviceDataSnapshot fetchLatestValues(TbContext ctx, EntityId originator) throws ExecutionException, InterruptedException { | 113 | private DeviceDataSnapshot fetchLatestValues(TbContext ctx, EntityId originator) throws ExecutionException, InterruptedException { |
88 | - DeviceDataSnapshot result = new DeviceDataSnapshot(deviceProfile.getEntityKeys()); | 114 | + Set<EntityKey> entityKeysToFetch = deviceProfile.getEntityKeys(); |
115 | + DeviceDataSnapshot result = new DeviceDataSnapshot(entityKeysToFetch); | ||
116 | + addEntityKeysToSnapshot(ctx, originator, entityKeysToFetch, result); | ||
117 | + return result; | ||
118 | + } | ||
89 | 119 | ||
120 | + private void addEntityKeysToSnapshot(TbContext ctx, EntityId originator, Set<EntityKey> entityKeysToFetch, DeviceDataSnapshot result) throws InterruptedException, ExecutionException { | ||
90 | Set<String> serverAttributeKeys = new HashSet<>(); | 121 | Set<String> serverAttributeKeys = new HashSet<>(); |
91 | Set<String> clientAttributeKeys = new HashSet<>(); | 122 | Set<String> clientAttributeKeys = new HashSet<>(); |
92 | Set<String> sharedAttributeKeys = new HashSet<>(); | 123 | Set<String> sharedAttributeKeys = new HashSet<>(); |
@@ -94,7 +125,7 @@ class DeviceState { | @@ -94,7 +125,7 @@ class DeviceState { | ||
94 | Set<String> latestTsKeys = new HashSet<>(); | 125 | Set<String> latestTsKeys = new HashSet<>(); |
95 | 126 | ||
96 | Device device = null; | 127 | Device device = null; |
97 | - for (EntityKey entityKey : deviceProfile.getEntityKeys()) { | 128 | + for (EntityKey entityKey : entityKeysToFetch) { |
98 | String key = entityKey.getKey(); | 129 | String key = entityKey.getKey(); |
99 | switch (entityKey.getType()) { | 130 | switch (entityKey.getType()) { |
100 | case SERVER_ATTRIBUTE: | 131 | case SERVER_ATTRIBUTE: |
@@ -159,8 +190,6 @@ class DeviceState { | @@ -159,8 +190,6 @@ class DeviceState { | ||
159 | addToSnapshot(result, commonAttributeKeys, | 190 | addToSnapshot(result, commonAttributeKeys, |
160 | ctx.getAttributesService().find(ctx.getTenantId(), originator, DataConstants.SERVER_SCOPE, serverAttributeKeys).get()); | 191 | ctx.getAttributesService().find(ctx.getTenantId(), originator, DataConstants.SERVER_SCOPE, serverAttributeKeys).get()); |
161 | } | 192 | } |
162 | - | ||
163 | - return result; | ||
164 | } | 193 | } |
165 | 194 | ||
166 | private void addToSnapshot(DeviceDataSnapshot snapshot, Set<String> commonAttributeKeys, List<AttributeKvEntry> data) { | 195 | private void addToSnapshot(DeviceDataSnapshot snapshot, Set<String> commonAttributeKeys, List<AttributeKvEntry> data) { |
@@ -192,4 +221,8 @@ class DeviceState { | @@ -192,4 +221,8 @@ class DeviceState { | ||
192 | } | 221 | } |
193 | } | 222 | } |
194 | 223 | ||
224 | + public DeviceProfileId getProfileId() { | ||
225 | + return deviceProfile.getProfileId(); | ||
226 | + } | ||
227 | + | ||
195 | } | 228 | } |
@@ -16,15 +16,6 @@ | @@ -16,15 +16,6 @@ | ||
16 | package org.thingsboard.rule.engine.profile; | 16 | package org.thingsboard.rule.engine.profile; |
17 | 17 | ||
18 | import lombok.extern.slf4j.Slf4j; | 18 | import lombok.extern.slf4j.Slf4j; |
19 | -import org.apache.commons.lang3.BooleanUtils; | ||
20 | -import org.apache.kafka.clients.producer.KafkaProducer; | ||
21 | -import org.apache.kafka.clients.producer.Producer; | ||
22 | -import org.apache.kafka.clients.producer.ProducerConfig; | ||
23 | -import org.apache.kafka.clients.producer.ProducerRecord; | ||
24 | -import org.apache.kafka.clients.producer.RecordMetadata; | ||
25 | -import org.apache.kafka.common.header.Headers; | ||
26 | -import org.apache.kafka.common.header.internals.RecordHeader; | ||
27 | -import org.apache.kafka.common.header.internals.RecordHeaders; | ||
28 | import org.thingsboard.rule.engine.api.EmptyNodeConfiguration; | 19 | import org.thingsboard.rule.engine.api.EmptyNodeConfiguration; |
29 | import org.thingsboard.rule.engine.api.RuleEngineDeviceProfileCache; | 20 | import org.thingsboard.rule.engine.api.RuleEngineDeviceProfileCache; |
30 | import org.thingsboard.rule.engine.api.RuleNode; | 21 | import org.thingsboard.rule.engine.api.RuleNode; |
@@ -32,22 +23,14 @@ import org.thingsboard.rule.engine.api.TbContext; | @@ -32,22 +23,14 @@ import org.thingsboard.rule.engine.api.TbContext; | ||
32 | import org.thingsboard.rule.engine.api.TbNode; | 23 | import org.thingsboard.rule.engine.api.TbNode; |
33 | import org.thingsboard.rule.engine.api.TbNodeConfiguration; | 24 | import org.thingsboard.rule.engine.api.TbNodeConfiguration; |
34 | import org.thingsboard.rule.engine.api.TbNodeException; | 25 | import org.thingsboard.rule.engine.api.TbNodeException; |
35 | -import org.thingsboard.rule.engine.api.TbRelationTypes; | ||
36 | -import org.thingsboard.rule.engine.api.util.TbNodeUtils; | ||
37 | -import org.thingsboard.rule.engine.kafka.TbKafkaNodeConfiguration; | ||
38 | import org.thingsboard.server.common.data.DeviceProfile; | 26 | import org.thingsboard.server.common.data.DeviceProfile; |
39 | import org.thingsboard.server.common.data.EntityType; | 27 | import org.thingsboard.server.common.data.EntityType; |
40 | import org.thingsboard.server.common.data.id.DeviceId; | 28 | import org.thingsboard.server.common.data.id.DeviceId; |
41 | -import org.thingsboard.server.common.data.id.EntityId; | ||
42 | import org.thingsboard.server.common.data.plugin.ComponentType; | 29 | import org.thingsboard.server.common.data.plugin.ComponentType; |
43 | import org.thingsboard.server.common.msg.TbMsg; | 30 | import org.thingsboard.server.common.msg.TbMsg; |
44 | -import org.thingsboard.server.common.msg.TbMsgMetaData; | 31 | +import org.thingsboard.server.dao.util.mapping.JacksonUtil; |
45 | 32 | ||
46 | -import java.nio.charset.Charset; | ||
47 | -import java.nio.charset.StandardCharsets; | ||
48 | -import java.util.HashMap; | ||
49 | import java.util.Map; | 33 | import java.util.Map; |
50 | -import java.util.Properties; | ||
51 | import java.util.concurrent.ConcurrentHashMap; | 34 | import java.util.concurrent.ConcurrentHashMap; |
52 | import java.util.concurrent.ExecutionException; | 35 | import java.util.concurrent.ExecutionException; |
53 | 36 | ||
@@ -76,7 +59,6 @@ public class TbDeviceProfileNode implements TbNode { | @@ -76,7 +59,6 @@ public class TbDeviceProfileNode implements TbNode { | ||
76 | /** | 59 | /** |
77 | * TODO: | 60 | * TODO: |
78 | * 1. Duration in the alarm conditions; | 61 | * 1. Duration in the alarm conditions; |
79 | - * 2. Update of the Profile (rules); | ||
80 | * 3. Update of the Device attributes (client, server and shared); | 62 | * 3. Update of the Device attributes (client, server and shared); |
81 | * 4. Dynamic values evaluation; | 63 | * 4. Dynamic values evaluation; |
82 | */ | 64 | */ |
@@ -86,26 +68,37 @@ public class TbDeviceProfileNode implements TbNode { | @@ -86,26 +68,37 @@ public class TbDeviceProfileNode implements TbNode { | ||
86 | EntityType originatorType = msg.getOriginator().getEntityType(); | 68 | EntityType originatorType = msg.getOriginator().getEntityType(); |
87 | if (EntityType.DEVICE.equals(originatorType)) { | 69 | if (EntityType.DEVICE.equals(originatorType)) { |
88 | DeviceId deviceId = new DeviceId(msg.getOriginator().getId()); | 70 | DeviceId deviceId = new DeviceId(msg.getOriginator().getId()); |
89 | - DeviceState deviceState = getOrCreateDeviceState(ctx, msg, deviceId); | ||
90 | - if (deviceState != null) { | ||
91 | - deviceState.process(ctx, msg); | 71 | + if (msg.getType().equals("ENTITY_UPDATED")) { |
72 | + //TODO: handle if device profile id has changed. | ||
92 | } else { | 73 | } else { |
93 | - ctx.tellFailure(msg, new IllegalStateException("Device profile for device [" + deviceId + "] not found!")); | 74 | + DeviceState deviceState = getOrCreateDeviceState(ctx, deviceId); |
75 | + if (deviceState != null) { | ||
76 | + deviceState.process(ctx, msg); | ||
77 | + } else { | ||
78 | + ctx.tellFailure(msg, new IllegalStateException("Device profile for device [" + deviceId + "] not found!")); | ||
79 | + } | ||
94 | } | 80 | } |
95 | } else if (EntityType.DEVICE_PROFILE.equals(originatorType)) { | 81 | } else if (EntityType.DEVICE_PROFILE.equals(originatorType)) { |
96 | - //TODO: check that the profile rule set was changed. If yes - invalidate the rules. | 82 | + if (msg.getType().equals("ENTITY_UPDATED")) { |
83 | + DeviceProfile deviceProfile = JacksonUtil.fromString(msg.getData(), DeviceProfile.class); | ||
84 | + for (DeviceState state : deviceStates.values()) { | ||
85 | + if (deviceProfile.getId().equals(state.getProfileId())) { | ||
86 | + state.updateProfile(ctx, deviceProfile); | ||
87 | + } | ||
88 | + } | ||
89 | + } | ||
97 | ctx.tellSuccess(msg); | 90 | ctx.tellSuccess(msg); |
98 | } else { | 91 | } else { |
99 | ctx.tellSuccess(msg); | 92 | ctx.tellSuccess(msg); |
100 | } | 93 | } |
101 | } | 94 | } |
102 | 95 | ||
103 | - private DeviceState getOrCreateDeviceState(TbContext ctx, TbMsg msg, DeviceId deviceId) { | 96 | + private DeviceState getOrCreateDeviceState(TbContext ctx, DeviceId deviceId) { |
104 | DeviceState deviceState = deviceStates.get(deviceId); | 97 | DeviceState deviceState = deviceStates.get(deviceId); |
105 | if (deviceState == null) { | 98 | if (deviceState == null) { |
106 | DeviceProfile deviceProfile = cache.get(ctx.getTenantId(), deviceId); | 99 | DeviceProfile deviceProfile = cache.get(ctx.getTenantId(), deviceId); |
107 | if (deviceProfile != null) { | 100 | if (deviceProfile != null) { |
108 | - deviceState = new DeviceState(new DeviceProfileState(deviceProfile)); | 101 | + deviceState = new DeviceState(deviceId, new DeviceProfileState(deviceProfile)); |
109 | deviceStates.put(deviceId, deviceState); | 102 | deviceStates.put(deviceId, deviceState); |
110 | } | 103 | } |
111 | } | 104 | } |
@@ -127,8 +127,8 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> { | @@ -127,8 +127,8 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> { | ||
127 | 127 | ||
128 | updateForm(entity: DeviceProfile) { | 128 | updateForm(entity: DeviceProfile) { |
129 | this.entityForm.patchValue({name: entity.name}); | 129 | this.entityForm.patchValue({name: entity.name}); |
130 | - this.entityForm.patchValue({type: entity.type}); | ||
131 | - this.entityForm.patchValue({transportType: entity.transportType}); | 130 | + this.entityForm.patchValue({type: entity.type}, {emitEvent: false}); |
131 | + this.entityForm.patchValue({transportType: entity.transportType}, {emitEvent: false}); | ||
132 | this.entityForm.patchValue({profileData: entity.profileData}); | 132 | this.entityForm.patchValue({profileData: entity.profileData}); |
133 | this.entityForm.patchValue({defaultRuleChainId: entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null}); | 133 | this.entityForm.patchValue({defaultRuleChainId: entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null}); |
134 | this.entityForm.patchValue({description: entity.description}); | 134 | this.entityForm.patchValue({description: entity.description}); |
@@ -17,11 +17,11 @@ | @@ -17,11 +17,11 @@ | ||
17 | --> | 17 | --> |
18 | <mat-form-field [formGroup]="selectRuleChainFormGroup" class="mat-block"> | 18 | <mat-form-field [formGroup]="selectRuleChainFormGroup" class="mat-block"> |
19 | <input matInput type="text" placeholder="{{ ruleChainLabel | translate }}" | 19 | <input matInput type="text" placeholder="{{ ruleChainLabel | translate }}" |
20 | - #entityInput | 20 | + #ruleChainInput |
21 | formControlName="ruleChainId" | 21 | formControlName="ruleChainId" |
22 | (focusin)="onFocus()" | 22 | (focusin)="onFocus()" |
23 | [required]="required" | 23 | [required]="required" |
24 | - [matAutocomplete]="entityAutocomplete"> | 24 | + [matAutocomplete]="ruleChainAutocomplete"> |
25 | <button *ngIf="selectRuleChainFormGroup.get('ruleChainId').value && !disabled" | 25 | <button *ngIf="selectRuleChainFormGroup.get('ruleChainId').value && !disabled" |
26 | type="button" | 26 | type="button" |
27 | matSuffix mat-button mat-icon-button aria-label="Clear" | 27 | matSuffix mat-button mat-icon-button aria-label="Clear" |
@@ -29,10 +29,10 @@ | @@ -29,10 +29,10 @@ | ||
29 | <mat-icon class="material-icons">close</mat-icon> | 29 | <mat-icon class="material-icons">close</mat-icon> |
30 | </button> | 30 | </button> |
31 | <mat-autocomplete class="tb-autocomplete" | 31 | <mat-autocomplete class="tb-autocomplete" |
32 | - #entityAutocomplete="matAutocomplete" | ||
33 | - [displayWith]="displayEntityFn"> | ||
34 | - <mat-option *ngFor="let entity of filteredRuleChains | async" [value]="entity"> | ||
35 | - <span [innerHTML]="entity.name | highlight:searchText"></span> | 32 | + #ruleChainAutocomplete="matAutocomplete" |
33 | + [displayWith]="displayRuleChainFn"> | ||
34 | + <mat-option *ngFor="let ruleChain of filteredRuleChains | async" [value]="ruleChain"> | ||
35 | + <span [innerHTML]="ruleChain.name | highlight:searchText"></span> | ||
36 | </mat-option> | 36 | </mat-option> |
37 | <mat-option *ngIf="!(filteredRuleChains | async)?.length" [value]="null" class="tb-not-found"> | 37 | <mat-option *ngIf="!(filteredRuleChains | async)?.length" [value]="null" class="tb-not-found"> |
38 | <div class="tb-not-found-content" (click)="$event.stopPropagation()"> | 38 | <div class="tb-not-found-content" (click)="$event.stopPropagation()"> |
@@ -66,7 +66,8 @@ export class RuleChainAutocompleteComponent implements ControlValueAccessor, OnI | @@ -66,7 +66,8 @@ export class RuleChainAutocompleteComponent implements ControlValueAccessor, OnI | ||
66 | @Input() | 66 | @Input() |
67 | disabled: boolean; | 67 | disabled: boolean; |
68 | 68 | ||
69 | - @ViewChild('entityInput', {static: true}) entityInput: ElementRef; | 69 | + @ViewChild('ruleChainInput', {static: true}) ruleChainInput: ElementRef; |
70 | + @ViewChild('ruleChainInput', {read: MatAutocompleteTrigger}) ruleChainAutocomplete: MatAutocompleteTrigger; | ||
70 | 71 | ||
71 | filteredRuleChains: Observable<Array<BaseData<EntityId>>>; | 72 | filteredRuleChains: Observable<Array<BaseData<EntityId>>>; |
72 | 73 | ||
@@ -117,9 +118,9 @@ export class RuleChainAutocompleteComponent implements ControlValueAccessor, OnI | @@ -117,9 +118,9 @@ export class RuleChainAutocompleteComponent implements ControlValueAccessor, OnI | ||
117 | ngAfterViewInit(): void {} | 118 | ngAfterViewInit(): void {} |
118 | 119 | ||
119 | getCurrentEntity(): BaseData<EntityId> | null { | 120 | getCurrentEntity(): BaseData<EntityId> | null { |
120 | - const currentEntity = this.selectRuleChainFormGroup.get('ruleChainId').value; | ||
121 | - if (currentEntity && typeof currentEntity !== 'string') { | ||
122 | - return currentEntity as BaseData<EntityId>; | 121 | + const currentRuleChain = this.selectRuleChainFormGroup.get('ruleChainId').value; |
122 | + if (currentRuleChain && typeof currentRuleChain !== 'string') { | ||
123 | + return currentRuleChain as BaseData<EntityId>; | ||
123 | } else { | 124 | } else { |
124 | return null; | 125 | return null; |
125 | } | 126 | } |
@@ -180,8 +181,8 @@ export class RuleChainAutocompleteComponent implements ControlValueAccessor, OnI | @@ -180,8 +181,8 @@ export class RuleChainAutocompleteComponent implements ControlValueAccessor, OnI | ||
180 | } | 181 | } |
181 | } | 182 | } |
182 | 183 | ||
183 | - displayEntityFn(entity?: BaseData<EntityId>): string | undefined { | ||
184 | - return entity ? entity.name : undefined; | 184 | + displayRuleChainFn(ruleChain?: BaseData<EntityId>): string | undefined { |
185 | + return ruleChain ? ruleChain.name : undefined; | ||
185 | } | 186 | } |
186 | 187 | ||
187 | fetchRuleChain(searchText?: string): Observable<Array<BaseData<EntityId>>> { | 188 | fetchRuleChain(searchText?: string): Observable<Array<BaseData<EntityId>>> { |
@@ -193,12 +194,14 @@ export class RuleChainAutocompleteComponent implements ControlValueAccessor, OnI | @@ -193,12 +194,14 @@ export class RuleChainAutocompleteComponent implements ControlValueAccessor, OnI | ||
193 | clear() { | 194 | clear() { |
194 | this.selectRuleChainFormGroup.get('ruleChainId').patchValue('', {emitEvent: true}); | 195 | this.selectRuleChainFormGroup.get('ruleChainId').patchValue('', {emitEvent: true}); |
195 | setTimeout(() => { | 196 | setTimeout(() => { |
196 | - this.entityInput.nativeElement.blur(); | ||
197 | - this.entityInput.nativeElement.focus(); | 197 | + this.ruleChainInput.nativeElement.blur(); |
198 | + this.ruleChainInput.nativeElement.focus(); | ||
198 | }, 0); | 199 | }, 0); |
199 | } | 200 | } |
200 | 201 | ||
201 | createDefaultRuleChain($event: Event, ruleChainName: string) { | 202 | createDefaultRuleChain($event: Event, ruleChainName: string) { |
203 | + $event.preventDefault(); | ||
204 | + this.ruleChainAutocomplete.closePanel(); | ||
202 | this.ruleChainService.createDefaultRuleChain(ruleChainName).subscribe((ruleChain) => { | 205 | this.ruleChainService.createDefaultRuleChain(ruleChainName).subscribe((ruleChain) => { |
203 | this.updateView(ruleChain.id.id); | 206 | this.updateView(ruleChain.id.id); |
204 | }); | 207 | }); |