Commit e494145d0e4c3e8cc138752987815a7ce0adc607
Committed by
GitHub
Merge pull request #23 from thingsboard/feature/attributes
Feature/attributes
Showing
18 changed files
with
199 additions
and
63 deletions
... | ... | @@ -29,6 +29,8 @@ public class DataConstants { |
29 | 29 | public static final String SERVER_SCOPE = "SERVER_SCOPE"; |
30 | 30 | public static final String SHARED_SCOPE = "SHARED_SCOPE"; |
31 | 31 | |
32 | + public static final String[] ALL_SCOPES = {CLIENT_SCOPE, SHARED_SCOPE, SERVER_SCOPE}; | |
33 | + | |
32 | 34 | public static final String ALARM = "ALARM"; |
33 | 35 | public static final String ERROR = "ERROR"; |
34 | 36 | public static final String LC_EVENT = "LC_EVENT"; | ... | ... |
... | ... | @@ -18,6 +18,8 @@ package org.thingsboard.server.common.msg.core; |
18 | 18 | import lombok.ToString; |
19 | 19 | import org.thingsboard.server.common.msg.session.MsgType; |
20 | 20 | |
21 | +import java.util.Collections; | |
22 | +import java.util.Optional; | |
21 | 23 | import java.util.Set; |
22 | 24 | |
23 | 25 | @ToString |
... | ... | @@ -28,6 +30,10 @@ public class BasicGetAttributesRequest extends BasicRequest implements GetAttrib |
28 | 30 | private final Set<String> clientKeys; |
29 | 31 | private final Set<String> sharedKeys; |
30 | 32 | |
33 | + public BasicGetAttributesRequest(Integer requestId) { | |
34 | + this(requestId, Collections.emptySet(), Collections.emptySet()); | |
35 | + } | |
36 | + | |
31 | 37 | public BasicGetAttributesRequest(Integer requestId, Set<String> clientKeys, Set<String> sharedKeys) { |
32 | 38 | super(requestId); |
33 | 39 | this.clientKeys = clientKeys; |
... | ... | @@ -40,13 +46,13 @@ public class BasicGetAttributesRequest extends BasicRequest implements GetAttrib |
40 | 46 | } |
41 | 47 | |
42 | 48 | @Override |
43 | - public Set<String> getClientAttributeNames() { | |
44 | - return clientKeys; | |
49 | + public Optional<Set<String>> getClientAttributeNames() { | |
50 | + return Optional.of(clientKeys); | |
45 | 51 | } |
46 | 52 | |
47 | 53 | @Override |
48 | - public Set<String> getSharedAttributeNames() { | |
49 | - return sharedKeys; | |
54 | + public Optional<Set<String>> getSharedAttributeNames() { | |
55 | + return Optional.ofNullable(sharedKeys); | |
50 | 56 | } |
51 | 57 | |
52 | 58 | } | ... | ... |
... | ... | @@ -15,6 +15,7 @@ |
15 | 15 | */ |
16 | 16 | package org.thingsboard.server.common.msg.core; |
17 | 17 | |
18 | +import java.util.Optional; | |
18 | 19 | import java.util.Set; |
19 | 20 | |
20 | 21 | import org.thingsboard.server.common.msg.session.FromDeviceMsg; |
... | ... | @@ -22,7 +23,7 @@ import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg; |
22 | 23 | |
23 | 24 | public interface GetAttributesRequest extends FromDeviceRequestMsg { |
24 | 25 | |
25 | - Set<String> getClientAttributeNames(); | |
26 | - Set<String> getSharedAttributeNames(); | |
26 | + Optional<Set<String>> getClientAttributeNames(); | |
27 | + Optional<Set<String>> getSharedAttributeNames(); | |
27 | 28 | |
28 | 29 | } | ... | ... |
... | ... | @@ -175,6 +175,23 @@ public class SubscriptionManager { |
175 | 175 | } |
176 | 176 | } |
177 | 177 | |
178 | + public void onAttributesUpdateFromServer(PluginContext ctx, DeviceId deviceId, String scope, List<AttributeKvEntry> attributes) { | |
179 | + Optional<ServerAddress> serverAddress = ctx.resolve(deviceId); | |
180 | + if (!serverAddress.isPresent()) { | |
181 | + onLocalSubscriptionUpdate(ctx, deviceId, SubscriptionType.ATTRIBUTES, s -> { | |
182 | + List<TsKvEntry> subscriptionUpdate = new ArrayList<TsKvEntry>(); | |
183 | + for (AttributeKvEntry kv : attributes) { | |
184 | + if (s.isAllKeys() || s.getKeyStates().containsKey(kv.getKey())) { | |
185 | + subscriptionUpdate.add(new BasicTsKvEntry(kv.getLastUpdateTs(), kv)); | |
186 | + } | |
187 | + } | |
188 | + return subscriptionUpdate; | |
189 | + }); | |
190 | + } else { | |
191 | + rpcHandler.onAttributesUpdate(ctx, serverAddress.get(), deviceId, scope, attributes); | |
192 | + } | |
193 | + } | |
194 | + | |
178 | 195 | private void updateSubscriptionState(String sessionId, Subscription subState, SubscriptionUpdate update) { |
179 | 196 | log.trace("[{}] updating subscription state {} using onUpdate {}", sessionId, subState, update); |
180 | 197 | update.getLatestValues().entrySet().forEach(e -> subState.setKeyState(e.getKey(), e.getValue())); | ... | ... |
... | ... | @@ -43,7 +43,7 @@ public class TelemetryStoragePlugin extends AbstractPlugin<EmptyComponentConfigu |
43 | 43 | |
44 | 44 | public TelemetryStoragePlugin() { |
45 | 45 | this.subscriptionManager = new SubscriptionManager(); |
46 | - this.restMsgHandler = new TelemetryRestMsgHandler(); | |
46 | + this.restMsgHandler = new TelemetryRestMsgHandler(subscriptionManager); | |
47 | 47 | this.ruleMsgHandler = new TelemetryRuleMsgHandler(subscriptionManager); |
48 | 48 | this.websocketMsgHandler = new TelemetryWebsocketMsgHandler(subscriptionManager); |
49 | 49 | this.rpcMsgHandler = new TelemetryRpcMsgHandler(subscriptionManager); | ... | ... |
... | ... | @@ -24,10 +24,6 @@ import org.thingsboard.server.extensions.core.plugin.telemetry.sub.SubscriptionT |
24 | 24 | @NoArgsConstructor |
25 | 25 | public class AttributesSubscriptionCmd extends SubscriptionCmd { |
26 | 26 | |
27 | - public AttributesSubscriptionCmd(int cmdId, String deviceId, String keys, boolean unsubscribe) { | |
28 | - super(cmdId, deviceId, keys, unsubscribe); | |
29 | - } | |
30 | - | |
31 | 27 | @Override |
32 | 28 | public SubscriptionType getType() { |
33 | 29 | return SubscriptionType.ATTRIBUTES; | ... | ... |
... | ... | @@ -26,6 +26,7 @@ public abstract class SubscriptionCmd implements TelemetryPluginCmd { |
26 | 26 | private int cmdId; |
27 | 27 | private String deviceId; |
28 | 28 | private String keys; |
29 | + private String scope; | |
29 | 30 | private boolean unsubscribe; |
30 | 31 | |
31 | 32 | public abstract SubscriptionType getType(); |
... | ... | @@ -62,6 +63,14 @@ public abstract class SubscriptionCmd implements TelemetryPluginCmd { |
62 | 63 | this.unsubscribe = unsubscribe; |
63 | 64 | } |
64 | 65 | |
66 | + public String getScope() { | |
67 | + return scope; | |
68 | + } | |
69 | + | |
70 | + public void setKeys(String keys) { | |
71 | + this.keys = keys; | |
72 | + } | |
73 | + | |
65 | 74 | @Override |
66 | 75 | public String toString() { |
67 | 76 | return "SubscriptionCmd [deviceId=" + deviceId + ", tags=" + keys + ", unsubscribe=" + unsubscribe + "]"; | ... | ... |
... | ... | @@ -26,11 +26,6 @@ public class TimeseriesSubscriptionCmd extends SubscriptionCmd { |
26 | 26 | |
27 | 27 | private long timeWindow; |
28 | 28 | |
29 | - public TimeseriesSubscriptionCmd(int cmdId, String deviceId, String keys, boolean unsubscribe, long timeWindow) { | |
30 | - super(cmdId, deviceId, keys, unsubscribe); | |
31 | - this.timeWindow = timeWindow; | |
32 | - } | |
33 | - | |
34 | 29 | public long getTimeWindow() { |
35 | 30 | return timeWindow; |
36 | 31 | } | ... | ... |
... | ... | @@ -29,6 +29,7 @@ import org.thingsboard.server.extensions.api.plugins.handlers.DefaultRestMsgHand |
29 | 29 | import org.thingsboard.server.extensions.api.plugins.rest.PluginRestMsg; |
30 | 30 | import org.thingsboard.server.extensions.api.plugins.rest.RestRequest; |
31 | 31 | import org.thingsboard.server.extensions.core.plugin.telemetry.AttributeData; |
32 | +import org.thingsboard.server.extensions.core.plugin.telemetry.SubscriptionManager; | |
32 | 33 | import org.thingsboard.server.extensions.core.plugin.telemetry.TsData; |
33 | 34 | |
34 | 35 | import javax.servlet.ServletException; |
... | ... | @@ -39,6 +40,12 @@ import java.util.stream.Collectors; |
39 | 40 | @Slf4j |
40 | 41 | public class TelemetryRestMsgHandler extends DefaultRestMsgHandler { |
41 | 42 | |
43 | + private final SubscriptionManager subscriptionManager; | |
44 | + | |
45 | + public TelemetryRestMsgHandler(SubscriptionManager subscriptionManager) { | |
46 | + this.subscriptionManager = subscriptionManager; | |
47 | + } | |
48 | + | |
42 | 49 | @Override |
43 | 50 | public void handleHttpGetRequest(PluginContext ctx, PluginRestMsg msg) throws ServletException { |
44 | 51 | RestRequest request = msg.getRequest(); |
... | ... | @@ -74,9 +81,8 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler { |
74 | 81 | if (!StringUtils.isEmpty(scope)) { |
75 | 82 | attributes = ctx.loadAttributes(deviceId, scope); |
76 | 83 | } else { |
77 | - attributes = ctx.loadAttributes(deviceId, DataConstants.CLIENT_SCOPE); | |
78 | - attributes.addAll(ctx.loadAttributes(deviceId, DataConstants.SERVER_SCOPE)); | |
79 | - attributes.addAll(ctx.loadAttributes(deviceId, DataConstants.SHARED_SCOPE)); | |
84 | + attributes = new ArrayList<>(); | |
85 | + Arrays.stream(DataConstants.ALL_SCOPES).forEach(s -> attributes.addAll(ctx.loadAttributes(deviceId, s))); | |
80 | 86 | } |
81 | 87 | List<String> keys = attributes.stream().map(attrKv -> attrKv.getKey()).collect(Collectors.toList()); |
82 | 88 | msg.getResponseHolder().setResult(new ResponseEntity<>(keys, HttpStatus.OK)); |
... | ... | @@ -99,9 +105,8 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler { |
99 | 105 | if (!StringUtils.isEmpty(scope)) { |
100 | 106 | attributes = getAttributeKvEntries(ctx, scope, deviceId, keys); |
101 | 107 | } else { |
102 | - attributes = getAttributeKvEntries(ctx, DataConstants.CLIENT_SCOPE, deviceId, keys); | |
103 | - attributes.addAll(getAttributeKvEntries(ctx, DataConstants.SHARED_SCOPE, deviceId, keys)); | |
104 | - attributes.addAll(getAttributeKvEntries(ctx, DataConstants.SERVER_SCOPE, deviceId, keys)); | |
108 | + attributes = new ArrayList<>(); | |
109 | + Arrays.stream(DataConstants.ALL_SCOPES).forEach(s -> attributes.addAll(getAttributeKvEntries(ctx, s, deviceId, keys))); | |
105 | 110 | } |
106 | 111 | List<AttributeData> values = attributes.stream().map(attribute -> new AttributeData(attribute.getLastUpdateTs(), |
107 | 112 | attribute.getKey(), attribute.getValue())).collect(Collectors.toList()); |
... | ... | @@ -145,6 +150,7 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler { |
145 | 150 | @Override |
146 | 151 | public void onSuccess(PluginContext ctx, Void value) { |
147 | 152 | msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.OK)); |
153 | + subscriptionManager.onAttributesUpdateFromServer(ctx, deviceId, scope, attributes); | |
148 | 154 | } |
149 | 155 | |
150 | 156 | @Override |
... | ... | @@ -172,7 +178,8 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler { |
172 | 178 | DeviceId deviceId = DeviceId.fromString(pathParams[0]); |
173 | 179 | String scope = pathParams[1]; |
174 | 180 | if (DataConstants.SERVER_SCOPE.equals(scope) || |
175 | - DataConstants.SHARED_SCOPE.equals(scope)) { | |
181 | + DataConstants.SHARED_SCOPE.equals(scope) || | |
182 | + DataConstants.CLIENT_SCOPE.equals(scope)) { | |
176 | 183 | String keysParam = request.getParameter("keys"); |
177 | 184 | if (!StringUtils.isEmpty(keysParam)) { |
178 | 185 | String[] keys = keysParam.split(","); | ... | ... |
... | ... | @@ -19,6 +19,7 @@ import com.google.protobuf.InvalidProtocolBufferException; |
19 | 19 | import lombok.RequiredArgsConstructor; |
20 | 20 | import lombok.extern.slf4j.Slf4j; |
21 | 21 | import org.thingsboard.server.common.data.id.DeviceId; |
22 | +import org.thingsboard.server.common.data.kv.*; | |
22 | 23 | import org.thingsboard.server.common.msg.cluster.ServerAddress; |
23 | 24 | import org.thingsboard.server.extensions.api.plugins.PluginContext; |
24 | 25 | import org.thingsboard.server.extensions.api.plugins.handlers.RpcMsgHandler; |
... | ... | @@ -42,9 +43,10 @@ public class TelemetryRpcMsgHandler implements RpcMsgHandler { |
42 | 43 | private final SubscriptionManager subscriptionManager; |
43 | 44 | |
44 | 45 | private static final int SUBSCRIPTION_CLAZZ = 1; |
45 | - private static final int SUBSCRIPTION_UPDATE_CLAZZ = 2; | |
46 | - private static final int SESSION_CLOSE_CLAZZ = 3; | |
47 | - private static final int SUBSCRIPTION_CLOSE_CLAZZ = 4; | |
46 | + private static final int ATTRIBUTES_UPDATE_CLAZZ = 2; | |
47 | + private static final int SUBSCRIPTION_UPDATE_CLAZZ = 3; | |
48 | + private static final int SESSION_CLOSE_CLAZZ = 4; | |
49 | + private static final int SUBSCRIPTION_CLOSE_CLAZZ = 5; | |
48 | 50 | |
49 | 51 | @Override |
50 | 52 | public void process(PluginContext ctx, RpcMsg msg) { |
... | ... | @@ -55,6 +57,9 @@ public class TelemetryRpcMsgHandler implements RpcMsgHandler { |
55 | 57 | case SUBSCRIPTION_UPDATE_CLAZZ: |
56 | 58 | processRemoteSubscriptionUpdate(ctx, msg); |
57 | 59 | break; |
60 | + case ATTRIBUTES_UPDATE_CLAZZ: | |
61 | + processAttributeUpdate(ctx, msg); | |
62 | + break; | |
58 | 63 | case SESSION_CLOSE_CLAZZ: |
59 | 64 | processSessionClose(ctx, msg); |
60 | 65 | break; |
... | ... | @@ -76,6 +81,17 @@ public class TelemetryRpcMsgHandler implements RpcMsgHandler { |
76 | 81 | subscriptionManager.onRemoteSubscriptionUpdate(ctx, proto.getSessionId(), convert(proto)); |
77 | 82 | } |
78 | 83 | |
84 | + private void processAttributeUpdate(PluginContext ctx, RpcMsg msg) { | |
85 | + AttributeUpdateProto proto; | |
86 | + try { | |
87 | + proto = AttributeUpdateProto.parseFrom(msg.getMsgData()); | |
88 | + } catch (InvalidProtocolBufferException e) { | |
89 | + throw new RuntimeException(e); | |
90 | + } | |
91 | + subscriptionManager.onAttributesUpdateFromServer(ctx, DeviceId.fromString(proto.getDeviceId()), proto.getScope(), | |
92 | + proto.getDataList().stream().map(this::toAttribute).collect(Collectors.toList())); | |
93 | + } | |
94 | + | |
79 | 95 | private void processSubscriptionCmd(PluginContext ctx, RpcMsg msg) { |
80 | 96 | SubscriptionProto proto; |
81 | 97 | try { |
... | ... | @@ -167,11 +183,7 @@ public class TelemetryRpcMsgHandler implements RpcMsgHandler { |
167 | 183 | } else { |
168 | 184 | Map<String, List<Object>> data = new TreeMap<>(); |
169 | 185 | proto.getDataList().forEach(v -> { |
170 | - List<Object> values = data.get(v.getKey()); | |
171 | - if (values == null) { | |
172 | - values = new ArrayList<>(); | |
173 | - data.put(v.getKey(), values); | |
174 | - } | |
186 | + List<Object> values = data.computeIfAbsent(v.getKey(), k -> new ArrayList<>()); | |
175 | 187 | for (int i = 0; i < v.getTsCount(); i++) { |
176 | 188 | Object[] value = new Object[2]; |
177 | 189 | value[0] = v.getTs(i); |
... | ... | @@ -182,4 +194,59 @@ public class TelemetryRpcMsgHandler implements RpcMsgHandler { |
182 | 194 | return new SubscriptionUpdate(proto.getSubscriptionId(), data); |
183 | 195 | } |
184 | 196 | } |
197 | + | |
198 | + public void onAttributesUpdate(PluginContext ctx, ServerAddress address, DeviceId deviceId, String scope, List<AttributeKvEntry> attributes) { | |
199 | + ctx.sendPluginRpcMsg(new RpcMsg(address, ATTRIBUTES_UPDATE_CLAZZ, getAttributesUpdateProto(deviceId, scope, attributes).toByteArray())); | |
200 | + } | |
201 | + | |
202 | + private AttributeUpdateProto getAttributesUpdateProto(DeviceId deviceId, String scope, List<AttributeKvEntry> attributes) { | |
203 | + AttributeUpdateProto.Builder builder = AttributeUpdateProto.newBuilder(); | |
204 | + builder.setDeviceId(deviceId.toString()); | |
205 | + builder.setScope(scope); | |
206 | + attributes.forEach( | |
207 | + attr -> { | |
208 | + AttributeUpdateValueListProto.Builder dataBuilder = AttributeUpdateValueListProto.newBuilder(); | |
209 | + dataBuilder.setKey(attr.getKey()); | |
210 | + dataBuilder.setTs(attr.getLastUpdateTs()); | |
211 | + dataBuilder.setValueType(attr.getDataType().ordinal()); | |
212 | + switch (attr.getDataType()) { | |
213 | + case BOOLEAN: | |
214 | + dataBuilder.setBoolValue(attr.getBooleanValue().get()); | |
215 | + break; | |
216 | + case LONG: | |
217 | + dataBuilder.setLongValue(attr.getLongValue().get()); | |
218 | + break; | |
219 | + case DOUBLE: | |
220 | + dataBuilder.setDoubleValue(attr.getDoubleValue().get()); | |
221 | + break; | |
222 | + case STRING: | |
223 | + dataBuilder.setStrValue(attr.getStrValue().get()); | |
224 | + break; | |
225 | + } | |
226 | + builder.addData(dataBuilder.build()); | |
227 | + } | |
228 | + ); | |
229 | + return builder.build(); | |
230 | + } | |
231 | + | |
232 | + private AttributeKvEntry toAttribute(AttributeUpdateValueListProto proto) { | |
233 | + KvEntry entry = null; | |
234 | + DataType type = DataType.values()[proto.getValueType()]; | |
235 | + switch (type) { | |
236 | + case BOOLEAN: | |
237 | + entry = new BooleanDataEntry(proto.getKey(), proto.getBoolValue()); | |
238 | + break; | |
239 | + case LONG: | |
240 | + entry = new LongDataEntry(proto.getKey(), proto.getLongValue()); | |
241 | + break; | |
242 | + case DOUBLE: | |
243 | + entry = new DoubleDataEntry(proto.getKey(), proto.getDoubleValue()); | |
244 | + break; | |
245 | + case STRING: | |
246 | + entry = new StringDataEntry(proto.getKey(), proto.getStrValue()); | |
247 | + break; | |
248 | + } | |
249 | + return new BaseAttributeKvEntry(entry, proto.getTs()); | |
250 | + } | |
251 | + | |
185 | 252 | } | ... | ... |
... | ... | @@ -58,10 +58,14 @@ public class TelemetryRuleMsgHandler extends DefaultRuleMsgHandler { |
58 | 58 | ctx.reply(new ResponsePluginToRuleMsg(msg.getUid(), tenantId, ruleId, response)); |
59 | 59 | } |
60 | 60 | |
61 | - private List<AttributeKvEntry> getAttributeKvEntries(PluginContext ctx, DeviceId deviceId, String scope, Set<String> names) { | |
61 | + private List<AttributeKvEntry> getAttributeKvEntries(PluginContext ctx, DeviceId deviceId, String scope, Optional<Set<String>> names) { | |
62 | 62 | List<AttributeKvEntry> attributes; |
63 | - if (!names.isEmpty()) { | |
64 | - attributes = ctx.loadAttributes(deviceId, scope, new ArrayList<>(names)); | |
63 | + if (names.isPresent()) { | |
64 | + if (!names.get().isEmpty()) { | |
65 | + attributes = ctx.loadAttributes(deviceId, scope, new ArrayList<>(names.get())); | |
66 | + } else { | |
67 | + attributes = ctx.loadAttributes(deviceId, scope); | |
68 | + } | |
65 | 69 | } else { |
66 | 70 | attributes = Collections.emptyList(); |
67 | 71 | } | ... | ... |
... | ... | @@ -104,7 +104,13 @@ public class TelemetryWebsocketMsgHandler extends DefaultWebsocketMsgHandler { |
104 | 104 | SubscriptionState sub; |
105 | 105 | if (keysOptional.isPresent()) { |
106 | 106 | List<String> keys = new ArrayList<>(keysOptional.get()); |
107 | - List<AttributeKvEntry> data = ctx.loadAttributes(deviceId, DataConstants.CLIENT_SCOPE, keys); | |
107 | + List<AttributeKvEntry> data = new ArrayList<>(); | |
108 | + if (StringUtils.isEmpty(cmd.getScope())) { | |
109 | + Arrays.stream(DataConstants.ALL_SCOPES).forEach(s -> data.addAll(ctx.loadAttributes(deviceId, s, keys))); | |
110 | + } else { | |
111 | + data.addAll(ctx.loadAttributes(deviceId, cmd.getScope(), keys)); | |
112 | + } | |
113 | + | |
108 | 114 | List<TsKvEntry> attributesData = data.stream().map(d -> new BasicTsKvEntry(d.getLastUpdateTs(), d)).collect(Collectors.toList()); |
109 | 115 | sendWsMsg(ctx, sessionRef, new SubscriptionUpdate(cmd.getCmdId(), attributesData)); |
110 | 116 | |
... | ... | @@ -114,7 +120,12 @@ public class TelemetryWebsocketMsgHandler extends DefaultWebsocketMsgHandler { |
114 | 120 | |
115 | 121 | sub = new SubscriptionState(sessionId, cmd.getCmdId(), deviceId, SubscriptionType.ATTRIBUTES, false, subState); |
116 | 122 | } else { |
117 | - List<AttributeKvEntry> data = ctx.loadAttributes(deviceId, DataConstants.CLIENT_SCOPE); | |
123 | + List<AttributeKvEntry> data = new ArrayList<>(); | |
124 | + if (StringUtils.isEmpty(cmd.getScope())) { | |
125 | + Arrays.stream(DataConstants.ALL_SCOPES).forEach(s -> data.addAll(ctx.loadAttributes(deviceId, s))); | |
126 | + } else { | |
127 | + data.addAll(ctx.loadAttributes(deviceId, cmd.getScope())); | |
128 | + } | |
118 | 129 | List<TsKvEntry> attributesData = data.stream().map(d -> new BasicTsKvEntry(d.getLastUpdateTs(), d)).collect(Collectors.toList()); |
119 | 130 | sendWsMsg(ctx, sessionRef, new SubscriptionUpdate(cmd.getCmdId(), attributesData)); |
120 | 131 | ... | ... |
... | ... | @@ -36,6 +36,12 @@ message SubscriptionUpdateProto { |
36 | 36 | repeated SubscriptionUpdateValueListProto data = 5; |
37 | 37 | } |
38 | 38 | |
39 | +message AttributeUpdateProto { | |
40 | + string deviceId = 1; | |
41 | + string scope = 2; | |
42 | + repeated AttributeUpdateValueListProto data = 3; | |
43 | +} | |
44 | + | |
39 | 45 | message SessionCloseProto { |
40 | 46 | string sessionId = 1; |
41 | 47 | } |
... | ... | @@ -54,4 +60,14 @@ message SubscriptionUpdateValueListProto { |
54 | 60 | string key = 1; |
55 | 61 | repeated int64 ts = 2; |
56 | 62 | repeated string value = 3; |
63 | +} | |
64 | + | |
65 | +message AttributeUpdateValueListProto { | |
66 | + string key = 1; | |
67 | + int64 ts = 2; | |
68 | + int32 valueType = 3; | |
69 | + string strValue = 4; | |
70 | + int64 longValue = 5; | |
71 | + double doubleValue = 6; | |
72 | + bool boolValue = 7; | |
57 | 73 | } |
\ No newline at end of file | ... | ... |
... | ... | @@ -167,17 +167,13 @@ public class JsonCoapAdaptor implements CoapTransportAdaptor { |
167 | 167 | |
168 | 168 | private FromDeviceMsg convertToGetAttributesRequest(SessionContext ctx, Request inbound) throws AdaptorException { |
169 | 169 | List<String> queryElements = inbound.getOptions().getUriQuery(); |
170 | - if (queryElements == null || queryElements.size() == 0) { | |
171 | - log.warn("[{}] Query is empty!", ctx.getSessionId()); | |
172 | - throw new AdaptorException(new IllegalArgumentException("Query is empty!")); | |
173 | - } | |
174 | - | |
175 | - Set<String> clientKeys = toKeys(ctx, queryElements, "clientKeys"); | |
176 | - Set<String> sharedKeys = toKeys(ctx, queryElements, "sharedKeys"); | |
177 | - if (clientKeys.isEmpty() && sharedKeys.isEmpty()) { | |
178 | - throw new AdaptorException("No clientKeys and serverKeys parameters!"); | |
170 | + if (queryElements != null || queryElements.size() > 0) { | |
171 | + Set<String> clientKeys = toKeys(ctx, queryElements, "clientKeys"); | |
172 | + Set<String> sharedKeys = toKeys(ctx, queryElements, "sharedKeys"); | |
173 | + return new BasicGetAttributesRequest(0, clientKeys, sharedKeys); | |
174 | + } else { | |
175 | + return new BasicGetAttributesRequest(0); | |
179 | 176 | } |
180 | - return new BasicGetAttributesRequest(0, clientKeys, sharedKeys); | |
181 | 177 | } |
182 | 178 | |
183 | 179 | private Set<String> toKeys(SessionContext ctx, List<String> queryElements, String attributeName) throws AdaptorException { |
... | ... | @@ -191,7 +187,7 @@ public class JsonCoapAdaptor implements CoapTransportAdaptor { |
191 | 187 | if (!StringUtils.isEmpty(keys)) { |
192 | 188 | return new HashSet<>(Arrays.asList(keys.split(","))); |
193 | 189 | } else { |
194 | - return Collections.emptySet(); | |
190 | + return null; | |
195 | 191 | } |
196 | 192 | } |
197 | 193 | ... | ... |
... | ... | @@ -182,7 +182,7 @@ public class CoapServerTest { |
182 | 182 | public void testNoKeysAttributesGetRequest() { |
183 | 183 | CoapClient client = new CoapClient(getBaseTestUrl() + DEVICE1_TOKEN + "/" + FeatureType.ATTRIBUTES.name().toLowerCase() + "?data=key1,key2"); |
184 | 184 | CoapResponse response = client.setTimeout(6000).get(); |
185 | - Assert.assertEquals(ResponseCode.BAD_REQUEST, response.getCode()); | |
185 | + Assert.assertEquals(ResponseCode.CONTENT, response.getCode()); | |
186 | 186 | } |
187 | 187 | |
188 | 188 | @Test | ... | ... |
... | ... | @@ -38,6 +38,7 @@ import org.thingsboard.server.common.transport.auth.DeviceAuthService; |
38 | 38 | import org.thingsboard.server.transport.http.session.HttpSessionCtx; |
39 | 39 | |
40 | 40 | import java.util.Arrays; |
41 | +import java.util.Collections; | |
41 | 42 | import java.util.HashSet; |
42 | 43 | import java.util.Set; |
43 | 44 | |
... | ... | @@ -60,20 +61,22 @@ public class DeviceApiController { |
60 | 61 | |
61 | 62 | @RequestMapping(value = "/{deviceToken}/attributes", method = RequestMethod.GET, produces = "application/json") |
62 | 63 | public DeferredResult<ResponseEntity> getDeviceAttributes(@PathVariable("deviceToken") String deviceToken, |
63 | - @RequestParam(value = "clientKeys", required = false) String clientKeys, | |
64 | - @RequestParam(value = "sharedKeys", required = false) String sharedKeys) { | |
64 | + @RequestParam(value = "clientKeys", required = false, defaultValue = "") String clientKeys, | |
65 | + @RequestParam(value = "sharedKeys", required = false, defaultValue = "") String sharedKeys) { | |
65 | 66 | DeferredResult<ResponseEntity> responseWriter = new DeferredResult<ResponseEntity>(); |
66 | - if (StringUtils.isEmpty(clientKeys) && StringUtils.isEmpty(sharedKeys)) { | |
67 | - responseWriter.setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST)); | |
68 | - } else { | |
69 | - HttpSessionCtx ctx = getHttpSessionCtx(responseWriter); | |
70 | - if (ctx.login(new DeviceTokenCredentials(deviceToken))) { | |
71 | - Set<String> clientKeySet = new HashSet<>(Arrays.asList(clientKeys.split(","))); | |
72 | - Set<String> sharedKeySet = new HashSet<>(Arrays.asList(clientKeys.split(","))); | |
73 | - process(ctx, new BasicGetAttributesRequest(0, clientKeySet, sharedKeySet)); | |
67 | + HttpSessionCtx ctx = getHttpSessionCtx(responseWriter); | |
68 | + if (ctx.login(new DeviceTokenCredentials(deviceToken))) { | |
69 | + GetAttributesRequest request; | |
70 | + if (StringUtils.isEmpty(clientKeys) && StringUtils.isEmpty(sharedKeys)) { | |
71 | + request = new BasicGetAttributesRequest(0); | |
74 | 72 | } else { |
75 | - responseWriter.setResult(new ResponseEntity<>(HttpStatus.UNAUTHORIZED)); | |
73 | + Set<String> clientKeySet = !StringUtils.isEmpty(clientKeys) ? new HashSet<>(Arrays.asList(clientKeys.split(","))) : null; | |
74 | + Set<String> sharedKeySet = !StringUtils.isEmpty(sharedKeys) ? new HashSet<>(Arrays.asList(sharedKeys.split(","))) : null; | |
75 | + request = new BasicGetAttributesRequest(0, clientKeySet, sharedKeySet); | |
76 | 76 | } |
77 | + process(ctx, request); | |
78 | + } else { | |
79 | + responseWriter.setResult(new ResponseEntity<>(HttpStatus.UNAUTHORIZED)); | |
77 | 80 | } |
78 | 81 | |
79 | 82 | return responseWriter; | ... | ... |
... | ... | @@ -162,8 +162,13 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor { |
162 | 162 | Integer requestId = Integer.valueOf(topicName.substring(MqttTransportHandler.ATTRIBUTES_REQUEST_TOPIC_PREFIX.length())); |
163 | 163 | String payload = inbound.payload().toString(UTF8); |
164 | 164 | JsonElement requestBody = new JsonParser().parse(payload); |
165 | - return new BasicGetAttributesRequest(requestId, | |
166 | - toStringSet(requestBody, "clientKeys"), toStringSet(requestBody, "sharedKeys")); | |
165 | + Set<String> clientKeys = toStringSet(requestBody, "clientKeys"); | |
166 | + Set<String> sharedKeys = toStringSet(requestBody, "sharedKeys"); | |
167 | + if (clientKeys == null && sharedKeys == null) { | |
168 | + return new BasicGetAttributesRequest(requestId); | |
169 | + } else { | |
170 | + return new BasicGetAttributesRequest(requestId, clientKeys, sharedKeys); | |
171 | + } | |
167 | 172 | } catch (RuntimeException e) { |
168 | 173 | log.warn("Failed to decode get attributes request", e); |
169 | 174 | throw new AdaptorException(e); |
... | ... | @@ -189,7 +194,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor { |
189 | 194 | if (element != null) { |
190 | 195 | return new HashSet<>(Arrays.asList(element.getAsString().split(","))); |
191 | 196 | } else { |
192 | - return Collections.emptySet(); | |
197 | + return null; | |
193 | 198 | } |
194 | 199 | } |
195 | 200 | ... | ... |
... | ... | @@ -293,7 +293,8 @@ function DeviceService($http, $q, $filter, telemetryWebsocketService, types) { |
293 | 293 | var deviceAttributesSubscription = deviceAttributesSubscriptionMap[subscriptionId]; |
294 | 294 | if (!deviceAttributesSubscription) { |
295 | 295 | var subscriptionCommand = { |
296 | - deviceId: deviceId | |
296 | + deviceId: deviceId, | |
297 | + scope: attributeScope | |
297 | 298 | }; |
298 | 299 | |
299 | 300 | var type = attributeScope === types.latestTelemetry.value ? | ... | ... |